track completion progress and save to disk

This commit is contained in:
4am 2020-05-01 19:06:53 -04:00
parent 8bbb20fd6d
commit dabfccdba2
13 changed files with 904 additions and 249 deletions

View File

@ -26,6 +26,7 @@ asm:
$(ACME) -r build/million.lst src/million.a 2>build/log
cp res/work.po "$(BUILDDISK)".po >>build/log
cp res/_FileInformation.txt build/ >>build/log
$(CADIUS) ADDFILE "${BUILDDISK}".po "/MILLION/" "res/PROGRESS" >>build/log
$(CADIUS) ADDFILE "${BUILDDISK}".po "/MILLION/" "build/MILLION.SYSTEM" >>build/log
for f in res/levels/*; do $(CADIUS) ADDFILE "${BUILDDISK}".po "/MILLION/" "$$f" >>build/log; done
@ -36,3 +37,5 @@ mount:
open "$(BUILDDISK)".po
all: clean asm mount
al: all

BIN
res/PROGRESS Normal file

Binary file not shown.

View File

@ -1 +1,2 @@
MILLION.SYSTEM=Type(FF),AuxType(2000),Access(C3)
PROGRESS=Type(06),AuxType(8E10),Access(C3)

View File

@ -2,6 +2,24 @@
;(c) 2020 by 4am
;
;------------------------------------------------------------------------------
; YE OLDE GRAND UNIFIED MEMORY MAP
;
; MAIN MEMORY
; 0300..03CF - data structures for current puzzle
; 0400..07FF - text page (kept black for switching)
; 0800..19FF - scroll routines (generated at startup)
; 1A00..1BFF - HGR lookup tables
; 1C00..1FFF - ProDOS file buffer for puzzle files
; 2000..3FFF - hi-res page
; 4000+ - program code (relocated to here at startup)
; ...unused...
; 8A00..8DFF - ProDOS file buffer for PROGRESS file
; 8E10..8F37 - progress of puzzles in current world
; 8F38..8FFF - address lookup table for puzzles within following world data
; 9000..BEFF - world data (read from disk)
;------------------------------------------------------------------------------
; soft switches
KBD = $C000 ; last key pressed (if any)
STOREOFF = $C000 ; STA then use the following 4 flags:
@ -37,23 +55,25 @@ ROM_IN0 = $FE89 ; SETKBD
ROM_PR0 = $FE93 ; SETVID
; zero page
PARAM = $00 ; word (used by PARAMS_ON_STACK macro, so basically everywhere)
;PTR = $02 ; word
;SRC = $04 ; word
;DEST = $06 ; word
;SAVE = $08 ; word
PARAM = $00 ; word (used by PARAMS_ON_STACK macro)
HTAB = $24 ; byte
VTAB = $25 ; byte
;RNDSEED = $4E ; word
; application constants
; zero page
original_char = $ED ; byte
char = $EE ; byte
charrow = $EF ; byte
GENSCROLLDOWN = $0800 ; 0x0900 bytes
GENSCROLLUP = $1100 ; 0x0900 bytes
HGRLO = $1A00 ; 0xC0 bytes
HGRHI = $1B00 ; 0xC0 bytes
PRODOSFILEBUFFER = $1C00 ; 0x0400 bytes
LEVELLO = $8F38 ; 0x64 bytes
LEVELHI = $8F9C ; 0x64 bytes
HGRLO = $1A00 ; 0x00C0 bytes
HGRHI = $1B00 ; 0x00C0 bytes
WORLDFILEBUFFER = $1C00 ; 0x0400 bytes
PROGRESSFILEBUFFER = $8A00; 0x0400 bytes
PACKEDPROGRESS = $8E10 ; 0x00C0 bytes
PROGRESS = $8ED0 ; 0x0068 bytes
PUZZLELO = $8F38 ; 0x0064 bytes
PUZZLEHI = $8F9C ; 0x0064 bytes
WORLDDATA = $9000 ; 0x2F00 bytes max

View File

@ -21,6 +21,7 @@ CMD_NEWLINE = $03C9 ; set line-by-line read mode
CMD_READ = $04CA ; read an open file
CMD_WRITE = $04CB ; write to an open file
CMD_CLOSE = $01CC ; close an open file
CMD_FLUSH = $01CD ; flush an open, written file to disk
CMD_SETMARK = $02CE ; change position in an open file
CMD_SETEOF = $02D0 ; set file size
@ -28,14 +29,16 @@ CMD_SETEOF = $02D0 ; set file size
; OpenFile
; open file via ProDOS MLI
;
; in: A/Y address of pathname
; in: stack contains 4 bytes of parameters:
; +1 [word] address of filename
; +3 [word] ProDOS file reference number
; out: if C set, open failed and A contains error code
; if C clear, open succeeded and A contains file reference number
;------------------------------------------------------------------------------
OpenFile
+ST16 mliparam+1
+LDADDR PRODOSFILEBUFFER
+ST16 mliparam+3
+PARAMS_ON_STACK 4
+LDPARAMPTR 1, mliparam+1 ; store filename
+LDPARAMPTR 3, mliparam+3 ; store address of ProDOS file buffer
+LDADDR CMD_OPEN
jsr mli
bcs +
@ -55,6 +58,7 @@ OpenFile
; out: if C set, read failed and A contains error code
; if C clear, read succeeded and A contains the same
; file reference number that was passed in
; stack set to next instruction after parameters
;------------------------------------------------------------------------------
ReadFile
+PARAMS_ON_STACK 5
@ -69,9 +73,64 @@ ReadFile
lda mliparam+1 ; if no error, return file reference number
+ rts
;------------------------------------------------------------------------------
; WriteFile
; write to an open file via ProDOS MLI
;
; in: A = file reference number
; stack contains 4 bytes of parameters:
; +1 [word] address of data buffer
; +3 [word] length of data buffer
; out: if C set, save failed and A contains error code
; if C clear, write succeeded and A contains the same file reference
; number that was passed in
;------------------------------------------------------------------------------
WriteFile
sta mliparam+1 ; store file reference number
+PARAMS_ON_STACK 4
+LDPARAMPTR 1, mliparam+2 ; store data buffer address
+LDPARAMPTR 3, mliparam+4 ; store data buffer length
+LDADDR CMD_WRITE
jsr mli
bcs + ; error, A contains MLI error code
lda mliparam+1 ; no error, return file reference number
+ rts
;------------------------------------------------------------------------------
; SetMarkTo0
; set file position within an open file to the beginning of the file
;
; in: A = file reference number
; out: if C set, save failed and A contains error code
; if C clear, write succeeded and A contains the same file reference
; number that was passed in
;------------------------------------------------------------------------------
SetMarkTo0
sta mliparam+1 ; store file reference number
lda #0
sta mliparam+2
sta mliparam+3
sta mliparam+4
+LDADDR CMD_SETMARK
bne mli ; always branches
;------------------------------------------------------------------------------
; FlushFile
; flush an open file (actually writes it to disk, but without closing it)
;
; in: A = file reference number
; out: if error, C set and A contains error code
; if success, C clear
;------------------------------------------------------------------------------
FlushFile
sta mliparam+1 ; store file reference number
+LDADDR CMD_FLUSH
bne mli ; always branches
;------------------------------------------------------------------------------
; CloseFile
; close an open file
;
; in: A = file reference number
; out: if error, C set and A contains error code
; if success, C clear
@ -88,6 +147,8 @@ CloseFile
; out: does not return
;------------------------------------------------------------------------------
Quit
lda #0
jsr CloseFile
+LDADDR CMD_QUIT
; /!\ execution falls through here
;------------------------------------------------------------------------------
@ -106,12 +167,12 @@ mli sta mlicmd ; store command code
mlicmd !byte 00 ; command number
!word mliparam ; address of parameter table
rts
mliparam !byte $FE,$FE,$FE,$FE
filetype !byte $FE ; file type (set by MLI get_file_info)
mliparam !byte $FD,$FD,$FD,$FD
filetype !byte $FD ; file type (set by MLI get_file_info)
auxtype ; auxiliary file type (2 bytes, set by MLI get_file_info)
refnum !byte $FE ; file refnum (set by MLI open)
mlilen !byte $FE,$FE ; file length (set by MLI read)
blocks !byte $FE,$FE ; blocks used (set by getvolumeinfo)
refnum !byte $FD ; file refnum (set by MLI open)
mlilen !byte $FD,$FD ; file length (set by MLI read)
blocks !byte $FD,$FD ; blocks used (set by getvolumeinfo)
; member is also used by createfile
!byte $FE,$FE,$FE,$FE,$FE,$FE,$FE,$FE
!byte $FD,$FD,$FD,$FD,$FD,$FD,$FD,$FD
; used by get_file_info

View File

@ -1,39 +1,72 @@
;license:MIT
;(c) 2020 by 4am
;
; disk functions to load puzzles, and load & save puzzle completion status
;
; Public functions:
; - MaybeLoadWorldFromDisk
; - LoadProgressFromDisk
; - LoadProgressFromMemory
; - LoadPuzzleFromMemory
; - HasPuzzleBeenCompleted
; - MarkPuzzleCompleted
; - FindPackedProgressAddr
;
; Public variables:
; - gWorldID
; - gPuzzleID
;
; Public constants:
; - kPuzzleWidths
;
gLevelID
gWorldID ; a world is a group of 100 similar puzzles (12 worlds in total, 0-based)
!byte $FD ; 0x00..0x0B
gPuzzleID
gPuzzleID ; ID of current puzzle within the current world (100 puzzles per world, 0-based)
!byte $FD ; 0x00..0x63
kLevelWidths
!byte 4,5,6,7
kPuzzleWidths ; width in letters of the puzzles in each world
!byte 4,5,6,7 ; (every puzzle in a world is the same width)
!byte 4,5,6,7
!byte 4,5,6,7
MaybeLoadLevelsFromDisk
; in: A = level ID
progressRefNum ; [byte][private]
!byte $FF ; 0xFF means 'no progress file open'
checksum = $FB
packedvalue = $FC
bitcount = $FD
;------------------------------------------------------------------------------
; MaybeLoadWorldFromDisk
; load a world (100 puzzles) from a file on disk
; caches the last world loaded to reduce unnecessary disk access
;
; in: A = world ID
; out: C clear if file loaded and parsed successfully (or it was already in memory)
; C set if file could not be loaded and/or parsed
; other registers/flags clobbered
;------------------------------------------------------------------------------
MaybeLoadWorldFromDisk
clc
adc #$41
cmp @fileLoaded
beq @exit
sta @filename+1
+LDADDR @filename
bne +
clc
rts
+ sta @filename+1
jsr OpenFile
!word @filename
!word WORLDFILEBUFFER
bcs @exit
sta @refnum
jsr ReadFile
@refnum !byte $FD ; SMC
!word $9000 ; load address
!word WORLDDATA ; load address
!word $2F00 ; maximum length to read
php
bcs @closeAndPopStatus
plp
jsr ParseLevelData
jsr @PreParseWorldData
cpx #100
bne +
clc
@ -53,9 +86,9 @@ MaybeLoadLevelsFromDisk
!byte 1 ; length
!raw $FD ; SMC
ParseLevelData
; find the starting address of each of the 100 puzzles in this file
; in: $9000+ contains raw level data from disk
@PreParseWorldData
; find the starting address of each of the 100 puzzles in this world
; in: $9000+ contains raw world data from disk
lda #$FF
sta $FE
lda #$8F
@ -76,54 +109,294 @@ ParseLevelData
cmp #$5B ; '[' ends the parsing
beq @exit
lda $FE
sta LEVELLO, x
sta PUZZLELO, x
lda $FF
sta LEVELHI, x
sta PUZZLEHI, x
inx
bne @skipLine ; always branches
@IncAndGetChar
inc $FE
bne +
inc $FF
+ lda ($FE),y
; in: Y = 0
+INCADDR $FE
lda ($FE),y
cmp #$0D ; CR - hide it
beq @IncAndGetChar
@exit rts
rts
ParseOnePuzzle
ldx gPuzzleID
lda LEVELLO, x
;------------------------------------------------------------------------------
; LoadProgressFromDisk
;
; see comments in MarkPuzzleCompleted for file format
;
; in: none
; out: C clear if file was read successfully, and
; progressRefNum contains ProDOS file reference number (file is left
; open)
; C set if error occurred, and
; progressRefNum = 0xFF
;------------------------------------------------------------------------------
LoadProgressFromDisk
jsr OpenFile
!word @progressFile
!word PROGRESSFILEBUFFER
bcs +
sta @refnum
sta progressRefNum
jsr ReadFile
@refnum !byte $FD ; SMC
!word PACKEDPROGRESS ; data address
!word $00C0 ; data length
+ rts
@progressFile
!byte 8
!raw "PROGRESS"
;------------------------------------------------------------------------------
; LoadProgressFromMemory
; unpacks one world's progress into a 1-byte-per-puzzle array for easier access
;
; in: progress file must be in memory (call MaybeLoadProgressFromDisk and
; check C)
; out: A = next puzzle ID
;------------------------------------------------------------------------------
LoadProgressFromMemory
lda gWorldID
jsr FindPackedProgressAddr
; ($FE) -> start of the 16-byte structure for this world
; zero destination array
ldx #103
lda #0
- sta PROGRESS, x
dex
bpl -
; unpack values
ldy #0 ; index into ($FE) (source data)
ldx #0 ; index into PROGRESS (destination data)
-- lda #8
sta bitcount
lda ($FE), y
- lsr
rol PROGRESS, x ; note: PROGRESS structure is 104 bytes (a multiple of 8),
inx ; even though only the first 100 bytes are used
dec bitcount
bne -
iny
cpy #$0D
bne --
lda ($FE), y ; A = next puzzle ID
rts
;------------------------------------------------------------------------------
; LoadPuzzleFromMemory
; loads one puzzle from the in-memory cache of the world file, into the
; 'now playing this puzzle' data structures
;
; in: X = puzzle ID
; world file must be in memory (call MaybeLoadWorldFromDisk and check C)
; out: all registers and flags clobbered
;------------------------------------------------------------------------------
LoadPuzzleFromMemory
lda PUZZLELO, x
sta $FE
lda LEVELHI, x
lda PUZZLEHI, x
sta $FF
bne @addLine ; always branches
@lineLoop
jsr @inc
+INCADDR $FE
@addLine
jsr AddLineToPuzzle
ldx gLevelID
ldy kLevelWidths, x
- jsr @inc
dey
bne -
ldx gWorldID
lda kPuzzleWidths, x
clc
adc $FE
sta $FE
bcc +
inc $FF
+
ldy #0
lda ($FE), y
cmp #$2C ; ,
beq @lineLoop
@targetWordLoop
jsr @inc
+INCADDR $FE
jsr AddTargetWordToPuzzle
ldx gLevelID
ldy kLevelWidths, x
- jsr @inc
dey
bne -
ldx gWorldID
lda kPuzzleWidths, x
clc
adc $FE
sta $FE
bcc +
inc $FF
+
ldy #0
lda ($FE), y
cmp #$2C ; ,
beq @targetWordLoop
rts
@inc
inc $FE
bne +
;------------------------------------------------------------------------------
; HasPuzzleBeenCompleted
;
; in: X = puzzle ID
; out: C clear if puzzle had previously been marked as completed
; C set if puzzle has never been marked as completed
; X/Y preserved
; A clobbered
; other flags clobbered
;------------------------------------------------------------------------------
HasPuzzleBeenCompleted
lda PROGRESS, x
beq +
clc
rts
+ sec
rts
;------------------------------------------------------------------------------
; MarkPuzzleCompleted
;
; in: X = puzzle ID
; out: all registers and flags clobbered
;------------------------------------------------------------------------------
MarkPuzzleCompleted
; Completion status for the current world (100 puzzles) is stored unpacked in
; memory for easy access
; Mark this puzzle as completed in the unpacked table
lda #1
sta PROGRESS, x
; Find the next uncompleted puzzle
clc
+HIDE_NEXT_BYTE
-- sec
- lda PROGRESS, x
beq +
inx
cpx #100
bne -
ldx #0 ; start over at the puzzle 0
bcc --
; If we fall through here, it means all puzzles in this world
; have been completed (yay!), and we'll leave X at 0.
; If we branch here, we found the next uncompleted puzzle
; and X contains that puzzle ID.
+ txa
pha ; push next puzzle ID
;
; Completion status for all puzzles across all worlds is stored in packed
; bitfields, 1 bit per puzzle, so that the file on disk fits in a single
; block (actually a single sector).
;
; This is the file format (0xC0 bytes):
; +0x00 bit 0 - completion status for world 0x00, puzzle 0x00
; bit 1 - completion status for world 0x00, puzzle 0x01
; bit 2 - completion status for world 0x00, puzzle 0x02
; bit 3 - completion status for world 0x00, puzzle 0x03
; bit 4 - completion status for world 0x00, puzzle 0x04
; bit 5 - completion status for world 0x00, puzzle 0x05
; bit 6 - completion status for world 0x00, puzzle 0x06
; bit 7 - completion status for world 0x00, puzzle 0x07
; ...
; +0x0C bit 0 - completion status for world 0x00, puzzle 0x60
; bit 1 - completion status for world 0x00, puzzle 0x61
; bit 2 - completion status for world 0x00, puzzle 0x62
; bit 3 - completion status for world 0x00, puzzle 0x63
; bit 4-7 always 0
; +0x0D next puzzle to play in world 0x00 (0x00..0x63)
; +0x0E percent complete (0x00..0x64)
; +0x0F XOR of bytes +0x00..+0x0E
; +0x10..+0x1F same format but for world 0x01
; +0x20..+0x2F same format but for world 0x02
; +0x30..+0x3F same format but for world 0x03
; +0x40..+0x4F same format but for world 0x04
; +0x50..+0x5F same format but for world 0x05
; +0x60..+0x6F same format but for world 0x06
; +0x70..+0x7F same format but for world 0x07
; +0x80..+0x8F same format but for world 0x08
; +0x90..+0x9F same format but for world 0x09
; +0xA0..+0xAF same format but for world 0x0A
; +0xB0..+0xBF same format but for world 0x0B
;
; Among other things, this means that a file filled with zeroes is valid
; and means that no puzzles have been completed in any world and you should
; start at the beginning. So that's nice.
;
lda gWorldID
jsr FindPackedProgressAddr
; ($FE) -> start of the 16-byte structure for this world
; zero out all 16 bytes
ldy #$0F
lda #$00
- sta ($FE), y
dey
bpl -
sta checksum
; build packed values
ldy #0
ldx #0
-- lda #8
sta bitcount
lda #0
sta packedvalue
- lda PROGRESS, x
ror
ror packedvalue
inx
dec bitcount
bne -
lda packedvalue
sta ($FE), y
eor checksum
sta checksum
iny
cpy #$0D
bne --
pla ; A = next puzzle ID (pushed earlier)
sta ($FE), y
eor checksum
sta checksum
; count number of completed puzzles
ldy #0
ldx #99
- lda PROGRESS, x
beq +
iny
+ dex
bpl -
tya ; A = number of puzzles completed (out of 100, so also the percent complete)
ldy #$0E
sta ($FE), y
eor checksum
iny
sta ($FE), y
lda progressRefNum
jsr SetMarkTo0
lda progressRefNum
jsr WriteFile
!word PACKEDPROGRESS
!word $00C0
lda progressRefNum
jsr FlushFile
ldy #$0D
lda ($FE), y
rts
FindPackedProgressAddr
; in: A = world ID
; out: ($FE) points to first byte of this world's packed progress (16-byte structure)
; A/Y clobbered
; X preserved
; all flags clobbered
pha
+LDADDR PACKEDPROGRESS
+ST16 $FE
pla
asl
asl
asl
asl
clc
adc $FE
sta $FE
bcc +
inc $FF
+ rts
rts

View File

@ -112,6 +112,14 @@
+
}
; increment a 16-bit value stored at an address
!macro INCADDR .addr {
inc .addr
bne +
inc .addr+1
+
}
; compare a 16-bit value in A (low) and Y (high) to an absolute address
!macro CMP16ADDR .addr {
cmp .addr

View File

@ -10,40 +10,57 @@ row = $FD
!source "src/constants.a" ; no code
!source "src/macros.a" ; no code
!source "src/million.init.a" ; one-time initialization code
!source "src/hw.vbl.init.a"
!source "src/million.init.a" ; one-time initialization code, exits via Start
!source "src/hw.vbl.init.a" ; TODO hook this up
FirstMover
!pseudopc $4000 {
Start
jsr LoadProgressFromDisk
jsr TitlePage
; C set if title sequence ended prematurely (i.e. main menu requires full redraw)
- jsr MainMenu
; C set if main menu requires full redraw (e.g. title sequence ended prematurely)
@GoToMainMenu
jsr MainMenu
bcc +
jmp Quit
+ sta gLevelID
jsr MaybeLoadLevelsFromDisk ; A = level ID from 'select level' page
bcc +
+ sta gWorldID
jsr MaybeLoadWorldFromDisk ; A = world ID from selection page
bcc @PlayNext
jmp $FF59 ; TODO
+ ldx gLevelID
lda kLevelLeftMargins, x
sta GlobalLeftMargin
lda kLevelWidths, x
jsr InitPuzzleStorage
lda #0
@PlayNext
jsr LoadProgressFromMemory
; A = next puzzle ID
@Play
sta gPuzzleID
;jsr FindNextUnsolvedPuzzle ; TODO
jsr ParseOnePuzzle
ldx gWorldID
lda kWorldLeftMargins, x
sta GlobalLeftMargin
lda kPuzzleWidths, x
jsr InitPuzzleStorage
ldx gPuzzleID
jsr LoadPuzzleFromMemory
lda #0
sta selected_logical_column
jsr RedrawPuzzle
sta gSelectedLogicalColumn
jsr DrawPuzzleChrome
jsr ClearAndDrawPuzzle
jsr AnimatePuzzleIntoPlace
jsr DrawColumnSelectionIndicator
jsr PlayEventLoop
cmp #kCompletedPuzzle
bne +
jsr AnimatePuzzleCompleted
ldx gPuzzleID
jsr MarkPuzzleCompleted
; A - next puzzle ID
jmp @Play
+ cmp #kRequestedRestart
bne +
jsr Home
lda gPuzzleID
jmp @Play
+
sec
bcs - ; always branches
bcs @GoToMainMenu
!source "src/puzzle.a"
!source "src/hw.vbl.a"

View File

@ -3,11 +3,28 @@
;
; data storage routines for puzzles
;
; Public functions:
; - InitPuzzleStorage
; - AddLineToPuzzle
; - AddTargetWordToPuzzle
; - CheckForTargetWord
; - FindLetterInColumn
; - ScrollPuzzleDown
; - 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
puzzle_data = $030A ; [0x08 bytes] character data
; 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]
@ -24,17 +41,11 @@ puzzle_words = $0353 ; [0x80 bytes]
InitPuzzleStorage
; in: A = logical puzzle width (number of letters in each word, 4..7)
sta puzzle_logical_width
ldx #$CF
lda #0
sta puzzle_logical_height
sta puzzle_word_count
ldx #8
- sta puzzle_offsets-1, x
- sta puzzle_logical_width, x
dex
bne -
ldx #$7F
- sta puzzle_words, x
dex
bpl -
rts
AddLineToPuzzle
@ -47,7 +58,7 @@ AddLineToPuzzle
tax
ldy #0
- lda ($FE), y
sta puzzle_data, x
sta puzzle_data0, x
inx
iny
cpy puzzle_logical_width
@ -152,11 +163,11 @@ ScrollPuzzleDown
lda puzzle_data1, y
sta puzzle_data2, y
lda puzzle_data, y
lda puzzle_data0, y
sta puzzle_data1, y
lda #0
sta puzzle_data, y
sta puzzle_data0, y
lda puzzle_offsets, y
clc
@ -179,7 +190,7 @@ ScrollPuzzleUp
beq @fail
lda puzzle_data1, y
sta puzzle_data, y
sta puzzle_data0, y
lda puzzle_data2, y
sta puzzle_data1, y
@ -213,3 +224,18 @@ ScrollPuzzleUp
rts
@fail sec
rts
IsPuzzleComplete
; out: C clear if puzzle is complete (all letters have been matched)
; C set if puzzle is not yet complete
ldx #$47
- lda puzzle_data0, x
beq @keepChecking
bmi @keepChecking
sec
rts
@keepChecking
dex
bpl -
clc
rts

View File

@ -113,3 +113,56 @@ SoftBell
dex
bne -
rts
nonZeroDigits = $EE
paddingCharacter = $EF
ToASCIIString
; convert byte value to length-prefixed 3-digit decimal number as ASCII string with given padding character
; in: X = any number (0..255 obviously)
; A = padding character (e.g. '0' or ' ')
; out: $F1 = 0x03
; $F2..$F4 = ASCII digits of decimal representation
; clobbers $EE,$EF,$F0
; all flags & registers clobbered
sta paddingCharacter
stx $F0
ldx #0
stx nonZeroDigits
stx $F1
@outer lda #0
pha
@inner lda $F0
cmp @kPowersOfTen, x
bcc @digitDone
sbc @kPowersOfTen, x
sta $F0
lda $F1
sbc #0
sta $F1
pla
adc #0
pha
jmp @inner
@digitDone
pla
beq @maybeUsePaddingChar
inc nonZeroDigits
- ora #$30
bne + ; always branches
@maybeUsePaddingChar
cpx #2
beq -
ldy nonZeroDigits
bne -
lda paddingCharacter
+ sta $F2, x
inx
cpx #$03
bcc @outer
lda #3 ; length byte
sta $F1
rts
@kPowersOfTen
!byte 100
!byte 10
!byte 1

View File

@ -13,8 +13,8 @@ MainMenu
; C set if full screen clear & redraw is required
; (will happen if key is pressed during title screen, or
; if user returns to main menu from play or any other screen)
; out: C clear if user selected a level to play, and
; A = level ID
; out: C clear if user selected a world to play, and
; A = world ID
; C set if user selected quit
bcc @noredraw
jsr Home
@ -22,13 +22,13 @@ MainMenu
sta GlobalLeftMargin
ldy #3
- ldx #1
lda titleline1-2, y
lda kTitleLine1-2, y
jsr DrawLargeCharacter
inx
lda titleline2-2, y
lda kTitleLine2-2, y
jsr DrawLargeCharacter
inx
lda titleline3-2, y
lda kTitleLine3-2, y
jsr DrawLargeCharacter
iny
cpy #$0A
@ -53,9 +53,9 @@ MainMenu
jsr SoftBell
jmp -
@eventReturn
jsr SelectLevel
jsr SelectWorld
bcs @noselection
rts ; return to caller with A = level ID
rts ; return to caller with A = world ID
@noselection
jmp MainMenu ; C is already set so this will do a full redraw
@eventEsc
@ -63,27 +63,27 @@ MainMenu
rts
counter = $F2
selectedlevel = $F3
selectedworld = $F3
SelectLevel
SelectWorld
; in: none
; out: C clear if level was selected, and
; A = 0-based level ID
; C set if no level was selected and A is undefined
; out: C clear if world was selected, and
; A = 0-based world ID
; C set if no world was selected and A is undefined
jsr Home
+LDADDR leveldescriptions ; TODO update level descriptions with % complete
jsr UpdateWorldPercents
+LDADDR worlddescriptions
+ST16 $FE
lda #0
sta counter
sta selectedlevel ; TODO set selected level from prefs
sta selectedworld ; TODO set selected world from prefs
lda #6
sta VTAB
- lda #10
sta HTAB
+LD16 $FE
ldx counter
cpx selectedlevel
cpx selectedworld
beq +
ldx #20
jsr DrawHeavySilkBuffer
@ -102,12 +102,12 @@ SelectLevel
cmp #12
bcc -
+PRINT_AT levelhelp, 23, 0
+PRINT_AT worldhelp, 23, 0
bit CLEARKBD
@selectLevelLoop
@selectWorldLoop
lda KBD
bpl @selectLevelLoop
bpl @selectWorldLoop
bit CLEARKBD
and #$7F
cmp #$0B ; up arrow
@ -125,7 +125,7 @@ SelectLevel
rts
@eventReturn
jsr Home
lda selectedlevel
lda selectedworld
clc
rts
@eventEsc
@ -133,56 +133,56 @@ SelectLevel
rts
@eventLeftArrow
@eventUpArrow
jsr RedrawPreviouslySelectedLevel
ldx selectedlevel
jsr RedrawPreviouslySelectedWorld
ldx selectedworld
bne +
ldx #12
+ dex
stx selectedlevel
jsr DrawNewlySelectedLevel
jmp @selectLevelLoop
stx selectedworld
jsr DrawNewlySelectedWorld
jmp @selectWorldLoop
@eventRightArrow
@eventDownArrow
jsr RedrawPreviouslySelectedLevel
ldx selectedlevel
jsr RedrawPreviouslySelectedWorld
ldx selectedworld
cpx #11
bne +
ldx #$FF
+ inx
stx selectedlevel
jsr DrawNewlySelectedLevel
jmp @selectLevelLoop
stx selectedworld
jsr DrawNewlySelectedWorld
jmp @selectWorldLoop
RedrawPreviouslySelectedLevel
lda selectedlevel
RedrawPreviouslySelectedWorld
lda selectedworld
tax
clc
adc #6
sta VTAB
lda #10
sta HTAB
jsr GetLevelDescription
jsr GetWorldDescription
+LD16 $FE
ldx #20
jmp DrawHeavySilkBuffer
DrawNewlySelectedLevel
lda selectedlevel
DrawNewlySelectedWorld
lda selectedworld
tax
clc
adc #6
sta VTAB
lda #10
sta HTAB
jsr GetLevelDescription
jsr GetWorldDescription
+LD16 $FE
ldx #20
jmp DrawHeavySilkBufferInverse
GetLevelDescription
; in: X = 0-based level ID
; out: $FE/$FF points to level description buffer
+LDADDR leveldescriptions
GetWorldDescription
; in: X = 0-based world ID
; out: $FE/$FF points to world description buffer
+LDADDR worlddescriptions
+ST16 $FE
jmp +
- lda $FE
@ -195,6 +195,37 @@ GetLevelDescription
bpl -
rts
worldindex=$FB
UpdateWorldPercents
+LDADDR worlddescriptions
+ST16 $FC
lda #0
sta worldindex
-- jsr FindPackedProgressAddr
ldy #$0E
lda ($FE), y
tax
lda #$20
jsr ToASCIIString
ldx #2
ldy #17
- lda $F2, x
sta ($FC), y
dey
dex
bpl -
lda $FC
clc
adc #20
sta $FC
bcc +
inc $FD
+ inc worldindex
lda worldindex
cmp #12
bne --
rts
asterisk
!byte 1
!byte "*"
@ -214,7 +245,7 @@ disclaimer
!byte 40
!raw "* NOT GUARANTEED, ACTUAL COUNT MAY VARY."
leveldescriptions
worlddescriptions
!raw " 4X3, EASY 0% "
!raw " 5X3, BASIC 0% "
!raw " 6X3, SIMPLE 0% "
@ -228,6 +259,36 @@ leveldescriptions
!raw " 6X5, TRICKY 0% "
!raw " 7X5, COMPLEX 0% "
levelhelp
kWorldShortNames
!byte 3
!raw "4X3"
!byte 3
!raw "5X3"
!byte 3
!raw "6X3"
!byte 3
!raw "7X3"
!byte 3
!raw "4X4"
!byte 3
!raw "5X4"
!byte 3
!raw "6X4"
!byte 3
!raw "7X4"
!byte 3
!raw "4X5"
!byte 3
!raw "5X5"
!byte 3
!raw "6X5"
!byte 3
!raw "7X5"
kDash
!byte 1
!raw "-"
worldhelp
!byte 40
!raw "ARROWS TO SELECT, RETURN TO PLAY, OR ESC"

View File

@ -5,17 +5,26 @@
;
; Public functions:
; - PlayEventLoop
; - ClearAndDrawPuzzle
; - DrawPuzzleChrome
; - AnimatePuzzleIntoPlace
; - AnimatePuzzleCompleted
;
selected_logical_column
; codes returned from PlayEventLoop to explain why the event loop ended
kCompletedPuzzle = 1
kRequestedRestart = 2
kPressedEsc = 3
gSelectedLogicalColumn
!byte 0
kLevelLeftMargins
kWorldLeftMargins
!byte 15,13,12,10
!byte 15,13,12,10
!byte 15,13,12,10
kLevelRightMargins
kWorldRightMargins
!byte 27,28,30,31
!byte 27,28,30,31
!byte 27,28,30,31
@ -23,6 +32,15 @@ kLevelRightMargins
kStartingColor
!byte $D5,$AA
;------------------------------------------------------------------------------
; 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: A = reason why event loop ended (see list above)
; all other registers and flags clobbered
;------------------------------------------------------------------------------
PlayEventLoop
bit CLEARKBD
- lda KBD
@ -39,6 +57,8 @@ PlayEventLoop
beq @eventRightArrow
cmp #$1B ; Esc
beq @eventEsc
cmp #$12 ; Ctrl-R
beq @eventCtrlR
cmp #$61
bcc +
and #$DF
@ -46,54 +66,65 @@ PlayEventLoop
bcc +
cmp #$5B
bcs +
jsr @eventLetter
jmp @eventLetter
+ jmp PlayEventLoop
@eventLeftArrow
ldy selected_logical_column
ldy gSelectedLogicalColumn
jsr EraseColumnSelectionIndicator
bne +
ldy puzzle_logical_width
+ dey
sty selected_logical_column
sty gSelectedLogicalColumn
jsr DrawColumnSelectionIndicator
jmp @done
jmp PlayEventLoop
@eventRightArrow
ldy selected_logical_column
ldy gSelectedLogicalColumn
jsr EraseColumnSelectionIndicator
iny
cpy puzzle_logical_width
bcc +
ldy #0
+ sty selected_logical_column
+ sty gSelectedLogicalColumn
jsr DrawColumnSelectionIndicator
jmp @done
jmp PlayEventLoop
@eventLetter
ldy selected_logical_column
ldy gSelectedLogicalColumn
jsr FindLetterInColumn
bcs +
; TODO
jmp PlayEventLoop
@eventEsc
+ rts
lda #kPressedEsc
rts
@eventCtrlR
lda #kRequestedRestart
rts
@eventUpArrow
ldy selected_logical_column
ldy gSelectedLogicalColumn
jsr ScrollPuzzleUp
bcs @fail
jsr ScrollUp
jsr CheckForTargetWord
bcc FoundTargetWord
bcc @foundTargetWord
bcs @done
@fail jsr SoftBell
@done jmp PlayEventLoop
@eventDownArrow
ldy selected_logical_column
ldy gSelectedLogicalColumn
jsr ScrollPuzzleDown
bcs @fail
jsr ScrollDown
jsr CheckForTargetWord
bcc FoundTargetWord
bcs @done ; always branches
bcc @foundTargetWord
bcs @done
FoundTargetWord
@foundTargetWord
ldx #4
ldy #0
- lda puzzle_data4, y
@ -101,90 +132,24 @@ FoundTargetWord
iny
cpy puzzle_logical_width
bne -
; TODO check whether puzzle is complete
jmp PlayEventLoop
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 gLevelID
ldy kLevelRightMargins, x
sty @right+1
ldy GlobalLeftMargin
dey
tya
and #1
tax
lda kStartingColor, x
- sta ($FE), y
eor #$7F
iny
@right cpy #$FD ; SMC
bcc -
jsr IsPuzzleComplete
bcs @done
lda #kCompletedPuzzle
rts
EraseColumnSelectionIndicator
; out: preserves X/Y
stx @x+1
sty @y+1
;------------------------------------------------------------------------------
; ClearAndDrawPuzzle
; clears screen and draws current puzzle (but not column selection indicator or
; other UI elements)
;
; in: none
; out: all registers and flags clobbered
;------------------------------------------------------------------------------
ClearAndDrawPuzzle
; jsr Home
; bit TEXTMODE
jsr DrawThinLines
@x ldx #$FD ; SMC
@y ldy #$FD ; SMC
rts
DrawColumnSelectionIndicator
; out: preserves X/Y
stx @x+1
sty @y+1
ldx #$55
jsr DrawOneSelectionIndicator
ldx #$6B
jsr DrawOneSelectionIndicator
@x ldx #$FD ; SMC
@y ldy #$FD ; SMC
rts
DrawOneSelectionIndicator
lda HGRLO, x
sta $FE
lda HGRHI, x
sta $FF
ldy GlobalLeftMargin
dey
tya
and #1
eor #1
tax
lda kStartingColor, x
ldx selected_logical_column
beq +
- iny
iny
iny
eor #$7F
dex
bne -
+ ldx #4
- sta ($FE), y
eor #$7F
iny
dex
bne -
rts
RedrawPuzzle
jsr Home
bit TEXTMODE
jsr DrawThinLines
+LDADDR puzzle_data
+LDADDR puzzle_data0
+ST16 $FE
ldx #0 ; logical row
-- ldy #0 ; logical column
@ -206,9 +171,15 @@ RedrawPuzzle
bit GFXMODE
rts
;------------------------------------------------------------------------------
; AnimatePuzzleIntoPlace
;
; in: none
; out: all registers and flags clobbered
;------------------------------------------------------------------------------
AnimatePuzzleIntoPlace
ldx gLevelID
ldy kLevelWidths, x
ldx gWorldID
ldy kPuzzleWidths, x
sty @max+1
ldy #0
-- ldx #4
@ -220,3 +191,164 @@ AnimatePuzzleIntoPlace
@max cpy #$FD ; SMC
bne --
rts
;------------------------------------------------------------------------------
; DrawPuzzleChrome
; draw all elements on puzzle screen that are not the actual puzzle
; (e.g. column selection indicator, game title, help text)
;
; in: none
; out: all registers and flags clobbered
;------------------------------------------------------------------------------
DrawPuzzleChrome
+PRINT_AT kTitleLine1, 0, 0
+PRINT_AT kTitleLine2, 1, 0
+PRINT_AT kTitleLine3, 2, 0
+LDADDR kWorldShortNames
+ST16 $FE
lda gWorldID
asl
asl
clc
adc $FE
sta $FE
bcc +
inc $FF
+
lda #4
sta VTAB
lda #0
sta HTAB
+LD16 $FE
jsr DrawHeavySilkString
+LDADDR kDash
jsr DrawHeavySilkString
ldx gPuzzleID
inx ; visible puzzle number is 1-based
lda #$30 ; padding character ('0')
jsr ToASCIIString
+LDADDR $00F1
jsr DrawHeavySilkString
jmp DrawColumnSelectionIndicator
;------------------------------------------------------------------------------
; 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 kStartingColor, 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 kStartingColor, x
ldx gSelectedLogicalColumn
beq +
- iny
iny
iny
eor #$7F
dex
bne -
+ ldx #4
- sta ($FE), y
eor #$7F
iny
dex
bne -
rts

View File

@ -7,19 +7,19 @@
; Public functions:
; - TitlePage
;
; Public variables:
; - titleline1
; - titleline2
; - titleline3
; Public constants:
; - kTitleLine1
; - kTitleLine2
; - kTitleLine3
titlechar = $FD
titleline1
kTitleLine1
!byte 7
!raw "MILLION"
titleline2
kTitleLine2
!byte 7
!raw "PERFECT"
titleline3
kTitleLine3
!byte 7
!raw "LETTERS"
@ -79,11 +79,11 @@ TitlePage
beq @line3
@random lda titlechar
jmp + ; can't use a BNE because <titlechar> will actually be 0 the third time around
@line1 lda titleline1-2, y
@line1 lda kTitleLine1-2, y
bne +
@line2 lda titleline2-2, y
@line2 lda kTitleLine2-2, y
bne +
@line3 lda titleline3-2, y
@line3 lda kTitleLine3-2, y
+ jsr DrawLargeCharacter
lda titlechar
beq +