diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..05bab1d --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +## 2.0 - 2025-08-30 + +_This release removes the tape version of the game._ + +### Changed + +- Game code is now modularized so that only the current module plus a resident master is loaded into memory + +### Added + +- Simple sound effects (replacing the standard *BELL* from the monitor routines) +- Game save and load +- High score save + +### Removed + +- Tape variant of the game + +## 1.0 - 2025-08-20 + +_First release._ \ No newline at end of file diff --git a/Makefile b/Makefile index 798da8b..ec1e49e 100644 --- a/Makefile +++ b/Makefile @@ -5,23 +5,35 @@ ACMD=/d/Users/hkzla/software/applecommander/AppleCommander-ac-1.11.0.jar JAVA=/c/Program\ Files/Microsoft/jdk-21.0.8.9-hotspot/bin/java.exe # Program output -PRG=output +SW_NAME=tk2048 + +MASTER_PRG=master +INTRO_PRG=intro +DLOG_PRG=dlog +GAME_PRG=game # Libraries LIBS=clib-6502.a +MASTER_ASM_SRCS = tk2k_startup_master.s disk2.s master_func.s +MASTER_C_SRCS = master_main.c dos_floppy.c -# Common source files -ASM_SRCS = tk2k_startup.s preserve_zero_pages.s input_asm.s -C_SRCS = main.c monitor_subroutines.c utility.c \ - game_graphics.c game_logic.c input.c \ - line_data.c tiles.c graph_misc_data.c \ - arrows_pic.c loading_screen.c +INTRO_ASM_SRCS = tk2k_startup_module.s preserve_zero_pages.s +INTRO_C_SRCS = intro_main.c utility.c + +DLOG_ASM_SRCS = tk2k_startup_module.s preserve_zero_pages.s sound.s +DLOG_C_SRCS = dlog_main.c input.c utility.c game_graphics.c line_data.c + +GAME_ASM_SRCS = tk2k_startup_module.s preserve_zero_pages.s input_asm.s sound.s +GAME_C_SRCS = game_main.c input.c utility.c game_graphics.c line_data.c game_logic.c arrows_pic.c tiles.c graph_misc_data.c # Object files -OBJS = $(ASM_SRCS:%.s=%.o) $(C_SRCS:%.c=%.o) +MASTER_OBJS = $(MASTER_ASM_SRCS:%.s=%.o) $(MASTER_C_SRCS:%.c=%.o) +INTRO_OBJS = $(INTRO_ASM_SRCS:%.s=%.o) $(INTRO_C_SRCS:%.c=%.o) +DLOG_OBJS = $(DLOG_ASM_SRCS:%.s=%.o) $(DLOG_C_SRCS:%.c=%.o) +GAME_OBJS = $(GAME_ASM_SRCS:%.s=%.o) $(GAME_C_SRCS:%.c=%.o) -all: $(PRG).wav $(PRG).woz +all: $(SW_NAME).woz %.o: %.s as6502 --core=6502 --list-file=$(@:%.o=obj/%.lst) -o obj/$@ $< @@ -29,26 +41,46 @@ all: $(PRG).wav $(PRG).woz %.o: %.c cc6502 --core=6502 -O2 --list-file=$(@:%.o=obj/%.lst) --char-is-unsigned --pedantic-errors -o obj/$@ $< -$(PRG).hex: $(OBJS) - (cd obj ; ln6502 -g ../linker-files/linker.scm $^ -o ../out/$@ $(LIBS) -l --cross-reference --cstartup=tk2k --no-automatic-placement-rules --output-format intel-hex --rom-code) - -$(PRG).bin: $(PRG).hex - (cd out ; objcopy -I ihex -O binary $(PRG).hex $(PRG).bin) - -$(PRG).wav: $(PRG).bin - (cd out ; c2t -3 $(PRG).bin,801 $(PRG).wav) +$(MASTER_PRG).hex: $(MASTER_OBJS) + (cd obj ; ln6502 -g ../linker-files/master.scm $^ -o ../out/$@ $(LIBS) -l --cross-reference --cstartup=tk2k --no-automatic-placement-rules --output-format intel-hex --rom-code) -$(PRG).dsk: $(PRG).bin - (cd out ; cp ../dsk/TK2048_AUTO_BRUN.dsk ./$(PRG).dsk; cat $(PRG).bin | $(JAVA) -jar $(ACMD) -p $(PRG).dsk HELLO B 0x801) +$(INTRO_PRG).hex: $(INTRO_OBJS) + (cd obj ; ln6502 -g ../linker-files/module.scm $^ -o ../out/$@ $(LIBS) -l --cross-reference --cstartup=tk2k --no-automatic-placement-rules --output-format intel-hex --rom-code) + +$(DLOG_PRG).hex: $(DLOG_OBJS) + (cd obj ; ln6502 -g ../linker-files/module.scm $^ -o ../out/$@ $(LIBS) -l --cross-reference --cstartup=tk2k --no-automatic-placement-rules --output-format intel-hex --rom-code) -$(PRG).woz: $(PRG).dsk - (cd out ; dsk2woz ./$(PRG).dsk ./$(PRG).woz) +$(GAME_PRG).hex: $(GAME_OBJS) + (cd obj ; ln6502 -g ../linker-files/module.scm $^ -o ../out/$@ $(LIBS) -l --cross-reference --cstartup=tk2k --no-automatic-placement-rules --output-format intel-hex --rom-code) + +$(MASTER_PRG).bin: $(MASTER_PRG).hex + (cd out ; objcopy -I ihex -O binary $(MASTER_PRG).hex $(MASTER_PRG).bin) + +$(INTRO_PRG).bin: $(INTRO_PRG).hex + (cd out ; objcopy -I ihex -O binary $(INTRO_PRG).hex $(INTRO_PRG).bin) + +$(DLOG_PRG).bin: $(DLOG_PRG).hex + (cd out ; objcopy -I ihex -O binary $(DLOG_PRG).hex $(DLOG_PRG).bin) + +$(GAME_PRG).bin: $(GAME_PRG).hex + (cd out ; objcopy -I ihex -O binary $(GAME_PRG).hex $(GAME_PRG).bin) + +$(SW_NAME).dsk: $(MASTER_PRG).bin $(INTRO_PRG).bin $(DLOG_PRG).bin $(GAME_PRG).bin + (cd out ; cp ../dsk/TK2048_AUTO_BRUN.dsk ./$(SW_NAME).dsk; \ + cat $(MASTER_PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk HELLO B 0x800; \ + cat $(INTRO_PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk INTRO b; \ + cat $(DLOG_PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk DLOG b; \ + cat $(GAME_PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk GAME b; \ + cat ../data/LOADS.bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk LOADS b; \ + cat ../data/STATE.bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk STATE b;) + +$(SW_NAME).woz: $(SW_NAME).dsk + (cd out ; dsk2woz ./$(SW_NAME).dsk ./$(SW_NAME).woz) clean: - -(cd obj ; rm $(OBJS) $(OBJS:%.o=%.lst)) + -rm obj/*.o -rm obj/*.lst -rm out/*.hex -rm out/*.bin - -rm out/*.wav -rm out/*.dsk -rm out/*.woz \ No newline at end of file diff --git a/README.md b/README.md index 3b1350c..34a756e 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,14 @@ By using this project You will agree that I cannot be held responsible if it wil ## Introduction -I recently built myself a Microdigital TK2000, a clone of an almost-clone (the MPF-II) of an Apple II. +![TK2048 V2.0 title screen](pics/title.png) -Building it from the files found in [this github repository](https://github.com/clemarfolly/Microdigital-TK2000) proved to be a [bit of an adventure](https://github.com/hkzlab/TK2000_Build_Notes), -but I took a liking to the computer, so I started developing [boards](https://codeberg.org/hkzlab) for it. +This game is an excuse and a testbed for the development on the Microdigital TK2000 clone [I recently built for myself](https://github.com/hkzlab/TK2000_Build_Notes). -Hardware fiddling is fine, but given that there isn't much software available for the machine and that I wanted to experiment with the [Calypsi toolchain](https://www.calypsi.cc/) a bit, I decided to -pick a simple game concept and develop it for the machine. +My primary objective with this game is building something where I can add support for all my expansion boards (well, at least where it makes a sliver of sense) so I can test and PoC them, beside, +I want to experiment with the [Calypsi toolchain](https://www.calypsi.cc/). -I decided for 2048, the now-classic tile sliding game, as it provided several advantages as a first time/playground project: +I decided for a port of 2048, the now-classic tile sliding game, as it provided several advantages as a first time/playground project: - Doesn't need complex graphics hardware or advanced artist skills - It can be drawn in B/W with static graphics, but animations and colors can be added in the future @@ -25,7 +24,11 @@ I decided for 2048, the now-classic tile sliding game, as it provided several ad - Can be expanded with support for additional hardware (sound cards, video cards, timers) if so desired, but it's not necessary - Floppy drive can be potentially used not only to load the game, but also for saving the high score -![TK2048 main game shown on a green phosphor CRT](pics/screenshot.jpg) +![TK2048 V1.0 main game shown on a green phosphor CRT](pics/screenshot.jpg) + +### Support + +If you wish to support me in building new hardware and software for old machines, [throw a few euros in my direction via Ko-Fi](https://ko-fi.com/hkzlab) ☕! ### Current state @@ -33,13 +36,18 @@ The game is playable, and the following features have been implemented: - B/W mode - Single graphical tileset -- Crude sound (only beeps) +- Crude sound - Control via keyboard - 5x5 grid, with randomized start and new tiles +- Game save/load +- Highscore saving ## How to play -You can move the tiles using the cursor keys. Pushing CTRL-R will make you lose the game and restart it. +- You can move the tiles using the cursor keys +- `CTRL-R` during the game will clear your score and restart +- `CTRL-S` during the game will save the game on floppy and let you continue +- `CTRL-L` during the game will load the previous save from floppy and let you continue from there The game ends once you reach a tile with a value of 2048. @@ -47,28 +55,6 @@ The game ends once you reach a tile with a value of 2048. Just put your floppy in the first drive and power on the TK2000. It will autoboot. -### Tape version - -Power on the TK2000 with the sound player connected to the EAR input. Once at the `>` prompt, type the following - -``` -CALL -159 -``` - -This will drop you into the monitor, `@` prompt. Type - -``` -801.4EBBRA -``` - -And press play on tape. If the load completes successfully, the computer will beep and give you back the prompt. Now type: - -``` -801G -``` - -This will start the game. - ## How to build You need the following: @@ -77,11 +63,7 @@ You need the following: - [Calypsi 6502 toolchain](https://www.calypsi.cc/) (tested with 5.10) - [AppleCommander](https://github.com/AppleCommander/AppleCommander), "ac" command line version (tested with 1.9.0) - [dsk2woz](https://github.com/TomHarte/dsk2woz) -- vitasam's fork of [c2t](https://github.com/vitasam/c2t) ## The future -I plan to refactor the whole game, modularize it, and leverage loading of single modules from a floppy disk. This -should give more flexibility, expandability, free some memory and allow me to exercise the floppy drive routines I wrote. - -As such, V1.0 will probably be the last one supporting loading from tape. \ No newline at end of file +I plan to add (optional) support for several expansion boards I'm working on, starting with a VDP board sporting a TMS9918A graphic processor. \ No newline at end of file diff --git a/data/LOADS.bin b/data/LOADS.bin new file mode 100644 index 0000000..be529d4 Binary files /dev/null and b/data/LOADS.bin differ diff --git a/data/STATE.bin b/data/STATE.bin new file mode 100644 index 0000000..65f57c2 Binary files /dev/null and b/data/STATE.bin differ diff --git a/graphics/loading_screen.aseprite b/graphics/loading_screen.aseprite index a6c2a59..279674d 100644 Binary files a/graphics/loading_screen.aseprite and b/graphics/loading_screen.aseprite differ diff --git a/graphics/loading_screen.png b/graphics/loading_screen.png index 2bb2d8e..1800be7 100644 Binary files a/graphics/loading_screen.png and b/graphics/loading_screen.png differ diff --git a/linker-files/linker.scm b/linker-files/linker.scm deleted file mode 100644 index 1a74116..0000000 --- a/linker-files/linker.scm +++ /dev/null @@ -1,19 +0,0 @@ -(define memories - '((memory zeroPage (address (#x56 . #xff)) (type ram) ;;; Should be starting at 0x56, ending at 0xff, but experiments in the monitor have shown doc is not to be trusted if monitor routines are used - (section registers zpage zzpage)) - (memory firstPage (address (#x100 . #x1ff)) (section stack)) - (memory reserved (address (#x200 . #x7ff)) (type ram)) - (memory program (address (#x801 . #x1fff)) (type ram) - (section (programStart #x801) (dii_critical_wr_code #x804) dii_critical_rd_code startup code)) - (memory displayPage1 (address (#x2000 . #x3fff)) (type ram) (section loadscreen)) - (memory upperData (address (#x4000 . #x85ff)) (type ram) (section switch idata cdata data_init_table)) ;;; usermem goes from 0x4000 to 0x9FFFF (included), we are splitting it - (memory upperMem (address (#x8600 . #x95ff)) (type ram) (section cstack zdata data heap zpsave)) - (memory dosMem (address (#x9600 . #x9fff)) (type ram)) - (memory displayPage2 (address (#xa000 . #xbfff)) (type ram)) - (memory io (address (#xc000 . #xc0ff)) (type ram)) - (memory rombank (address (#xc100 . #xffff)) (type rom)) - - (block cstack (size #x800)) - (block stack (size #x100)) - (block heap (size #x400)) - )) \ No newline at end of file diff --git a/linker-files/master.scm b/linker-files/master.scm new file mode 100644 index 0000000..2097742 --- /dev/null +++ b/linker-files/master.scm @@ -0,0 +1,30 @@ +(define memories + '((memory zeroPage (address (#x56 . #xff)) (type ram) + (section registers zpage zzpage)) + (memory firstPage (address (#x100 . #x1ff)) (section stack)) + (memory reserved (address (#x200 . #x7ff)) (type ram)) + (memory program (address (#x800 . #x15ff)) (type ram) + (section (programStart #x800) (dii_critical_wr_code #x803) (dii_critical_rd_code #x90b) startup code switch idata cdata data_init_table)) +;;; (memory exportedTables (address (#x1c00 . #x1c1f)) (type ram) (section functionTable)) ;;; Exported function table can be found here + (memory dataMem (address (#x1600 . #x1fff)) (type ram) (section cstack zdata data heap zpsave)) + (memory displayPage1 (address (#x2000 . #x3fff)) (type ram)) + (memory upperMem (address (#x4000 . #x99ff)) (type ram)) + (memory sharedMem (address (#x9a00 . #x9bff)) (type ram)) ;;; This memory page will be used to pass parameters and data between the master and the modules, and to save the game state + (memory diskBuffer (address (#x9c00 . #x9fff)) (type ram)) ;;; This memory will be used by the disk II routines as buffer + (memory displayPage2 (address (#xa000 . #xbfff)) (type ram)) + (memory io (address (#xc000 . #xc0ff)) (type ram)) + (memory rombank (address (#xc100 . #xffff)) (type rom)) + + (block cstack (size #x600)) + (block heap (size #x000)) + (block stack (size #x100)) + )) + + + ;;; Floppy buffer structure + ;;; The decoding table for the reads should be generated ad 0x0356 + ;;; The "twos" buffer should be located at 0x9C00 + ;;; The encoding table should be located at 0x9C56 + ;;; The "sixes" buffer should be located at 0x9D00, and it should overlap with the buffer used for DOS catalog reads + ;;; We leave pages 9E and 9F alone, as resetting back with those modified leads to breakage + \ No newline at end of file diff --git a/linker-files/module.scm b/linker-files/module.scm new file mode 100644 index 0000000..81c846b --- /dev/null +++ b/linker-files/module.scm @@ -0,0 +1,19 @@ +(define memories + '((memory zeroPage (address (#x56 . #xff)) (type ram) + (section registers zpage zzpage)) + (memory firstPage (address (#x100 . #x1ff)) (section stack)) + (memory reserved (address (#x200 . #x1fff)) (type ram)) + (memory displayPage1 (address (#x2000 . #x3fff)) (type ram)) + (memory upperProg (address (#x4000 . #x93ff)) (type ram) (section (programStart #x4000) startup code switch idata cdata data_init_table)) + (memory upperData (address (#x9400 . #x99ff)) (type ram) (section cstack zdata data heap)) + (memory sharedMem (address (#x9a00 . #x9bff)) (type ram)) ;;; This memory page will be used to pass parameters and data between the master and the modules, and to save the game state + (memory diskBuffer (address (#x9c00 . #x9eff)) (type ram)) + (memory zeroPageBackup (address (#x9f00 . #x9fff)) (type ram) (section (zpsave #x9f00))) + (memory displayPage2 (address (#xa000 . #xbfff)) (type ram)) + (memory io (address (#xc000 . #xc0ff)) (type ram)) + (memory rombank (address (#xc100 . #xffff)) (type rom)) + + (block cstack (size #x400)) + (block heap (size #x020)) + (block stack (size #x100)) + )) \ No newline at end of file diff --git a/pics/title.png b/pics/title.png new file mode 100644 index 0000000..1156bd5 Binary files /dev/null and b/pics/title.png differ diff --git a/src/disk2.h b/src/disk2.h new file mode 100644 index 0000000..4238f06 --- /dev/null +++ b/src/disk2.h @@ -0,0 +1,46 @@ +#ifndef _DISK2_HEADER_ +#define _DISK2_HEADER_ + +#include + +#define DII_MAX_TRACK 96 +#define DECODING_MAPPING_TABLE_DEFAULT_ADDRESS 0x0356 +#define ENCODING_MAPPING_TABLE_DEFAULT_ADDRESS 0x9D56 +#define WRITE_SIXES_BUFFER_DEFAULT_ADDRESS 0x9C00 +#define WRITE_TWOS_BUFFER_DEFAULT_ADDRESS 0x9D00 + +void dii_generate_6bit_decoding_mapping_table(uint8_t* dest); +void dii_generate_6bit_encoding_mapping_table(uint8_t* dest); +void dii_encode_gcr62_data(uint8_t *src, uint8_t* dest6, uint8_t* dest2); +void dii_power_on(uint8_t io_offset, uint8_t drive_no); +void dii_power_off(uint8_t io_offset); +void dii_head_reposition(uint8_t io_offset, uint8_t cur_track, uint8_t dest_track); + +/** + * Patch the dii_write_sector routine to change the memory addresses used. + * buf2 and buf6 must start at the beginning of a memory page, nibtab must fit completely inside a page. + */ +void dii_patch_write(uint8_t* buf6, uint8_t* buf2, uint8_t *nibtab); + +/** + * This routine requires the decoding mapping table to be initialized beforehand at + * DECODING_MAPPING_TABLE_DEFAULT_ADDRESS, and uses space in page 3 as a buffer + * + * If the return value is 0, the read failed + */ +uint8_t dii_read_sector(uint8_t io_offset, uint8_t track, uint8_t sector, uint8_t *buffer, uint8_t aonly); + +/** + * Writes gcr 6+2 encoded data to the chosen sector. The track number is used to verify that we're on the correct + * track before writing. This routine does not move the head. + * The data must be pre-encoded. + * + * By default, it writes 86 bytes from the "twos" buffer located at 0x9C00, 256 bytes of "sixes" from the buffer + * at 0x9D00 and using a 63 entries translation table preloaded at 0x9C56. + * If these addresses need to be changes, use the dii_patch_write routine. + * + * If the return value is > 0, the write failed + */ +uint8_t dii_write_sector(uint8_t io_offset, uint8_t track, uint8_t sector); + +#endif /* _DISK2_HEADER_ */ diff --git a/src/disk2.s b/src/disk2.s new file mode 100644 index 0000000..f29f44a --- /dev/null +++ b/src/disk2.s @@ -0,0 +1,726 @@ + .rtmodel version,"1" + .rtmodel core,"6502" + + .extern _Vfp + .extern _Vsp + .extern _Zp + +;;; I/O offsets +DII_BASE: .equ 0xC080 +PH0_OFF: .equ DII_BASE + 0x00 +PH0_ON: .equ DII_BASE + 0x01 +PH1_OFF: .equ DII_BASE + 0x02 +PH1_ON: .equ DII_BASE + 0x03 +PH2_OFF: .equ DII_BASE + 0x04 +PH2_ON: .equ DII_BASE + 0x05 +PH3_OFF: .equ DII_BASE + 0x06 +PH3_ON: .equ DII_BASE + 0x07 +PWR_DWN: .equ DII_BASE + 0x08 +PWR_UP: .equ DII_BASE + 0x09 +SEL_D1: .equ DII_BASE + 0x0A +SEL_D2: .equ DII_BASE + 0x0B +READ_SW: .equ DII_BASE + 0x0C +WRITE_SW: .equ DII_BASE + 0x0D +CLEAR_SW: .equ DII_BASE + 0x0E +SHIFT_SW: .equ DII_BASE + 0x0F + +;;; Monitor routines +MON_WAIT .equ 0xFCA8 + +;;; Memory addresses +CONV_TABLE .equ 0x0356 ; 6+2 conversion table generated by BOOT0 +PAGE3_BUF .equ 0x0300 + +;;; Summing up the calling convention as... +;;; Parameters are passed in register A and pseudo registers _Zp[0-7]. +;;; Registers X, Y and pseudo registers 0–23 are destroyed by a function call. +;;; Pseudo registers 24–47 must be preserved. + + .section code,text + +;;; dii_generate_6bit_decoding_mapping_table: +;;; Generates a 6+2 decoding table at the chosen address +;;; Parameters: +;;; - address [_Zp[0], _Zp[1]]: Address for the generation of the table +;;; +;;; Returns: Nothing +dii_generate_6bit_decoding_mapping_table: +T_REGX$: .equ _Zp+4 +T_REGY$: .equ _Zp+3 +T_BITS$: .equ _Zp+2 +P_DESTH$: .equ _Zp+1 +P_DESTL$: .equ _Zp+0 + + ldy #0x00 + ldx #0x03 + +CreateTableLoop$: + stx zp:T_BITS$ + txa + asl a ; Arithmetic Shift Left, will put the high bit in carry + bit zp:T_BITS$ ; Check of the overlap for the shifted value. If two one overlaps it means that there are two adjacent 1s + beq reject$ ; Nope, refuse this number + ora zp:T_BITS$ ; Merge shifted and non-shifted number + eor #0xff ; Invert the value, we'll look for adjacent zeros (which have now become 1s) + and #0x7e ; Discard msb and lsb, as one is mandatory and the other falsely generated with the shift +check_dub0$:bcs reject$ ; Branch on C = 1. Initial bhi bit set or adjacent 0 bits set + lsr a ; Shift right. lower bit into carry + bne check_dub0$ ; Check all the bits until we're left with a 0 + tya ; We have a good one. Store it in the table + sty zp:T_REGY$ ; Save y + stx zp:T_REGX$ ; Save x + ldy zp:T_REGX$ ; Y <- X + sta (zp:P_DESTL$),y ; Bytes that reference this will have highest bit set, so this'll be accessed using CONV_TABLE-128 + ldy zp:T_REGY$ ; Recover y + iny +reject$: inx ; Next candidate + bpl CreateTableLoop$ + + rts + +;;; dii_generate_6bit_encoding_mapping_table: +;;; Generates a 6bit mapping table to be used with GCR 6+2 +;;; Parameters: +;;; - address [_Zp[0], _Zp[1]]: Address for the generation of the table +;;; +;;; Returns: Nothing +dii_generate_6bit_encoding_mapping_table: +T_BITS$: .equ _Zp+2 +P_DESTH$: .equ _Zp+1 +P_DESTL$: .equ _Zp+0 + + ldy #0x00 + ldx #0x03 + +CreateTableLoop$: + stx zp:T_BITS$ + txa + asl a + bit zp:T_BITS$ + beq reject$ + ora zp:T_BITS$ + eor #0xff + and #0x7e +check_dub0$:bcs reject$ + lsr a + bne check_dub0$ + txa + ora #0x80 + sta (zp:P_DESTL$),y + iny +reject$: inx ; Next candidate + bpl CreateTableLoop$ + + rts + +;;; dii_encode_gcr62_data: +;;; Split 256 bytes of data into 256 + 86 6-bit nibbles +;;; Parameters: +;;; - source [_Zp[0], _Zp[1]]: Address for the 256 bytes data source +;;; - buf6 [_Zp[2], _Zp[3]]: Address for the destination buffer that will contain 256 "sixes" nibble entries +;;; - buf2 [_Zp[4], _Zp[5]]: Address for the destination buffer that will contain 86 "twos" nibble entries +;;; +;;; Returns: Nothing +;;; +;;; We will go through the 86 bytes three times (which means a total of 258 runs). Note that we have a source +;;; of 256 bytes, plus, the result will have to be 256+86, so, in the "twos" buffer we'll have one entry containing +;;; (86*3)-256 = 2 spurious (duplicated entries); +;;; +dii_encode_gcr62_data: +T_REGY$: .equ _Zp+7 +T_BITS$: .equ _Zp+6 +P_DEST2H$: .equ _Zp+5 +P_DEST2L$: .equ _Zp+4 +P_DEST6H$: .equ _Zp+3 +P_DEST6L$: .equ _Zp+2 +P_SRCH$: .equ _Zp+1 +P_SRCL$: .equ _Zp+0 + + ldx #0x00 ; This will address the current byte of the "twos" + ldy #0x02 ; We'll use this as an offset into the source (and sixes) buffer. Note that this means the first call + ; will have BASE+1, the second BASE+0, while the third BASE+255, and will decrease from there + ; onward. Here are the two spurious entries. +prenib1$: dey + sty zp:T_REGY$ ; Save y + txa ; Copy x to a + tay ; then a to y ---- x -> y + lda (P_DEST2L$),y ; Recover the current 'two' + sta zp:T_BITS$ ; Save it on a temp register + ldy zp:T_REGY$ ; Recover y + + lda (zp:P_SRCL$),y ; Get current byte from the source buffer + lsr a ; Put the two least significant bits into the corrisponding byte of the "twos" + rol zp:T_BITS$ + lsr a + rol zp:T_BITS$ ; Move two bits in + sta (zp:P_DEST6L$),y; The remaining six bites will go in the 'sixes' buffer + sty zp:T_REGY$ ; Save y + txa ; Move X to Y + tay + lda zp:T_BITS$ ; Recover the updated 'two' + sta (P_DEST2L$),y ; Save the updated 'two' + ldy zp:T_REGY$ ; Recover y + inx + cpx #86 + bcc prenib1$ ; We'll repeat this 86 times + ldx #0 + tya + bne prenib1$ ; After decreasing y 258 times, we'll end up with 0 here (2 - 86 - 86 - 86 = -256) and go on + + ldy #85 +prenib2$: lda (zp:P_DEST2L$),y + and #0x3f + sta (zp:P_DEST2L$),y + dey + bpl prenib2$ + + rts + +;;; dii_power_on: +;;; Selects and powers on a disk drive +;;; Parameters: +;;; - offset [A]: offset from the base address (DII_BASE) to access the Disk II interface +;;; - drive [_Zp[0]]: Drive number, 0 for first drive, 1 for the next. Only the first bit will be considered +;;; +;;; Returns: Nothing +dii_power_on: +P_OFFSET$: .equ _Zp+1 +P_DRVNO$: .equ _Zp+0 + + and #0x70 ; Mask the offset with 0x70, so we keep the bits relevant for "slot" selection + sta zp:P_OFFSET$ ; Save the cleaned offset, we'll use it later to calculate the offset to turn on the drive + + tax + lda CLEAR_SW,x ; Enter read mode + lda READ_SW,x + + lda #0x01 ; Keep only the lsb of the drive number parameter + and zp:P_DRVNO$ + ora zp:P_OFFSET$ ; Combine the result with the "slot" offset + tax + lda SEL_D1,x ; Select the proper drive + + lda zp:P_OFFSET$ + tax + lda PWR_UP,x ; Drive Power up + + jsr __short_wait ; Wait for the driver to power on and the motor to spin up + rts + +;;; dii_power_off: +;;; Selects and powers on a disk drive +;;; Parameters: +;;; - offset [A]: Offset from the base address (DII_BASE) to access the Disk II interface +;;; +;;; Returns: Nothing +dii_power_off: + and #0x70 ; Mask the offset with 0x70, so we keep the bits relevant for "slot" selection + tax + lda PWR_DWN,x ; Drive Power down + + rts + +;;; dii_head_reposition: +;;; Repositions the head according to the current track and destination track. +;;; Keep in mind that the parameters to this function are taking "phase movements" into consideration, not the +;;; DOS track number. +;;; There is a correspondance of 1:2 between DOS tracks and phase movements, which means that TWO phase movements +;;; will shift ONE track. +;;; With standard DOS, all EVEN numbered tracks (0 included) are positioned under "phase 0" of the head, all ODD +;;; numbered tracks will be positioned under "phase 2". Moving to phase 1 or 3 will get us to half-tracks. +;;; Parameters: +;;; - offset [A]: Offset from the base address (DII_BASE) to access the Disk II interface +;;; - curtrack [_Zp[0]]: Current track number +;;; - desttrack [_Zp[1]]: Desired destination track number +;;; +;;; Returns: Nothing +dii_head_reposition: +P_OFFSET$: .equ _Zp+2 +P_DTRACK$: .equ _Zp+1 +P_CTRACK$: .equ _Zp+0 + + and #0x70 + sta zp:P_OFFSET$ ; Save the slot offset into a zp register +H_MNLOOP$: lda zp:P_CTRACK$ ; Check and branch depending on relationship between current and destination track + cmp zp:P_DTRACK$ + beq H_ALLDONE$ + bcc H_MOVUP$ + bcs H_MOVDOWN$ + +H_MOVDOWN$: dec zp:P_CTRACK$ ; Prepare to move down a track + jmp H_DOWORK$ + +H_MOVUP$: inc zp:P_CTRACK$ ; Prepare to move up a track + +H_DOWORK$: lda zp:P_CTRACK$ + and #0x03 ; We're going to keep only the 3 least significant bits of the current track + asl a ; Shifting them left, now we're using the 4 msb of the byte + ; This multiplication is necessary because the phase on (or off) registers + ; are spaced by two between each other, and this calculates the offset. See later. + ora zp:P_OFFSET$ ; And now combine the slot number with the track value we calculated in the previous step... + tay ; a -> y + lda PH0_ON,y ; We are offsetting the base to reach for both the slot number and the proper phase + lda #0x56 + jsr MON_WAIT ; Call the monitor wait routine + lda PH0_OFF,y ; Now turn the phase off + jmp H_MNLOOP$ ; And go back checking the status of the relationship between current and destination tracks +H_ALLDONE$: rts ; We're done + + +__short_wait: + lda #0xEF + sta zp:_Zp ; We'll use the first two zp registers to hold a counter + lda #0xD8 + sta zp:_Zp+1 +MWAITA$: ldy #0x12 +MWAITB$: dey + bne MWAITB$ + inc zp:_Zp + bne MWAITA$ + inc zp:_Zp+1 + bne MWAITA$ + rts + + +;;; dii_patch_write: +;;; Modifies the addresses in the dii_write_sector routine to change where data is read +;;; Parameters: +;;; - buf6 [_Zp[0] and _Zp[1]]: Address for the "sixes" buffer. 256 bytes in length. Must start at the beginning of a page+ +;;; - buf2 [_Zp[2] and _Zp[3]]: Address for the "twos" buffer. 86 bytes in length. Must start at the beginning of a page +;;; - nibtab [_Zp[4] and _Zp[5]]: Address for the translation table for nibbles. 63 bytes in length. +;;; +;;; Returns: nothing +;;; +dii_patch_write: +P_NIBTH$: .equ _Zp+5 +P_NIBTL$: .equ _Zp+4 +P_BUF2H$: .equ _Zp+3 +P_BUF2L$: .equ _Zp+2 +P_BUF6H$: .equ _Zp+1 +P_BUF6L$: .equ _Zp+0 + + ;;; Common changes + lda #0x00 ; Make sure we start at the beginning of the page + sta _b2add01+1 + sta _b2add02+1 + sta _b6add01+1 + sta _b6add02+1 + + ;;; Change the address for buf2 + lda zp:P_BUF2H$ ; Overwrite the most significant byte + sta _b2add01+2 + sta _b2add02+2 + + dec zp:P_BUF2H$ ; Make sure we save this address for buf2 - 1, needed for page crossing + lda zp:P_BUF2H$ + sta _b2madd01+2 + + lda #0xff + sta _b2madd01+1 + + ;;; Change the address for buf6 + lda zp:P_BUF6H$ ; Overwrite the most significant byte + sta _b6add01+2 + sta _b6add02+2 + + ;;; Nibble table address + lda zp:P_NIBTL$ + sta _nibadd01+1 + sta _nibadd02+1 + sta _nibadd03+1 + + lda zp:P_NIBTH$ + sta _nibadd01+2 + sta _nibadd02+2 + sta _nibadd03+2 + + rts + +;;; Timing critical code here, must not cross page boundary + + .section dii_critical_rd_code,text,noreorder + +;;; dii_read_sector: +;;; Reads and decode a 256 byte sector from the Disk II interface. +;;; It is shamelessly copied and then slightly adaptet from the BOOT0 code in the Disk II interface PROM. +;;; Parameters: +;;; - offset [A]: Offset from the base address (DII_BASE) to access the Disk II interface +;;; - track [_Zp[0]]: Search for this track +;;; - sector [_Zp[1]]: Search for the sector specified in the low nibble of this byte. If high nibble is != 0, do not read the data, and return after address header +;;; - destination [_Zp[2] and _Zp[3]]: Destination address for a 256b buffer for the data +;;; - destination [_Zp[4]]: If != 0, only the address data will be read +;;; +;;; Returns: Nothing +insta_return: + rts +dii_read_sector: +RETRIES$: .equ 0xff +T_HCHKS$: .equ _Zp+8 +T_BITS$: .equ _Zp+7 +T_RETRYC$: .equ _Zp+6 +P_OFFSET$: .equ _Zp+5 +P_ADDRONLY$:.equ _Zp+4 +P_DESTH$: .equ _Zp+3 +P_DESTL$: .equ _Zp+2 +P_SECTOR$: .equ _Zp+1 +P_TRACK$: .equ _Zp+0 + + ; Save the slot offset, properly cleaned, into a zp register + and #0x70 + sta zp:P_OFFSET$ + tax + + ; Load the retry counter + lda #RETRIES$ ; Set up the retry counter + sta zp:T_RETRYC$ + + ;;; Address header is the sequence (D5 AA 96). Data header is (D5 AA AD). +RdSect$: + dec zp:T_RETRYC$ + beq insta_return ; Out of retries + + clc ; Clear carry. C=0 will have the code look for an address, C=1 for data +RdSect_C$: php ; Push state on stack + +RdByte1$: lda READ_SW,x + bpl RdByte1$ ; Read the first byte +ChkD5$: eor #0xD5 ; An xor of 0xD5 with 0xD5 will get ourselves a 0, something else otherwise + bne RdByte1$ ; Not the first byte, keep searching + +RdByte2$: lda READ_SW,x + bpl RdByte2$ ; Read the second byte + cmp #0xAA + bne ChkD5$ ; Not the second byte, check if it's the first... + nop + +RdByte3$: lda READ_SW,x + bpl RdByte3$ + cmp #0x96 ; Check if third byte is 0x96 + beq FoundAddr$ ; We found an address! + + plp ; Pull the status byte + bcc RdSect$ ; We did not want to read data (C=0), so we start searching again + eor #0xAD ; We did want to read data! So check if it's the last byte of a data sequence + beq FoundData$ ; It is, read the data! (A = 0 at this point) + + ;;; ADDRESS: Data in the address header is stored in 4+4 GCR encoding +FoundAddr$: lda #0x00 ; Clear A + sta zp:T_HCHKS$ ; Save 0 to the temporary checksum in ZP + ldy #0x05 ; Dummy write (+5), Volume # (+4), Track # (+3), Sector # (+2), checksum (+1), in decreasing order. We'll ignore the epilogue bytes +hdr_loop$: sta (zp:P_DESTL$),y ; We'll use the destination buffer, temporarily... + eor zp:T_HCHKS$ ; Running and saving the checksum + sta zp:T_HCHKS$ + +RdByteA1$: lda READ_SW,x + bpl RdByteA1$ + rol a ; Rotate the bits left (the first byte is odd encoded 1 b7 1 b5 1 b3 1 b1). + sta zp:T_BITS$ +RdByteA2$: lda READ_SW,x + bpl RdByteA2$ ; Read the second byte (the second byte is even encoded 1 b6 1 b4 1 b2 1 b0) + and zp:T_BITS$ ; Merge with the first byte + dey + bne hdr_loop$ ; Are we at the last element? + +;RdByteA3$: lda READ_SW,x ; Ignore the epilogue bytes... +; bpl RdByteA3$ +;ChkDE$ eor #0xDE +; bne RdByteA3$ +; +;RdByteA4$: lda READ_SW,x +; bpl RdByteA4$ +; eor #0xAA +; bne ChkDE$ + + plp ; Pull the status byte + + lda zp:T_HCHKS$ ; Verify if checksum is bad. + bne RdSect$ ; If so, go back searching for an address... + + ldy #2 ; Point to the saved sector # in memory + lda (zp:P_DESTL$),y + cmp zp:P_SECTOR$ ; Check if this is the sector we want + bne RdSect$ ; If not, go back searching for an address... + iny ; Point to the saved track # in memory + lda (zp:P_DESTL$),y + cmp zp:P_TRACK$ ; Check if it is the track we want + bne RdSect$ ; If not, go back searching for an address... + + lda zp:P_ADDRONLY$ + bne insta_return + bcs RdSect_C$ ; Yes, go back reading the sector without clearing the Carry, which will be 1, so we'll read data at next run + + ;;; DATA. 6+2 encoded sector data + ;;; The values we'll read are encoded in a way that they'll have the range 0x96 - 0xff + ;;; A will be 0 on entry here + ;;; Read the 2s! +FoundData$: ldy #86 ; We'll read 86 bytes of data in 0x300-0x355 +Read2Loop$: sty zp:T_BITS$ ; Each byte of these will contain 3 sets of "2 bits" encoded inside +RdByteD1$: ldy READ_SW,x + bpl RdByteD1$ + eor CONV_TABLE-128,y; Note that we have 0x02d6 + 0x96 = 0x36c, our first table entry for the conversion. Also, note we're doing a rolling xor + ldy zp:T_BITS$ + dey + sta PAGE3_BUF,y ; Store the result of the 2s decoding in page 3 + bne Read2Loop$ ; Loop until we read them all + + ;;; Read the 6s! +Read6Loop$: sty zp:T_BITS$ ; y should be 255 at this point, which means we'll read 256 bytes +RdByteD2$: ldy READ_SW,x + bpl RdByteD2$ + eor CONV_TABLE-128,y; Going on with the rolling xor + ldy zp:T_BITS$ + sta (zp:P_DESTL$),y ; Store the result on the destination buffer + iny + bne Read6Loop$ ; Keep reading and converting 256 6s + + ;;; Verify the checksum! +RdByteD3$: ldy READ_SW,x + bpl RdByteD3$ + eor CONV_TABLE-128,y; Finally check if the checksum matches... + bne EndRdSect$ ; No, probably something got corrupted. Retry the read... + + ;;; Decode 6+2 data. The high 6 bits of every byte are already in place. We need to get the remaining 2 in + ldy #0x00 +init_x$: ldx #86 ; We'll go through the 2-bit pieces three times +decod_loop$:dex ; Decrement x + bmi init_x$ ; Branch on N = 1 (negative set), make sure we roll x. 0x02ff -> 0x0355 as it is an offset + lda (zp:P_DESTL$),y ; For each byte in the data buffer + lsr PAGE3_BUF,x ; grab the low two bits from the 2s at 0x0300 - 0x0355 (we'll shift them in the carry) + rol a ; and roll them in the least significant bits of the byte (and roll the carry in!) + lsr PAGE3_BUF,x + rol a + sta (zp:P_DESTL$),y ; Store the decoded byte back + iny + bne decod_loop$ + + ;;; Done! + lda zp:T_RETRYC$ ; Recover the number of retries + + rts + ;;; We're too far off the beginning, we cannot do a quick branch with an offset +EndRdSect$: + jmp RdSect$ + +;;; Timing critical code here, must not cross page boundary + + .section dii_critical_wr_code,text,noreorder + + +;;; dii_write_sector: +;;; Writes a 342 byte from the pointed GCR 6+2 data buffers. +;;; Parameters: +;;; - offset [A]: Offset from the base address (DII_BASE) to access the Disk II interface +;;; - track [_Zp[0]]: Check that we're writing on this track +;;; - sector [_Zp[1]]: Write on this sector +;;; +;;; Returns: 0 for a completed write, 0x10 for a write protected disk +;;; +;;; MAKE SURE THE FOLLOWING ALL FITS INSIDE A SINGLE PAGE!!! +;;; Note that the addresses used in this function will modified by calling dii_patch_write +;;; +wr_err_return: + lda READ_SW,x ; Back into read mode + lda #0x10 ; This will be the error code + rts +dii_write_sector: +RETRIES$: .equ 0xff + ; Note that the following buffers MUST be at the start of a page for write timing to be respected +__BUF2S: .equ 0x9D00 ; Default to this, it'll be overwritten when modifying this code. 86 entries. +__BUF6S: .equ 0x9C00 ; 256 entries. +__NIBTAB: .equ 0x9D56 ; This will contain the translation table for writing nibbles. 63 entries. +__OFFSETP6 .equ 0x0678 + +__T_VOL: .equ _Zp+9 +__T_TRK: .equ _Zp+8 +__T_SEC: .equ _Zp+7 +__T_CHK: .equ _Zp+6 ; 7, 8, 9 also taken +__T_HCHKS: .equ _Zp+5 +__T_HOLDNIB:.equ _Zp+4 +__T_RETRYC: .equ _Zp+3 +__P_OFFSET: .equ _Zp+2 +__P_SECTOR: .equ _Zp+1 +__P_TRACK: .equ _Zp+0 + ; Save the slot offset, properly cleaned, into a zp register and on page 6 + and #0x70 + sta zp:__P_OFFSET + sta __OFFSETP6 + tax + + lda #RETRIES$ ; Set up the retry counter + sta zp:__T_RETRYC + + ;;; Search for the address prologue (D5 AA 96) +RdSect$: + dec zp:__T_RETRYC + beq wr_err_return ; Out of retries + + ;;; Read the address prologue +RdByte1$: lda READ_SW,x + bpl RdByte1$ ; Read the first byte +ChkD5$: cmp #0xD5 + bne RdByte1$ ; Not the first byte, keep searching + nop + +RdByte2$: lda READ_SW,x + bpl RdByte2$ + cmp #0xaa + bne ChkD5$ + ldy #0x03 + +RdByte3$: lda READ_SW,x + bpl RdByte3$ + cmp #0x96 + bne ChkD5$ + + ;;; Read the address fields and store them in ZP + lda #0x00 +RdAddrFld$: sta zp:__T_HCHKS +RdByte4$: lda READ_SW,x + bpl RdByte4$ + rol a + sta zp:__T_HOLDNIB +RdByte5$: lda READ_SW,x + bpl RdByte5$ + and zp:__T_HOLDNIB + sta __T_CHK,y + eor zp:__T_HCHKS + dey + bpl RdAddrFld$ + + tay + bne RdSect$ ; Checksum error, retry + + ;;; Read the bit-slip nibbles +RdByte6$: lda READ_SW,x + bpl RdByte6$ + cmp #0xde + bne RdSect$ + nop + +RdByte7$: lda READ_SW,x + bpl RdByte7$ + cmp #0xaa + bne RdSect$ + nop + + ;;; Check we're on the correct track + lda __T_TRK + cmp zp:__P_TRACK + bne wr_err_return + + ;;; Check we're on the correct sector, or retry the read + lda __T_SEC + cmp zp:__P_SECTOR + bne RdSect$ + + lda WRITE_SW,x ; Combination of these two in sequence checks for write protect + lda CLEAR_SW,x ; and enables the write sequence. + bmi wr_err_return ; Write protected disk. Branch on N = 1 +_b2add01: + lda __BUF2S ; Get the first 2-bit encode byte + sta zp:__T_HOLDNIB ; and save it for future use. + + ;;; Write a 5-sync gap between the address epilogue and the data prologue + ;;; Write of the sync bytes will take 36 cycles + lda #0xff ; Sync byte + sta SHIFT_SW,x ; Write 1 sync byte + ora READ_SW,x + pha ; (3 cycles) + pla ; (4 cycles) + nop ; (2 cycles) + ldy #4 ; (2 cycles), plus load a counter to write 4 more syncs +__wr4ff: pha ; (3 cycles) + pla ; (4 cycles) + jsr __write2 ; (12 cycles before, 6 after) + dey ; (2 cycles) + bne __wr4ff ; (2 or 3 cycles) + + ;;; Write the data prologue (D5 AA AD) + lda #0xd5 ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + lda #0xaa ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + lda #0xad ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + + ;;; Convert & write contents of the buffers to the disk! + ;;; Start with the 2-bits + ;;; + ;;; At this point, we're 10 cycles after the previous write + ;;; Writes will take 32 cycles + tya + ldy #0x56 ; We'll use this to count back from the beginning of the "twos" buffer + bne __doeor ; This will ALWAYS be taken +_b2add02: +__getnibl: lda __BUF2S,y +_b2madd01: +__doeor: eor __BUF2S - 1,y ; Note that this will always cross a page boundary, and thus be 5 cycles!! + tax ; Get index for translation table +_nibadd01: + lda __NIBTAB,x ; Get byte from translation table + ldx zp:__P_OFFSET + sta WRITE_SW,x + lda READ_SW,x + dey + bne __getnibl + + ;;; Proceed with the 6-bits + ;;; y will be 0 at this point + lda zp:__T_HOLDNIB + nop +_b6add01: +__scndeor: eor __BUF6S,y + tax ; Get index for translation table +_nibadd02: + lda __NIBTAB,x ; Get byte from translation table + ldx __OFFSETP6 ; Retrieve the slot offset from page 6 + sta WRITE_SW,x ; Write "sixes", byte 87 and onward + lda READ_SW,x +_b6add02: + lda __BUF6S,y ; Read the next byte + iny ; 00 to ff + bne __scndeor + + ;;; Convert and write the checksum byte + tax ; Get the index on the table +_nibadd03: + lda __NIBTAB,x + ldx zp:__P_OFFSET + jsr __write3 ; Write the checksum + + ;;; Write the epilogue (DE AA EB) + lda #0xde ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + lda #0xaa ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + lda #0xeb ; (2 cycles) + jsr __write1 ; (14 cycles before, 6 after) + + ;;; Write a sync byte + lda #0xff + jsr __write1 ; (14 cycles before, 6 after) + lda CLEAR_SW,x + lda READ_SW,x ; Back into read mode + lda #0x00 + rts + +__write1: + clc ; (2 cycles) +__write2: + pha ; (3 cycles) + pla ; (4 cycles) +__write3: + sta WRITE_SW,x ; (5 cycles) Load latch from data bus + ora READ_SW,x ; (4 cycles) and write byte. Must have previously selected SHIFT_SW + rts ; (6 cycles) + +;;; Declaration of public symbols + .public dii_generate_6bit_decoding_mapping_table + .public dii_generate_6bit_encoding_mapping_table + .public dii_encode_gcr62_data + .public dii_power_on + .public dii_power_off + .public dii_head_reposition + .public dii_patch_write + .public dii_read_sector + .public dii_write_sector diff --git a/src/dlog_data.h b/src/dlog_data.h new file mode 100644 index 0000000..7eaebb9 --- /dev/null +++ b/src/dlog_data.h @@ -0,0 +1,16 @@ +#ifndef _DLOG_DATA_HEADER_ +#define _DLOG_DATA_HEADER_ + +#include + +#define DLOG_MODE_START 0 +#define DLOG_MODE_WIN 1 +#define DLOG_MODE_LOSE 2 + + +typedef struct { + uint8_t mode; + uint16_t score; +} dlog_data; + +#endif /* _DLOG_DATA_HEADER_ */ diff --git a/src/dlog_main.c b/src/dlog_main.c new file mode 100644 index 0000000..1cbeaf0 --- /dev/null +++ b/src/dlog_main.c @@ -0,0 +1,64 @@ +#include +#include + +#include + +#include "game_graphics.h" +#include "monitor_subroutines.h" +#include "utility.h" +#include "mem_map.h" +#include "shared_page.h" +#include "state_page.h" +#include "dlog_data.h" +#include "game_data.h" +#include "input.h" +#include "sound.h" + +// External initialization requirements +#pragma require __preserve_zp +#pragma require __data_initialization_needed + +static state_page_data* state_page = (state_page_data*)STATE_PAGE; +static shared_page_data *shared_page = (shared_page_data*)SHARED_PAGE; + +void main(void) { + dlog_data *dld = (dlog_data *)(shared_page->module_data); + game_data *gad = (game_data *)(shared_page->module_data); + + // Make sure the buffers are pointing to the correct memory + initialize_display_buffers(); + + switch(dld->mode) { + case DLOG_MODE_WIN: + snd_festive(); + ddraw_endgame_box(1, dld->score, state_page->hi_score); + break; + case DLOG_MODE_LOSE: + snd_sad_scale(); + ddraw_endgame_box(-1, dld->score, state_page->hi_score); + break; + default: + case DLOG_MODE_START: + ddraw_endgame_box(0, dld->score, state_page->hi_score); + snd_start(); + break; + }; + + if(dld->score > state_page->hi_score) { // New high-score. We need to save it. + state_page->hi_score = dld->score; + shared_page->master_command = MASTER_COMMAND_SAVE; + } else { + shared_page->master_command = MASTER_COMMAND_NONE; + } + + shared_page->next_module_idx = 4; // Go to the GAME module + gad->mode = GAME_MODE_NORMAL; // Set the proper start mode for the game + + while(!read_any_key()) { + lfsr_update(); + } + + snd_mod_button(); + + return; +} diff --git a/src/dos_floppy.c b/src/dos_floppy.c new file mode 100644 index 0000000..3865551 --- /dev/null +++ b/src/dos_floppy.c @@ -0,0 +1,185 @@ +#include "dos_floppy.h" + +#include + +#include "disk2.h" + +#define DEFAULT_READ_RETRIES 2 + +static uint8_t * const bufferMem = (uint8_t*)0x9E00; + +static uint8_t dos2phys_sector_map[] = { 0, 13, 11, 9, 7, 5, 3, 1, 14, 12, 10, 8, 6, 4, 2, 15 }; +// Translation table from https://github.com/fadden/CiderPress2/blob/main/DiskArc/GeneralChunkAccess.cs + +static void __recalibrate_head(uint8_t io_offset, uint8_t dest_track); +static uint8_t __read_sector(uint8_t io_offset, uint8_t track, uint8_t sector, uint8_t *dest, uint8_t retries); + +static void __recalibrate_head(uint8_t io_offset, uint8_t dest_track) { + dii_head_reposition(io_offset, DII_MAX_TRACK, 0); + dii_head_reposition(io_offset, 0, TRK_DOS2RAW(dest_track)); +} + +static uint8_t __read_sector(uint8_t io_offset, uint8_t track, uint8_t sector, uint8_t *dest, uint8_t retries) { + // We assume to be at the correct track already + + do { + if(dii_read_sector(io_offset, track, dos2phys_sector_map[sector], dest, 0)) // Make sure we translate the dos sector to a physical sector + return 1; // Ok if we can, return with a good read! + __recalibrate_head(io_offset, track); // Nope, recalibrate and try again + } while(retries--); + + // Fail + return 0; +} + +FDE* df_search_file(uint8_t io_offset, const uint8_t *f_name, uint8_t *cur_trk) { + // Move the head to track 17 + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(DOS_VTOC_TRACK)); + *cur_trk = TRK_DOS2RAW(DOS_VTOC_TRACK); + + // Read the VTOC sector at the beginning of the DOS buffer + if(!__read_sector(io_offset, DOS_VTOC_TRACK, DOS_VTOC_SECTOR, bufferMem, DEFAULT_READ_RETRIES)) + return NULL; + + // At this point we have the VTOC + VTOC *vtoc = (VTOC*)bufferMem; + + // Move to the catalog track + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(vtoc->catalog_trk)); + *cur_trk = TRK_DOS2RAW(vtoc->catalog_trk); + + // Read the first catalog sector + if(!__read_sector(io_offset, vtoc->catalog_trk, vtoc->catalog_sec, bufferMem, DEFAULT_READ_RETRIES)) + return NULL; + + CAT_Sector *cats = (CAT_Sector*)bufferMem; + + do { + for (uint8_t fde_idx = 0; fde_idx < FDE_PER_CAT; fde_idx++) { + uint8_t found = 0; + if( cats->f_descriptor[fde_idx].tsl_trk == 0 || + cats->f_descriptor[fde_idx].tsl_trk == 0xFF) continue; // File deleted or entry unused + + uint8_t *fde_f_name = cats->f_descriptor[fde_idx].f_name; + + found = 1; + for (uint8_t fname_idx = 0; fname_idx < FNAME_SIZE; fname_idx++) { + if(!f_name[fname_idx]) break; // We found a 0 byte: it indicates that the filename string ends here, + // if we did not get a mismatch, it means we found the correct file. + + if(f_name[fname_idx] != fde_f_name[fname_idx]) { + found = 0; // Nah, filename differs + break; + } + } + + if (found) // Ok, we found the entry! + return &(cats->f_descriptor[fde_idx]); + } + + if (!cats->catalog_trk) break; // This was the last sector, we failed. + + // Move to the track for the next catalog sector + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(cats->catalog_trk)); + *cur_trk = TRK_DOS2RAW(cats->catalog_trk); + + // Read the next catalog sector + if(!__read_sector(io_offset, cats->catalog_trk, cats->catalog_sec, bufferMem, DEFAULT_READ_RETRIES)) + return NULL; + } while(1); + + return NULL; +} + +TSList_Sector* df_get_tslist(uint8_t io_offset, uint8_t tsl_trk, uint8_t tsl_sec, uint8_t *cur_trk) { + // Move the head to track indicated for the first entry in the TSL + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(tsl_trk)); + *cur_trk = TRK_DOS2RAW(tsl_trk); + + // Read the tsl sector + if(!__read_sector(io_offset, tsl_trk, tsl_sec, bufferMem, DEFAULT_READ_RETRIES)) + return NULL; + + return (TSList_Sector*)bufferMem; +} + +uint8_t df_read_file(uint8_t io_offset, uint8_t tsl_trk, uint8_t tsl_sec, uint8_t *dest, uint8_t *cur_trk) { + uint8_t read_sectors = 0; + + TSList_Sector* tsl = df_get_tslist(io_offset, tsl_trk, tsl_sec, cur_trk); + if(!tsl) return 0; + + do { + for(uint8_t ts = 0; ts < TSLIST_SIZE; ts++) { + if(!(tsl->ts_list[ts].trk)) break; // Found an empty pair, break out of this loop (an the next check will probably find this is going to be the last sector of the T/S list too) + + // Move to the track + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(tsl->ts_list[ts].trk)); + *cur_trk = TRK_DOS2RAW(tsl->ts_list[ts].trk); + + // Read the sector into destination + if(!__read_sector(io_offset, tsl->ts_list[ts].trk, tsl->ts_list[ts].sec, dest, DEFAULT_READ_RETRIES)) + return 0; // Failed read + + dest += 0x100; // Next page + read_sectors++; + } + + if(!(tsl->tsl_trk)) break; // Last sector + // Otherwise, read the next + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(tsl->tsl_trk)); + *cur_trk = TRK_DOS2RAW(tsl->tsl_trk); + + // Read the tsl sector + if(!__read_sector(io_offset, tsl->tsl_trk, tsl->tsl_sec, bufferMem, DEFAULT_READ_RETRIES)) // The read has failed + return 0; + } while(1); + + return read_sectors; +} + +#include "mem_map.h" + +uint8_t df_overwrite_file(uint8_t io_offset, uint8_t tsl_trk, uint8_t tsl_sec, uint8_t *src, uint8_t *cur_trk) { + uint8_t written_sectors = 0; + + TSList_Sector* tsl = df_get_tslist(io_offset, tsl_trk, tsl_sec, cur_trk); + if(!tsl) return 0; + + do { + for(uint8_t ts = 0; ts < TSLIST_SIZE; ts++) { + if(!(tsl->ts_list[ts].trk)) break; // Found an empty pair, break out of this loop (an the next check will probably find this is going to be the last sector of the T/S list too) + + // Encode the data + dii_encode_gcr62_data(src, (uint8_t*)WRITE_SIXES_BUFFER_DEFAULT_ADDRESS, (uint8_t*)WRITE_TWOS_BUFFER_DEFAULT_ADDRESS); + + // Move to the track + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(tsl->ts_list[ts].trk)); + *cur_trk = TRK_DOS2RAW(tsl->ts_list[ts].trk); + + // Write the sector into destination + if(dii_write_sector(io_offset, tsl->ts_list[ts].trk, dos2phys_sector_map[tsl->ts_list[ts].sec])) { + return 0; // Failed write + } + + src += 0x100; // Next page + written_sectors++; + } + + if(!(tsl->tsl_trk)) break; // Last sector + + // Otherwise, write the next + dii_head_reposition(io_offset, *cur_trk, TRK_DOS2RAW(tsl->tsl_trk)); + *cur_trk = TRK_DOS2RAW(tsl->tsl_trk); + + // Read the tsl sector + if(!__read_sector(io_offset, tsl->tsl_trk, tsl->tsl_sec, bufferMem, DEFAULT_READ_RETRIES)) // The read has failed + return 0; + } while(1); + + return written_sectors; +} + +uint8_t df_read_file_desc(uint8_t io_offset, const FDE *f_descriptor, uint8_t *dest, uint8_t *cur_trk) { + return df_read_file(io_offset, f_descriptor->tsl_trk, f_descriptor->tsl_sec, dest, cur_trk); +} diff --git a/src/dos_floppy.h b/src/dos_floppy.h new file mode 100644 index 0000000..5c6fcbc --- /dev/null +++ b/src/dos_floppy.h @@ -0,0 +1,81 @@ +#ifndef _DOS_FLOPPY_HEADER_ +#define _DOS_FLOPPY_HEADER_ + +#include + +#define DOS_VTOC_TRACK 17 +#define DOS_VTOC_SECTOR 0 + +#define FNAME_SIZE 30 +#define FDE_PER_CAT 7 +#define TSLIST_SIZE 122 + +#define TRK_RAW2DOS(x) ((x) >> 1) +#define TRK_DOS2RAW(x) ((x) << 1) + +/* + * This header includes structures used to parse data from DOS 3.3 - style floppies + */ + +// The following struct maps what is found on Track 17, Sector 00 of a DOS 3.3 style floppy +typedef struct { + uint8_t _unused_00; // 00 + uint8_t catalog_trk; // 01, the following track/sector points to the first of the CAT_Sector(s), containing a list of the file entries on the disk + uint8_t catalog_sec; // 02 + uint8_t dos_version; // 03 + uint8_t _unused_01[2]; // 04-05 + uint8_t volume; // 06 + uint8_t _unused_02[32]; // 07-26 + uint8_t max_trksec; // 27 + uint8_t _unused_03[8]; // 28-2F + uint8_t last_trk; // 30 + uint8_t alloc_dir; // 31 + uint8_t _unused_04[2]; // 32-33 + uint8_t tot_trks; // 34 + uint8_t sec_per_trk; // 35 + uint16_t sec_size; // 36 - 37 + uint32_t sec_bitmap[50];// 38 - FF, first byte contains sectors F - 8, second 7 - 0, third and fourth are unused +} VTOC; + +typedef struct { // 35 bytes each + uint8_t tsl_trk; // 00, pointer to the first "Track/Sector List" sector (TSList_Sector). If this is FF, + // it means the file was deleted (and the original track is moved to the last byte of + // the f_name field), if 00, it means the entry is unused (which means that track 00 + // can never be used for files). + uint8_t tsl_sec; // 01 + uint8_t f_type; // 02, File type and flags. If the most significant bit (0x80) is set, the file is locked + uint8_t f_name[FNAME_SIZE]; // 03-20, // A0 (nbsp) will be used as padding + uint16_t f_len; // 21-22, length of file in sectors. Usually, only the first byte is used +} FDE; + +// The following struct contains a list of file descriptors, FDEs +typedef struct { + uint8_t _unused_00; // 00 + uint8_t catalog_trk; // 01, the following track/sector points to the next catalog sector. These will be 00/00 if this is the last in the list + uint8_t catalog_sec; // 02 + uint8_t _unused_01[8]; // 03-0A + FDE f_descriptor[FDE_PER_CAT]; // 0B-FF +} CAT_Sector; + +typedef struct { + uint8_t trk; + uint8_t sec; +} TS_Entry; + +typedef struct { + uint8_t _unused_00; // 00 + uint8_t tsl_trk; // 01, points to the next TSList_Sector if needed. 0 otherwise + uint8_t tsl_sec; // 02 + uint8_t _unused_01[2]; // 03-04 + uint16_t sec_offset; // 05-06, offset (in sectors) in the file of the first sector pointed by this entry + uint8_t _unused_02[5]; // 07-0B + TS_Entry ts_list[TSLIST_SIZE]; // 0C-FF, Sectors representing the file +} TSList_Sector; + +FDE* df_search_file(uint8_t io_offset, const uint8_t * f_name, uint8_t *cur_trk); +TSList_Sector* df_get_tslist(uint8_t io_offset, uint8_t tsl_trk, uint8_t tsl_sec, uint8_t *cur_trk); +uint8_t df_read_file(uint8_t io_offset, uint8_t tsl_trk, uint8_t tsl_sec, uint8_t *dest, uint8_t *cur_trk); +uint8_t df_read_file_desc(uint8_t io_offset, const FDE *f_descriptor, uint8_t *dest, uint8_t *cur_trk); +uint8_t df_overwrite_file(uint8_t io_offset, uint8_t tsl_trk, uint8_t tsl_sec, uint8_t *src, uint8_t *cur_trk); + +#endif /* _DOS_FLOPPY_HEADER_ */ diff --git a/src/game_data.h b/src/game_data.h new file mode 100644 index 0000000..63f2e48 --- /dev/null +++ b/src/game_data.h @@ -0,0 +1,14 @@ +#ifndef _GAME_DATA_HEADER_ +#define _GAME_DATA_HEADER_ + +#include + +#define GAME_MODE_NORMAL 0 +#define GAME_MODE_LOAD 1 + + +typedef struct { + uint8_t mode; +} game_data; + +#endif /* _GAME_DATA_HEADER_ */ diff --git a/src/game_graphics.c b/src/game_graphics.c index d06e014..9aa1818 100644 --- a/src/game_graphics.c +++ b/src/game_graphics.c @@ -78,13 +78,13 @@ static const uint8_t box_content_start[BOX_CONTENT_SIZE] = { 0, 0, 0, 0, 0, 0, 0,203, 77, 0, 0, 77, 0, 0, 77,192, 77, 0, 77, 77,211, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 77, 77,211, 0, 0, 77, 0, 0, 77, 0, 77, 0, 77,198, 84, 0, 0, 77, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 20, 11, 50, 48, 52, 56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 22, 50, 46, 48, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 8, 9, 7, 8, 45, 19, 3, 15, 18, 5, 58, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; @@ -99,14 +99,14 @@ static const uint8_t box_new_hi_score[BOX_WIDTH] = { #define INSTR_BOX_X 28 #define INSTR_BOX_Y 127 static const uint8_t instruction_box[INSTR_BOX_SIZE] = { - 3, 21, 18, 19, 15, 18, 0, 0, 11, 5, 25, 19, - 19, 12, 9, 4, 5, 0, 0, 20, 9, 12, 5, 19, + 3, 20, 18, 12, 45, 18, 0, 18, 5, 19, 5, 20, + 3, 20, 18, 12, 45, 19, 0, 19, 1, 22, 5, 0, + 3, 20, 18, 12, 45, 12, 0, 12, 15, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 5, 17, 21, 1, 12, 19, 0, 13, 5, 18, 7, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 5, 1, 3, 8, 0, 0, 50, 48, 52, 56, 33, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 79, 0, 50, 48, 50, 53, 0, 49, 46, 48, 0, 79 + 79, 0, 50, 48, 50, 53, 0, 50, 46, 48, 0, 79 }; @@ -261,6 +261,8 @@ void ddraw_endgame_box(int8_t done, uint16_t score, uint16_t hi_score) { if(score > hi_score) { draw_graph_char_box(ENDGAME_BOX_X_OFFSET + 2, (ENDGAME_BOX_Y_OFFSET + (2 * CHAR_HEIGHT) + (13 * CHAR_HEIGHT)), BOX_WIDTH, 1, box_new_hi_score, front_buf); } + } else { // Print the loaded high score + direct_draw_number(hi_score, ENDGAME_BOX_SCORE_LEN, ENDGAME_BOX_X_OFFSET + 22, ENDGAME_BOX_Y_OFFSET + (16 * CHAR_HEIGHT), front_buf); } } @@ -301,11 +303,14 @@ void swap_display_buffers(void) { PEEK(((uint16_t)front_buf == DISPLAY_PAGE_1) ? IO_DISPLAY_PAGE1 : IO_DISPLAY_PAGE2); } -void clear_display_buffers(void) { - // Clear the buffers - memset((void*)DISPLAY_PAGE_1, 0, DISPLAY_PAGE_SIZE); - memset((void*)DISPLAY_PAGE_2, 0, DISPLAY_PAGE_SIZE); +void sync_display1_buffer(void) { + if(((uint16_t)front_buf == DISPLAY_PAGE_1)) return; // We're already displaying buffer 1 + // We need to copy from the secondary display to the primary + memcpy(back_buf, front_buf, DISPLAY_PAGE_SIZE); +} + +void initialize_display_buffers(void) { PEEK(IO_DISPLAY_PAGE1); // Select the first display page // Restore the buffer ordering @@ -313,6 +318,14 @@ void clear_display_buffers(void) { back_buf = (uint8_t*)DISPLAY_PAGE_2; } +void clear_display_buffers(void) { + // Clear the buffers + memset((void*)DISPLAY_PAGE_1, 0, DISPLAY_PAGE_SIZE); + memset((void*)DISPLAY_PAGE_2, 0, DISPLAY_PAGE_SIZE); + + initialize_display_buffers(); +} + void draw_field_borders_on_buffer(uint8_t brd, uint8_t* buf) { // Horizontal borders for(uint8_t col = 0; col < (GRID_SIDE * (GRID_CELL_SIDE/7)) + 1; col++) { diff --git a/src/game_graphics.h b/src/game_graphics.h index 126ac18..72fcf99 100644 --- a/src/game_graphics.h +++ b/src/game_graphics.h @@ -18,6 +18,7 @@ #define GRAPH_ARROW_LEFT 2 #define GRAPH_ARROW_RIGHT 3 +void initialize_display_buffers(void); void ddraw_field_borders_on_buffer(uint8_t brd); void draw_game_background(uint16_t hi_score); void draw_number(uint16_t n, uint8_t len, uint8_t x, uint8_t y); @@ -28,5 +29,6 @@ void clear_display_buffers(void); void clear_box(uint8_t w, uint8_t h, uint8_t off_x, uint8_t off_y, uint8_t *disp_buf); void ddraw_direction_arrows(uint8_t dir); void ddraw_endgame_box(int8_t done, uint16_t score, uint16_t hi_score); +void sync_display1_buffer(void); #endif /* _GAME_GRAPHICS_HEADER_ */ diff --git a/src/game_main.c b/src/game_main.c new file mode 100644 index 0000000..1043aff --- /dev/null +++ b/src/game_main.c @@ -0,0 +1,165 @@ +#include +#include + +#include + +#include "monitor_subroutines.h" +#include "utility.h" +#include "mem_map.h" +#include "shared_page.h" +#include "state_page.h" +#include "dlog_data.h" +#include "game_data.h" +#include "input.h" +#include "game_logic.h" +#include "game_graphics.h" +#include "monitor_subroutines.h" +#include "sound.h" + +// External initialization requirements +#pragma require __preserve_zp +#pragma require __data_initialization_needed + +#define MOVES_TEXT_X 32 +#define MOVES_TEXT_Y 61 +#define MOVES_TEXT_WIDTH 5 + +#define SCORE_TEXT_X 32 +#define SCORE_TEXT_Y 29 +#define SCORE_TEXT_WIDTH 5 + +#define HIGH_TEXT_X 32 +#define HIGH_TEXT_Y 107 + +#define WIN_SCORE_BONUS 10000 + +static state_page_data* state_page = (state_page_data*)STATE_PAGE; +static shared_page_data *shared_page = (shared_page_data*)SHARED_PAGE; + +void main(void) { + uint16_t moves_count = 0; + uint16_t score = 0; + int8_t done = 0; + + // By default, once we return from this, return to the DLOG module and give the master no command to execute + shared_page->master_command = MASTER_COMMAND_NONE; + shared_page->next_module_idx = 3; // Go to the DLOG module + + dlog_data *dld = (dlog_data *)(shared_page->module_data); + dlog_data *gad = (dlog_data *)(shared_page->module_data); + + // Make sure the buffers are pointing to the correct memory and are clear + clear_display_buffers(); + + // Reset the game, calculate the initial score depending on which tiles we randomly get + score = reset_game(); + + // Load the game + if(gad->mode == GAME_MODE_LOAD) { + gad->mode = GAME_MODE_NORMAL; + + memcpy(get_front_grid(), (void*)(state_page->save_grid), GRID_SIDE * GRID_SIDE); + moves_count = state_page->saved_moves_count; + score = calculate_score(); + + // We loaded an empty save, just restart the game + if (!score) score = reset_game(); + } + + // Draw the initial state of the game + draw_game_background(state_page->hi_score); + draw_number(moves_count, MOVES_TEXT_WIDTH, MOVES_TEXT_X, MOVES_TEXT_Y); + draw_number(score, SCORE_TEXT_WIDTH, SCORE_TEXT_X, SCORE_TEXT_Y); + draw_tiles(); + + // Swap graphical buffers + swap_display_buffers(); + + while(1) { // Game loop + lfsr_update(); + + switch(read_kb()) { + case K_UP: + SND_TAP(); + done = step_game(GAME_STEP_UP); + ddraw_direction_arrows(GRAPH_ARROW_UP); + break; + case K_DOWN: + SND_TAP(); + done = step_game(GAME_STEP_DOWN); + ddraw_direction_arrows(GRAPH_ARROW_DOWN); + break; + case K_LEFT: + SND_TAP(); + done = step_game(GAME_STEP_LEFT); + ddraw_direction_arrows(GRAPH_ARROW_LEFT); + break; + case K_RIGHT: + SND_TAP(); + done = step_game(GAME_STEP_RIGHT); + ddraw_direction_arrows(GRAPH_ARROW_RIGHT); + break; + case K_CTRL_R: + snd_mod_button(); + score = 0; // We'll reset the score + done = -1; + break; + case K_CTRL_S: // The following two will return early + snd_mod_button(); + memcpy((void*)(state_page->save_grid), get_front_grid(), GRID_SIDE * GRID_SIDE); + state_page->saved_moves_count = moves_count; + shared_page->master_command = MASTER_COMMAND_SAVE; + case K_CTRL_L: + snd_mod_button(); + sync_display1_buffer(); + shared_page->next_module_idx = 4; + gad->mode = GAME_MODE_LOAD; + return; + default: + continue; // Do nothing, loop again + } + + // Increase the count of moves we made (unless we lost or reset the game) + if(done >= 0) moves_count++; + + // Draw the number of moves + draw_number(moves_count, MOVES_TEXT_WIDTH, MOVES_TEXT_X, MOVES_TEXT_Y); + + // Draw the moved tiles + draw_tiles(); + + // If we have won, or we got a reset request, break out of this loop + if(done) { + score += (done > 0) ? WIN_SCORE_BONUS : 0; + draw_number(score, SCORE_TEXT_WIDTH, SCORE_TEXT_X, SCORE_TEXT_Y); + swap_display_buffers(); // Make sure we show the latest changes + break; + } + + // Unable to add a tile: we ran out of space and lost!!! + uint8_t random_tile_off = add_random_tile(); + if(!random_tile_off) { + done = -1; // Lost the game + break; + } + + score = calculate_score(); + + // Draw the score + draw_number(score, SCORE_TEXT_WIDTH, SCORE_TEXT_X, SCORE_TEXT_Y); + + swap_display_buffers(); + + // Draw the new tile directly on the front buffer, this way we make it appear with an "animation" + ddraw_single_tile(random_tile_off - 1); + } + + // Sync the display buffers + sync_display1_buffer(); + + dld->mode = (done > 0) ? DLOG_MODE_WIN : DLOG_MODE_LOSE; + dld->score = score; + + + return; +} diff --git a/src/input.h b/src/input.h index 0c40334..614bcbf 100644 --- a/src/input.h +++ b/src/input.h @@ -9,6 +9,8 @@ #define K_LEFT 3 #define K_RIGHT 4 #define K_CTRL_R 5 +#define K_CTRL_S 6 +#define K_CTRL_L 7 uint8_t read_kb(void); diff --git a/src/input_asm.s b/src/input_asm.s index d143a8b..0d64445 100644 --- a/src/input_asm.s +++ b/src/input_asm.s @@ -10,7 +10,10 @@ K_UP .equ 1 K_DOWN .equ 2 K_LEFT .equ 3 K_RIGHT .equ 4 -K_CTRL_R .equ 5 +K_CTRL_R .equ 5 +K_CTRL_S .equ 6 +K_CTRL_L .equ 7 + __internal_read_kb: IO_DATAOUT$:.equ 0xC000 @@ -30,7 +33,26 @@ IO_KB_CTRL_HI$: .equ 0xC05F ror a lda #K_CTRL_R bcs Return$ - + lda #0x02 + sta IO_DATAOUT$ + lda IO_DATAIN$ + ror a + ror a + ror a + ror a + ror a + lda #K_CTRL_S + bcs Return$ + lda #0x40 + sta IO_DATAOUT$ + lda IO_DATAIN$ + ror a + ror a + ror a + ror a + ror a + lda #K_CTRL_L + bcs Return$ NoCtrl$: lda IO_KB_CTRL_LOW$ ldy #3 diff --git a/src/intro_main.c b/src/intro_main.c new file mode 100644 index 0000000..4595ad0 --- /dev/null +++ b/src/intro_main.c @@ -0,0 +1,39 @@ +#include +#include + +#include + +#include "monitor_subroutines.h" +#include "utility.h" +#include "mem_map.h" +#include "shared_page.h" + +// External initialization requirements +#pragma require __preserve_zp +#pragma require __data_initialization_needed + +static shared_page_data *shared_page = (shared_page_data*)SHARED_PAGE; + +void main(void) { + uint8_t wait_count = 0x40; + + // Initialize the register for LFSR + lfsr_init(0xF00D); + + // Clear the memory used to pass parameters to the next module + memset((void*)(shared_page->module_data), 0, MODULE_DATA_SIZE); + + // TODO: Detect expansion hardware, and choose what to load according to that + + // Delay a bit + while(wait_count--) WAIT(0xFF); + + // Clear the display memory + memset((void*)DISPLAY_PAGE_2, 0, DISPLAY_PAGE_SIZE); + memset((void*)DISPLAY_PAGE_1, 0, DISPLAY_PAGE_SIZE); + + shared_page->master_command = MASTER_COMMAND_NONE; + shared_page->next_module_idx = 3; // DLOG module is next! + + return; +} diff --git a/src/loading_screen.c b/src/loading_screen.c index e3790bd..0aceb00 100644 --- a/src/loading_screen.c +++ b/src/loading_screen.c @@ -1,6 +1,6 @@ #include -__attribute__((section("loadscreen"))) const uint8_t __loading_screen[] = { +/*__attribute__((section("loadscreen")))*/ const uint8_t __loading_screen[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x60, 0x30, 0x40, 0x2D, 0x36, 0x18, 0x18, 0x4C, 0x01, 0x0C, 0x06, 0x18, 0x43, 0x19, 0x6C, 0x30, 0x43, 0x0D, 0x06, 0x60, 0x00, 0x06, 0x07, 0x00, 0x33, 0x18, 0x6C, 0x30, 0x43, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x19, 0x33, 0x66, 0x7C, 0x33, 0x66, 0x4C, 0x19, 0x00, 0x00, 0x00, 0x00, 0x70, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0C, 0x66, 0x30, 0x18, 0x43, 0x01, 0x03, 0x00, 0x00, 0x10, 0x00, 0x00, 0x7F, 0x7F, 0x1F, 0x00, 0x00, diff --git a/src/main.c b/src/main.c deleted file mode 100644 index cd2b163..0000000 --- a/src/main.c +++ /dev/null @@ -1,152 +0,0 @@ -#include - -#include -#include - -#include "utility.h" -#include "mem_registers.h" -#include "mem_map.h" -#include "input.h" -#include "game_logic.h" -#include "game_graphics.h" -#include "monitor_subroutines.h" - -// External initialization requirements -#pragma require __preserve_zp -//#pragma require __call_heap_initialize -#pragma require __data_initialization_needed - -// Make sure the loading screen is included -#pragma require __loading_screen - -#define MOVES_TEXT_X 32 -#define MOVES_TEXT_Y 61 -#define MOVES_TEXT_WIDTH 5 - -#define SCORE_TEXT_X 32 -#define SCORE_TEXT_Y 29 -#define SCORE_TEXT_WIDTH 5 - -#define HIGH_TEXT_X 32 -#define HIGH_TEXT_Y 107 - -#define WIN_SCORE_BONUS 10000 - -void init(void); - -// Low level initialization -void init(void) { - POKE(P3_PWRDUP, 0); // Dirty the value checked by the reset vector - PEEK(IO_ROMSEL); // Make sure the ROM is selected - - PEEK(IO_DISPLAY_BW); // Disable colors - - // Clear display memory - clear_display_buffers(); -} - -__task int main(void) { - uint16_t moves_count; - uint16_t score = 0; - uint16_t hi_score = 0; - int8_t done = 0; - - init(); - - while(1){ // Outer loop - moves_count = 0; - - // Draw a screen and wait for a key press here here - BELL1(); - ddraw_endgame_box(done, score, hi_score); - while(!read_any_key()) { - lfsr_update(); - } - BELL1(); - clear_display_buffers(); // Clear display again - - // Check if we have a new high-score from a previous game - if (score > hi_score) hi_score = score; - - // Reset the game, calculate the initial score depending on which tiles we randomly get - score = reset_game(); - - // Draw the initial state of the game - draw_game_background(hi_score); - draw_number(moves_count, MOVES_TEXT_WIDTH, MOVES_TEXT_X, MOVES_TEXT_Y); - draw_number(score, SCORE_TEXT_WIDTH, SCORE_TEXT_X, SCORE_TEXT_Y); - draw_tiles(); - - // Swap graphical buffers - swap_display_buffers(); - - while(1) { // Game loop - lfsr_update(); - - switch(read_kb()) { - case K_UP: - BELL1(); - done = step_game(GAME_STEP_UP); - ddraw_direction_arrows(GRAPH_ARROW_UP); - break; - case K_DOWN: - BELL1(); - done = step_game(GAME_STEP_DOWN); - ddraw_direction_arrows(GRAPH_ARROW_DOWN); - break; - case K_LEFT: - BELL1(); - done = step_game(GAME_STEP_LEFT); - ddraw_direction_arrows(GRAPH_ARROW_LEFT); - break; - case K_RIGHT: - BELL1(); - done = step_game(GAME_STEP_RIGHT); - ddraw_direction_arrows(GRAPH_ARROW_RIGHT); - break; - case K_CTRL_R: - BELL1(); - score = 0; // We'll reset the score - done = -1; - break; - default: - continue; // Do nothing, loop again - } - - // Increase the count of moves we made - moves_count++; - - // If we have won, or we got a reset request, break out of this loop - if(done) { - score += (done > 0) ? WIN_SCORE_BONUS : 0; - break; - } - - // Draw the number of moves - draw_number(moves_count, MOVES_TEXT_WIDTH, MOVES_TEXT_X, MOVES_TEXT_Y); - - // Draw the moved tiles - draw_tiles(); - - // Unable to add a tile: we ran out of space and lost!!! - uint8_t random_tile_off = add_random_tile(); - if(!random_tile_off) { - done = -1; // Lost the game - break; - } - - score = calculate_score(); - - // Draw the score - draw_number(score, SCORE_TEXT_WIDTH, SCORE_TEXT_X, SCORE_TEXT_Y); - - swap_display_buffers(); - - // Draw the new tile directly on the front buffer, this way we make it appear with an "animation" - ddraw_single_tile(random_tile_off - 1); - } - - }; - - return 0; -} diff --git a/src/master_func.s b/src/master_func.s new file mode 100644 index 0000000..cbc77b7 --- /dev/null +++ b/src/master_func.s @@ -0,0 +1,23 @@ + .rtmodel version,"1" + .rtmodel core,"6502" + + .extern dii_encode_gcr62_data + .extern dii_power_on + .extern dii_power_off + .extern dii_head_reposition + .extern dii_read_sector + .extern dii_write_sector + + .section functionTable,text + + ;;; Function table + ;;; Export some functions here to be used by modules +__exported_func_table: + .word dii_encode_gcr62_data + .word dii_power_on + .word dii_power_off + .word dii_head_reposition + .word dii_read_sector + .word dii_write_sector + + .public __exported_func_table \ No newline at end of file diff --git a/src/master_main.c b/src/master_main.c new file mode 100644 index 0000000..9d85844 --- /dev/null +++ b/src/master_main.c @@ -0,0 +1,185 @@ +#include + +#include + +#include + +#include "shared_page.h" +#include "state_page.h" +#include "monitor_subroutines.h" +#include "utility.h" +#include "mem_map.h" +#include "mem_registers.h" +#include "disk2.h" +#include "dos_floppy.h" + +// External initialization requirements +#pragma require __data_initialization_needed + +// Require the exported function table to be present +//#pragma require __exported_func_table + +#define DEFAULT_DRIVE_CONTROLLER_OFFSET 0x10 + +static uint8_t *module_page = (uint8_t*)MODULE_PAGE; +static shared_page_data * shared_page = (shared_page_data*)SHARED_PAGE; + +#define FILE_LIST_LEN 5 +#define FNAME_LEN 6 +#define STATE_FILE_IDX 1 + +#define AUTOLOAD_FILES 3 // Autoload the first 3 files + +// We'll limit ourselves to files with a name of max 5 chars, to save some memory +static const uint8_t file_table[FILE_LIST_LEN][FNAME_LEN] = { + { 0x80 | 'L', 0x80 | 'O', 0x80 | 'A', 0x80 | 'D', 0x80 | 'S', 0x00}, // LOADS (this is not an executable, but will be used to show the loading screen). + { 0x80 | 'S', 0x80 | 'T', 0x80 | 'A', 0x80 | 'T', 0x80 | 'E', 0x00}, // STATE (this is not an executable, but will be used to save/load the game and scores). + { 0x80 | 'I', 0x80 | 'N', 0x80 | 'T', 0x80 | 'R', 0x80 | 'O', 0x00}, // INTRO (this executable will show the initial presentation picture) + { 0x80 | 'D', 0x80 | 'L', 0x80 | 'O', 0x80 | 'G', 0xA0, 0x00}, // DLOG (startup, win, lose dialogs) + { 0x80 | 'G', 0x80 | 'A', 0x80 | 'M', 0x80 | 'E', 0xA0, 0x00}, // GAME (the actual game) +}; + +static uint8_t file_trksec[FILE_LIST_LEN][2]; // This will hold track/sector for initial ts list sector for every one of the listed files. Populated at startup. +static uint16_t file_load_address[FILE_LIST_LEN] = { // This will hold the load address for the files + DISPLAY_PAGE_1, + STATE_PAGE, + MODULE_PAGE, + MODULE_PAGE, + MODULE_PAGE, +}; + +static void init(void); +static void init_floppy_data(uint8_t *cur_trk, uint8_t *cur_file); +static uint8_t fill_trksec_list(uint8_t* cur_trk); +static uint8_t calculate_crc8(uint8_t* data, uint8_t len); + +// Low level initialization +static void init(void) { + POKE(P3_PWRDUP, 0); // Dirty the value checked by the reset vector + PEEK(IO_ROMSEL); // Make sure the ROM is selected + PEEK(DISPLAY_PAGE_1); // Select display page 1 + PEEK(IO_DISPLAY_BW); // Disable colors + + // Generate the decoding table + dii_generate_6bit_decoding_mapping_table((uint8_t*)DECODING_MAPPING_TABLE_DEFAULT_ADDRESS); + // Generate the encoding table + dii_generate_6bit_encoding_mapping_table((uint8_t*)ENCODING_MAPPING_TABLE_DEFAULT_ADDRESS); +} + +static uint8_t fill_trksec_list(uint8_t* cur_trk) { + uint8_t file_counter = 0; + + for(uint8_t file_idx = 0; file_idx < FILE_LIST_LEN; file_idx++) { + FDE* f_desc = df_search_file(DEFAULT_DRIVE_CONTROLLER_OFFSET, file_table[file_idx], cur_trk); + if(f_desc) { + file_trksec[file_idx][0] = f_desc->tsl_trk; + file_trksec[file_idx][1] = f_desc->tsl_sec; + + file_counter++; + } else { + file_trksec[file_idx][0] = 0; + file_trksec[file_idx][1] = 0; + } + } + + return file_counter; +} + +// Load the info we need from floppy +static void init_floppy_data(uint8_t *cur_trk, uint8_t *cur_file) { + uint8_t initialized_correctly = 0; + + // Power on the drive and reset the head + dii_power_on(DEFAULT_DRIVE_CONTROLLER_OFFSET, 0); + dii_head_reposition(DEFAULT_DRIVE_CONTROLLER_OFFSET, 96, 0); // Head bang back to track 0! + + + // Build a table that maps every file we're interested in to the track/sector of the first T/S list entry. + initialized_correctly = fill_trksec_list(cur_trk) == FILE_LIST_LEN; + + // Load the first files to autoload + for(uint8_t file_num = 0; file_num < AUTOLOAD_FILES && initialized_correctly; file_num++) { + initialized_correctly = df_read_file(DEFAULT_DRIVE_CONTROLLER_OFFSET, file_trksec[file_num][0], file_trksec[file_num][1], (uint8_t*)file_load_address[file_num], cur_trk) && initialized_correctly; + *cur_file = file_num; + shared_page->next_module_idx = file_num; + } + + // Power off + dii_power_off(DEFAULT_DRIVE_CONTROLLER_OFFSET); + + // Check the CRC for the state page + uint8_t crc = calculate_crc8((uint8_t*)file_load_address[STATE_FILE_IDX], sizeof(state_page_data) - 1); + if(((state_page_data*)file_load_address[STATE_FILE_IDX])->crc != crc) { + memset(((void*)file_load_address[STATE_FILE_IDX]), 0, sizeof(state_page_data)); + } + + // If something went wrong, trigger a break + if (!initialized_correctly) __asm volatile(" brk\n":::); +} + +#define CRC8RDALLAS_POLY 0x31 +static uint8_t calculate_crc8(uint8_t* data, uint8_t len) { + uint8_t crc = 0; + + for(uint8_t data_idx = 0; data_idx < len; data_idx++) { + uint8_t carry; + uint8_t d = data[data_idx]; + + for (uint8_t i = 8; i > 0; i--) { + carry = (crc & 0x80); + crc <<= 1; + if (d & 1) crc |= 1; + d >>= 1; + if (carry) crc ^= CRC8RDALLAS_POLY; + } + } + + return crc; +} + +__task int main(void) { + uint8_t cur_trk = 0; + uint8_t cur_file = 0; + uint8_t keep_going = 1; + + __disable_interrupts(); + + init(); + init_floppy_data(&cur_trk, &cur_file); + + __enable_interrupts(); + + + do { + if((cur_file != shared_page->next_module_idx) || (shared_page->master_command == MASTER_COMMAND_SAVE)) { + __disable_interrupts(); + dii_power_on(DEFAULT_DRIVE_CONTROLLER_OFFSET, 0); + + // Check if we need to load another module + if(cur_file != shared_page->next_module_idx) { + cur_file = shared_page->next_module_idx; + // Read the next module + keep_going = df_read_file(DEFAULT_DRIVE_CONTROLLER_OFFSET, file_trksec[cur_file][0], file_trksec[cur_file][1], (uint8_t*)file_load_address[cur_file], &cur_trk); + } + + // Check if we need to save the state page + if (shared_page->master_command == MASTER_COMMAND_SAVE) { + uint8_t crc = calculate_crc8((uint8_t*)file_load_address[STATE_FILE_IDX], sizeof(state_page_data) - 1); + ((state_page_data*)file_load_address[STATE_FILE_IDX])->crc = crc; + keep_going = keep_going && df_overwrite_file(DEFAULT_DRIVE_CONTROLLER_OFFSET, file_trksec[STATE_FILE_IDX][0], file_trksec[STATE_FILE_IDX][1], (uint8_t*)file_load_address[STATE_FILE_IDX], &cur_trk); + } + + dii_power_off(DEFAULT_DRIVE_CONTROLLER_OFFSET); + __enable_interrupts(); + } + + shared_page->master_command = MASTER_COMMAND_NONE; + + // Execute the module + (((void (*)(void))(MODULE_PAGE))()); + } while(keep_going); + + __asm volatile(" brk\n":::); + + return 0; +} diff --git a/src/mem_map.h b/src/mem_map.h index 1e0fa00..1f3aaac 100644 --- a/src/mem_map.h +++ b/src/mem_map.h @@ -5,7 +5,10 @@ #define DISPLAY_PAGE_1 0x2000 #define DISPLAY_PAGE_2 0xA000 - #define ROMRAM_BANK 0xC100 +#define SHARED_PAGE 0x9B00 +#define STATE_PAGE 0x9A00 +#define MODULE_PAGE 0x4000 + #endif /* _MEMORY_MAP_HEADER_ */ diff --git a/src/shared_page.h b/src/shared_page.h new file mode 100644 index 0000000..1a3c54c --- /dev/null +++ b/src/shared_page.h @@ -0,0 +1,21 @@ +#ifndef _SHARED_PAGE_HEADER_ +#define _SHARED_PAGE_HEADER_ + +#include + +#define MASTER_COMMAND_NONE 0 +#define MASTER_COMMAND_SAVE 1 + +#define MODULE_DATA_SIZE 128 +#define MASTER_DATA_SIZE 126 + +typedef struct { + // master -> module + volatile uint8_t module_data[MODULE_DATA_SIZE]; + // module -> master + volatile uint8_t master_command; + volatile uint8_t next_module_idx; // Index in the file table for the next module to execute + volatile uint8_t master_data[MASTER_DATA_SIZE]; +} shared_page_data; + +#endif /* _SHARED_PAGE_HEADER_ */ diff --git a/src/sound.h b/src/sound.h new file mode 100644 index 0000000..922cf0f --- /dev/null +++ b/src/sound.h @@ -0,0 +1,16 @@ +#ifndef _SOUND_HEADER_ +#define _SOUND_HEADER_ + +#include + +#define SND_TAP() (snd_beep_lower(0x08, 0xFF)) + +void snd_beep(uint8_t duration, uint8_t pitch); +void snd_beep_lower(uint8_t duration, uint8_t pitch); + +void snd_start(void); +void snd_sad_scale(void); +void snd_festive(void); +void snd_mod_button(void); + +#endif /* _SOUND_HEADER_ */ diff --git a/src/sound.s b/src/sound.s new file mode 100644 index 0000000..5adb1a8 --- /dev/null +++ b/src/sound.s @@ -0,0 +1,161 @@ + .rtmodel version,"1" + .rtmodel core,"6502" + + .extern _Vfp + .extern _Vsp + .extern _Zp + +;;; I/O ports +IO_SPK: .equ 0xC030 + +;;; Monitor routines +MON_WAIT .equ 0xFCA8 + +;;; snd_beep +;;; Generates a sound of configurable duration and pitch +;;; See https://github.com/tilleul/apple2/blob/master/2liners/the%20art%20of%202-liners/SCRN_PLOT_your_sound_routine.md +;;; Parameters: +;;; - duration [A]: duration of the sound +;;; - pitch [_Zp[0]]: sound pitch +;;; +;;; Returns: Nothing +snd_beep: +P_PITCH$: .equ _Zp+0 + tax ; Move accumulator with duration into register x +-- ldy zp:P_PITCH$ ; Load the pitch from zp + lda IO_SPK ; Toggle the speaker +- dey + bne - + dex ; Decrement the time left + bne -- ; Back at loading the pitch if we need to go on + rts + +snd_beep_lower: +P_PITCH$: .equ _Zp+0 + tax ; Move accumulator with duration into register x +-- ldy zp:P_PITCH$ ; Load the pitch from zp + lda IO_SPK ; Toggle the speaker +- dey + nop + nop + nop + bne - + dex ; Decrement the time left + bne -- ; Back at loading the pitch if we need to go on + rts + +snd_mod_button: +P_PITCH$: .equ _Zp+0 + lda #0xAA + sta zp:P_PITCH$ + lda #0x25 + jsr snd_beep_lower + lda #0xCC + sta zp:P_PITCH$ + lda #0x0A + jsr snd_beep_lower + rts + +snd_start: +P_PITCH$: .equ _Zp+0 + lda #0xCC + sta zp:P_PITCH$ + lda #0x99 + jsr snd_beep_lower + lda #0xAA + sta zp:P_PITCH$ + lda #0x80 + jsr snd_beep_lower + lda #0x88 + sta zp:P_PITCH$ + lda #0xff + jsr snd_beep_lower + rts + +snd_sad_scale: +T_IDX$: .equ _Zp+1 +P_PITCH$: .equ _Zp+0 + ldy #6 +- lda __sad_scale_pitches,y + sta zp:P_PITCH$ + lda #0x90 + sty zp:T_IDX$ + jsr snd_beep_lower + ldy zp:T_IDX$ + dey + bpl - + rts + +__sad_scale_pitches: + .byte 0xFF + .byte 0xFF + .byte 0xFF + .byte 0xCC + .byte 0xAA + .byte 0x88 + .byte 0x88 + +snd_festive: +T_IDX$: .equ _Zp+1 +P_PITCH$: .equ _Zp+0 + ldy #10 +- lda __festive_pitches,y + sta zp:P_PITCH$ + lda __festive_duration,y + sty zp:T_IDX$ + jsr snd_beep + ldy zp:T_IDX$ + + lda __festive_wait,y + jsr MON_WAIT ; Call the monitor wait routine + + dey + bpl - + rts + +__festive_pitches: + .byte 0xEE + .byte 0xCC + .byte 0xAA + .byte 0x99 + .byte 0xAA + .byte 0xCC + .byte 0xCC + .byte 0x88 + .byte 0xAA + .byte 0xCC + .byte 0xCC + +__festive_duration: + .byte 0xFF + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + .byte 0x80 + +__festive_wait: + .byte 0x70 + .byte 0x70 + .byte 0x70 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + .byte 0xA0 + +;;; Declaration of public symbols + .public snd_beep + .public snd_beep_lower + .public snd_sad_scale + .public snd_festive + .public snd_mod_button + .public snd_start diff --git a/src/state_page.h b/src/state_page.h new file mode 100644 index 0000000..d2598ce --- /dev/null +++ b/src/state_page.h @@ -0,0 +1,18 @@ +#ifndef _STATE_PAGE_HEADER_ +#define _STATE_PAGE_HEADER_ + +#include + +#include "game_logic.h" + +#define STATE_PAGE_DATA (251 - (GRID_SIDE * GRID_SIDE)) + +typedef struct { + volatile uint16_t hi_score; + volatile uint16_t saved_moves_count; + volatile uint8_t save_grid[GRID_SIDE * GRID_SIDE]; + volatile uint8_t data[STATE_PAGE_DATA]; + volatile uint8_t crc; +} state_page_data; + +#endif /* _STATE_PAGE_HEADER_ */ diff --git a/src/tk2k_startup.s b/src/tk2k_startup_master.s similarity index 99% rename from src/tk2k_startup.s rename to src/tk2k_startup_master.s index aa48beb..3736666 100644 --- a/src/tk2k_startup.s +++ b/src/tk2k_startup_master.s @@ -86,6 +86,7 @@ __call_heap_initialize: sta zp:_Zp sta zp:_Zp+1 jsr main + jmp exit ;;; *************************************************************************** diff --git a/src/tk2k_startup_module.s b/src/tk2k_startup_module.s new file mode 100644 index 0000000..e0c254d --- /dev/null +++ b/src/tk2k_startup_module.s @@ -0,0 +1,123 @@ + .rtmodel cstartup,"tk2k" + .rtmodel version, "1" + .rtmodel core, "*" + + ;; External declarations + .section cstack + .section heap + .section data_init_table + .section registers ; pseudo registers in zero page + .section zpsave ; this is where pseudo registers are saved + + .extern main, exit, __low_level_init + .extern _Zp, _Vsp, _Vfp + + .pubweak __program_root_section, __program_start + +call: .macro dest + jsr \dest + .endm + + .section programStart, root +__program_root_section: + jmp __program_start + .section startup, root, noreorder +__program_start: + .section startup, noreorder + .pubweak __preserve_zp_needed +__preserve_zp_needed: + ldx #0x00 ; Save the whole zero-page, a bit wasteful, will have to look into it in the future +- lda zp:0x00,x + sta .sectionStart zpsave,x + inx + bne - + + .section startup, root, noreorder + lda #.byte0(.sectionEnd cstack) + sta zp:_Vsp + lda #.byte1(.sectionEnd cstack) + sta zp:_Vsp+1 + jsr __low_level_init + +;;; Initialize data sections if needed. + .section startup, noroot, noreorder + .pubweak __data_initialization_needed + .extern __initialize_sections +__data_initialization_needed: + lda #.byte0 (.sectionStart data_init_table) + sta zp:_Zp + lda #.byte1 (.sectionStart data_init_table) + sta zp:_Zp+1 + lda #.byte0 (.sectionEnd data_init_table) + sta zp:_Zp+2 + lda #.byte1 (.sectionEnd data_init_table) + sta zp:_Zp+3 + call __initialize_sections + + .section startup, noroot, noreorder + .pubweak __call_initialize_global_streams + .extern __initialize_global_streams +__call_initialize_global_streams: + call __initialize_global_streams + +;;; **** Initialize heap if needed. + .section startup, noroot, noreorder + .pubweak __call_heap_initialize + .extern __heap_initialize, __default_heap +__call_heap_initialize: + lda #.byte0 __default_heap + sta zp:_Zp+0 + lda #.byte1 __default_heap + sta zp:_Zp+1 + lda #.byte0 (.sectionStart heap) + sta zp:_Zp+2 + lda #.byte1 (.sectionStart heap) + sta zp:_Zp+3 + lda #.byte0 (.sectionSize heap) + sta zp:_Zp+4 + lda #.byte1 (.sectionSize heap) + sta zp:_Zp+5 + call __heap_initialize + + .section startup, root, noreorder + tsx + stx _InitialStack ; for exit() + lda #0 ; argc = 0 + sta zp:_Zp + sta zp:_Zp+1 + jsr main + + ;;; Restore the zeropage + ldx #0x00 ; Restore the whole zero-page +- lda .sectionStart zpsave,x + sta zp:0x00,x + inx + bne - + + rts + +;;; *************************************************************************** +;;; +;;; __low_level_init - custom low level initialization +;;; +;;; This default routine just returns doing nothing. You can provide your own +;;; routine, either in C or assembly for doing custom low leve initialization. +;;; +;;; *************************************************************************** + + .section code + .pubweak __low_level_init +__low_level_init: + rts + +;;; *************************************************************************** +;;; +;;; Keep track of the initial stack pointer so that it can be restores to make +;;; a return back on exit(). +;;; +;;; *************************************************************************** + + .section zdata, bss + .pubweak _InitialStack +_InitialStack: + .space 1 \ No newline at end of file diff --git a/src/utility.c b/src/utility.c index 06fc881..5be83ab 100644 --- a/src/utility.c +++ b/src/utility.c @@ -30,14 +30,18 @@ uint8_t bit_count(uint8_t b) { return b; } +void lfsr_init(uint16_t reg) { + *((uint16_t*)LFSR_REGISTER_ADDRESS) = 0xF00D; +} + uint16_t lfsr_update(void) { - static uint16_t lfsr = 0xF00D; + uint16_t *lfsr = ((uint16_t*)LFSR_REGISTER_ADDRESS); - lfsr ^= lfsr >> 7; - lfsr ^= lfsr << 9; - lfsr ^= lfsr >> 13; + *lfsr ^= (*lfsr) >> 7; + *lfsr ^= (*lfsr) << 9; + *lfsr ^= (*lfsr) >> 13; - return lfsr; + return *lfsr; } /* diff --git a/src/utility.h b/src/utility.h index 6723446..365fa3d 100644 --- a/src/utility.h +++ b/src/utility.h @@ -18,9 +18,16 @@ #define ARRAY_SIZE(x) (sizeof(x)/sizeof((x)[0])) +// 0x356 - 0x357 actually fall inside the +// decoding table for DISK II sector reading, +// but they're unused bytes! So we can use them +// to store the lfsr_update +#define LFSR_REGISTER_ADDRESS 0x0356 + void num_to_decbuf(uint16_t n, uint8_t len, uint8_t *buf); uint8_t bit_reverse(uint8_t b); uint8_t bit_count(uint8_t b); +void lfsr_init(uint16_t reg); uint16_t lfsr_update(void); //void print_line(const char* line, uint8_t off_x, uint8_t off_y);