Here's yet another program - which uses both the drawing routine and the overlay loader that I have posted about earlier, and the preprocessor I've mentioned.
This time, the program is an implementation of the classic Snake game.
Classic WASD control scheme, plus R for reset/restart after a crash (hitting the wall or yourself). (Only lowercase, though. I'd need one more instruction to also handle uppercase, and I don't have room for that, plus it'd be slower.)
This is, yet again, for the default mode of Senbir, though it only barely fits. It requires that the overlay loader is in ROM (instead of including it on disk), and still fills the disk entirely (all 256 words). A couple of the overlays use all the available RAM (with the median being 19 words). And every register - all 16 of them - is used for something.
It's awkwardly slow during gameplay (though I haven't really measured it, I think it's about 5 or 6 seconds per step), but given enough patience, it's playable.
And that's after I tried pretty hard to optimize it...
I believe most of that slowness is because the main loop of the game is 3 overlays long, with another overlay for when we need to add a new food, and a couple more to handle a crash and restart. (Plus a couple for initialization.) I'm pretty sure that the repeated overlay loading takes a lot more time than running the game code itself does. So, no surprise really, the main limiting factor for speed is the RAM size.
Which is only part of the reason the main runtime scratch space of the game is actually on the disk - another reason is that with the TC-06 ISA it's simply easier to read/write from/to the disk than from/to dynamic areas of the RAM.
While I've fixed several bugs already, it's not quite bug-free yet, though the only known bug (besides the slowness) is pretty unlikely, as it requires you to fill the entire board with your snake first. (If you do that, then crash into something and restart, you're left with no food in the new game... Wouldn't be hard to fix if I had room for more code, but I don't, so... yeah.)
I've actually been considering replacing that restart code with simply re-initializing the game as if you rebooted. It would save some code space, and fix that bug. But it wouldn't look as nice, nor (probably) be as fast. Oh well. We'll see.
Anyway, the source code is on GitHub, along with the preprocessor it uses.
I've included an already-preprocessed copy of the code below, in case anyone wants to just run it without going through the trouble of getting the source and using the preprocessor.
In this copy, I've removed all the lines that only contained a comment, to shorten it down (preprocessor output was 335 lines). If you want to read the code, not just use it, I'd recommend looking at the actual source code.
DATAC 00000000000000000000000000010010 // End address for overlay
MOVI 6 16 // R6 = ram[..] : load address of data to be drawn
MATH 7 7 1 // R7 = 0 : initialization for updating only parts of it
MATH 3 3 1 // R3 = 0 : initialization for updating only parts of it
MATH 2 2 1 // R2 = 0 : initialization for updating only parts of it
GETDATA 1 3 6 // R1 = read(disk, R6) : load next step
MATH 15 6 0 // R6++ : update address
PMOV 1 2 0 8 0 0 // R2[0:8] = R1[ 0: 8] : set start pixel
PMOV 1 3 9 17 9 0 // R3[0:8] = R1[ 9:17] : set end pixel
IFJMP 1 14 0 // jump to done if R2 == R3
PMOV 1 7 18 26 18 0 // R7[0:8] = R1[18:26] : set position increment
SETDATA 0 3 2 // Draw a pixel from R2
MATH 7 2 0 // R2 += R7 : increment position
IFJMP 1 10 1 // jump to loop if R2 != R3
JMP 1 4 // jump to next otherwise
MOVI 2 17
JMP 3 9
DATAC 00000000000000000000000000010011
DATAC 00000000000000000000000000011001
DATAC 10000100110111011100000000100000 // Main area fill, TL-to-BR
DATAC 01111011100111111111111100000000 // Bottom row, R-to-L
DATAC 01000011000111111111111111100000 // Left column, B-to-T
DATAC 01000100010000000000000100000000 // Top row, L-to-R
DATAC 01111100110000000000000000100000 // Right column, T-to-B
NIL // End of list
DATAC 00000000000000000000000000101100 // End address for overlay
MOVI 0 18 // R0 = 6
MOVI 4 13 // R4 = start position (and color)
MOVI 5 14 // R5 = direction
MOVI 8 17 // R8 = 14
MOVI 9 15 // R9 = address of history array
MOVI 10 16 // R10 = max history size
MATH 15 11 5 // R11 = 1 : add two segments to start (1+food)
MATH 9 12 5 // R12 = R9 : tail is at first element of array
MATH 9 13 5 // R13 = R9 : head is at first element of array
SETDATA 1 3 9 4 // Save head into array
SETDATA 0 3 4 // Draw current head pos
SET 2 3 00101101
JMP 3 9
DATAC 00011110000000000000000000000000
DATAC 00000000000000000000000011111110
DATAC 00000000000000000000000010101000
DATAC 00000000000000000000000001010100
DATAC 00000000000000000000000000001110
DATAC 00000000000000000000000000000110
DATAC 00000000000000000000000001000011 // End address for overlay
MATH 13 2 5 // R2 = R13 : head of list
MATH 15 2 0 // R2++ : add one
MATH 10 2 4 // R2 %= R10 : mod size of list
MATH 12 3 5 // R3 = R12 : tail of list
MATH 10 3 4 // R3 %= R10 : mod size of list
IFJMP 1 19 0 // if head is one behind tail, then screen is full
MATH 15 11 0 // R11++ : add a segment to the snake tail
MATH 8 2 6 // R2 = random number within 0-13 : X pos - 1 of food
MATH 0 3 6 // R3 = random number within 0-5 : Y pos -1 of food
MATH 15 2 0 // R2++ : X pos of food
MATH 15 3 0 // R3++ : Y pos of food
PMOV 3 3 0 31 25 0 // R3 = .. : move Y pos into place
PMOV 2 3 28 31 28 0 // R3 = .. : move X pos into place
SET 3 3 00000010 // R3 = .. : set expected color
GETDATA 0 3 3 // R1 = current pixel in that position
PMOV 1 2 0 31 2 0 // R2 = R1(shifted) : current pixel for comparison
IFJMP 1 7 1 // if not the same, try again
PMOV 14 1 2 3 2 0 // R1 = .. : move color into place
SETDATA 0 3 1 // Draw food pixel
MOVI 2 21 // high bytes are set, so can't use SET here.
JMP 3 9
DATAC 00000000000000000000000010010100
DATAC 00000000000000000000000001010111 // End address for overlay
GETDATA 1 3 5 // R1 = read(disk, R5) : load direction value
MATH 1 4 0 // R4 += R1 : update position by adding direction value to it
PMOV 4 6 2 31 2 0 // R6[0:29] = R4[2:31] : set R6 to screen address of new pos
MATH 2 2 1 // R2 = 0
MATH 11 3 5 // R3 = R11
IFJMP 1 8 0 // if R11 == 0 then goto erase_segment
MATH 15 11 1 // R11-- : decrement number of segments to add
JMP 1 14
GETDATA 1 3 12 // R1 = read(disk, R12) : load tail position from history
MATH 15 12 0 // R12++ : increment history position of last tail segment
MATH 10 12 4 // R12 %= R10 : wrap around size of history
MATH 9 12 0 // R12 += R9 : re-add address of array to its index
PMOV 14 1 3 4 3 0 // R1[0:1] = R14[3:4] : set color for old pos to bg-color
SETDATA 0 3 1 // erase old position
GETDATA 0 3 6 // R1 = old pixel from the new position
SETDATA 0 3 4 // draw new position
MATH 1 6 5 // R6 = R1 : keep old pixel for later
SET 2 3 01011000
JMP 3 9
DATAC 00000000000000000000000001101000 // End address for overlay
MATH 15 13 0 // R13++ : increment history position of head segment
MATH 10 13 4 // R13 %= R10 : wrap around size of history
MATH 9 13 0 // R13 += R9 : re-add address of array to its index
SETDATA 1 3 13 4 // write current position to head of history array
PMOV 15 6 0 29 2 1 // R6[2:31] = 0 : clear all but color
PMOV 6 3 0 31 2 0 // R3 = R6(shifted) : copy color for comparison
SET 2 3 00000010 // R2 = 0b10 : the background color for comparison
IFJMP 1 14 0 // if old color was bg-color, goto done
SET 2 3 00000011
IFJMP 1 12 0 // if old color was food color, goto food
SET 2 3 01101001
JMP 3 9
SET 2 3 00101101
JMP 3 9
SET 2 3 10010100
JMP 3 9
DATAC 00000000000000000000000001111100 // End address for overlay
SETDATA 0 0 0000000000000000000000 // set status pixel to black to show crashed
SET 2 3 01110010 // R2 = 'r'
GETDATA 2 0 0000000000000000000000 // R1 = keyboard input
MATH 1 3 5 // R3 = R1 : for comparison
IFJMP 1 2 1 // if R2 != R3 then it was not pressed yet, so loop
MATH 13 3 5 // R3 = R13 : snake head
MATH 12 2 5 // R2 = R12 : snake tail
GETDATA 1 3 2 // R1 = read(disk, R2) : load tail position from history
PMOV 14 1 3 4 3 0 // R1[0:1] = R14[3:4] : set color for old pos to bg-color
SETDATA 0 3 1 // erase old position
IFJMP 1 15 0 // if tail == head then we're done
MATH 15 2 0 // R2++ : increment history position of last tail segment
MATH 10 2 4 // R2 %= R10 : wrap around size of history
MATH 9 2 0 // R2 += R9 : re-add address of array to its index
JMP 1 7 // loop back up for next tail segment
MATH 13 12 5 // R12 = R13 : reset tail position address to that of the head
MATH 1 6 5 // R6 = R1 : save head pixel so we don't need to re-load it
SET 2 3 01111101
JMP 3 9
DATAC 00000000000000000000000010010011 // End address for overlay
MATH 2 2 1 // R2 = 0
MATH 3 3 1 // R3 = 0
PMOV 6 3 6 8 9 0 // R3[29:31] = R6[6:8] : copy Y position
MOVI 7 20 // R7 = 7
MATH 7 3 4 // R3 %= R7 : mod-7 so that both 0 and 7 become 0
IFJMP 1 10 0 // if head-X is 0 or 15, goto make_white
PMOV 6 3 2 5 6 0 // R3[28:31] = R6[2:5] : copy X position
SET 7 3 00001111 // R7 = 15
MATH 7 3 4 // R3 %= R7 : mod-15 so that both 0 and 15 become 0
IFJMP 1 12 1 // if head-X is not 0 or 15, goto not_white
PMOV 15 6 30 31 2 1 // R6[0:1] = R15[30:31] : set color to 0b01
SETDATA 0 3 6 // draw pixel as white (to fix the border)
MOVI 4 21 // R4 = start position (and color)
SET 11 3 00000010 // R11 = 2 : add two segments immediately
PMOV 11 5 30 31 0 0 // R5[offset] = 0b10 : set starting direction
SETDATA 0 0 0100000000000000000000 // reset status pixel to white
SETDATA 1 3 13 4 // save head position to history
SETDATA 0 3 4 // draw head
SET 2 3 10010100
JMP 3 9
DATAC 00000000000000000000000000000111
DATAC 00011110000000000000000000000000
DATAC 00000000000000000000000010100111 // End address for overlay
MATH 2 2 1 // R2 = 0
GETDATA 2 0 0000000000000000000000 // R1 = keyboard input
MATH 1 6 5 // R6 = R1 : keep this key for later
GETDATA 2 0 0000000000000000000000 // R1 = keyboard input
MATH 1 3 5 // R3 = R1 : for comparison
IFJMP 1 2 1 // if R2 != R3 then another key was pressed, so loop
MATH 6 3 5 // R3 = R6 : for comparison
SET 2 3 01110111 // R2 = 'w' (up)
IFJMP 1 16 0 // if this key pressed, goto set_direction
SET 2 3 01100001 // R2 = 'a' (left)
IFJMP 1 16 0 // if this key pressed, goto set_direction
SET 2 3 01110011 // R2 = 's' (down)
IFJMP 1 16 0 // if this key pressed, goto set_direction
SET 2 3 01100100 // R2 = 'd' (right)
IFJMP 1 16 0 // if this key pressed, goto set_direction
JMP 1 17
PMOV 6 5 29 30 1 1 // R5[offset] = new direction (taken from key)
SET 2 3 01000100
JMP 3 9
NILLIST 84
DATAC 11111100000000000000000000000000 // 0b00 : a : left
DATAC 00000000100000000000000000000000 // 0b01 : s : down
DATAC 00000100000000000000000000000000 // 0b10 : d : right
DATAC 11111111100000000000000000000000 // 0b11 : w : up