Close

Code Golf Snake: part deux

A project log for YGREC8

A byte-wide stripped-down version of the YGREC16 architecture

yann-guidon-ygdesYann Guidon / YGDES 12/17/2023 at 22:110 Comments

So far the last log brought us the following code:

; snake.y8

; in Bank 2 :
.EQU last_choice 1

.ORG 0
; init
set last_choice A2
set Right_Handler D2

;
; ...

Top_Handler:
  set D1 D2
  ; ...
  set Select_direction PC

Right_Handler:
  set D1 D2
; ...
  set Select_direction PC

Bottom_Handler:
  set D1 D2
; ...
  set Select_direction PC

Left_Handler:
  set D1 D2
; ...
  set Select_direction PC

; ....

Select_direction:
  set last_choice A2
  in buttons R1 ; read the external state
  and 15 R1 ; mask only the relevant buttons
  set D2 PC IFZ ; jump to last choice if no button
  add (jump_table-1) R1
  LDCL R1 PC ; lookup and jump

jump_table:
; .DW 0 ; 0000  => handled by the dispatcher
.DW Top_Handler     ; 0001 (it's a priority encoder)
.DW Right_Handler   ; 0010
.DW Top_Handler     ; 0011
.DW Bottom_Handler  ; 0100
.DW Top_Handler     ; 0101
.DW Right_Handler   ; 0110
.DW Top_Handler     ; 0111
.DW Left_Handler    ; 1000
.DW Top_Handler     ; 1001
.DW Right_Handler   ; 1010
.DW Top_Handler     ; 1011
.DW Bottom_Handler  ; 1100
.DW Top_Handler     ; 1101
.DW Right_Handler   ; 1110
.DW Top_Handler     ; 1111

This is where coordinates must be updated. I define the resolution of one of my flipdot boards: the 24×16 squares from Hannio.

; constants
.EQU MAX_X 24
.EQU MAX_Y 16
; variables' addresses:
.EQU X_Head 2
.EQU Y_Head 3
.EQU X_Tail 4
.EQU Y_Tail 5

Since the lookup/jump table uses only one index, we can use A1 again to point at other things and preload the X and Y before the jump:

Select_direction:
  set X_Head A1
  set D1 R2
  set Y_Head A1
  set D1 R3

  set last_choice A2
  in buttons R1 ; read the external state
  and 15 R1 ; mask only the relevant buttons
  set D2 PC IFZ ; jump to last choice if no button
  add (jump_table-1) R1
  LDCL R1 PC ; lookup and jump

And now that the "default" choice is handled individually, the jump table could be relocated and save one instruction in the loop. The very first, unused, entry can be a jump to the initialisation code.

.ORG 0
  set init PC ; jump over the jumptable
; 0000 is already decoded
.DW Top_Handler     ; 0001 (it's a priority encoder)
.DW Right_Handler   ; 0010
.DW Top_Handler     ; 0011
;.......
.DW Top_Handler     ; 1101
.DW Right_Handler   ; 1110
.DW Top_Handler     ; 1111

init:
  set X_Head A1
  set (MAX_X/2) D1
  set Y_Head A1
  set (MAX_Y/2) D1
;...

 The handlers can now be elaborated a bit:

Top_Handler:
  SET ($+1) D1
  ADD -1 R3
  SET -1 PC IFC ; jump to failure if negative
  ; ...

Right_Handler:
  set ($+1) D1
  ADD  1 R2
  CMPU (MAX_X-1) R2
  SET -1 PC IFC ; jump to failure if overflow
; ...

Bottom_Handler:
  set ($+1) D1
  ADD  1 R3
  CMPU (MAX_Y-1) R2
  SET -1 PC IFC ; jump to failure if overflow
; ...

Left_Handler:
  set ($+1) D1
  ADD  -1 R2
  SET -1 PC IFC ; jump to failure if negative
; ...

The address -1 in the code is a trampoline to the routine that handles the end of the game:

Failure:
  ; play the defeat song here.
  HLT

.ORG 255
  set Failure PC

This way, the immediate address is short and the jump can be conditional, called from about anywhere at no cost. The addresses 0 to 7 are already allocated to the priority/jump table, but there are 7 more "short addresses" for trampolines to other routines.

Now comes the big problem. The memory.

There are only 256 bytes per bank and only 2 banks by default. To get things going, we assume the playground is only 16×16 sites so one site is one byte. This is enough to store the required information since this buffer does not directly represent the graphics (each dot is black or white, controlled externally).

Each site needs to store the following information:

That's 4 bits only out of 8 and we will see later how to compact them. Let's first lay out the logic and solve addressing later.

Discussions