Kitsune's Curse
This is an original game by Juan J. Martinez for the Amstrad CPC 464 or compatible.
Get the official releases from Kitsune's Curse.
The game is finished, no further development is planned.
The code has been open sourced in case the community finds it useful and helps making new games!
Requirements
- A POSIX environment (Linux is perfect, Debian recommended)
- gcc (C & C++), GNU make, cmake, Python 3
- SDCC 4.0; later version may not work as SDCC changed the calling convention!
Building
Run make
.
After a successful build, it should end with something like:
chksize 2816 ../build/main.map
***
Max: 46080 bytes
Current: 42774 bytes (490 bytes left)
***
To build and run on an emulator make cpcec
(there are other targets).
Other targets
make clean
to remove output of compilation and generated filesmake cleanall
to also remove the compiled tools
Technical details
This game uses an engine that tracks dirty tiles and minimises the amount of drawing on the screen. It is essentially the same engine used in "The Dawn of Kernel". See "Mini-buffer engine" for further info.
Memory Map:
Address | Content |
---|---|
0x0100 |
mini-buffers |
0x0b00 |
Game's code / ROM |
0xa2fd |
Game's data / RAM |
0xb215 |
Stack limit |
0xc000 |
Video memory |
(game's data and the stack limit may not be exact, see build/main.map
for
details once the game is built)
Mini-buffer engine
The game uses "mini-buffers" to build the scene based on dirty tiles.
Instead of tracking the areas of the screen that need redrawing between frames using a big buffer, when a part of the screen needs redrawing a small buffer is allocated and background and masked sprites are drawn to it. When the scene is finished, those mini-buffers are copied to the screen in a fast loop.
This reduces the memory requirements considerably, with some impact in the performance derived of the mini-buffer management. It can be a good engine for mid-size games targeting 64K that don't require a large amount of sprites on screen (this game supports up to 10 sprites of 8 * 24 pixels).
The memory area dedicated to the mini-buffers is calculated with this formula:
(TW * TH / 2) * (SPRITE_TH * 2 + 2) * MAX_SPRITES
- TW: tile width in bytes (pixels / 2).
- TH: tile height in pixels.
- SPRITES_TH: sprites height in tiles (of TH pixels).
- MAX_SPRITES: maximum number of sprites we may need to draw.
In "Kitsune's Curse" this is 2560
bytes (placed on 0x0100
before the game's
code), which is very little memory (e.g. a back buffer by hardware would
require 16384
bytes).
The dirty tiles are tracked with a tile map of the size of the playable screen
(TMW * TMH; which is 20 * 20 or 160x160 pixels). Each entry on the map has a
mini-buffer address that must be under 0x8000
because the most significant
bit on the address is used as dirty flag, a pointer to the background tile, and
the screen address where it should be drawn.
Then there are functions to manipulate the tile map, and those functions either validate (no need to draw) or invalidate tiles (the tiles are "dirty" and need to be drawn on the screen).
The tile map is drawn on screen with a call to update screen. At this point, all the mini-buffers are freed and available to start drawing the next frame.
This game updates at 16.6 FPS, so there are 3 full frames to deal with game logic update, prepare mini-buffers, and update the screen.
Useful flags
There are some defines to help troubleshoot issues:
DEBUG
: show some debug information on screen (e.g. player checkpoints).SPRITE_DEBUG
: will use tile 14 to erase backgrounds providing a visual cue of how the erase is behaving.FENCE_DEBUG
: keep count of the used mini-buffers and boundaries when accessing several functions. On error, the screen border will change color (e.g. 0x4b when getting a tile off-bounds, 0x58 when run out of mini-buffers, etc), seesplib.c
for details.
Map structure
The game has 60 screens, using 7682
bytes (128.03
bytes per screen on average).
The map was designed using tiled exporting JSON
and processed by a Python tool. See data/stage.json
.
The binary data includes n maps, with each map:
- 1 byte: map data length, not including flags (0 for empty map; no more data included)
- 1 byte: flags
- map length bytes: map data (4-bit per tile) H x W x n (4-bit per tile, compressed with
apultra
) - m bytes: entity data (0xff for end marker)
The map flags are:
- 0-4: blocked (up, down, left, right)
- 5-7: tileset
The converter tool expects layers Map
and Entities
, and some predefined
entity type (on the object name).
By default each entity uses one sprite, unless a different weight is set for that type (e.g. the "link" entity type to connect screens doesn't use sprites). This information is used by the converter to validate that all screens are valid.
Entities can be persistent (tracking state; e.g. if a door was unlocked), otherwise they always respawn when the player enters the screen.
See tools/map.py
for details.
Licence
- The source code of the game is licensed GPL 3.0.
- The assets are licensed CC-BY-SA-NC.
The tools/libraries included that I don't own have their own copyright notices and license (some are public domain, others are open source).