Code status: illustrative code. Adapt the listing to your program and check it on your target machine. This page links no assembly or execution record for the listing itself.
Overview
Grid-based games need movement that respects boundaries. Check direction input, verify the new position is valid, then update. This pattern prevents the cursor or player from leaving the play area while keeping movement responsive.
Calling contract
Initialise cursor_row and cursor_col inside 0–7 and supply direction 0–4. Here key_pressed is a direction code, not inherently an edge: the maintained keyboard adapter supplies held codes, so call only on a new press if you want one move per press. Space code 5 moves neither axis.
Own the two coordinate bytes and one shared key_pressed byte. If the adapter already declares that byte, omit the duplicate declaration below. move_cursor changes AF; its rendering hooks determine any additional register effects. Keep clear_cursor and draw_cursor consistent with the same position and display ownership. The supplied hooks deliberately draw nothing; replace them before expecting a visible cursor.
Code
; =============================================================================
; GRID MOVEMENT WITH BOUNDS - ZX SPECTRUM
; Move cursor/entity with boundary checking
; Taught: Game 1 (Ink War), Unit 1
; Cost depends on the assembled caller and memory placement.
; =============================================================================
BOARD_SIZE equ 8 ; Grid dimensions (8x8)
; Move entity based on key_pressed value
; key_pressed: 1=up, 2=down, 3=left, 4=right
move_cursor:
ld a, (key_pressed)
or a
ret z ; No key pressed
call clear_cursor ; Remove old cursor visual
ld a, (key_pressed)
; === Check Up ===
cp 1
jr nz, .not_up
ld a, (cursor_row)
or a ; At row 0?
jr z, .done ; Already at top - can't move
dec a
ld (cursor_row), a
jr .done
.not_up:
; === Check Down ===
cp 2
jr nz, .not_down
ld a, (cursor_row)
cp BOARD_SIZE-1 ; At last row?
jr z, .done ; Already at bottom - can't move
inc a
ld (cursor_row), a
jr .done
.not_down:
; === Check Left ===
cp 3
jr nz, .not_left
ld a, (cursor_col)
or a ; At column 0?
jr z, .done ; Already at left - can't move
dec a
ld (cursor_col), a
jr .done
.not_left:
; === Check Right ===
cp 4
jr nz, .done
ld a, (cursor_col)
cp BOARD_SIZE-1 ; At last column?
jr z, .done ; Already at right - can't move
inc a
ld (cursor_col), a
.done:
call draw_cursor ; Draw new cursor
ret
; Placeholder routines
clear_cursor: ret
draw_cursor: ret
; Variables
cursor_row: defb 0 ; Current row (0 to BOARD_SIZE-1)
cursor_col: defb 0 ; Current column (0 to BOARD_SIZE-1)
key_pressed: defb 0 ; Direction: 0=none, 1-4=directionTrade-offs
| Aspect | Cost |
|---|---|
| CPU | Not measured for this illustrative listing; include the caller and hardware scheduling costs |
| Memory | Assemble the routine and supplied drawing hooks to obtain its size |
| Limitation | Only handles one direction per frame |
When to use: Grid-based games (board games, puzzle games, tactical games).
When to avoid: Continuous pixel-based movement - use velocity and pixel coordinates instead.
How the Bounds Check Works
Each direction uses a different boundary condition:
| Direction | Check | Boundary |
|---|---|---|
| Up | or a (is zero?) |
Row 0 |
| Down | cp SIZE-1 |
Row 7 |
| Left | or a (is zero?) |
Column 0 |
| Right | cp SIZE-1 |
Column 7 |
The or a instruction sets the zero flag if A is 0, providing a quick “at minimum” check without needing a compare.
Extending to Variable Boundaries
For non-square or scrolling play areas:
MIN_ROW equ 2 ; Top boundary
MAX_ROW equ 21 ; Bottom boundary
MIN_COL equ 1 ; Left boundary
MAX_COL equ 30 ; Right boundary
; Up check becomes:
ld a, (cursor_row)
cp MIN_ROW ; At minimum row?
jr z, .done
; Down check becomes:
ld a, (cursor_row)
cp MAX_ROW
jr z, .doneRelated
Patterns: Keyboard Reading, Game Loop (HALT)
Vault: ULA | ZX Spectrum