;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 ;------------------------------------------------------------------------------ ; 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