The snake game
The whole ladder in one program: a board of cells packed into words, a body moved by a loop, the keyboard polled every frame, food from a generator of its own, and two words of the screen changing per frame.
The whole ladder in one program. A snake of green cells crosses a board of 32 by 32, the w, a,
s and d 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 goes to the console as you play, and
the board turns dark red 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 generator are subroutines, the keyboard is polled the way Move a square with the keyboard polls it, and only what changed is redrawn the way A bouncing ball does it. What is new is the board kept as cells, one byte for the column and one for the row packed into a word, which becomes an address only at the moment something is drawn.
body is an array of words, one per segment, with the head at body[0], and a segment is a cell:
0x050C is column 5, row 12. Packing the two into one word is what makes a comparison between two
cells a single beq, which the self collision test does once per segment and the food test does
once per frame.
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, which is why the
cell it was in is read into $s3 before the shift runs.
Growing is that same word put back. When the head reaches the food, the cell the tail was leaving is
written one place past the end of the body and length goes up by one, so the segment that was
about to vanish stays where it is. $s5 then says the tail did not move this frame, and the drawing
skips the erase.
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. The four wall tests run on $t5 and $t6 while they are still separate
numbers, because a column of -1 packed back into a byte is 255 and no test after the sll could
tell the two apart.
The keys 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 turning back means eating your own neck on the next frame.
The food goes wherever a 32 bit xorshift generator says. Three shifts and three xor
instructions turn a number into the next one of a sequence, which is as random as a program with no
clock and no dice can be. Both coordinates are andi with 31, since 32 is a power of two and the
low five bits of any number are already a column.
Three sw instructions reach the screen in a frame and two of them change anything: the cell the
tail left, the cell the head arrived in, and the food, which is repainted whether it moved or not.
The M68K version of this game redraws every segment of the snake into an off screen image and shows
the whole image at once, because that machine has a task for drawing off screen; the bitmap display
is the picture itself, so the cheapest correct frame is the one that writes the fewest words. A
frame here is about 120 instructions, where clearing the board and redrawing everything would be
four thousand.
The score is printed to the console, and it is printed there because there is nowhere else to put
it. The M68K draws it onto the screen with a task that puts text at a pixel position; the bitmap
display has no text of any kind. Apart from the wait that paces a frame, every syscall in this
program is printing or the exit at the end.
fill_grid and draw_cell both read the colour out of $s0 and the base of the grid out of $s1,
and draw_cell promises to destroy $t0 and $t1 and nothing else, which is what lets the loop
that draws the starting body keep its pointer in $s2 and the caller keep the head in $s4. Those
comments above the labels are the whole of the agreement, and this program has four of them.
With nobody typing, the snake runs straight to the right, eats the food on the way, and hits the
wall 26 frames later, and the red board and the console line are done by about 11500 instructions.
That is the game the verification run plays. The runFor of 100000 is the budget the Playground
gets before it stops; a game you are playing ends when you make it end.
Try changing seed: .word 0x1F123BB5 to 0x2545F491. The first food is written into the data
section and does not move, but the one after it falls at column 26 of row 11 instead of column 7 of
row 8. The sequence is fixed by where it starts, so the same program run twice gives the same game
twice, which is what makes a program with a generator like this one debuggable at all.