million-perfect-letters/src/ui.play.a

484 lines
13 KiB
Plaintext

;license:MIT
;(c) 2020-2 by 4am
;
; main event loop for playing puzzles
;
; Public functions:
; - PlayEventLoop
; - ClearAndDrawPuzzle
; - DrawPuzzleChrome
; - AnimatePuzzleIntoPlace
; - AnimatePuzzleCompleted
;
; Codes returned by event handlers
kKeepPlaying = 0 ; This code is checked with BEQ/BNE, so it must be 0
kCompletedPuzzle = 1 ; All non-zero codes will exit play event loop
kRequestedRestart = 2 ; with the code in X so caller knows what happened
kRequestedSkip = 3
kReturnToSelectWorld = 4
gSelectedLogicalColumn
!byte 0
gLastKeyPressed
!byte 0
kWorldLeftMargins ; 12 bytes, one for each world
!byte 15,13,12,10
!byte 15,13,12,10
!byte 15,13,12,10
kWorldRightMargins ; 12 bytes, one for each world
!byte 27,28,30,31
!byte 27,28,30,31
!byte 27,28,30,31
kThinLineStartingColor
!byte $D5,$AA
kPlayKeys ; must keep in sync with kPlayKeyHandlersLo/Hi arrays
!byte $0B ; up arrow
!byte $0A ; down arrow
!byte $08 ; left arrow
!byte $15 ; right arrow
!byte $1B ; Esc
!byte $12 ; Ctrl-R
!byte $0E ; Ctrl-N
!byte $13 ; Ctrl-S
!byte $0D ; Return
!byte $2F ; /
!byte $3F ; ?
!byte $00 ; A-Z
kPlayKeyHandlersLo
!byte <PlayEventUpArrow
!byte <PlayEventDownArrow
!byte <PlayEventLeftArrow
!byte <PlayEventRightArrow
!byte <PlayEventEsc
!byte <PlayEventCtrlR
!byte <PlayEventCtrlN
!byte <PlayEventCtrlS
!byte <PlayEventReturn
!byte <PlayEventQuestion
!byte <PlayEventQuestion
!byte <PlayEventLetter
kPlayKeyHandlersHi
!byte >PlayEventUpArrow
!byte >PlayEventDownArrow
!byte >PlayEventLeftArrow
!byte >PlayEventRightArrow
!byte >PlayEventEsc
!byte >PlayEventCtrlR
!byte >PlayEventCtrlN
!byte >PlayEventCtrlS
!byte >PlayEventReturn
!byte >PlayEventQuestion
!byte >PlayEventQuestion
!byte >PlayEventLetter
;------------------------------------------------------------------------------
; PlayEventLoop
; main event loop for playing a puzzle
;
; in: puzzle has been loaded into memory, drawn on screen, animated, &c.
; and is ready to play
; out: X = reason why event loop ended (see list above)
; all other registers & flags clobbered
;------------------------------------------------------------------------------
PlayEventLoop
bit CLEARKBD
- lda KBD
bpl -
bit CLEARKBD
and #$7F
sta gLastKeyPressed
jsr HideMessage
ldx #0
- ldy kPlayKeys, x
beq @checkForLetter
cpy gLastKeyPressed
beq @dispatch
inx
bne -
@checkForLetter
cmp #$61
bcc +
cmp #$7B
bcs PlayEventLoop
and #$DF
sta gLastKeyPressed
+ cmp #$41
bcc PlayEventLoop
cmp #$5B
bcs PlayEventLoop
@dispatch
lda kPlayKeyHandlersLo, x
sta @j+1
lda kPlayKeyHandlersHi, x
sta @j+2
@j jsr $FDFD ; SMC
beq PlayEventLoop
rts
PlayEventEsc
ldx #kReturnToSelectWorld ; caller will exit play event loop
rts
PlayEventCtrlR
ldx #kRequestedRestart ; caller will exit play event loop
rts
PlayEventCtrlN
ldx #kRequestedSkip ; caller will exit play event loop
rts
PlayEventCtrlS
jsr ToggleSoundPref
ldx #kKeepPlaying
rts
PlayEventReturn
ldy gSelectedLogicalColumn
beq +
jsr EraseColumnSelectionIndicator
jmp MoveToFirstColumn
+ ldx #kKeepPlaying
rts
PlayEventDownArrow
ldy gSelectedLogicalColumn
jsr ScrollPuzzleUp
bcs CantMove
jsr ScrollUp
jmp CheckAfterArrow
PlayEventUpArrow
ldy gSelectedLogicalColumn
jsr ScrollPuzzleDown
bcc +
CantMove
jsr PlayErrorSound
KeepPlaying
ldx #kKeepPlaying
rts
+ jsr ScrollDown
CheckAfterArrow
jsr CheckForTargetWord
bcs KeepPlaying
jsr MarkTargetWord
jsr CheckForPuzzleComplete
bne +
jmp PlayNextChord
+ jmp PlayFinalChord
PlayEventLeftArrow
ldy gSelectedLogicalColumn
jsr EraseColumnSelectionIndicator
bne +
ldy puzzle_logical_width
+ dey
sty gSelectedLogicalColumn
jsr DrawColumnSelectionIndicator
ldx #kKeepPlaying
rts
PlayEventRightArrow
ldy gSelectedLogicalColumn
jsr EraseColumnSelectionIndicator
iny
cpy puzzle_logical_width
bcc +
MoveToFirstColumn
ldy #0
+ sty gSelectedLogicalColumn
jsr DrawColumnSelectionIndicator
ldx #kKeepPlaying
rts
PlayEventQuestion
jsr HelpEventLoop
jsr Home
jsr DrawPuzzleChrome
jsr DrawPuzzle
jsr DrawColumnSelectionIndicator
ldx #kKeepPlaying
rts
- jsr PlayNextChord
PlayEventLetter
lda gLastKeyPressed
ldy gSelectedLogicalColumn
jsr FindLetterInColumn
bcc +
jsr PlayErrorSound ; didn't find letter, we're done
ldx #kKeepPlaying
rts
+ beq PlayEventRightArrow ; found letter but it's already on center row, we're done, exit through right arrow handler
bmi @up ; scroll up or down ONCE, then reassess
jsr ScrollPuzzleDown
jsr ScrollDown
jmp +
@up jsr ScrollPuzzleUp
jsr ScrollUp
+ jsr CheckForTargetWord
bcs PlayEventLetter ; no word, check if more scrolling is required
jsr MarkTargetWord ; show that we've finished a word
jsr CheckForPuzzleComplete
beq - ; if puzzle isn't complete, check if more scrolling is required
jmp PlayFinalChord ; puzzle is complete, play final sound and return to caller with X = kCompletedPuzzle
MarkTargetWord
; in: none
; out: all registers & flags clobbered
ldx #4
ldy #0
- lda puzzle_data4, y
jsr DrawLargeCharacter
iny
cpy puzzle_logical_width
bne -
ldy #0
- lda #1
jsr ScrollUpBy
iny
cpy puzzle_logical_width
bne -
ldy #0
- lda #1
jsr ScrollDownBy
iny
cpy puzzle_logical_width
bne -
rts
CheckForPuzzleComplete
; in: none
; out: X = play event loop code
jsr IsPuzzleComplete
bcs +
ldx #kCompletedPuzzle
rts
+ ldx #kKeepPlaying
rts
;------------------------------------------------------------------------------
; DrawPuzzle
; draws letters in current puzzle (but not column selection indicator or
; other UI elements)
;
; in: none
; out: all registers and flags clobbered
;------------------------------------------------------------------------------
DrawPuzzle
jsr DrawThinLines
+LDADDR puzzle_data0
+ST16 $FE
ldx #0 ; logical row
-- ldy #0 ; logical column
- lda ($FE), y
jsr DrawLargeCharacter
iny
cpy puzzle_logical_width
bne -
lda $FE
clc
adc #8
sta $FE
bcc +
inc $FF
+ inx
cpx #9
bne --
rts
;------------------------------------------------------------------------------
; AnimatePuzzleIntoPlace
;
; in: none
; out: all registers and flags clobbered
;------------------------------------------------------------------------------
AnimatePuzzleIntoPlace
ldx gWorldID
ldy kPuzzleWidths, x
sty @max+1
ldy #0
-- ldx #4
- jsr ScrollPuzzleDownUnconditionally
jsr ScrollDown
dex
bne -
iny
@max cpy #$FD ; SMC
bne --
jsr ShowMessage
+LDADDR noMessage
jmp SetNextMessage
;
;------------------------------------------------------------------------------
; DrawPuzzleChrome
; draw all elements on puzzle screen that are not the actual puzzle
; (game title, level)
;
; in: none
; out: all registers and flags clobbered
;------------------------------------------------------------------------------
DrawPuzzleChrome
+LDADDR kWorldShortNames
+ST16 $FE
lda gWorldID
asl
asl
clc
adc $FE
sta $FE
bcc +
inc $FF
+
lda #2
sta VTAB
lda #33
sta HTAB
+LD16 $FE
jsr DrawHeavySilkString
+LDADDR sDash
jsr DrawHeavySilkString
ldx gPuzzleID
inx ; visible puzzle number is 1-based
lda #$30 ; padding character ('0')
jsr ToASCIIString
+LDADDR $00F1
jsr DrawHeavySilkString
DrawPuzzleChromeCommon
+PRINT_AT sTitleLine1, 0, 0
+PRINT_AT sTitleLine2, 1, 0
+PRINT_AT sTitleLine3, 2, 0
+PRINT_AT sLevel, 0, 34
rts
;------------------------------------------------------------------------------
; AnimatePuzzleCompleted
;
; in: none
; out: all registers and flags clobbered
;------------------------------------------------------------------------------
AnimatePuzzleCompleted
jsr DrawThinLines
ldx gWorldID
lda kPuzzleWidths, x
sta @max+1
ldy #0
ldx #0
- jsr ScrollDown
inc puzzle_offsets, x
lda puzzle_offsets, x
cmp #9
bne -
inx
iny
@max cpy #$FD ; SMC
bne -
rts
;------------------------------------------------------------------------------
; DrawThinLines [private]
;
; in: none
; out: all registers and flags clobbered
;------------------------------------------------------------------------------
DrawThinLines
ldx #$55
jsr DrawThinLine
ldx #$6B
; /!\ execution falls through here
DrawThinLine
; in: X = HGR row (0x00..0xBF)
ldy HGRLO, x
sty $FE
ldy HGRHI, x
sty $FF
ldx gWorldID
ldy kWorldRightMargins, x
sty @right+1
ldy GlobalLeftMargin
dey
tya
and #1
tax
lda kThinLineStartingColor, x
- sta ($FE), y
eor #$7F
iny
@right cpy #$FD ; SMC
bcc -
rts
;------------------------------------------------------------------------------
; EraseColumnSelectionIndicator [private]
;
; in: none
; out: preserves X/Y
;------------------------------------------------------------------------------
EraseColumnSelectionIndicator
stx @x+1
sty @y+1
jsr DrawThinLines
@x ldx #$FD ; SMC
@y ldy #$FD ; SMC
rts
;------------------------------------------------------------------------------
; DrawColumnSelectionIndicator [private]
;
; in: none
; out: preserves X/Y
;------------------------------------------------------------------------------
DrawColumnSelectionIndicator
stx @x+1
sty @y+1
ldx #$55
jsr DrawOneSelectionIndicator
ldx #$6B
jsr DrawOneSelectionIndicator
@x ldx #$FD ; SMC
@y ldy #$FD ; SMC
rts
;------------------------------------------------------------------------------
; DrawOneSelectionIndicator [private]
;
; in: none
; out: all registers and flags clobbered
;------------------------------------------------------------------------------
DrawOneSelectionIndicator
lda HGRLO, x
sta $FE
lda HGRHI, x
sta $FF
ldy GlobalLeftMargin
dey
tya
and #1
eor #1
tax
lda kThinLineStartingColor, x
ldx gSelectedLogicalColumn
beq +
- iny
iny
iny
eor #$7F
dex
bne -
+ ldx #4
- sta ($FE), y
eor #$7F
iny
dex
bne -
rts