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.
Code
; =============================================================================
; GRID MOVEMENT WITH BOUNDS - ZX SPECTRUM
; Move cursor/entity with boundary checking
; Taught: Game 1 (Ink War), Unit 1
; CPU: ~80 cycles | Memory: ~60 bytes
; =============================================================================
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=direction
Trade-offs
| Aspect | Cost |
|---|---|
| CPU | ~80 cycles per move |
| Memory | ~60 bytes |
| 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, .done
Related
Patterns: Keyboard Reading, Game Loop (HALT)
Vault: ZX Spectrum