A Battle System in CHIP8

Making the foolish choice to begin with a bit of visual polish, we now have a status bar that displays the player’s health and energy:

These hearts are tiny! Just 3x3 each. 574 bytes

Shortly after this, I discovered that conditionals in the Octo language that use > are not particularly efficient. Reworking most of the routines to use direct comparisons (==, !=, etc) instead saved quite a few bytes. So now that we have health, we need something to reduce that health, and:

Oh no! 899 bytes

Not pictured here is our first problematic bug, specifically regarding newlines. On the CHIP8 system, especially the original VIP interpreter, the modern strategy of “draw the whole screen every frame” is far too inefficient. Instead you’re meant to leverage its XOR sprite drawing to erase just the portion of the screen you want to change, sort of a crude partial repaint. Since there’s no way to clear just a part of the screen, this means that to clear a line of text, it must be drawn again in place.

The “SLIME appears!” text above is actually two separate strings, which means that to un-display it, the same code path used to build that string needs to be called again. In theory this sounds okay, but for more complicated strings made of many parts, the state tracking becomes quite fragile and prone to error. Furthermore, this ruins a nice idea I had to overload the \n operator and have it automatically perform a line clear during text display. This needed a rework.

Enter str_concat and friends:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
: _set_str
: target_string i := 0
return

: str_copy
# loads a string into the buffer, erasing any previous contents
# Set a null terminator to the start of the buffer
v0 := 0
i := concat_buffer
save v0
# Now concat with empty string, effectively replacing the buffer
# with our new string
:call str_concat
return

: str_concat
# concatenates the target_string to the end of the buffer
:alias source_index v1
:alias target_index v2
source_index := 0
target_index := 0
# find the null terminator and skip over it
loop
i := concat_buffer
i += target_index
load v0
while v0 != 0
target_index += 1
again
# target index now points to the null terminator, which we
# will overwrite with the start of the new string
loop
:call _set_str
i += source_index
load v0
i := concat_buffer
i += target_index
save v0
source_index += 1
target_index += 1
while v0 != 0
again
return

By using a small memory buffer, we can instead build a single line of text from as many component parts as needed. This is surprisingly quick, so I settled on this method for all string building in the game. The final result is drawn all at once, then we pause to wait for input, draw it again to erase, and move onwards.

Once that was in place, I implemented the last pieces of the battle menu and closed the loop on the most basic form of gameplay. All player input is driven by the [1], [2] and [3] keys on the hexpad, which selects menu entries and targets enemies. A tiny bit of visual polish confirms the player’s selections, and… hang on… what have these slimes been eating!?

Oh... oh dear... 1794 bytes.

-Zeta