The snake game
The whole ladder in one program: a board of cells packed one to a byte, a body moved by a loop, keys polled every frame, food from a generator of its own, and a score on the Screen and in the transcript.
The whole ladder in one program. A snake of green squares crosses a board of 15 by 11 cells, the arrow keys steer it, it grows by one segment every time it reaches the food, and it ends when its head leaves the board or runs into its own body. The score sits above the board while you play and the last one goes into the transcript when you lose.
Click the Screen panel before you press a key, the same as in Move a square with the keyboard, and press Run again to play another game.
You need to know: everything above it on the ladder. The body is an array walked with a pointer and moved with a loop, the drawing and the digits are subroutines, the frame is drawn off screen and shown the way A bouncing ball does, and the keys are polled the way Move a square with the keyboard does. What is new is the board kept as cells, one byte for the whole of a position, which becomes pixels only at the moment something is drawn.
A cell is one byte: the column in the high nibble and the row in the low one, so 0x35 is
column 3, row 5. Four bits hold a number up to 15, so the packing caps the board at 16 by 16 cells,
and the Screen settles it at 15 by 11: fifteen columns of 16 pixels is the 240 the Screen was
resized to, and eleven rows of 16 fill what is left under the two text rows. The reason for packing
it that way is that comparing two cells is then a single cp. The M68K uses a whole word per cell
and 32 by 24 cells, because it has a cmp.w to compare them with; here a 16 bit comparison would be
an or a and an sbc hl, de that destroys one of the two values, so the board was made to fit the
byte.
The nibbles are what make the drawing cheap. A column in the high nibble is the column times 16
already, so and 0xF0 is the whole of the x arithmetic, and the row needs four rlca and the 16
pixel offset of the two text rows above the board. No multiplication happens anywhere in
draw_cell, which matters on a machine that has none.
The snake moves by shifting: every segment takes the place of the one in front of it, from the tail backwards so that nothing is overwritten before it has been read, and then the head is given its new cell. The tail therefore disappears from where it was without any code saying so. Growing is one extra byte: the new last segment is put on top of the old one, so for one frame two segments sit in the same cell and the shift pulls them apart on the next.
The head's new cell is the old one plus the direction, and dx and dy are counted in cells, so
they are 1, 0 or -1. cp COLS catches both walls at once: a column of 15 fails it the obvious way,
and a column of -1 is FF as an unsigned byte, which fails it as well. Both tests have to run
before the column is packed back into a nibble, since rlca cannot tell -1 from 15.
The arrows do not move the snake, they call try_direction, and it refuses a direction that is the
exact opposite of the one the snake is going: dx + nx and dy + ny are both zero only when the
new way is backwards, and or h is what asks about the two of them in one test. Turning back means
eating your own neck on the next frame.
The food goes wherever a sixteen bit xorshift generator says. Three shifts and three xor
instructions turn a number into the next one of a sequence that goes through all 65535 non-zero
values before it repeats, which is as random as a program with no clock and no dice can be. Two of
the three shifts cost nothing at all here: x ^ (x << 8) only changes the high byte, because the
low half of x << 8 is zero, and x ^ (x >> 9) only changes the low byte for the same reason. Only
the x << 7 needs a loop. The column is masked down to four bits and the one value over the board
is folded back onto the first column, and the row is folded the same way, so the first five rows
come up twice as often as the other six.
The score goes out through the console character port and the number port, the same two ports Print
a string used, which is why it appears on the Screen and in the transcript at once. The Screen has
no text command of its own, so the two text rows above the board are the only place text can go.
The frame therefore clears the board with a rectangle that starts at TOP, and command 9, which
would wipe the whole image, is used once at the very start. draw_score runs once at the start and
once per point, and the transcript ends up with one line per score.
A frame is the polls, the move, the collisions, the food, then a clear, one square per segment, the
score row left alone and command 13 to show the lot. Port 0x40 sets the pace: put a number of
hundredths in b, read the port, and the program waits that long without freezing the editor, so
Stop still answers while the snake is between cells.
Press Run and touch nothing and the snake walks into the right hand wall after eleven moves, which
is 2458 instructions including the whole of the setup. Try changing seed: .dw 0xACE1 to any other
value that is not zero. The food falls in a different order, because the sequence is fixed by where
it starts: run the same program twice and you get the same game twice, which is what makes a program
with a generator like this one worth debugging.