Move Validation
Error feedback when you try to claim an occupied cell. The game pushes back.
Try claiming a cell that’s already taken. In the previous unit, nothing happened. That’s confusing - did the game register your keypress? Did something break?
This unit adds clear feedback: when you try to claim an occupied cell, you hear an error buzz and the border flashes red. The turn doesn’t change. You know exactly what went wrong.
Run It
pasmonext --sna inkwar.asm inkwar.sna

Claim a few cells. Then try to claim one that’s already taken. You’ll hear a harsh buzz and see the border flash red. Your turn doesn’t switch - you get another chance.
Validation Logic
The updated try_claim routine now branches on the cell state:
; ----------------------------------------------------------------------------
; Try Claim Cell
; ----------------------------------------------------------------------------
; Attempts to claim the current cell. If already claimed, shows error feedback.
try_claim:
call get_cell_state
or a
jr z, .tc_valid
; Cell already claimed - error feedback
call sound_error
call flash_border_error
call update_border ; Restore correct border colour
ret
.tc_valid:
; Valid move - claim the cell
call claim_cell
call sound_claim
ld a, (current_player)
xor 3
ld (current_player), a
call draw_ui ; Update scores and turn indicator
call update_border
call draw_cursor
ret
The key change is replacing ret nz with a jump to error handling. If the cell is empty (state = 0), we proceed with the claim. If it’s occupied (state ≠ 0), we provide feedback and return without changing turns.
The Error Sound
A harsh, low-frequency buzz signals “that’s wrong”:
; ----------------------------------------------------------------------------
; Sound - Error
; ----------------------------------------------------------------------------
; Harsh buzz for invalid move
sound_error:
ld b, 30 ; Duration
.se_loop:
push bc
; Low frequency buzz (longer delay = lower pitch)
ld a, $10
out (KEY_PORT), a
ld c, 80 ; Longer delay for low pitch
.se_delay1:
dec c
jr nz, .se_delay1
xor a
out (KEY_PORT), a
ld c, 80
.se_delay2:
dec c
jr nz, .se_delay2
pop bc
djnz .se_loop
ret
Compared to the pleasant rising tone for valid claims, this sound is deliberately unpleasant:
- Longer delay (80 cycles) creates a lower pitch
- Shorter duration (30 loops) keeps it brief
- The result is a quick “BZZT” that says “nope”
The Border Flash
Visual feedback reinforces the audio:
; ----------------------------------------------------------------------------
; Flash Border Error
; ----------------------------------------------------------------------------
; Flash border red briefly to indicate error
flash_border_error:
; Flash red 3 times
ld b, 3
.fbe_loop:
push bc
; Red border
ld a, ERROR_BORDER
out (KEY_PORT), a
; Short delay (about 3 frames)
ld bc, 8000
.fbe_delay1:
dec bc
ld a, b
or c
jr nz, .fbe_delay1
; Black border (brief off)
xor a
out (KEY_PORT), a
; Short delay
ld bc, 4000
.fbe_delay2:
dec bc
ld a, b
or c
jr nz, .fbe_delay2
pop bc
djnz .fbe_loop
ret
The border flashes red three times, with black gaps between flashes. Then update_border restores the correct player colour. The flashing is fast enough to feel urgent but not so fast it’s imperceptible.
Why Both Audio and Visual?
Good feedback uses multiple channels:
- Audio grabs attention even when you’re not looking at the screen
- Visual confirms what happened and draws your eye to the relevant area
- Together, they create unmistakable feedback
In 1980s arcades, this pattern was everywhere. Error sounds and screen flashes told players “try again” without breaking the flow.
Delay Timing
The delays use simple countdown loops:
ld bc, 8000
.delay:
dec bc
ld a, b
or c
jr nz, .delay
At 3.5 MHz, this gives roughly 8000 × 30 cycles ≈ 70ms per flash. We check b or c because DEC BC doesn’t set the zero flag - we need to explicitly test if BC has reached zero.
The Complete Code
; ============================================================================
; INK WAR - Unit 5: Move Validation
; ============================================================================
; Adds error feedback when trying to claim an already-occupied cell.
; Invalid moves: error buzz + red border flash. Turn doesn't change.
;
; Controls: Q=Up, A=Down, O=Left, P=Right, SPACE=Claim
; ============================================================================
org 32768
; ----------------------------------------------------------------------------
; Constants
; ----------------------------------------------------------------------------
ATTR_BASE equ $5800
DISPLAY_FILE equ $4000
CHAR_SET equ $3C00 ; ROM character set base address
BOARD_ROW equ 8
BOARD_COL equ 12
BOARD_SIZE equ 8
; Display positions
SCORE_ROW equ 2 ; Score display row
P1_SCORE_COL equ 10 ; "P1: nn" column
P2_SCORE_COL equ 18 ; "P2: nn" column
TURN_ROW equ 4 ; Turn indicator row
TURN_COL equ 14 ; Turn indicator column
; Customised colours (from Unit 3)
EMPTY_ATTR equ %01101000 ; Cyan paper + BRIGHT
BORDER_ATTR equ %01110000 ; Yellow paper + BRIGHT
CURSOR_ATTR equ %01111000 ; White paper + BRIGHT
P1_ATTR equ %01010000 ; Red paper + BRIGHT
P2_ATTR equ %01001000 ; Blue paper + BRIGHT
P1_CURSOR equ %01111010 ; White paper + Red ink + BRIGHT
P2_CURSOR equ %01111001 ; White paper + Blue ink + BRIGHT
; Text display attributes
TEXT_ATTR equ %00111000 ; White paper, black ink
P1_TEXT equ %01010111 ; Red paper, white ink + BRIGHT
P2_TEXT equ %01001111 ; Blue paper, white ink + BRIGHT
P1_BORDER equ 2
P2_BORDER equ 1
ERROR_BORDER equ 2 ; Red border for errors
; Keyboard ports
KEY_PORT equ $fe
ROW_QAOP equ $fb
ROW_ASDF equ $fd
ROW_YUIOP equ $df
ROW_SPACE equ $7f
; Game states
STATE_EMPTY equ 0
STATE_P1 equ 1
STATE_P2 equ 2
; ----------------------------------------------------------------------------
; Entry Point
; ----------------------------------------------------------------------------
start:
call init_screen
call init_game
call draw_board_border
call draw_board
call draw_ui ; Draw score and turn display
call draw_cursor
call update_border
main_loop:
halt
call read_keyboard
call handle_input
jp main_loop
; ----------------------------------------------------------------------------
; Initialise Screen
; ----------------------------------------------------------------------------
init_screen:
xor a
out (KEY_PORT), a
; Clear display file (pixels)
ld hl, DISPLAY_FILE
ld de, DISPLAY_FILE+1
ld bc, 6143
ld (hl), 0
ldir
; Clear all attributes to white paper, black ink
ld hl, ATTR_BASE
ld de, ATTR_BASE+1
ld bc, 767
ld (hl), TEXT_ATTR ; White background for text areas
ldir
ret
; ----------------------------------------------------------------------------
; Draw UI
; ----------------------------------------------------------------------------
; Draws score display and turn indicator
draw_ui:
call draw_scores
call draw_turn_indicator
ret
; ----------------------------------------------------------------------------
; Draw Scores
; ----------------------------------------------------------------------------
; Displays "P1: nn P2: nn" with player colours
draw_scores:
; Count cells for each player
call count_cells
; Draw P1 label "P1:"
ld b, SCORE_ROW
ld c, P1_SCORE_COL
ld a, 'P'
call print_char
inc c
ld a, '1'
call print_char
inc c
ld a, ':'
call print_char
inc c
; Print P1 score
ld a, (p1_count)
call print_two_digits
; Set P1 colour attribute
ld a, SCORE_ROW
ld c, P1_SCORE_COL
ld b, 5 ; "P1:nn" = 5 characters
ld e, P1_TEXT
call set_attr_range
; Draw P2 label "P2:"
ld b, SCORE_ROW
ld c, P2_SCORE_COL
ld a, 'P'
call print_char
inc c
ld a, '2'
call print_char
inc c
ld a, ':'
call print_char
inc c
; Print P2 score
ld a, (p2_count)
call print_two_digits
; Set P2 colour attribute
ld a, SCORE_ROW
ld c, P2_SCORE_COL
ld b, 5
ld e, P2_TEXT
call set_attr_range
ret
; ----------------------------------------------------------------------------
; Draw Turn Indicator
; ----------------------------------------------------------------------------
; Shows "TURN" with current player's colour
draw_turn_indicator:
; Print "TURN"
ld b, TURN_ROW
ld c, TURN_COL
ld a, 'T'
call print_char
inc c
ld a, 'U'
call print_char
inc c
ld a, 'R'
call print_char
inc c
ld a, 'N'
call print_char
; Set attribute based on current player
ld a, (current_player)
cp 1
jr z, .dti_p1
ld e, P2_TEXT
jr .dti_set
.dti_p1:
ld e, P1_TEXT
.dti_set:
ld a, TURN_ROW
ld c, TURN_COL
ld b, 4 ; "TURN" = 4 chars
call set_attr_range
ret
; ----------------------------------------------------------------------------
; Print Character
; ----------------------------------------------------------------------------
; A = ASCII character (32-127), B = row (0-23), C = column (0-31)
; Writes character directly to display file using ROM character set
print_char:
push bc
push de
push hl
push af
; Calculate character data address: CHAR_SET + char*8
ld l, a
ld h, 0
add hl, hl
add hl, hl
add hl, hl ; HL = char * 8
ld de, CHAR_SET
add hl, de ; HL = source address
push hl ; Save character data address
; Calculate display file address
; Screen address: high byte varies with row, low byte = column
ld a, b ; A = row (0-23)
and %00011000 ; Get which third (0, 8, 16)
add a, $40 ; Add display file base high byte
ld d, a
ld a, b ; A = row
and %00000111 ; Get line within character row
rrca
rrca
rrca ; Shift to bits 5-7
add a, c ; Add column
ld e, a ; DE = screen address
pop hl ; HL = character data
; Copy 8 bytes (8 pixel rows of character)
ld b, 8
.pc_loop:
ld a, (hl)
ld (de), a
inc hl
inc d ; Next screen line (add 256)
djnz .pc_loop
pop af
pop hl
pop de
pop bc
ret
; ----------------------------------------------------------------------------
; Print Two Digits
; ----------------------------------------------------------------------------
; A = number (0-99), B = row, C = column (will advance by 2)
; Prints number as two digits
print_two_digits:
push bc
; Calculate tens digit
ld d, 0 ; Tens counter
.ptd_tens:
cp 10
jr c, .ptd_print
sub 10
inc d
jr .ptd_tens
.ptd_print:
push af ; Save units digit
; Print tens digit
ld a, d
add a, '0'
call print_char
inc c
; Print units digit
pop af
add a, '0'
call print_char
inc c
pop bc
ret
; ----------------------------------------------------------------------------
; Count Cells
; ----------------------------------------------------------------------------
; Counts cells owned by each player
count_cells:
xor a
ld (p1_count), a
ld (p2_count), a
ld hl, board_state
ld b, 64 ; 64 cells
.cc_loop:
ld a, (hl)
cp STATE_P1
jr nz, .cc_not_p1
ld a, (p1_count)
inc a
ld (p1_count), a
jr .cc_next
.cc_not_p1:
cp STATE_P2
jr nz, .cc_next
ld a, (p2_count)
inc a
ld (p2_count), a
.cc_next:
inc hl
djnz .cc_loop
ret
; ----------------------------------------------------------------------------
; Set Attribute Range
; ----------------------------------------------------------------------------
; A = row, C = start column, B = count, E = attribute
set_attr_range:
push bc
push de
; Calculate start address: ATTR_BASE + row*32 + col
ld l, a
ld h, 0
add hl, hl
add hl, hl
add hl, hl
add hl, hl
add hl, hl ; HL = row * 32
ld a, c
add a, l
ld l, a
ld bc, ATTR_BASE
add hl, bc ; HL = attribute address
pop de ; E = attribute
pop bc ; B = count
.sar_loop:
ld (hl), e
inc hl
djnz .sar_loop
ret
; ----------------------------------------------------------------------------
; Update Border
; ----------------------------------------------------------------------------
update_border:
ld a, (current_player)
cp 1
jr z, .ub_p1
ld a, P2_BORDER
jr .ub_set
.ub_p1:
ld a, P1_BORDER
.ub_set:
out (KEY_PORT), a
ret
; ----------------------------------------------------------------------------
; Initialise Game State
; ----------------------------------------------------------------------------
init_game:
ld hl, board_state
ld b, 64
xor a
.ig_loop:
ld (hl), a
inc hl
djnz .ig_loop
ld a, 1
ld (current_player), a
xor a
ld (cursor_row), a
ld (cursor_col), a
ld (p1_count), a
ld (p2_count), a
ret
; ----------------------------------------------------------------------------
; Draw Board Border
; ----------------------------------------------------------------------------
draw_board_border:
ld c, BOARD_ROW-1
ld d, BOARD_COL-1
ld b, BOARD_SIZE+2
call draw_border_row
ld c, BOARD_ROW+BOARD_SIZE
ld d, BOARD_COL-1
ld b, BOARD_SIZE+2
call draw_border_row
ld c, BOARD_ROW
ld d, BOARD_COL-1
ld b, BOARD_SIZE
call draw_border_col
ld c, BOARD_ROW
ld d, BOARD_COL+BOARD_SIZE
ld b, BOARD_SIZE
call draw_border_col
ret
draw_border_row:
push bc
.dbr_loop:
push bc
push de
ld a, c
ld l, a
ld h, 0
add hl, hl
add hl, hl
add hl, hl
add hl, hl
add hl, hl
ld a, d
add a, l
ld l, a
ld bc, ATTR_BASE
add hl, bc
ld (hl), BORDER_ATTR
pop de
pop bc
inc d
djnz .dbr_loop
pop bc
ret
draw_border_col:
push bc
.dbc_loop:
push bc
push de
ld a, c
ld l, a
ld h, 0
add hl, hl
add hl, hl
add hl, hl
add hl, hl
add hl, hl
ld a, d
add a, l
ld l, a
ld bc, ATTR_BASE
add hl, bc
ld (hl), BORDER_ATTR
pop de
pop bc
inc c
djnz .dbc_loop
pop bc
ret
; ----------------------------------------------------------------------------
; Draw Board
; ----------------------------------------------------------------------------
draw_board:
ld b, BOARD_SIZE
ld c, BOARD_ROW
.db_row:
push bc
ld b, BOARD_SIZE
ld d, BOARD_COL
.db_col:
push bc
ld a, c
ld l, a
ld h, 0
add hl, hl
add hl, hl
add hl, hl
add hl, hl
add hl, hl
ld a, d
add a, l
ld l, a
ld bc, ATTR_BASE
add hl, bc
ld (hl), EMPTY_ATTR
pop bc
inc d
djnz .db_col
pop bc
inc c
djnz .db_row
ret
; ----------------------------------------------------------------------------
; Draw Cursor
; ----------------------------------------------------------------------------
draw_cursor:
call get_cell_state
cp STATE_P1
jr z, .dc_p1
cp STATE_P2
jr z, .dc_p2
ld a, CURSOR_ATTR
jr .dc_set
.dc_p1:
ld a, P1_CURSOR
jr .dc_set
.dc_p2:
ld a, P2_CURSOR
.dc_set:
push af
ld a, (cursor_row)
add a, BOARD_ROW
ld l, a
ld h, 0
add hl, hl
add hl, hl
add hl, hl
add hl, hl
add hl, hl
ld a, (cursor_col)
add a, BOARD_COL
add a, l
ld l, a
ld bc, ATTR_BASE
add hl, bc
pop af
ld (hl), a
ret
; ----------------------------------------------------------------------------
; Clear Cursor
; ----------------------------------------------------------------------------
clear_cursor:
call get_cell_state
cp STATE_P1
jr z, .clc_p1
cp STATE_P2
jr z, .clc_p2
ld a, EMPTY_ATTR
jr .clc_set
.clc_p1:
ld a, P1_ATTR
jr .clc_set
.clc_p2:
ld a, P2_ATTR
.clc_set:
push af
ld a, (cursor_row)
add a, BOARD_ROW
ld l, a
ld h, 0
add hl, hl
add hl, hl
add hl, hl
add hl, hl
add hl, hl
ld a, (cursor_col)
add a, BOARD_COL
add a, l
ld l, a
ld bc, ATTR_BASE
add hl, bc
pop af
ld (hl), a
ret
; ----------------------------------------------------------------------------
; Get Cell State
; ----------------------------------------------------------------------------
get_cell_state:
ld a, (cursor_row)
add a, a
add a, a
add a, a
ld hl, board_state
ld b, 0
ld c, a
add hl, bc
ld a, (cursor_col)
ld c, a
add hl, bc
ld a, (hl)
ret
; ----------------------------------------------------------------------------
; Read Keyboard
; ----------------------------------------------------------------------------
read_keyboard:
xor a
ld (key_pressed), a
ld a, ROW_QAOP
in a, (KEY_PORT)
bit 0, a
jr nz, .rk_not_q
ld a, 1
ld (key_pressed), a
ret
.rk_not_q:
ld a, ROW_ASDF
in a, (KEY_PORT)
bit 0, a
jr nz, .rk_not_a
ld a, 2
ld (key_pressed), a
ret
.rk_not_a:
ld a, ROW_YUIOP
in a, (KEY_PORT)
bit 1, a
jr nz, .rk_not_o
ld a, 3
ld (key_pressed), a
ret
.rk_not_o:
ld a, ROW_YUIOP
in a, (KEY_PORT)
bit 0, a
jr nz, .rk_not_p
ld a, 4
ld (key_pressed), a
ret
.rk_not_p:
ld a, ROW_SPACE
in a, (KEY_PORT)
bit 0, a
jr nz, .rk_not_space
ld a, 5
ld (key_pressed), a
.rk_not_space:
ret
; ----------------------------------------------------------------------------
; Handle Input
; ----------------------------------------------------------------------------
handle_input:
ld a, (key_pressed)
or a
ret z
cp 5
jr z, try_claim
call clear_cursor
ld a, (key_pressed)
cp 1
jr nz, .hi_not_up
ld a, (cursor_row)
or a
jr z, .hi_done
dec a
ld (cursor_row), a
jr .hi_done
.hi_not_up:
cp 2
jr nz, .hi_not_down
ld a, (cursor_row)
cp BOARD_SIZE-1
jr z, .hi_done
inc a
ld (cursor_row), a
jr .hi_done
.hi_not_down:
cp 3
jr nz, .hi_not_left
ld a, (cursor_col)
or a
jr z, .hi_done
dec a
ld (cursor_col), a
jr .hi_done
.hi_not_left:
cp 4
jr nz, .hi_done
ld a, (cursor_col)
cp BOARD_SIZE-1
jr z, .hi_done
inc a
ld (cursor_col), a
.hi_done:
call draw_cursor
ret
; ----------------------------------------------------------------------------
; Try Claim Cell
; ----------------------------------------------------------------------------
try_claim:
call get_cell_state
or a
jr z, .tc_valid
; Cell already claimed - error feedback
call sound_error
call flash_border_error
call update_border ; Restore correct border colour
ret
.tc_valid:
; Valid move - claim the cell
call claim_cell
call sound_claim
ld a, (current_player)
xor 3
ld (current_player), a
call draw_ui ; Update scores and turn indicator
call update_border
call draw_cursor
ret
; ----------------------------------------------------------------------------
; Claim Cell
; ----------------------------------------------------------------------------
claim_cell:
ld a, (cursor_row)
add a, a
add a, a
add a, a
ld hl, board_state
ld b, 0
ld c, a
add hl, bc
ld a, (cursor_col)
ld c, a
add hl, bc
ld a, (current_player)
ld (hl), a
push af
ld a, (cursor_row)
add a, BOARD_ROW
ld l, a
ld h, 0
add hl, hl
add hl, hl
add hl, hl
add hl, hl
add hl, hl
ld a, (cursor_col)
add a, BOARD_COL
add a, l
ld l, a
ld bc, ATTR_BASE
add hl, bc
pop af
cp 1
jr z, .clm_is_p1
ld (hl), P2_ATTR
ret
.clm_is_p1:
ld (hl), P1_ATTR
ret
; ----------------------------------------------------------------------------
; Sound - Claim
; ----------------------------------------------------------------------------
sound_claim:
ld hl, 400
ld b, 20
.scl_loop:
push bc
push hl
ld b, h
ld c, l
.scl_tone:
ld a, $10
out (KEY_PORT), a
call .scl_delay
xor a
out (KEY_PORT), a
call .scl_delay
dec bc
ld a, b
or c
jr nz, .scl_tone
pop hl
pop bc
ld de, 20
or a
sbc hl, de
djnz .scl_loop
ret
.scl_delay:
push bc
ld b, 5
.scl_delay_loop:
djnz .scl_delay_loop
pop bc
ret
; ----------------------------------------------------------------------------
; Sound - Error
; ----------------------------------------------------------------------------
; Harsh buzz for invalid move
sound_error:
ld b, 30 ; Duration
.se_loop:
push bc
; Low frequency buzz (longer delay = lower pitch)
ld a, $10
out (KEY_PORT), a
ld c, 80 ; Longer delay for low pitch
.se_delay1:
dec c
jr nz, .se_delay1
xor a
out (KEY_PORT), a
ld c, 80
.se_delay2:
dec c
jr nz, .se_delay2
pop bc
djnz .se_loop
ret
; ----------------------------------------------------------------------------
; Flash Border Error
; ----------------------------------------------------------------------------
; Flash border red briefly to indicate error
flash_border_error:
; Flash red 3 times
ld b, 3
.fbe_loop:
push bc
; Red border
ld a, ERROR_BORDER
out (KEY_PORT), a
; Short delay (about 3 frames)
ld bc, 8000
.fbe_delay1:
dec bc
ld a, b
or c
jr nz, .fbe_delay1
; Black border (brief off)
xor a
out (KEY_PORT), a
; Short delay
ld bc, 4000
.fbe_delay2:
dec bc
ld a, b
or c
jr nz, .fbe_delay2
pop bc
djnz .fbe_loop
ret
; ----------------------------------------------------------------------------
; Variables
; ----------------------------------------------------------------------------
cursor_row: defb 0
cursor_col: defb 0
key_pressed: defb 0
current_player: defb 1
p1_count: defb 0
p2_count: defb 0
board_state: defs 64, 0
; ----------------------------------------------------------------------------
; End
; ----------------------------------------------------------------------------
end start
Try This: Different Error Sound
; Higher pitch, longer duration (more annoying!)
sound_error:
ld b, 50 ; Longer
.se_loop:
; ... same inner loop ...
ld c, 30 ; Shorter delay = higher pitch
Try This: Screen Flash Instead
Instead of flashing the border, flash the whole screen by temporarily setting all attributes to red:
; Warning: This is more disruptive!
flash_screen_error:
ld hl, $5800
ld de, $5801
ld bc, 767
ld (hl), %01010000 ; Red paper
ldir
; ... delay ...
; Then redraw everything
What You’ve Learnt
- Input validation - Check before acting, provide feedback for invalid input
- Multi-channel feedback - Combine audio and visual cues for clarity
- Error sounds - Low frequency + short duration = unpleasant buzz
- Border control - OUT ($FE), A with colour in bits 0-2
- Delay loops - BC countdown for timing (DEC BC doesn’t set flags, need OR test)
What’s Next
In Unit 6, we’ll detect when the board is full and declare a winner. The game gets a proper ending.
What Changed
| 1 | 1 | ; ============================================================================ | |
| 2 | - | ; INK WAR - Unit 4: Score and Turn Display | |
| 2 | + | ; INK WAR - Unit 5: Move Validation | |
| 3 | 3 | ; ============================================================================ | |
| 4 | - | ; Adds score display and turn indicator to the game. | |
| 5 | - | ; Shows "P1: nn P2: nn" and indicates whose turn it is. | |
| 4 | + | ; Adds error feedback when trying to claim an already-occupied cell. | |
| 5 | + | ; Invalid moves: error buzz + red border flash. Turn doesn't change. | |
| 6 | 6 | ; | |
| 7 | 7 | ; Controls: Q=Up, A=Down, O=Left, P=Right, SPACE=Claim | |
| 8 | 8 | ; ============================================================================ | |
| ... | |||
| 45 | 45 | | |
| 46 | 46 | P1_BORDER equ 2 | |
| 47 | 47 | P2_BORDER equ 1 | |
| 48 | + | ERROR_BORDER equ 2 ; Red border for errors | |
| 48 | 49 | | |
| 49 | 50 | ; Keyboard ports | |
| 50 | 51 | KEY_PORT equ $fe | |
| ... | |||
| 757 | 758 | try_claim: | |
| 758 | 759 | call get_cell_state | |
| 759 | 760 | or a | |
| 760 | - | ret nz | |
| 761 | + | jr z, .tc_valid | |
| 762 | + | | |
| 763 | + | ; Cell already claimed - error feedback | |
| 764 | + | call sound_error | |
| 765 | + | call flash_border_error | |
| 766 | + | call update_border ; Restore correct border colour | |
| 767 | + | ret | |
| 761 | 768 | | |
| 769 | + | .tc_valid: | |
| 770 | + | ; Valid move - claim the cell | |
| 762 | 771 | call claim_cell | |
| 763 | 772 | call sound_claim | |
| 764 | 773 | | |
| ... | |||
| 860 | 869 | ld b, 5 | |
| 861 | 870 | .scl_delay_loop: | |
| 862 | 871 | djnz .scl_delay_loop | |
| 872 | + | pop bc | |
| 873 | + | ret | |
| 874 | + | | |
| 875 | + | ; ---------------------------------------------------------------------------- | |
| 876 | + | ; Sound - Error | |
| 877 | + | ; ---------------------------------------------------------------------------- | |
| 878 | + | ; Harsh buzz for invalid move | |
| 879 | + | | |
| 880 | + | sound_error: | |
| 881 | + | ld b, 30 ; Duration | |
| 882 | + | | |
| 883 | + | .se_loop: | |
| 884 | + | push bc | |
| 885 | + | | |
| 886 | + | ; Low frequency buzz (longer delay = lower pitch) | |
| 887 | + | ld a, $10 | |
| 888 | + | out (KEY_PORT), a | |
| 889 | + | ld c, 80 ; Longer delay for low pitch | |
| 890 | + | .se_delay1: | |
| 891 | + | dec c | |
| 892 | + | jr nz, .se_delay1 | |
| 893 | + | | |
| 894 | + | xor a | |
| 895 | + | out (KEY_PORT), a | |
| 896 | + | ld c, 80 | |
| 897 | + | .se_delay2: | |
| 898 | + | dec c | |
| 899 | + | jr nz, .se_delay2 | |
| 900 | + | | |
| 901 | + | pop bc | |
| 902 | + | djnz .se_loop | |
| 903 | + | | |
| 904 | + | ret | |
| 905 | + | | |
| 906 | + | ; ---------------------------------------------------------------------------- | |
| 907 | + | ; Flash Border Error | |
| 908 | + | ; ---------------------------------------------------------------------------- | |
| 909 | + | ; Flash border red briefly to indicate error | |
| 910 | + | | |
| 911 | + | flash_border_error: | |
| 912 | + | ; Flash red 3 times | |
| 913 | + | ld b, 3 | |
| 914 | + | | |
| 915 | + | .fbe_loop: | |
| 916 | + | push bc | |
| 917 | + | | |
| 918 | + | ; Red border | |
| 919 | + | ld a, ERROR_BORDER | |
| 920 | + | out (KEY_PORT), a | |
| 921 | + | | |
| 922 | + | ; Short delay (about 3 frames) | |
| 923 | + | ld bc, 8000 | |
| 924 | + | .fbe_delay1: | |
| 925 | + | dec bc | |
| 926 | + | ld a, b | |
| 927 | + | or c | |
| 928 | + | jr nz, .fbe_delay1 | |
| 929 | + | | |
| 930 | + | ; Black border (brief off) | |
| 931 | + | xor a | |
| 932 | + | out (KEY_PORT), a | |
| 933 | + | | |
| 934 | + | ; Short delay | |
| 935 | + | ld bc, 4000 | |
| 936 | + | .fbe_delay2: | |
| 937 | + | dec bc | |
| 938 | + | ld a, b | |
| 939 | + | or c | |
| 940 | + | jr nz, .fbe_delay2 | |
| 941 | + | | |
| 863 | 942 | pop bc | |
| 943 | + | djnz .fbe_loop | |
| 944 | + | | |
| 864 | 945 | ret | |
| 865 | 946 | | |
| 866 | 947 | ; ---------------------------------------------------------------------------- |