Skip to content

Grid Movement with Bounds

Move an entity on a grid with boundary checking. Prevents moving outside the play area.

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

Z80 assembly
; =============================================================================
; 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=direction

Trade-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:

Z80 assembly
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

Patterns: Keyboard Reading, Game Loop (HALT)

Vault: ULA | ZX Spectrum