million-perfect-letters/src/puzzle.a

354 lines
10 KiB
Plaintext

;license:MIT
;(c) 2020 by 4am
;
; data storage routines for puzzles
;
; Public functions:
; - InitPuzzleStorage
; - AddLineToPuzzle
; - AddTargetWordToPuzzle
; - CheckForTargetWord
; - FindLetterInColumn
; - ScrollPuzzleDown
; - ScrollPuzzleDownUnconditionally
; - ScrollPuzzleUp
; - IsPuzzleComplete
puzzle_logical_width = $0300 ; [0x01 byte ] number of letters per word (4..7)
puzzle_logical_height = $0301 ; [0x01 byte ] number of rows with letters (1..5)
puzzle_offsets = $0302 ; [0x08 bytes] how far each column has been scrolled down from top of grid
; 9 rows of data for current puzzle
; each byte is one of
; - letter with high bit off (unmatched letter)
; - letter with high bit on (matched letter, displayed in grey)
; - 0x00 (unused space)
; if letters per word is less than 7, extra bytes at end of each row are guaranteed to be 0x00
puzzle_data0 = $030A ; [0x08 bytes] character data
puzzle_data1 = $0312 ; [0x08 bytes]
puzzle_data2 = $031A ; [0x08 bytes]
puzzle_data3 = $0322 ; [0x08 bytes]
puzzle_data4 = $032A ; [0x08 bytes]
puzzle_data5 = $0332 ; [0x08 bytes]
puzzle_data6 = $033A ; [0x08 bytes]
puzzle_data7 = $0342 ; [0x08 bytes]
puzzle_data8 = $034A ; [0x08 bytes]
target_word_count = $0352 ; [0x01 byte ] number of records in <target_words>
target_words = $0353 ; [0x80 bytes]
; each 8-byte record is a <puzzle_logical_width>-length word, no prefix
; extra bytes in each record are guaranteed to be 0x00
; max 16 words per puzzle
;------------------------------------------------------------------------------
; InitPuzzleStorage
; initialize internal data structures before playing a new puzzle
;
; in: A = logical puzzle width (number of letters in each word, 4..7)
; out: A = 0
; X = 0
; Y preserved
; flags clobbered
;------------------------------------------------------------------------------
InitPuzzleStorage
sta puzzle_logical_width
ldx #$CF
lda #0
- sta puzzle_logical_width, x
dex
bne -
rts
;------------------------------------------------------------------------------
; AddLineToPuzzle
; add one puzzle line while constructing a puzzle
;
; in: ($FE) points to <puzzle_logical_width>-length character buffer
; (no prefix, no suffix)
; out: all registers & flags clobbered
;------------------------------------------------------------------------------
AddLineToPuzzle
lda puzzle_logical_height
asl
asl
asl
tax
ldy #0
- lda ($FE), y
cmp #$20
bne +
lda #0
+ sta puzzle_data0, x
inx
iny
cpy puzzle_logical_width
bne -
inc puzzle_logical_height
rts
;------------------------------------------------------------------------------
; AddTargetWordToPuzzle
; add one target word while constructing a puzzle
;
; in: ($FE) points to <puzzle_logical_width> length character buffer
; (no prefix, no suffix)
; out: all registers & flags clobbered
;------------------------------------------------------------------------------
AddTargetWordToPuzzle
lda target_word_count
asl
asl
asl
tax
ldy #0
- lda ($FE), y
sta target_words, x
inx
iny
cpy puzzle_logical_width
bne -
inc target_word_count
rts
unused_counter=$FD
;------------------------------------------------------------------------------
; CheckForTargetWord
; check if there is a target word in row 4 (between the lines)
;
; in: none
; out: C = 0 if a target word was found on row 4
; C = 1 if no target word found
; note: will still return C=1 if target word was found but all letters were
; already used (i.e. word had already been found before but player
; shifted letters out and back)
; all other registers & flags clobbered
;------------------------------------------------------------------------------
CheckForTargetWord
+LDADDR target_words
+ST16 $FE
ldx #0 ; word index
@checkword
ldy #0 ; character index
- lda puzzle_data4, y
and #$7F ; letters within puzzle have high bit set if they've been used before
cmp ($FE), y ; (which is fine, and on later puzzles is guaranteed to happen eventually)
bne @nextword
iny
cpy puzzle_logical_width
bne -
; found matching word
; set high bit on all letters in row 4
ldy #0
sty unused_counter ; to see if all letters were already used (i.e. this word was already found)
- lda puzzle_data4, y
bmi +
inc unused_counter
ora #$80
sta puzzle_data4, y
+ iny
cpy puzzle_logical_width
bne -
lda unused_counter
beq @notfound
clc
rts
@nextword
lda $FE
clc
adc #8
sta $FE
bcc +
inc $FF
+ inx
cpx target_word_count
bne @checkword
@notfound
sec
rts
lettertofind = $FD
;------------------------------------------------------------------------------
; FindLetterInColumn
; check whether a given column contains a given letter
; (used to support scroll-column-by-typing)
;
; in: A = letter (0x41..0x5A)
; Y = logical column to search
; out: C clear if letter was found in given column, and
; A = #$FF if letter was found below center column
; A = #$01 if letter was found above center column
; A = #$00 if letter was found in center column
; C set if letter was not found
; clobbers A/X
; preserves Y
; clobbers $FD,$FE,$FF
;------------------------------------------------------------------------------
FindLetterInColumn
ldx #$01
- cmp puzzle_data0, y
beq @down
cmp puzzle_data1, y
beq @down
cmp puzzle_data2, y
beq @down
cmp puzzle_data3, y
beq @down
cmp puzzle_data4, y
beq @even
cmp puzzle_data5, y
beq @up
cmp puzzle_data6, y
beq @up
cmp puzzle_data7, y
beq @up
cmp puzzle_data8, y
beq @up
ora #$80 ; check for used letters now
dex
beq - ; will only branch back once
sec ; no matches on used or unused letters, so we're done
rts
@down clc
lda #$01
rts
@up clc
lda #$FF
rts
@even clc
lda #$00
rts
;------------------------------------------------------------------------------
; ScrollPuzzleDown
; rearrange internal data structures to 'scroll' a column downwards if possible
; (does not update screen)
;
; in: Y = logical column to scroll
; out: C clear if puzzle was scrolled down
; C set if puzzle was already as far down as it can go
; preserves X/Y
;------------------------------------------------------------------------------
ScrollPuzzleDown
lda puzzle_data3, y
beq +
; /!\ execution falls through to ScrollPuzzleDownUnconditionally
;------------------------------------------------------------------------------
; ScrollPuzzleDownUnconditionally
; rearrange internal data structures to 'scroll' a column downwards
; (does not update screen)
;
; in: Y = logical column to scroll
; out: C = 0
; preserves X/Y
;------------------------------------------------------------------------------
ScrollPuzzleDownUnconditionally
lda puzzle_data7, y
sta puzzle_data8, y
lda puzzle_data6, y
sta puzzle_data7, y
lda puzzle_data5, y
sta puzzle_data6, y
lda puzzle_data4, y
sta puzzle_data5, y
lda puzzle_data3, y
sta puzzle_data4, y
lda puzzle_data2, y
sta puzzle_data3, y
lda puzzle_data1, y
sta puzzle_data2, y
lda puzzle_data0, y
sta puzzle_data1, y
lda #0
sta puzzle_data0, y
lda puzzle_offsets, y
clc
adc #1
sta puzzle_offsets, y
clc
rts
+ sec
rts
;------------------------------------------------------------------------------
; ScrollPuzzleUp
; rearrange internal data structures to 'scroll' a column upwards if possible
; (does not update screen)
;
; in: Y = logical column to scroll
; out: C clear if puzzle was scrolled up
; C set if puzzle was already as far up as it can go
; preserves X/Y
;------------------------------------------------------------------------------
ScrollPuzzleUp
lda puzzle_data5, y
beq +
lda puzzle_data1, y
sta puzzle_data0, y
lda puzzle_data2, y
sta puzzle_data1, y
lda puzzle_data3, y
sta puzzle_data2, y
lda puzzle_data4, y
sta puzzle_data3, y
lda puzzle_data5, y
sta puzzle_data4, y
lda puzzle_data6, y
sta puzzle_data5, y
lda puzzle_data7, y
sta puzzle_data6, y
lda puzzle_data8, y
sta puzzle_data7, y
lda #0
sta puzzle_data8, y
lda puzzle_offsets, y
sec
sbc #1
sta puzzle_offsets, y
clc
rts
+ sec
rts
;------------------------------------------------------------------------------
; IsPuzzleComplete
; check if all letters in puzzle have been used
;
; in: none
; out: C = 0 if puzzle is complete (all letters have been matched)
; C = 1 if puzzle is not yet complete
; A,X clobbered
; Y preserved
;------------------------------------------------------------------------------
IsPuzzleComplete
ldx #$47
- lda puzzle_data0, x
beq @keepChecking
bmi @keepChecking
sec
rts
@keepChecking
dex
bpl -
clc
rts