From 4fd200b7b72ec0f0a7c7dcd02dd868711c54bd72 Mon Sep 17 00:00:00 2001 From: hkz Date: Fri, 17 Oct 2025 09:34:26 +0200 Subject: [PATCH] Squashed commit of the following: commit ce479dacb94d333b0b1a864f56ec6da4f8c333dd Author: hkz Date: Fri Oct 17 09:31:15 2025 +0200 Update README and CHANGELOG commit 6840e57190db46ed6b81b350479b6708417b0307 Author: hkz Date: Wed Oct 15 13:25:23 2025 +0200 Rework graphics commit 0f0fe3ecb71bc4a578e40b31bf2d942b76467db5 Author: hkz Date: Wed Oct 15 13:10:55 2025 +0200 Rework graphics commit f258d81f73eacb2c8e750856c1d99be43411ffb4 Author: hkz Date: Wed Oct 15 12:54:29 2025 +0200 Rework colors commit 631097903b11f24d1dc8121f3c9f972a93bdb002 Author: hkz Date: Wed Oct 15 09:24:59 2025 +0200 Centralize versioning commit 3219687fd765d8a0778248346a5c06d18553e8b0 Author: hkz Date: Wed Oct 15 07:38:55 2025 +0200 move some definitions outside of utility.h commit 07d054ea512695778c2bca0daea6ac1dbdc0b82b Author: hkz Date: Tue Oct 14 14:03:24 2025 +0200 Tweak some initialization code commit d920f681395aa7ac7dfd2f6d1d515594296d1e36 Author: hkz Date: Tue Oct 14 11:04:53 2025 +0200 Reworked wait time before making a move in demo mode commit a3425993b1e6648fb10bb92c026ea83684ce007c Author: hkz Date: Tue Oct 14 08:57:48 2025 +0200 Tweak strings to indicate DEMO mode commit 46f3662322b18f7fe5e517b0daa47fc923bbfe22 Author: hkz Date: Tue Oct 14 08:39:41 2025 +0200 Rewritten the irq handler commit 2999c7c218980cd985da972caa457f16ebc64492 Author: hkz Date: Mon Oct 13 21:10:54 2025 +0200 fix saving commit e7a5006a4a3f71a8d37989fb00741457420227c7 Author: hkz Date: Mon Oct 13 21:00:29 2025 +0200 nitial working version for the VDP commit 735513e5c8bb03a0576957155c90eaf46b0f4231 Author: hkz Date: Mon Oct 13 19:36:20 2025 +0200 Fix game mode commit 76fed16432206f9d9b4cacec8114b98b64cae216 Author: hkz Date: Mon Oct 13 18:59:02 2025 +0200 Re-enable partial drawing commit f5cfc0f5dacba654922d784a37bd8097455dbe91 Author: hkz Date: Mon Oct 13 18:46:08 2025 +0200 Use signed arithmetics for game logic commit 5348adcd72e854b100c34fd74b61d4ea879e8761 Author: hkz Date: Mon Oct 13 17:53:49 2025 +0200 Enable tiles redrawing commit 31018463ada4589c066c49ba14882b7e4c6630ac Author: hkz Date: Mon Oct 13 17:48:52 2025 +0200 Fix joystick code commit 7c4385972d9bfb2c246b14c11643beed164cdd0a Author: hkz Date: Mon Oct 13 17:24:13 2025 +0200 Begin fixing game code commit 78604e6f7dd4d9a8b38b623ebb55c603ea840b7d Author: hkz Date: Mon Oct 13 16:24:29 2025 +0200 Begin writing code to update the tiles commit 26e94d295720112c3ad7bc679fcf35e6e37a73d7 Author: hkz Date: Mon Oct 13 11:43:41 2025 +0200 Begin wiring in the VDP code in the game module commit 28a1fbfc18d4288a9683590452c56fc3b338ce6b Author: hkz Date: Mon Oct 13 10:09:43 2025 +0200 Implement (untested) code to draw joystick commit d3d2207b4f5b51f90d8ff2b828c091ac3f347600 Author: hkz Date: Mon Oct 13 08:44:00 2025 +0200 Add dummy demo and game modules for VDP commit 5deb0d802f8d50f01d5cf503f783872a872beb31 Author: hkz Date: Sun Oct 12 21:27:15 2025 +0200 Update the charset commit d2cd7356ec0fd69edec71a4b37a64baf37fa773f Author: hkz Date: Sun Oct 12 21:19:43 2025 +0200 Tweak nametable for the dialog commit dd0e5ce53de3222fb20206ecc913ce830ec6bf9c Author: hkz Date: Sun Oct 12 21:12:24 2025 +0200 Integrate the new dialog module for the VDP commit b4469d514c5a5fdeee9d1e3a70c723d9529e948c Author: hkz Date: Sun Oct 12 15:25:31 2025 +0200 Add dummy module for VDP dialog commit d1dcdd1381f1d90b846897ebc058310d81a5075a Author: hkz Date: Sun Oct 12 12:15:23 2025 +0200 Add initializer module for VDP commit fd84d3abbbdfb40a3b614f7a009c1c88d8dfc6f6 Author: hkz Date: Tue Oct 7 20:15:46 2025 +0200 Begin defining binaries commit bd650081fcfe649783766f87c8455963d04d23fc Author: hkz Date: Tue Oct 7 15:02:56 2025 +0200 Add resources for two screens of the VDP version commit 58f7436c454deb7714a39604b478835db1e9765a Author: hkz Date: Tue Oct 7 12:52:38 2025 +0200 Add a module list with defines commit 969fe9deab2772ae0a98ab57eed787381e6d59a3 Author: hkz Date: Tue Oct 7 12:35:40 2025 +0200 Additional renaming commit 83552c2ad855d744ade4e1597956897b6903e7ed Author: hkz Date: Tue Oct 7 12:14:27 2025 +0200 Rename other graphic files, remove unused imports commit dbc1bebf9f980507970c5d6bd6d25c8439a31e1c Author: hkz Date: Tue Oct 7 11:59:16 2025 +0200 Begin renaming graphic files to mention they're for HGR --- CHANGELOG.md | 7 + Makefile | 60 +- README.md | 12 + data/vdp_charset.bin | Bin 0 -> 2048 bytes data/vdp_colortable.bin | Bin 0 -> 32 bytes data/vdp_nt_board.bin | Bin 0 -> 768 bytes data/vdp_nt_dialog.bin | Bin 0 -> 768 bytes data/vdp_sprite_tiles.bin | Bin 0 -> 384 bytes linker-files/module.scm | 36 +- linker-files/vdpin_module.scm | 19 + pics/vdp_mode.jpg | Bin 0 -> 97172 bytes src/demo_main.c | 5 +- src/dlog_main.c | 7 +- src/game_data.h | 33 +- src/game_graphics_demo.c | 3 - src/{game_graphics.c => game_hgr_graphics.c} | 13 +- src/{game_graphics.h => game_hgr_graphics.h} | 68 +- src/game_hgr_graphics_demo.c | 3 + src/game_logic.c | 298 ++++---- src/game_main.c | 331 ++++----- src/game_vdp_graphics.h | 19 + src/game_vdp_graphics.s | 323 +++++++++ ...raph_misc_data.c => hgr_graph_misc_data.c} | 62 +- ...raph_misc_data.h => hgr_graph_misc_data.h} | 36 +- src/hgr_graphics.h | 8 + src/{line_data.c => hgr_line_data.c} | 44 +- src/hgr_line_data.h | 10 + src/intro_main.c | 85 ++- src/line_data.h | 9 - src/master_main.c | 20 +- src/mem_registers.h | 99 +-- src/module_list.h | 13 + src/utility.c | 142 ++-- src/utility.h | 65 +- src/vddem_main.c | 197 ++++++ src/vddlg_main.c | 96 +++ src/vdgam_main.c | 198 ++++++ src/vdp.s | 10 + src/vdp_init.h | 8 + src/vdp_init.s | 161 +++++ src/vdp_utils.h | 20 + src/vdp_utils.s | 665 ++++++++++++++++++ src/vdpin_main.c | 34 + 43 files changed, 2545 insertions(+), 674 deletions(-) create mode 100644 data/vdp_charset.bin create mode 100644 data/vdp_colortable.bin create mode 100644 data/vdp_nt_board.bin create mode 100644 data/vdp_nt_dialog.bin create mode 100644 data/vdp_sprite_tiles.bin create mode 100644 linker-files/vdpin_module.scm create mode 100644 pics/vdp_mode.jpg delete mode 100644 src/game_graphics_demo.c rename src/{game_graphics.c => game_hgr_graphics.c} (98%) rename src/{game_graphics.h => game_hgr_graphics.h} (87%) create mode 100644 src/game_hgr_graphics_demo.c create mode 100644 src/game_vdp_graphics.h create mode 100644 src/game_vdp_graphics.s rename src/{graph_misc_data.c => hgr_graph_misc_data.c} (98%) rename src/{graph_misc_data.h => hgr_graph_misc_data.h} (70%) create mode 100644 src/hgr_graphics.h rename src/{line_data.c => hgr_line_data.c} (97%) create mode 100644 src/hgr_line_data.h delete mode 100644 src/line_data.h create mode 100644 src/module_list.h create mode 100644 src/vddem_main.c create mode 100644 src/vddlg_main.c create mode 100644 src/vdgam_main.c create mode 100644 src/vdp.s create mode 100644 src/vdp_init.h create mode 100644 src/vdp_init.s create mode 100644 src/vdp_utils.h create mode 100644 src/vdp_utils.s create mode 100644 src/vdpin_main.c diff --git a/CHANGELOG.md b/CHANGELOG.md index 00ab961..60e0919 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 3.0 - 2025-10-17 + +### Added + +- Support for the TK2000 VDP board + + ## 2.1 - 2025-10-07 ### Changed diff --git a/Makefile b/Makefile index 53d346b..68f225b 100644 --- a/Makefile +++ b/Makefile @@ -12,6 +12,10 @@ INTRO_PRG=intro DLOG_PRG=dlog GAME_PRG=game DEMO_PRG=demo +VDPIN_PRG=vdpin +VDDLG_PRG=vddlg +VDGAM_PRG=vdgam +VDDEM_PRG=vddem # Libraries LIBS=clib-6502.a @@ -19,17 +23,29 @@ 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 utility.c -INTRO_ASM_SRCS = tk2k_startup_module.s preserve_zero_pages.s -INTRO_C_SRCS = intro_main.c utility.c +INTRO_ASM_SRCS = tk2k_startup_module.s preserve_zero_pages.s vdp.s vdp_utils.s +INTRO_C_SRCS = intro_main.c utility.c input.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 +DLOG_C_SRCS = dlog_main.c input.c utility.c game_hgr_graphics.c hgr_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 +GAME_C_SRCS = game_main.c input.c utility.c game_hgr_graphics.c hgr_line_data.c game_logic.c arrows_pic.c tiles.c hgr_graph_misc_data.c DEMO_ASM_SRCS = tk2k_startup_module.s preserve_zero_pages.s input_asm.s sound.s -DEMO_C_SRCS = demo_main.c input.c utility.c game_graphics_demo.c line_data.c game_logic.c arrows_pic.c tiles.c graph_misc_data.c +DEMO_C_SRCS = demo_main.c input.c utility.c game_hgr_graphics_demo.c hgr_line_data.c game_logic.c arrows_pic.c tiles.c hgr_graph_misc_data.c + +VDPIN_ASM_SRCS = tk2k_startup_module.s preserve_zero_pages.s vdp.s vdp_utils.s vdp_init.s +VDPIN_C_SRCS = vdpin_main.c utility.c + +VDDLG_ASM_SRCS = tk2k_startup_module.s preserve_zero_pages.s sound.s vdp.s vdp_utils.s game_vdp_graphics.s +VDDLG_C_SRCS = vddlg_main.c input.c utility.c + +VDGAM_ASM_SRCS = tk2k_startup_module.s preserve_zero_pages.s input_asm.s sound.s vdp.s vdp_utils.s game_vdp_graphics.s +VDGAM_C_SRCS = vdgam_main.c input.c utility.c game_logic.c + +VDDEM_ASM_SRCS = tk2k_startup_module.s preserve_zero_pages.s input_asm.s sound.s vdp.s vdp_utils.s game_vdp_graphics.s +VDDEM_C_SRCS = vddem_main.c input.c utility.c game_logic.c # Object files MASTER_OBJS = $(MASTER_ASM_SRCS:%.s=%.o) $(MASTER_C_SRCS:%.c=%.o) @@ -37,6 +53,10 @@ 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) DEMO_OBJS = $(DEMO_ASM_SRCS:%.s=%.o) $(DEMO_C_SRCS:%.c=%.o) +VDPIN_OBJS = $(VDPIN_ASM_SRCS:%.s=%.o) $(VDPIN_C_SRCS:%.c=%.o) +VDDLG_OBJS = $(VDDLG_ASM_SRCS:%.s=%.o) $(VDDLG_C_SRCS:%.c=%.o) +VDGAM_OBJS = $(VDGAM_ASM_SRCS:%.s=%.o) $(VDGAM_C_SRCS:%.c=%.o) +VDDEM_OBJS = $(VDDEM_ASM_SRCS:%.s=%.o) $(VDDEM_C_SRCS:%.c=%.o) all: $(SW_NAME).woz @@ -61,6 +81,18 @@ $(GAME_PRG).hex: $(GAME_OBJS) $(DEMO_PRG).hex: $(DEMO_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) +$(VDPIN_PRG).hex: $(VDPIN_OBJS) + (cd obj ; ln6502 -g ../linker-files/vdpin_module.scm $^ -o ../out/$@ $(LIBS) -l --cross-reference --cstartup=tk2k --no-automatic-placement-rules --output-format intel-hex --rom-code) + +$(VDDLG_PRG).hex: $(VDDLG_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) + +$(VDGAM_PRG).hex: $(VDGAM_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) + +$(VDDEM_PRG).hex: $(VDDEM_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) @@ -75,14 +107,30 @@ $(GAME_PRG).bin: $(GAME_PRG).hex $(DEMO_PRG).bin: $(DEMO_PRG).hex (cd out ; objcopy -I ihex -O binary $(DEMO_PRG).hex $(DEMO_PRG).bin) + +$(VDPIN_PRG).bin: $(VDPIN_PRG).hex + (cd out ; objcopy -I ihex -O binary $(VDPIN_PRG).hex $(VDPIN_PRG).bin) -$(SW_NAME).dsk: $(MASTER_PRG).bin $(INTRO_PRG).bin $(DLOG_PRG).bin $(GAME_PRG).bin $(DEMO_PRG).bin +$(VDDLG_PRG).bin: $(VDDLG_PRG).hex + (cd out ; objcopy -I ihex -O binary $(VDDLG_PRG).hex $(VDDLG_PRG).bin) + +$(VDGAM_PRG).bin: $(VDGAM_PRG).hex + (cd out ; objcopy -I ihex -O binary $(VDGAM_PRG).hex $(VDGAM_PRG).bin) + +$(VDDEM_PRG).bin: $(VDDEM_PRG).hex + (cd out ; objcopy -I ihex -O binary $(VDDEM_PRG).hex $(VDDEM_PRG).bin) + +$(SW_NAME).dsk: $(MASTER_PRG).bin $(INTRO_PRG).bin $(DLOG_PRG).bin $(GAME_PRG).bin $(DEMO_PRG).bin $(VDPIN_PRG).bin $(VDDLG_PRG).bin $(VDGAM_PRG).bin $(VDDEM_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 $(DEMO_PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk DEMO b; \ + cat $(VDPIN_PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk VDPIN b; \ + cat $(VDDLG_PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk VDDLG b; \ + cat $(VDGAM_PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk VDGAM b; \ + cat $(VDDEM_PRG).bin | $(JAVA) -jar $(ACMD) -p $(SW_NAME).dsk VDDEM 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;) diff --git a/README.md b/README.md index 87359f6..6d24f63 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ If you wish to support me in building new hardware and software for old machines The game is playable, and the following features have been implemented: - B/W mode +- VDP board support - Single graphical tileset - Simple sound effects - Control via keyboard @@ -52,6 +53,17 @@ The game is playable, and the following features have been implemented: The game ends once you reach a tile with a value of 2048. +### VDP support + +The game supports the [TK2000 VDP board](https://codeberg.org/hkzlab/TK2000_VDPboard) configured at address `C0Cx`. + +![TK2048 VDP Game screen](pics/vdp_mode.jpg) + +The card gets automatically detected and used: once the game completes the initial load, everything will be displayed on the screen +connected to the VDP board, and the main screen connected to the TK2000 will be left blank. + +If you want to override detection and force the use of the main screen, just keep a button pressed while the title screen image loads. + ### Floppy version Just put your floppy in the first drive and power on the TK2000. It will autoboot. diff --git a/data/vdp_charset.bin b/data/vdp_charset.bin new file mode 100644 index 0000000000000000000000000000000000000000..658d81306ef6332440a7cd3d7ca3f45fb948b074 GIT binary patch literal 2048 zcmeHGA#B?~6#Z$c)zP}eXmO=!y|$!az_1`%t*wg8dT7|7nhZ}v0aJ3I;G`B98fMI6 zfrB0kY*=AnpjC9BO7{N6mdd(r8tb<&-|yf5@BjP%LbMS9Ard5|bOtFc9!X+2QOA{_ za^{OD4=9>MMV2Q71vrQCAsDi14Wkx5$IS*?@lqC4{;I2s-Qy8O2BokC% zfjvLbPKO@seke2R`(*XP_xuHgk*?U^8Xx|G7599I;^TI)EZg^c4^E|6bW8g*?n*R6kYUgN?HDXvB+Izbz< zEFqIk%1LGjED%fPw!k?Jnc1w!bNB;rd}R!HnGcP~QinMX{K~^Vn#jU=ATFPg(&3`G z1)q^zI`4A{WL;a)Ka3@c6i%!$8jlp}@_lzs*c!1sw;CvKXojTqbY^B#*iZrTd0v{E zJayP=%;T8nDuB~GPd$aUaAD|V0yFk0M4l{(qF5#}c2>3%-4qaL@WY?^`8;LZsV5Q=iE6ap8fq^R)W zOSE>sMC0Z1>h3bnvi$PyYPlS*dcFDi!Ct4ccW^%M^;X%honKT`sC>QR?=#8>lWZ#OV?@4h)Y8Vm;S-36&Ro)Z6JcQ**aucOPq%unx*2K~X{`WpYTMxK(tiFv4>pF literal 0 HcmV?d00001 diff --git a/data/vdp_colortable.bin b/data/vdp_colortable.bin new file mode 100644 index 0000000000000000000000000000000000000000..efd4f0e96a024e25db4de50d98c5bf29511357d4 GIT binary patch literal 32 dcmezH0Ra*gG<;wH0RxAGf&~`>9yA>I004AP6Ttug literal 0 HcmV?d00001 diff --git a/data/vdp_nt_board.bin b/data/vdp_nt_board.bin new file mode 100644 index 0000000000000000000000000000000000000000..92d3faae1a9c1895572a6798c883c7fb7bbb0bdf GIT binary patch literal 768 zcmYdD#{rTUz#s)eVUno?<&B;FgIsazj|T(X@(cnXfJdH?`*i)oT!V4zr@8$ap6(tD zxb0^c=0b?;FI vWfLPEULyu0*WeJxpb)4Wh|0`fw{71sh`ces4ui2{m@B`IzoQG18azw@eD6WO literal 0 HcmV?d00001 diff --git a/data/vdp_nt_dialog.bin b/data/vdp_nt_dialog.bin new file mode 100644 index 0000000000000000000000000000000000000000..937d2538c3254f3d5ff74b66cfbc2476d41a4607 GIT binary patch literal 768 zcmZQz7(^f=Ywgy(M{zqSGke{(eaFz{6Vh>jB#6osbPX_m>Zm*@1QOG5fMi%e4PzPr DL{B3^ literal 0 HcmV?d00001 diff --git a/data/vdp_sprite_tiles.bin b/data/vdp_sprite_tiles.bin new file mode 100644 index 0000000000000000000000000000000000000000..20a9083a60601ea103df9113fb54c0231378b5d2 GIT binary patch literal 384 zcmZQzU}RzdVkTw=C}?nSXaF(~K)FCkpe#a~0VK)LZ~#n!m_S*O0!9!U1VH*7AZ9=W zSa?`iSok1p5UJph5D=h%;PWuCFfs8U_zD363JwJbJ_`pA7Z(eHui)SiP#}Qd3mORs z3Rxrg98)?(L^=?BVJ&U}1tj?n0f{M7I1us{iUu4Uh6sMgl$MSOcMyD~R8>{gA|!r@ Zh>9CBKU7rJ6^XCn<>Hm4f>4i_1prt58&&`S literal 0 HcmV?d00001 diff --git a/linker-files/module.scm b/linker-files/module.scm index 81c846b..fd7c619 100644 --- a/linker-files/module.scm +++ b/linker-files/module.scm @@ -1,19 +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)) +(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 . #x935f)) (type ram) (section (programStart #x4000) startup code switch idata cdata data_init_table)) + (memory upperData (address (#x9360 . #x99ff)) (type ram) (section cstack zdata data heap intrzp)) + (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/linker-files/vdpin_module.scm b/linker-files/vdpin_module.scm new file mode 100644 index 0000000..2a0f734 --- /dev/null +++ b/linker-files/vdpin_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 . #x7fff)) (type ram) (section (programStart #x4000) startup code switch idata cdata data_init_table)) + (memory upperData (address (#x8000 . #x99ff)) (type ram) (section cstack zdata data heap intrzp)) + (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/vdp_mode.jpg b/pics/vdp_mode.jpg new file mode 100644 index 0000000000000000000000000000000000000000..68c65d91f6ee5a02bbefb1398496a3d230935602 GIT binary patch literal 97172 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 = MASTER_COMMAND_NONE; - shared_page->next_module_idx = 3; // Go to the DLOG module + shared_page->next_module_idx = MODULE_DLOG; // Go to the DLOG module dlog_data *dld = (dlog_data *)(shared_page->module_data); dlog_data *gad = (dlog_data *)(shared_page->module_data); diff --git a/src/dlog_main.c b/src/dlog_main.c index c371164..5c32d8f 100644 --- a/src/dlog_main.c +++ b/src/dlog_main.c @@ -3,7 +3,7 @@ #include -#include "game_graphics.h" +#include "game_hgr_graphics.h" #include "monitor_subroutines.h" #include "utility.h" #include "mem_map.h" @@ -13,6 +13,7 @@ #include "game_data.h" #include "input.h" #include "sound.h" +#include "module_list.h" // External initialization requirements #pragma require __preserve_zp @@ -54,7 +55,7 @@ void main(void) { shared_page->master_command = MASTER_COMMAND_NONE; } - shared_page->next_module_idx = 4; // Go to the GAME module + shared_page->next_module_idx = MODULE_GAME; // Go to the GAME module gad->mode = GAME_MODE_NORMAL; // Set the proper start mode for the game while(!read_any_key() && (wait_counter != WAIT_COUNTER_END)) { @@ -63,7 +64,7 @@ void main(void) { } if (wait_counter == WAIT_COUNTER_END) { - shared_page->next_module_idx = 5; // Actually go to the DEMO module + shared_page->next_module_idx = MODULE_DEMO; // Actually go to the DEMO module return; } diff --git a/src/game_data.h b/src/game_data.h index 63f2e48..a8a400b 100644 --- a/src/game_data.h +++ b/src/game_data.h @@ -1,14 +1,19 @@ -#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_ */ +#ifndef _GAME_DATA_HEADER_ +#define _GAME_DATA_HEADER_ + +#include + +#define GAME_VER_CH0 '3' +#define GAME_VER_CH1 '.' +#define GAME_VER_CH2 '0' + + +#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_demo.c b/src/game_graphics_demo.c deleted file mode 100644 index f33e2f1..0000000 --- a/src/game_graphics_demo.c +++ /dev/null @@ -1,3 +0,0 @@ -#define _DEMO_MODE_ 1 - -#include "game_graphics.c" diff --git a/src/game_graphics.c b/src/game_hgr_graphics.c similarity index 98% rename from src/game_graphics.c rename to src/game_hgr_graphics.c index bf1ed65..caac19b 100644 --- a/src/game_graphics.c +++ b/src/game_hgr_graphics.c @@ -1,16 +1,17 @@ -#include "game_graphics.h" +#include "game_hgr_graphics.h" #include +#include "game_data.h" #include "utility.h" #include "mem_map.h" #include "mem_registers.h" -#include "line_data.h" +#include "hgr_line_data.h" #include "game_logic.h" #include "tiles.h" #include "charset.h" #include "monitor_subroutines.h" -#include "graph_misc_data.h" +#include "hgr_graph_misc_data.h" #include "arrows_pic.h" #define SCREEN_WIDTH 280 @@ -78,7 +79,7 @@ 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, 49, 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, GAME_VER_CH0, GAME_VER_CH1, GAME_VER_CH2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -106,7 +107,7 @@ static const uint8_t instruction_box[INSTR_BOX_SIZE] = { 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, 50, 46, 49, 0, 79 + 7, 15, 15, 4, 0, 12, 21, 3, 11, 33, 33, 33 }; static const uint8_t demo_box[INSTR_BOX_SIZE] = { @@ -117,7 +118,7 @@ static const uint8_t demo_box[INSTR_BOX_SIZE] = { 4, 5, 0, 0, 4, 5, 13, 15, 0, 0, 13, 15, 15, 4, 5, 0, 0, 4, 5, 13, 15, 0, 0, 13, 13, 15, 4, 5, 0, 0, 4, 5, 13, 15, 0, 0, - 79, 0, 50, 48, 50, 53, 0, 50, 46, 49, 0, 79 + 0, 13, 15, 4, 5, 0, 0, 4, 5, 13, 15, 0 }; #ifdef _DEMO_MODE_ diff --git a/src/game_graphics.h b/src/game_hgr_graphics.h similarity index 87% rename from src/game_graphics.h rename to src/game_hgr_graphics.h index 72fcf99..e4234a7 100644 --- a/src/game_graphics.h +++ b/src/game_hgr_graphics.h @@ -1,34 +1,34 @@ -#ifndef _GAME_GRAPHICS_HEADER_ -#define _GAME_GRAPHICS_HEADER_ - -#include - -#define BRD_DOUBLING_UP(a) (a & 0x01) -#define BRD_DOUBLING_DOWN(a) (a & 0x02) -#define BRD_DOUBLING_LEFT(a) (a & 0x04) -#define BRD_DOUBLING_RIGHT(a) (a & 0x08) - -#define BRD_SKIP_UP(a) (a & 0x10) -#define BRD_SKIP_DOWN(a) (a & 0x20) -#define BRD_SKIP_LEFT(a) (a & 0x40) -#define BRD_SKIP_RIGHT(a) (a & 0x80) - -#define GRAPH_ARROW_UP 0 -#define GRAPH_ARROW_DOWN 1 -#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); -void draw_tiles(void); -void ddraw_single_tile(uint8_t offset); -void swap_display_buffers(void); -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_ */ +#ifndef _GAME_HGR_GRAPHICS_HEADER_ +#define _GAME_HGR_GRAPHICS_HEADER_ + +#include + +#define BRD_DOUBLING_UP(a) (a & 0x01) +#define BRD_DOUBLING_DOWN(a) (a & 0x02) +#define BRD_DOUBLING_LEFT(a) (a & 0x04) +#define BRD_DOUBLING_RIGHT(a) (a & 0x08) + +#define BRD_SKIP_UP(a) (a & 0x10) +#define BRD_SKIP_DOWN(a) (a & 0x20) +#define BRD_SKIP_LEFT(a) (a & 0x40) +#define BRD_SKIP_RIGHT(a) (a & 0x80) + +#define GRAPH_ARROW_UP 0 +#define GRAPH_ARROW_DOWN 1 +#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); +void draw_tiles(void); +void ddraw_single_tile(uint8_t offset); +void swap_display_buffers(void); +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_HGR_GRAPHICS_HEADER_ */ diff --git a/src/game_hgr_graphics_demo.c b/src/game_hgr_graphics_demo.c new file mode 100644 index 0000000..337c19a --- /dev/null +++ b/src/game_hgr_graphics_demo.c @@ -0,0 +1,3 @@ +#define _DEMO_MODE_ 1 + +#include "game_hgr_graphics.c" diff --git a/src/game_logic.c b/src/game_logic.c index aa19674..22867b5 100644 --- a/src/game_logic.c +++ b/src/game_logic.c @@ -1,149 +1,149 @@ -#include "game_logic.h" - -#include "utility.h" - -#include - -static uint8_t game_grid_alpha[GRID_SIDE * GRID_SIDE]; -static uint8_t game_grid_beta[GRID_SIDE * GRID_SIDE]; - -static uint8_t *front_grid = game_grid_alpha; -static uint8_t *back_grid = game_grid_beta; - -void swap_grids(void); - -void swap_grids(void) { - uint8_t *temp = front_grid; - - front_grid = back_grid; - back_grid = temp; -} - -uint8_t reset_game(void) { - uint8_t score = 0; - - // Clear the back and front grid - memset(back_grid, 0, GRID_SIDE * GRID_SIDE); - memset(front_grid, 0, GRID_SIDE * GRID_SIDE); - - // Then add two random tiles - score += (1 << front_grid[add_random_tile() - 1]); - score += (1 << front_grid[add_random_tile() - 1]); - - return score; -} - -uint8_t *get_front_grid(void) { - return front_grid; -} - -uint8_t add_random_tile(void) { - uint16_t rand = lfsr_update(); - uint8_t tile_val = (rand & 0x000F) > 0x0D ? 2 : 1; // ~90% chance of a tile of type 1 (a "2"), 10% of a type 2 (a "4") - - uint8_t free_tiles = 0; - uint8_t chosen_tile; - - for (uint8_t offset = 0; offset < GRID_SIDE * GRID_SIDE; offset++) { - if (!front_grid[offset]) free_tiles++; - } - - if(!free_tiles) return 0; - chosen_tile = (rand >> 8) % free_tiles; - - for (uint8_t offset = 0; offset < GRID_SIDE * GRID_SIDE; offset++) { - if (!front_grid[offset] && !(chosen_tile--)) { - front_grid[offset] = tile_val; - return offset + 1; - } - } - - return 0; // Return 0 if we were not able to place the tile, else we return (offset + 1) to indicate where the tile was placed -} - -int8_t step_game(uint8_t dir) { - int8_t done = 0; - uint8_t start_offset; - int8_t column_step; - int8_t row_step; - - /* - * UP: scans TOP to BOTTOM, RIGHT to LEFT - * DOWN: scans BOTTOM to TOP, RIGHT to LEFT - * LEFT: scans LEFT to RIGHT, TOP to BOTTOM - * RIGHT: scans RIGHT to LEFT, TOP to BOTTOM - */ - - switch(dir) { - case GAME_STEP_UP: - start_offset = GRID_SIDE - 1; - column_step = GRID_SIDE; - row_step = -1; - break; - case GAME_STEP_DOWN: - start_offset = (GRID_SIDE * GRID_SIDE) - 1; - column_step = -GRID_SIDE; - row_step = -1; - break; - case GAME_STEP_LEFT: - start_offset = 0; - column_step = 1; - row_step = GRID_SIDE; - break; - case GAME_STEP_RIGHT: - start_offset = GRID_SIDE - 1; - column_step = -1; - row_step = GRID_SIDE; - break; - }; - - // Clear the back grid - memset(back_grid, 0, GRID_SIDE * GRID_SIDE); - - for (uint8_t row = 0; row < GRID_SIDE; row++) { - for(uint8_t col = 0; col < GRID_SIDE; col++) { - uint8_t current_offset = start_offset + (col * column_step) + (row * row_step); - uint8_t sub_col; - - // Search for the first non-zero value in the front grid and copy it in the current place of the back grid (zeroing it in the front) - for(sub_col = col; sub_col < GRID_SIDE; sub_col++) { - uint8_t sub_col_offset = start_offset + (sub_col * column_step) + (row * row_step); - if(front_grid[sub_col_offset]) { - back_grid[current_offset] = front_grid[sub_col_offset]; - front_grid[sub_col_offset] = 0; - - break; - } - } - - // The value is still 0, we found nothing. On to the next row! - if (!back_grid[current_offset]) break; - - // Now search if there is an identical value following this one, so we can merge them - for(; sub_col < GRID_SIDE; sub_col++) { - uint8_t sub_col_offset = start_offset + (sub_col * column_step) + (row * row_step); - if(front_grid[sub_col_offset] == back_grid[current_offset]) { - back_grid[current_offset]++; // Merge them (by increasing the value of the current square and removing the merged one) - front_grid[sub_col_offset] = 0; - - done += back_grid[current_offset] == 11 ? 1 : 0; - break; - } else if (front_grid[sub_col_offset]) break; - } - } - } - - swap_grids(); - - return done; -} - -uint16_t calculate_score(void) { - uint16_t score = 0; - - for(uint8_t offset = 0; offset < GRID_SIDE * GRID_SIDE; offset++) { - if(front_grid[offset]) score += ((uint16_t)1 << front_grid[offset]); - } - - return score; -} +#include "game_logic.h" + +#include "utility.h" + +#include + +static uint8_t game_grid_alpha[GRID_SIDE * GRID_SIDE]; +static uint8_t game_grid_beta[GRID_SIDE * GRID_SIDE]; + +static uint8_t *front_grid = game_grid_alpha; +static uint8_t *back_grid = game_grid_beta; + +void swap_grids(void); + +void swap_grids(void) { + uint8_t *temp = front_grid; + + front_grid = back_grid; + back_grid = temp; +} + +uint8_t reset_game(void) { + uint8_t score = 0; + + // Clear the back and front grid + memset(back_grid, 0, GRID_SIDE * GRID_SIDE); + memset(front_grid, 0, GRID_SIDE * GRID_SIDE); + + // Then add two random tiles + score += (1 << front_grid[add_random_tile() - 1]); + score += (1 << front_grid[add_random_tile() - 1]); + + return score; +} + +uint8_t *get_front_grid(void) { + return front_grid; +} + +uint8_t add_random_tile(void) { + uint16_t rand = lfsr_update(); + uint8_t tile_val = (rand & 0x000F) > 0x0D ? 2 : 1; // ~90% chance of a tile of type 1 (a "2"), 10% of a type 2 (a "4") + + uint8_t free_tiles = 0; + uint8_t chosen_tile; + + for (int8_t offset = 0; offset < GRID_SIDE * GRID_SIDE; offset++) { + if (!front_grid[offset]) free_tiles++; + } + + if(!free_tiles) return 0; + chosen_tile = (rand >> 8) % free_tiles; + + for (int8_t offset = 0; offset < GRID_SIDE * GRID_SIDE; offset++) { + if (!front_grid[offset] && !(chosen_tile--)) { + front_grid[offset] = tile_val; + return offset + 1; + } + } + + return 0; // Return 0 if we were not able to place the tile, else we return (offset + 1) to indicate where the tile was placed +} + +int8_t step_game(uint8_t dir) { + int8_t done = 0; + int8_t start_offset; + int8_t column_step; + int8_t row_step; + + /* + * UP: scans TOP to BOTTOM, RIGHT to LEFT + * DOWN: scans BOTTOM to TOP, RIGHT to LEFT + * LEFT: scans LEFT to RIGHT, TOP to BOTTOM + * RIGHT: scans RIGHT to LEFT, TOP to BOTTOM + */ + + switch(dir) { + case GAME_STEP_UP: + start_offset = GRID_SIDE - 1; + column_step = GRID_SIDE; + row_step = -1; + break; + case GAME_STEP_DOWN: + start_offset = (GRID_SIDE * GRID_SIDE) - 1; + column_step = -GRID_SIDE; + row_step = -1; + break; + case GAME_STEP_LEFT: + start_offset = 0; + column_step = 1; + row_step = GRID_SIDE; + break; + case GAME_STEP_RIGHT: + start_offset = GRID_SIDE - 1; + column_step = -1; + row_step = GRID_SIDE; + break; + }; + + // Clear the back grid + memset(back_grid, 0, GRID_SIDE * GRID_SIDE); + + for (int8_t row = 0; row < GRID_SIDE; row++) { + for(int8_t col = 0; col < GRID_SIDE; col++) { + uint8_t current_offset = start_offset + (col * column_step) + (row * row_step); + uint8_t sub_col; + + // Search for the first non-zero value in the front grid and copy it in the current place of the back grid (zeroing it in the front) + for(sub_col = col; sub_col < GRID_SIDE; sub_col++) { + uint8_t sub_col_offset = start_offset + (sub_col * column_step) + (row * row_step); + if(front_grid[sub_col_offset]) { + back_grid[current_offset] = front_grid[sub_col_offset]; + front_grid[sub_col_offset] = 0; + + break; + } + } + + // The value is still 0, we found nothing. On to the next row! + if (!back_grid[current_offset]) break; + + // Now search if there is an identical value following this one, so we can merge them + for(; sub_col < GRID_SIDE; sub_col++) { + uint8_t sub_col_offset = start_offset + (sub_col * column_step) + (row * row_step); + if(front_grid[sub_col_offset] == back_grid[current_offset]) { + back_grid[current_offset]++; // Merge them (by increasing the value of the current square and removing the merged one) + front_grid[sub_col_offset] = 0; + + done += back_grid[current_offset] == 11 ? 1 : 0; + break; + } else if (front_grid[sub_col_offset]) break; + } + } + } + + swap_grids(); + + return done; +} + +uint16_t calculate_score(void) { + uint16_t score = 0; + + for(uint8_t offset = 0; offset < GRID_SIDE * GRID_SIDE; offset++) { + if(front_grid[offset]) score += ((uint16_t)1 << front_grid[offset]); + } + + return score; +} diff --git a/src/game_main.c b/src/game_main.c index 1043aff..2ae6867 100644 --- a/src/game_main.c +++ b/src/game_main.c @@ -1,165 +1,166 @@ -#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; -} +#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_hgr_graphics.h" +#include "monitor_subroutines.h" +#include "sound.h" +#include "module_list.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 = MODULE_DLOG; // 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 = MODULE_GAME; + 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/game_vdp_graphics.h b/src/game_vdp_graphics.h new file mode 100644 index 0000000..a6d4c38 --- /dev/null +++ b/src/game_vdp_graphics.h @@ -0,0 +1,19 @@ +#ifndef _GAME_VDP_GRAPHICS_HEADER_ +#define _GAME_VDP_GRAPHICS_HEADER_ + +#include + +#define JS_POS_CENTER 0 +#define JS_POS_UP 1 +#define JS_POS_DOWN 2 +#define JS_POS_LEFT 3 +#define JS_POS_RIGHT 4 + + +void vdp_clear_gamegrid(void); +void vdp_clear_dialog(void); +uint8_t vdp_draw_numtile(uint8_t type, uint8_t x, uint8_t y); +void vdp_redraw_tiles(uint8_t *grid); +void vdp_draw_joystick(uint8_t position); + +#endif /* _GAME_VDP_GRAPHICS_HEADER_ */ diff --git a/src/game_vdp_graphics.s b/src/game_vdp_graphics.s new file mode 100644 index 0000000..b09c2e9 --- /dev/null +++ b/src/game_vdp_graphics.s @@ -0,0 +1,323 @@ + .rtmodel version,"1" + .rtmodel core,"6502" + + .extern _Zp + .extern VDP_MEM, VDP_REG + + .extern vdp_point_to_vram_xy, vdp_hide_sprite, vdp_show_sprite, vdp_set_sprite_tile + +NUM_TILE_OFFSET: .equ 0x14 + + .section code,text + +vdp_redraw_tiles: +P_GRID_H$: .equ _Zp+9 +P_GRID_L$: .equ _Zp+8 + + lda zp:_Zp+0 + sta zp:P_GRID_L$ + lda zp:_Zp+1 + sta zp:P_GRID_H$ + + ldy #24 +SetSprtLoop$: + lda (zp:P_GRID_L$),y + bne SkipHide$ + pha + tya + pha + jsr vdp_hide_sprite + pla + tay + pla + jmp SkipSprite$ +SkipHide$: + sta zp:_Zp+0 + dec zp:_Zp+0 + tya + pha + jsr vdp_set_sprite_tile + pla + tay + pha + jsr vdp_show_sprite + pla + tay +SkipSprite$: + dey + bpl SetSprtLoop$ + + ldy #24 +SetTileLoop$: + tya + pha + lda TileNum_To_X_Map,y + sta zp:_Zp+0 + lda TileNum_To_Y_Map,y + sta zp:_Zp+1 + lda (zp:P_GRID_L$),y + + jsr vdp_draw_numtile + + pla + tay + dey + bpl SetTileLoop$ + + rts + +vdp_draw_joystick: +T_NT_IDX$: .equ _Zp+4 +P_POS$: .equ _Zp+2 +T_Y$: .equ _Zp+1 +T_X$: .equ _Zp+0 + + pha + + ldx #0 + stx zp:T_NT_IDX$ + + ; Clear the table + lda #29 + sta zp:T_X$ + lda #16 + sta zp:T_Y$ + jsr vdp_point_to_vram_xy + lda #0 + sta VDP_MEM + nop + + lda #29 + sta zp:T_X$ + lda #18 + sta zp:T_Y$ + jsr vdp_point_to_vram_xy + lda #0 + sta VDP_MEM + nop + + lda #28 + sta zp:T_X$ + lda #17 + sta zp:T_Y$ + jsr vdp_point_to_vram_xy + lda #0 + sta VDP_MEM + nop ; Slow down, we're using this while the IC is rendering + nop + sta VDP_MEM + nop + nop + sta VDP_MEM + nop + nop + + ; Jump to the correct code to handle joystick drawing + pla + asl a + tax + lda _draw_joystick_jumptable$,x + sta zp:_Zp+0 + lda _draw_joystick_jumptable$+1,x + sta zp:_Zp+1 + sec + jmp (_Zp+0) + +J_Center$: + ldx #29 + ldy #17 + bcs J_Done$ +J_Up$: + ldx #29 + ldy #16 + bcs J_Done$ +J_Down$: + ldx #29 + ldy #18 + bcs J_Done$ +J_Left$: + ldx #28 + ldy #17 + bcs J_Done$ +J_Right$: + ldx #30 + ldy #17 +J_Done$: + stx zp:T_X$ + sty zp:T_Y$ + jsr vdp_point_to_vram_xy + lda #120 + sta VDP_MEM + nop + nop + + rts +_draw_joystick_jumptable$: + .word J_Center$ + .word J_Up$ + .word J_Down$ + .word J_Left$ + .word J_Right$ + +vdp_clear_gamegrid: +T_NT_IDX$: .equ _Zp+4 +T_Y$: .equ _Zp+1 +T_X$: .equ _Zp+0 + + + lda #0x00 + sta zp:T_NT_IDX$ + lda #0x01 + sta zp:T_X$ + lda #19 + sta zp:T_Y$ + +ClearLine$: + jsr vdp_point_to_vram_xy + lda #0x00 + ldx #24 +ClearTile$: + sta VDP_MEM + dex + bne ClearTile$ + + dec zp:T_Y$ + bne ClearLine$ + + rts + +vdp_clear_dialog: +T_NT_IDX$: .equ _Zp+4 +T_Y$: .equ _Zp+1 +T_X$: .equ _Zp+0 + + + lda #0x01 + sta zp:T_NT_IDX$ + lda #0x04 + sta zp:T_X$ + lda #15 + sta zp:T_Y$ + ldy #9 + +ClearLine$: + jsr vdp_point_to_vram_xy + lda #0x00 + ldx #24 +ClearTile$: + sta VDP_MEM + dex + bne ClearTile$ + + dec zp:T_Y$ + dey + bne ClearLine$ + + rts + +;;; vdp_draw_numtile: +;;; Draws the 2048 tile at specified coordinates +;;; Parameters: +;;; - Type of tile [A] +;;; - X in game grid [Zp[0]] +;;; - Y in game grid [Zp[1]] +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, Y, X +;;; - Zp 0, 1, 2, 3, 4, 5, 6, 7 +;;; +vdp_draw_numtile: +T_LINE_COUNT$:.equ _Zp+7 +T_NT_IDX$: .equ _Zp+4 +T_COUNTER$: .equ _Zp+3 +P_TYPE$: .equ _Zp+2 +P_Y$: .equ _Zp+1 +P_X$: .equ _Zp+0 + + sta zp:P_TYPE$ + lda #0 + sta zp:T_NT_IDX$ ; the tiles here will be drawn only on game table + sta zp:T_COUNTER$ + + lda #3 + sta zp:T_LINE_COUNT$ + + ; Convert the coordinates + ldx zp:P_X$ + lda X_To_TileNum_Map,x + sta zp:P_X$ + + ldx zp:P_Y$ + lda Y_To_TileNum_Map,x + sta zp:P_Y$ + + ; Calculate the beginning of the tile section we're interested in + clc + lda #NUM_TILE_OFFSET + adc zp:P_TYPE$ + asl a + asl a + asl a + sta zp:P_TYPE$ + + ; Draw the tile +DrawNextLine$: + jsr vdp_point_to_vram_xy + ldx zp:T_COUNTER$ + + ldy #4 +SetTile$: + clc + lda TileNum_Offset_Map,x + adc zp:P_TYPE$ + sta VDP_MEM + nop + inx + dey + bne SetTile$ + + stx zp:T_COUNTER$ + inc zp:P_Y$ + dec zp:T_LINE_COUNT$ + bne DrawNextLine$ + + rts + +;;;;;;;;;;;;;;;;;; + .section data,data + +TileNum_Offset_Map: ; Offset from the beginning of the selected tilemap section, of the tiles composing the numeric-2048 tile + .byte 0x00, 0x04, 0x04, 0x02 ; First line + .byte 0x04, 0x04, 0x04, 0x04 ; Second + .byte 0x01, 0x04, 0x04, 0x03 ; Third and last + +; The following maps convert between X and Y in the game grid into the tile map grid +X_To_TileNum_Map: + .byte 0x01, 0x06, 0x0B, 0x10, 0x15 +Y_To_TileNum_Map: + .byte 0x01, 0x05, 0x09, 0x0D, 0x11 + +; The following maps convert between the tile number and the corresponding X and Y coordinates +TileNum_To_X_Map: + .byte 0x00, 0x01, 0x02, 0x03, 0x04 + .byte 0x00, 0x01, 0x02, 0x03, 0x04 + .byte 0x00, 0x01, 0x02, 0x03, 0x04 + .byte 0x00, 0x01, 0x02, 0x03, 0x04 + .byte 0x00, 0x01, 0x02, 0x03, 0x04 + +TileNum_To_Y_Map: + .byte 0x00, 0x00, 0x00, 0x00, 0x00 + .byte 0x01, 0x01, 0x01, 0x01, 0x01 + .byte 0x02, 0x02, 0x02, 0x02, 0x02 + .byte 0x03, 0x03, 0x03, 0x03, 0x03 + .byte 0x04, 0x04, 0x04, 0x04, 0x04 + +;;;;;;;;;;;;;;;;;; + .public vdp_draw_numtile + .public vdp_clear_gamegrid + .public vdp_clear_dialog + .public vdp_redraw_tiles + .public vdp_draw_joystick + \ No newline at end of file diff --git a/src/graph_misc_data.c b/src/hgr_graph_misc_data.c similarity index 98% rename from src/graph_misc_data.c rename to src/hgr_graph_misc_data.c index 0652f60..447e809 100644 --- a/src/graph_misc_data.c +++ b/src/hgr_graph_misc_data.c @@ -1,31 +1,31 @@ -#include - -const uint8_t score_pic_data[] = { - 0x00, 0x2C, 0x40, 0x40, 0x03, 0x05, 0x30, 0x00, 0x00, 0x7B, 0x70, 0x63, 0x06, 0x7B, 0x70, 0x0F, 0x00, 0x0F, - 0x78, 0x57, 0x0B, 0x47, 0x39, 0x00, 0x40, 0x03, 0x3C, 0x2E, 0x4E, 0x63, 0x39, 0x00, 0x60, 0x00, 0x3C, 0x1B, - 0x2E, 0x61, 0x39, 0x00, 0x00, 0x07, 0x5E, 0x1F, 0x6E, 0x71, 0x18, 0x00, 0x40, 0x1D, 0x4E, 0x0C, 0x66, 0x1D, - 0x7E, 0x03, 0x00, 0x3A, 0x0E, 0x4E, 0x67, 0x07, 0x7C, 0x01, 0x00, 0x30, 0x07, 0x0E, 0x77, 0x02, 0x0C, 0x00, - 0x00, 0x3C, 0x07, 0x66, 0x33, 0x06, 0x06, 0x00, 0x40, 0x1B, 0x47, 0x46, 0x3B, 0x0D, 0x06, 0x00, 0x30, 0x0F, - 0x23, 0x6E, 0x19, 0x0E, 0x7F, 0x00, 0x78, 0x01, 0x3E, 0x78, 0x18, 0x1C, 0x3F, 0x00, 0x18, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00 -}; - -const uint8_t moves_pic_data[] = { - 0x00, 0x06, 0x00, 0x1C, 0x06, 0x30, 0x00, 0x0B, 0x00, 0x3E, 0x0C, 0x36, 0x0E, 0x72, 0x6F, 0x1E, 0x00, 0x7F, - 0x0A, 0x5D, 0x06, 0x3A, 0x60, 0x03, 0x00, 0x57, 0x4D, 0x72, 0x0E, 0x3F, 0x70, 0x00, 0x40, 0x6F, 0x46, 0x71, - 0x26, 0x39, 0x18, 0x00, 0x50, 0x33, 0x67, 0x71, 0x47, 0x1B, 0x60, 0x01, 0x60, 0x19, 0x67, 0x30, 0x67, 0x7F, - 0x33, 0x07, 0x60, 0x15, 0x73, 0x3C, 0x67, 0x7C, 0x41, 0x0E, 0x70, 0x4C, 0x73, 0x38, 0x37, 0x0C, 0x00, 0x0C, - 0x68, 0x5C, 0x33, 0x1E, 0x1F, 0x06, 0x00, 0x0F, 0x30, 0x4C, 0x31, 0x1C, 0x1F, 0x06, 0x70, 0x06, 0x38, 0x26, - 0x71, 0x0E, 0x0E, 0x7F, 0x6C, 0x03, 0x38, 0x66, 0x41, 0x07, 0x07, 0x3F, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x06, 0x00 -}; - -const uint8_t high_pic_data[] = { - 0x00, 0x00, 0x06, 0x60, 0x21, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x64, 0x78, 0x51, 0x20, 0x00, 0x00, 0x00, - 0x0E, 0x6E, 0x1D, 0x72, 0x70, 0x00, 0x00, 0x00, 0x06, 0x7E, 0x1E, 0x33, 0x30, 0x00, 0x00, 0x00, 0x0E, 0x33, - 0x0E, 0x71, 0x18, 0x00, 0x00, 0x00, 0x07, 0x7B, 0x05, 0x38, 0x18, 0x00, 0x00, 0x00, 0x5F, 0x1B, 0x07, 0x79, - 0x1D, 0x00, 0x00, 0x00, 0x7B, 0x59, 0x73, 0x59, 0x0F, 0x00, 0x00, 0x40, 0x27, 0x5D, 0x43, 0x3C, 0x0A, 0x00, - 0x00, 0x20, 0x51, 0x4C, 0x61, 0x0A, 0x05, 0x00, 0x00, 0x40, 0x71, 0x4C, 0x63, 0x0C, 0x07, 0x00, 0x00, 0x60, - 0x61, 0x06, 0x31, 0x0E, 0x06, 0x00, 0x00, 0x60, 0x30, 0x02, 0x3E, 0x06, 0x03, 0x00, 0x00, 0x20, 0x20, 0x00, - 0x00, 0x02, 0x02, 0x00 -}; +#include + +const uint8_t score_pic_data[] = { + 0x00, 0x2C, 0x40, 0x40, 0x03, 0x05, 0x30, 0x00, 0x00, 0x7B, 0x70, 0x63, 0x06, 0x7B, 0x70, 0x0F, 0x00, 0x0F, + 0x78, 0x57, 0x0B, 0x47, 0x39, 0x00, 0x40, 0x03, 0x3C, 0x2E, 0x4E, 0x63, 0x39, 0x00, 0x60, 0x00, 0x3C, 0x1B, + 0x2E, 0x61, 0x39, 0x00, 0x00, 0x07, 0x5E, 0x1F, 0x6E, 0x71, 0x18, 0x00, 0x40, 0x1D, 0x4E, 0x0C, 0x66, 0x1D, + 0x7E, 0x03, 0x00, 0x3A, 0x0E, 0x4E, 0x67, 0x07, 0x7C, 0x01, 0x00, 0x30, 0x07, 0x0E, 0x77, 0x02, 0x0C, 0x00, + 0x00, 0x3C, 0x07, 0x66, 0x33, 0x06, 0x06, 0x00, 0x40, 0x1B, 0x47, 0x46, 0x3B, 0x0D, 0x06, 0x00, 0x30, 0x0F, + 0x23, 0x6E, 0x19, 0x0E, 0x7F, 0x00, 0x78, 0x01, 0x3E, 0x78, 0x18, 0x1C, 0x3F, 0x00, 0x18, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +const uint8_t moves_pic_data[] = { + 0x00, 0x06, 0x00, 0x1C, 0x06, 0x30, 0x00, 0x0B, 0x00, 0x3E, 0x0C, 0x36, 0x0E, 0x72, 0x6F, 0x1E, 0x00, 0x7F, + 0x0A, 0x5D, 0x06, 0x3A, 0x60, 0x03, 0x00, 0x57, 0x4D, 0x72, 0x0E, 0x3F, 0x70, 0x00, 0x40, 0x6F, 0x46, 0x71, + 0x26, 0x39, 0x18, 0x00, 0x50, 0x33, 0x67, 0x71, 0x47, 0x1B, 0x60, 0x01, 0x60, 0x19, 0x67, 0x30, 0x67, 0x7F, + 0x33, 0x07, 0x60, 0x15, 0x73, 0x3C, 0x67, 0x7C, 0x41, 0x0E, 0x70, 0x4C, 0x73, 0x38, 0x37, 0x0C, 0x00, 0x0C, + 0x68, 0x5C, 0x33, 0x1E, 0x1F, 0x06, 0x00, 0x0F, 0x30, 0x4C, 0x31, 0x1C, 0x1F, 0x06, 0x70, 0x06, 0x38, 0x26, + 0x71, 0x0E, 0x0E, 0x7F, 0x6C, 0x03, 0x38, 0x66, 0x41, 0x07, 0x07, 0x3F, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x06, 0x00 +}; + +const uint8_t high_pic_data[] = { + 0x00, 0x00, 0x06, 0x60, 0x21, 0x30, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x64, 0x78, 0x51, 0x20, 0x00, 0x00, 0x00, + 0x0E, 0x6E, 0x1D, 0x72, 0x70, 0x00, 0x00, 0x00, 0x06, 0x7E, 0x1E, 0x33, 0x30, 0x00, 0x00, 0x00, 0x0E, 0x33, + 0x0E, 0x71, 0x18, 0x00, 0x00, 0x00, 0x07, 0x7B, 0x05, 0x38, 0x18, 0x00, 0x00, 0x00, 0x5F, 0x1B, 0x07, 0x79, + 0x1D, 0x00, 0x00, 0x00, 0x7B, 0x59, 0x73, 0x59, 0x0F, 0x00, 0x00, 0x40, 0x27, 0x5D, 0x43, 0x3C, 0x0A, 0x00, + 0x00, 0x20, 0x51, 0x4C, 0x61, 0x0A, 0x05, 0x00, 0x00, 0x40, 0x71, 0x4C, 0x63, 0x0C, 0x07, 0x00, 0x00, 0x60, + 0x61, 0x06, 0x31, 0x0E, 0x06, 0x00, 0x00, 0x60, 0x30, 0x02, 0x3E, 0x06, 0x03, 0x00, 0x00, 0x20, 0x20, 0x00, + 0x00, 0x02, 0x02, 0x00 +}; diff --git a/src/graph_misc_data.h b/src/hgr_graph_misc_data.h similarity index 70% rename from src/graph_misc_data.h rename to src/hgr_graph_misc_data.h index e3f1724..c6fd0de 100644 --- a/src/graph_misc_data.h +++ b/src/hgr_graph_misc_data.h @@ -1,18 +1,18 @@ -#ifndef _GRAPH_MISC_DATA_HEADER_ -#define _GRAPH_MISC_DATA_HEADER_ - -#include - -#define SCORE_PIC_WIDTH_BYTES 8 -#define SCORE_PIC_HEIGHT 14 -extern const uint8_t score_pic_data[]; - -#define MOVES_PIC_WIDTH_BYTES 8 -#define MOVES_PIC_HEIGHT 14 -extern const uint8_t moves_pic_data[]; - -#define HIGH_PIC_WIDTH_BYTES 8 -#define HIGH_PIC_HEIGHT 14 -extern const uint8_t high_pic_data[]; - -#endif /* _GRAPH_MISC_DATA_HEADER_ */ +#ifndef _HGR_GRAPH_MISC_DATA_HEADER_ +#define _HGR_GRAPH_MISC_DATA_HEADER_ + +#include + +#define SCORE_PIC_WIDTH_BYTES 8 +#define SCORE_PIC_HEIGHT 14 +extern const uint8_t score_pic_data[]; + +#define MOVES_PIC_WIDTH_BYTES 8 +#define MOVES_PIC_HEIGHT 14 +extern const uint8_t moves_pic_data[]; + +#define HIGH_PIC_WIDTH_BYTES 8 +#define HIGH_PIC_HEIGHT 14 +extern const uint8_t high_pic_data[]; + +#endif /* _HGR_GRAPH_MISC_DATA_HEADER_ */ diff --git a/src/hgr_graphics.h b/src/hgr_graphics.h new file mode 100644 index 0000000..4851bd9 --- /dev/null +++ b/src/hgr_graphics.h @@ -0,0 +1,8 @@ +#ifndef _HGR_GRAPHICS_HEADER_ +#define _HGR_GRAPHICS_HEADER_ + +#define SCREEN_HEIGHT 192 +#define SCREEN_WIDTH_BYTES 128 +#define BYTES_PER_LINE 40 + +#endif /* _HGR_GRAPHICS_HEADER_ */ diff --git a/src/line_data.c b/src/hgr_line_data.c similarity index 97% rename from src/line_data.c rename to src/hgr_line_data.c index 992b832..95866e3 100644 --- a/src/line_data.c +++ b/src/hgr_line_data.c @@ -1,22 +1,22 @@ -#include - -#include "utility.h" - -const uint16_t line_offset_map[SCREEN_HEIGHT] = { - 0x0000, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x0080, 0x0480, 0x0880, 0x0C80, - 0x1080, 0x1480, 0x1880, 0x1C80, 0x0100, 0x0500, 0x0900, 0x0D00, 0x1100, 0x1500, 0x1900, 0x1D00, - 0x0180, 0x0580, 0x0980, 0x0D80, 0x1180, 0x1580, 0x1980, 0x1D80, 0x0200, 0x0600, 0x0A00, 0x0E00, - 0x1200, 0x1600, 0x1A00, 0x1E00, 0x0280, 0x0680, 0x0A80, 0x0E80, 0x1280, 0x1680, 0x1A80, 0x1E80, - 0x0300, 0x0700, 0x0B00, 0x0F00, 0x1300, 0x1700, 0x1B00, 0x1F00, 0x0380, 0x0780, 0x0B80, 0x0F80, - 0x1380, 0x1780, 0x1B80, 0x1F80, 0x0028, 0x0428, 0x0828, 0x0C28, 0x1028, 0x1428, 0x1828, 0x1C28, - 0x00A8, 0x04A8, 0x08A8, 0x0CA8, 0x10A8, 0x14A8, 0x18A8, 0x1CA8, 0x0128, 0x0528, 0x0928, 0x0D28, - 0x1128, 0x1528, 0x1928, 0x1D28, 0x01A8, 0x05A8, 0x09A8, 0x0DA8, 0x11A8, 0x15A8, 0x19A8, 0x1DA8, - 0x0228, 0x0628, 0x0A28, 0x0E28, 0x1228, 0x1628, 0x1A28, 0x1E28, 0x02A8, 0x06A8, 0x0AA8, 0x0EA8, - 0x12A8, 0x16A8, 0x1AA8, 0x1EA8, 0x0328, 0x0728, 0x0B28, 0x0F28, 0x1328, 0x1728, 0x1B28, 0x1F28, - 0x03A8, 0x07A8, 0x0BA8, 0x0FA8, 0x13A8, 0x17A8, 0x1BA8, 0x1FA8, 0x0050, 0x0450, 0x0850, 0x0C50, - 0x1050, 0x1450, 0x1850, 0x1C50, 0x00D0, 0x04D0, 0x08D0, 0x0CD0, 0x10D0, 0x14D0, 0x18D0, 0x1CD0, - 0x0150, 0x0550, 0x0950, 0x0D50, 0x1150, 0x1550, 0x1950, 0x1D50, 0x01D0, 0x05D0, 0x09D0, 0x0DD0, - 0x11D0, 0x15D0, 0x19D0, 0x1DD0, 0x0250, 0x0650, 0x0A50, 0x0E50, 0x1250, 0x1650, 0x1A50, 0x1E50, - 0x02D0, 0x06D0, 0x0AD0, 0x0ED0, 0x12D0, 0x16D0, 0x1AD0, 0x1ED0, 0x0350, 0x0750, 0x0B50, 0x0F50, - 0x1350, 0x1750, 0x1B50, 0x1F50, 0x03D0, 0x07D0, 0x0BD0, 0x0FD0, 0x13D0, 0x17D0, 0x1BD0, 0x1FD0 -}; +#include + +#include "hgr_graphics.h" + +const uint16_t line_offset_map[SCREEN_HEIGHT] = { + 0x0000, 0x0400, 0x0800, 0x0C00, 0x1000, 0x1400, 0x1800, 0x1C00, 0x0080, 0x0480, 0x0880, 0x0C80, + 0x1080, 0x1480, 0x1880, 0x1C80, 0x0100, 0x0500, 0x0900, 0x0D00, 0x1100, 0x1500, 0x1900, 0x1D00, + 0x0180, 0x0580, 0x0980, 0x0D80, 0x1180, 0x1580, 0x1980, 0x1D80, 0x0200, 0x0600, 0x0A00, 0x0E00, + 0x1200, 0x1600, 0x1A00, 0x1E00, 0x0280, 0x0680, 0x0A80, 0x0E80, 0x1280, 0x1680, 0x1A80, 0x1E80, + 0x0300, 0x0700, 0x0B00, 0x0F00, 0x1300, 0x1700, 0x1B00, 0x1F00, 0x0380, 0x0780, 0x0B80, 0x0F80, + 0x1380, 0x1780, 0x1B80, 0x1F80, 0x0028, 0x0428, 0x0828, 0x0C28, 0x1028, 0x1428, 0x1828, 0x1C28, + 0x00A8, 0x04A8, 0x08A8, 0x0CA8, 0x10A8, 0x14A8, 0x18A8, 0x1CA8, 0x0128, 0x0528, 0x0928, 0x0D28, + 0x1128, 0x1528, 0x1928, 0x1D28, 0x01A8, 0x05A8, 0x09A8, 0x0DA8, 0x11A8, 0x15A8, 0x19A8, 0x1DA8, + 0x0228, 0x0628, 0x0A28, 0x0E28, 0x1228, 0x1628, 0x1A28, 0x1E28, 0x02A8, 0x06A8, 0x0AA8, 0x0EA8, + 0x12A8, 0x16A8, 0x1AA8, 0x1EA8, 0x0328, 0x0728, 0x0B28, 0x0F28, 0x1328, 0x1728, 0x1B28, 0x1F28, + 0x03A8, 0x07A8, 0x0BA8, 0x0FA8, 0x13A8, 0x17A8, 0x1BA8, 0x1FA8, 0x0050, 0x0450, 0x0850, 0x0C50, + 0x1050, 0x1450, 0x1850, 0x1C50, 0x00D0, 0x04D0, 0x08D0, 0x0CD0, 0x10D0, 0x14D0, 0x18D0, 0x1CD0, + 0x0150, 0x0550, 0x0950, 0x0D50, 0x1150, 0x1550, 0x1950, 0x1D50, 0x01D0, 0x05D0, 0x09D0, 0x0DD0, + 0x11D0, 0x15D0, 0x19D0, 0x1DD0, 0x0250, 0x0650, 0x0A50, 0x0E50, 0x1250, 0x1650, 0x1A50, 0x1E50, + 0x02D0, 0x06D0, 0x0AD0, 0x0ED0, 0x12D0, 0x16D0, 0x1AD0, 0x1ED0, 0x0350, 0x0750, 0x0B50, 0x0F50, + 0x1350, 0x1750, 0x1B50, 0x1F50, 0x03D0, 0x07D0, 0x0BD0, 0x0FD0, 0x13D0, 0x17D0, 0x1BD0, 0x1FD0 +}; diff --git a/src/hgr_line_data.h b/src/hgr_line_data.h new file mode 100644 index 0000000..9466fe5 --- /dev/null +++ b/src/hgr_line_data.h @@ -0,0 +1,10 @@ +#ifndef _HGR_LINE_DATA_HEADER_ +#define _HGR_LINE_DATA_HEADER_ + +#include + +#include "hgr_graphics.h" + +extern const uint16_t line_offset_map[SCREEN_HEIGHT]; + +#endif /* _HGR_LINE_DATA_HEADER_ */ diff --git a/src/intro_main.c b/src/intro_main.c index 4595ad0..f40cfe5 100644 --- a/src/intro_main.c +++ b/src/intro_main.c @@ -1,39 +1,46 @@ -#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; -} +#include +#include + +#include + +#include "vdp_utils.h" + +#include "monitor_subroutines.h" +#include "utility.h" +#include "input.h" +#include "mem_map.h" +#include "shared_page.h" + +#include "module_list.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; + uint8_t vdp_detected; + + // 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); + + // Detect expansion hardware, and choose what to load according to that + vdp_detected = vdp_detect() && !read_any_key(); + + // 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 = vdp_detected ? MODULE_INIT_VDP : MODULE_DLOG; // If VDP is detected, load the module to initialize it, otherwise DLOG module is next! + + return; +} diff --git a/src/line_data.h b/src/line_data.h deleted file mode 100644 index 219744e..0000000 --- a/src/line_data.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef _LINE_DATA_HEADER_ -#define _LINE_DATA_HEADER_ - -#include -#include "utility.h" - -extern const uint16_t line_offset_map[SCREEN_HEIGHT]; - -#endif /* _LINE_DATA_HEADER */ diff --git a/src/master_main.c b/src/master_main.c index 4c5b4c0..85e9880 100644 --- a/src/master_main.c +++ b/src/master_main.c @@ -24,7 +24,7 @@ static uint8_t *module_page = (uint8_t*)MODULE_PAGE; static shared_page_data * shared_page = (shared_page_data*)SHARED_PAGE; -#define FILE_LIST_LEN 6 +#define FILE_LIST_LEN 10 #define FNAME_LEN 6 #define STATE_FILE_IDX 1 @@ -34,10 +34,14 @@ static shared_page_data * shared_page = (shared_page_data*)SHARED_PAGE; 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 | 'I', 0x80 | 'N', 0x80 | 'T', 0x80 | 'R', 0x80 | 'O', 0x00}, // INTRO (this executable will detect hardware and load the appropriate followup module) { 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) - { 0x80 | 'D', 0x80 | 'E', 0x80 | 'M', 0x80 | 'O', 0xA0, 0x00}, // DEMO (automatic demo) + { 0x80 | 'D', 0x80 | 'E', 0x80 | 'M', 0x80 | 'O', 0xA0, 0x00}, // DEMO (automatic demo), + { 0x80 | 'V', 0x80 | 'D', 0x80 | 'P', 0x80 | 'I', 0x80 | 'N', 0x00}, // VDPIN (Loads VDP resources), + { 0x80 | 'V', 0x80 | 'D', 0x80 | 'D', 0x80 | 'L', 0x80 | 'G', 0x00}, // VDDLG (VDP startup, win, lose dialogs), + { 0x80 | 'V', 0x80 | 'D', 0x80 | 'G', 0x80 | 'A', 0x80 | 'M', 0x00}, // VDGAM (Game, VDP version), + { 0x80 | 'V', 0x80 | 'D', 0x80 | 'D', 0x80 | 'E', 0x80 | 'M', 0x00}, // VDDEM (Demo, VDP version), }; 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. @@ -48,6 +52,10 @@ static uint16_t file_load_address[FILE_LIST_LEN] = { // This will hold the load MODULE_PAGE, MODULE_PAGE, MODULE_PAGE, + MODULE_PAGE, + MODULE_PAGE, + MODULE_PAGE, + MODULE_PAGE, }; static void init(void); @@ -123,17 +131,14 @@ __task int main(void) { uint8_t cur_file = 0; uint8_t keep_going = 1; - __disable_interrupts(); + __disable_interrupts(); // Keep the interrupts disabled in the master module 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 @@ -151,7 +156,6 @@ __task int main(void) { } dii_power_off(DEFAULT_DRIVE_CONTROLLER_OFFSET); - __enable_interrupts(); } shared_page->master_command = MASTER_COMMAND_NONE; diff --git a/src/mem_registers.h b/src/mem_registers.h index 5ca2394..5c11921 100644 --- a/src/mem_registers.h +++ b/src/mem_registers.h @@ -1,48 +1,51 @@ -#ifndef _MEM_REGISTERS_HEADER_ -#define _MEM_REGISTERS_HEADER_ - -#include - -#define ROM_MONITOR 0xFF61 - -#define ZP_WNDLFT 0x0020 // 0, left column of scroll window -#define ZP_WNDWDTH 0x0021 // 40, width of scroll window -#define ZP_WNDTOP 0x0022 // 0, top line of the scroll window -#define ZP_WNDBTM 0x0023 // 24, bottom line of the scroll window - -#define ZP_CH 0x0024 // Displacement from window left for the cursor -#define ZP_CV 0x0025 // Displacement from top of screen (not window!) for the cursor - -#define ZP_INVFLAG 0x0032 // Either 0x00 or 0x7F, set text color inversion -#define ZP_PROMPT 0x0033 // Prompt character - -#define ZP_RND 0x004E // Note that this is a 16bit register incremented by the RDKEY func - -#define P3_PWRDUP_REF 0x03F3 -#define P3_PWRDUP 0x03F4 // Already-powered-up indicator. If it is set to the content of 0x03F3 XOR'd with 0xA5, the soft reset vector is considered valid - - -#define DATAIN_KB_MASK 0x3F -#define DATAIN_PRNT_MASK 0x40 -#define DATAIN_TAPEIN_MASK 0x80 - -#define IO_DATAOUT 0xC000 // (W) To keyboard and printer port -#define IO_DATAIN 0xC010 // (R) Data input from keyboard (0:5), printer (6) and tape (7) -#define IO_TAPEOUT 0xC020 // (R) Data output for tape, read from here to output bit on tape -#define IO_SPEAKER 0xC030 // (R) Speaker toggle -#define IO_DISPLAY_COLOR 0xC050 // (R / W) Access here to enable the colorburst -#define IO_DISPLAY_BW 0xC051 // (R / W) Access here to disable the colorburst -#define IO_MTA_OFF 0xC052 // (?) -#define IO_MTA_ON 0xC053 // (?) -#define IO_DISPLAY_PAGE1 0xC054 // (R / W) Access here to select the primary display page -#define IO_DISPLAY_PAGE2 0xC055 // (R / W) Access here to select the secondary display page -#define IO_MTB_OFF 0xC056 // (?) -#define IO_MTB_ON 0xC057 // (?) -#define IO_PRNT_STRB_LO 0xC058 // (R / W) Access LO/HI/LO or HI/LO/HI consecutively depending on the type of strobe pulse to create -#define IO_PRNT_STRB_HI 0xC059 // (R / W) -#define IO_ROMSEL 0xC05A // (R / W) Access here will make region C100-FFFF a ROM area -#define IO_RAMSEL 0xC05B // (R / W) Access here will make region C100-FFFF a RAM area -#define IO_KB_CTRL_LOW 0xC05E // (R / W) Set the CTRL line to 0, access is through DATAIN -#define IO_KB_CTRL_HI 0xC05F // (R / W) Set the CTRL line to 1 - -#endif /* _MEM_REGISTERS_HEADER_ */ +#ifndef _MEM_REGISTERS_HEADER_ +#define _MEM_REGISTERS_HEADER_ + +#include + +#define NMI_HANDLER_ADDRESS 0x03FB +#define IRQ_HANDLER_ADDRESS 0x03FE + +#define ROM_MONITOR 0xFF61 + +#define ZP_WNDLFT 0x0020 // 0, left column of scroll window +#define ZP_WNDWDTH 0x0021 // 40, width of scroll window +#define ZP_WNDTOP 0x0022 // 0, top line of the scroll window +#define ZP_WNDBTM 0x0023 // 24, bottom line of the scroll window + +#define ZP_CH 0x0024 // Displacement from window left for the cursor +#define ZP_CV 0x0025 // Displacement from top of screen (not window!) for the cursor + +#define ZP_INVFLAG 0x0032 // Either 0x00 or 0x7F, set text color inversion +#define ZP_PROMPT 0x0033 // Prompt character + +#define ZP_RND 0x004E // Note that this is a 16bit register incremented by the RDKEY func + +#define P3_PWRDUP_REF 0x03F3 +#define P3_PWRDUP 0x03F4 // Already-powered-up indicator. If it is set to the content of 0x03F3 XOR'd with 0xA5, the soft reset vector is considered valid + + +#define DATAIN_KB_MASK 0x3F +#define DATAIN_PRNT_MASK 0x40 +#define DATAIN_TAPEIN_MASK 0x80 + +#define IO_DATAOUT 0xC000 // (W) To keyboard and printer port +#define IO_DATAIN 0xC010 // (R) Data input from keyboard (0:5), printer (6) and tape (7) +#define IO_TAPEOUT 0xC020 // (R) Data output for tape, read from here to output bit on tape +#define IO_SPEAKER 0xC030 // (R) Speaker toggle +#define IO_DISPLAY_COLOR 0xC050 // (R / W) Access here to enable the colorburst +#define IO_DISPLAY_BW 0xC051 // (R / W) Access here to disable the colorburst +#define IO_MTA_OFF 0xC052 // (?) +#define IO_MTA_ON 0xC053 // (?) +#define IO_DISPLAY_PAGE1 0xC054 // (R / W) Access here to select the primary display page +#define IO_DISPLAY_PAGE2 0xC055 // (R / W) Access here to select the secondary display page +#define IO_MTB_OFF 0xC056 // (?) +#define IO_MTB_ON 0xC057 // (?) +#define IO_PRNT_STRB_LO 0xC058 // (R / W) Access LO/HI/LO or HI/LO/HI consecutively depending on the type of strobe pulse to create +#define IO_PRNT_STRB_HI 0xC059 // (R / W) +#define IO_ROMSEL 0xC05A // (R / W) Access here will make region C100-FFFF a ROM area +#define IO_RAMSEL 0xC05B // (R / W) Access here will make region C100-FFFF a RAM area +#define IO_KB_CTRL_LOW 0xC05E // (R / W) Set the CTRL line to 0, access is through DATAIN +#define IO_KB_CTRL_HI 0xC05F // (R / W) Set the CTRL line to 1 + +#endif /* _MEM_REGISTERS_HEADER_ */ diff --git a/src/module_list.h b/src/module_list.h new file mode 100644 index 0000000..fb9f821 --- /dev/null +++ b/src/module_list.h @@ -0,0 +1,13 @@ +#ifndef _MODULE_LIST_HEADER_ +#define _MODULE_LIST_HEADER_ + +#define MODULE_DLOG 3 +#define MODULE_GAME 4 +#define MODULE_DEMO 5 +#define MODULE_INIT_VDP 6 +#define MODULE_DLOG_VDP 7 +#define MODULE_GAME_VDP 8 +#define MODULE_DEMO_VDP 9 + + +#endif /* _MODULE_LIST_HEADER_ */ diff --git a/src/utility.c b/src/utility.c index e019be9..fb29948 100644 --- a/src/utility.c +++ b/src/utility.c @@ -1,65 +1,77 @@ -#include "utility.h" - -#include "mem_registers.h" -#include "monitor_subroutines.h" -#include "line_data.h" - -void num_to_decbuf(uint16_t n, uint8_t len, uint8_t *buf) { - - for(uint8_t idx = 0; idx < len; idx++) { - buf[idx] = n % 10; - n /= 10; - } -} - -// https://stackoverflow.com/questions/2602823/in-c-c-whats-the-simplest-way-to-reverse-the-order-of-bits-in-a-byte -uint8_t bit_reverse(uint8_t b) { - b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; - b = (b & 0xCC) >> 2 | (b & 0x33) << 2; - b = (b & 0xAA) >> 1 | (b & 0x55) << 1; - - return b; -} - -// https://stackoverflow.com/questions/14009765/fastest-way-to-count-bits -uint8_t bit_count(uint8_t b) { - b = (b & 0x55) + (b >> 1 & 0x55); - b = (b & 0x33) + (b >> 2 & 0x33); - b = (b & 0x0f) + (b >> 4 & 0x0f); - - return b; -} - -void lfsr_init(uint16_t reg) { - *((uint16_t*)LFSR_REGISTER_ADDRESS) = 0xF00D; -} - -uint16_t lfsr_update(void) { - uint16_t *lfsr = ((uint16_t*)LFSR_REGISTER_ADDRESS); - - *lfsr ^= (*lfsr) >> 7; - *lfsr ^= (*lfsr) << 9; - *lfsr ^= (*lfsr) >> 13; - - return *lfsr; -} - -#define CRC8RDALLAS_POLY 0x31 -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; -} +#include "utility.h" + +#include "mem_registers.h" +#include "monitor_subroutines.h" + +void decbuf_to_ascii(uint8_t len, uint8_t *buf) { + for(uint8_t idx = 0; idx < len; idx++) { + buf[idx] += 0x30; + } + + // Swap the order in the array + uint8_t tmp; + for(uint8_t idx = 0; idx < len/2; idx++) { + tmp = buf[idx]; + buf[idx] = buf[(len - 1) - idx]; + buf[(len - 1) - idx] = tmp; + } +} + +void num_to_decbuf(uint16_t n, uint8_t len, uint8_t *buf) { + for(uint8_t idx = 0; idx < len; idx++) { + buf[idx] = n % 10; + n /= 10; + } +} + +// https://stackoverflow.com/questions/2602823/in-c-c-whats-the-simplest-way-to-reverse-the-order-of-bits-in-a-byte +uint8_t bit_reverse(uint8_t b) { + b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; + b = (b & 0xCC) >> 2 | (b & 0x33) << 2; + b = (b & 0xAA) >> 1 | (b & 0x55) << 1; + + return b; +} + +// https://stackoverflow.com/questions/14009765/fastest-way-to-count-bits +uint8_t bit_count(uint8_t b) { + b = (b & 0x55) + (b >> 1 & 0x55); + b = (b & 0x33) + (b >> 2 & 0x33); + b = (b & 0x0f) + (b >> 4 & 0x0f); + + return b; +} + +void lfsr_init(uint16_t reg) { + *((uint16_t*)LFSR_REGISTER_ADDRESS) = 0xF00D; +} + +uint16_t lfsr_update(void) { + uint16_t *lfsr = ((uint16_t*)LFSR_REGISTER_ADDRESS); + + *lfsr ^= (*lfsr) >> 7; + *lfsr ^= (*lfsr) << 9; + *lfsr ^= (*lfsr) >> 13; + + return *lfsr; +} + +#define CRC8RDALLAS_POLY 0x31 +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; +} diff --git a/src/utility.h b/src/utility.h index d30bb0a..96fc869 100644 --- a/src/utility.h +++ b/src/utility.h @@ -1,34 +1,31 @@ -#ifndef _UTILITY_HEADER_ -#define _UTILITY_HEADER_ - -#include - -#define SCREEN_HEIGHT 192 -#define SCREEN_WIDTH_BYTES 128 -#define BYTES_PER_LINE 40 - -#define PEEKZ(a) (*(volatile uint8_t* __attribute__((zpage)))(a)) -#define POKEZ(a, b) ((*(volatile uint8_t* __attribute__((zpage)))(a)) = b) - -#define PEEK(a) (*(volatile uint8_t*)(a)) -#define POKE(a, b) ((*(volatile uint8_t*)(a)) = b) - -#define PEEKW(a) (*(volatile uint16_t*)(a)) -#define POKEW(a, b) ((*(volatile uint16_t*)(a)) = b) - -#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); -uint8_t calculate_crc8(uint8_t* data, uint8_t len); - -#endif /* _UTILITY_HEADER_ */ +#ifndef _UTILITY_HEADER_ +#define _UTILITY_HEADER_ + +#include + +#define PEEKZ(a) (*(volatile uint8_t* __attribute__((zpage)))(a)) +#define POKEZ(a, b) ((*(volatile uint8_t* __attribute__((zpage)))(a)) = b) + +#define PEEK(a) (*(volatile uint8_t*)(a)) +#define POKE(a, b) ((*(volatile uint8_t*)(a)) = b) + +#define PEEKW(a) (*(volatile uint16_t*)(a)) +#define POKEW(a, b) ((*(volatile uint16_t*)(a)) = b) + +#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 decbuf_to_ascii(uint8_t len, uint8_t *buf); +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); +uint8_t calculate_crc8(uint8_t* data, uint8_t len); + +#endif /* _UTILITY_HEADER_ */ diff --git a/src/vddem_main.c b/src/vddem_main.c new file mode 100644 index 0000000..de44d1c --- /dev/null +++ b/src/vddem_main.c @@ -0,0 +1,197 @@ +#include +#include + +#include + +#include "vdp_utils.h" +#include "game_vdp_graphics.h" + +#include "monitor_subroutines.h" +#include "utility.h" +#include "mem_map.h" +#include "mem_registers.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 "monitor_subroutines.h" +#include "sound.h" +#include "module_list.h" + +// External initialization requirements +#pragma require __preserve_zp +#pragma require __data_initialization_needed + +#define HSCORE_TEXT_X 27 +#define HSCORE_TEXT_Y 13 + +#define SCORE_TEXT_X 27 +#define SCORE_TEXT_Y 4 + +#define MOVES_TEXT_X 27 +#define MOVES_TEXT_Y 8 + +#define BOTTOM_TEXT_X 1 +#define BOTTOM_TEXT_Y 23 + +#define MAX_DEMO_MOVES 30 + +#define RUNS_TO_SKIP 4000 + +static state_page_data* state_page = (state_page_data*)STATE_PAGE; +static shared_page_data *shared_page = (shared_page_data*)SHARED_PAGE; +static uint8_t text_buf[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +static volatile uint8_t key_pressed = 0; + +void main(void) { + uint16_t moves_count = 0; + uint16_t score = 0; + int8_t done = 0; + + __disable_interrupts(); // Make sure the interrupts are disabled + + // 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 = MODULE_DLOG_VDP; // 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 + vdp_hide_sprites(); + vdp_clear_gamegrid(); + vdp_switch_nt(0); // Make sure VDP shows the gamegrid + + // Setup the IRQ handler + POKEW(IRQ_HANDLER_ADDRESS, (uint16_t)vdp_irq_handler); + + // Reset the game, calculate the initial score depending on which tiles we randomly get + score = reset_game(); + + // Draw the initial state of the game + vdp_print_string(0, BOTTOM_TEXT_X, BOTTOM_TEXT_Y, "DEMO MODE Press any key to exit"); + + num_to_decbuf(state_page->hi_score, 5, text_buf); // High score + decbuf_to_ascii(5, text_buf); + vdp_print_string(0, HSCORE_TEXT_X, HSCORE_TEXT_Y, (char*)text_buf); + + num_to_decbuf(moves_count, 5, text_buf); // Moves count + decbuf_to_ascii(5, text_buf); + vdp_print_string(0, MOVES_TEXT_X, MOVES_TEXT_Y, (char*)text_buf); + + num_to_decbuf(score, 5, text_buf); // Score + decbuf_to_ascii(5, text_buf); + vdp_print_string(0, SCORE_TEXT_X, SCORE_TEXT_Y, (char*)text_buf); + + vdp_draw_joystick(JS_POS_CENTER); // Center the joystick + + vdp_redraw_tiles(get_front_grid()); + + uint16_t lfsr; + uint16_t current_run = 0; + + __enable_interrupts(); + while(1) { // Game loop + lfsr = lfsr_update(); + + // Any key will let us out of this, wait some time for a keypress + __disable_interrupts(); + if(read_any_key()) { + snd_mod_button(); + done = 1; + } + + if(!done && current_run <= RUNS_TO_SKIP) { + current_run++; + __enable_interrupts(); + continue; + } + + __disable_interrupts(); + current_run = 0; + + if(!done) { + switch((lfsr & 0x0003) + 1) { + case K_UP: + SND_TAP(); + vdp_draw_joystick(JS_POS_UP); + done = step_game(GAME_STEP_UP); + break; + case K_DOWN: + SND_TAP(); + vdp_draw_joystick(JS_POS_DOWN); + done = step_game(GAME_STEP_DOWN); + break; + case K_LEFT: + SND_TAP(); + vdp_draw_joystick(JS_POS_LEFT); + done = step_game(GAME_STEP_LEFT); + break; + case K_RIGHT: + SND_TAP(); + vdp_draw_joystick(JS_POS_RIGHT); + done = step_game(GAME_STEP_RIGHT); + break; + default: + __enable_interrupts(); + continue; // Do nothing, loop again + } + } + + + // Increase the count of moves we made (unless we lost or reset the game) + if(done >= 0) { + moves_count++; + done = done || (moves_count >= MAX_DEMO_MOVES); + } + + // Draw the number of moves + num_to_decbuf(moves_count, 5, text_buf); + decbuf_to_ascii(5, text_buf); + vdp_print_string(0, MOVES_TEXT_X, MOVES_TEXT_Y, (char*)text_buf); + + // Draw the moved tiles + vdp_redraw_tiles(get_front_grid()); + + // If we have won, or we got a reset request, break out of this loop + if(done) { + num_to_decbuf(score, 5, text_buf); // Score + decbuf_to_ascii(5, text_buf); + vdp_print_string(0, SCORE_TEXT_X, SCORE_TEXT_Y, (char*)text_buf); + + 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 + num_to_decbuf(score, 5, text_buf); // Score + decbuf_to_ascii(5, text_buf); + vdp_print_string(0, SCORE_TEXT_X, SCORE_TEXT_Y, (char*)text_buf); + vdp_redraw_tiles(get_front_grid()); + vdp_draw_joystick(JS_POS_CENTER); + + __enable_interrupts(); + } + + __disable_interrupts(); + + // Always go back to the start dialog + dld->mode = DLOG_MODE_START; + dld->score = 0; + + vdp_redraw_tiles(get_front_grid()); + + return; +} diff --git a/src/vddlg_main.c b/src/vddlg_main.c new file mode 100644 index 0000000..905f997 --- /dev/null +++ b/src/vddlg_main.c @@ -0,0 +1,96 @@ +#include +#include + +#include + +#include "vdp_utils.h" +#include "game_vdp_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" +#include "module_list.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; +static uint8_t score_buf[7] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +static char game_ver[5] = {'V', GAME_VER_CH0, GAME_VER_CH1, GAME_VER_CH2, '\0'}; + +#define WAIT_COUNTER_END 0xFFFF + +void main(void) { + uint16_t wait_counter = 0; + dlog_data *dld = (dlog_data *)(shared_page->module_data); + game_data *gad = (game_data *)(shared_page->module_data); + + __disable_interrupts(); // Make sure the interrupts are disabled + + num_to_decbuf(state_page->hi_score, 6, score_buf); + decbuf_to_ascii(6, score_buf); + + vdp_hide_sprites(); + vdp_clear_dialog(); + vdp_switch_nt(1); // Make sure VDP shows the correct dialog nametable + + vdp_print_string(1, 6, 10, "Current High-Score :"); + vdp_print_string(1, 13, 12, (char*)score_buf); + vdp_print_string(1, 5, 15, "Press any key to START"); + vdp_print_string(1, 1, 23, "hkz@social.chinwag.org"); + vdp_print_string(1, 28, 23, game_ver); + + switch(dld->mode) { + case DLOG_MODE_WIN: + snd_festive(); + vdp_print_string(1, 11, 8, "YOU WIN!!!"); + break; + case DLOG_MODE_LOSE: + snd_sad_scale(); + vdp_print_string(1, 11, 8, "GAME OVER!"); + break; + default: + case DLOG_MODE_START: + vdp_print_string(1, 7, 8, "WELCOME TO TK2048!"); + snd_start(); + break; + }; + + if(dld->score > state_page->hi_score) { // New high-score. We need to save it. + state_page->hi_score = dld->score; + + // Update the score! + num_to_decbuf(state_page->hi_score, 6, score_buf); + decbuf_to_ascii(6, score_buf); + vdp_print_string(1, 13, 12, (char*)score_buf); + vdp_print_string(1, 8, 13, "!NEW HIGH SCORE!"); + + shared_page->master_command = MASTER_COMMAND_SAVE; + } else { + shared_page->master_command = MASTER_COMMAND_NONE; + } + + shared_page->next_module_idx = MODULE_GAME_VDP; // Go to the GAME module + gad->mode = GAME_MODE_NORMAL; // Set the proper start mode for the game + + while(!read_any_key() && (wait_counter != WAIT_COUNTER_END)) { + wait_counter++; + lfsr_update(); + } + + if (wait_counter == WAIT_COUNTER_END) { + shared_page->next_module_idx = MODULE_DEMO_VDP; // Actually go to the DEMO module + return; + } + + snd_mod_button(); + + return; +} diff --git a/src/vdgam_main.c b/src/vdgam_main.c new file mode 100644 index 0000000..9ae1d1d --- /dev/null +++ b/src/vdgam_main.c @@ -0,0 +1,198 @@ +#include +#include + +#include + +#include "vdp_utils.h" +#include "game_vdp_graphics.h" + +#include "monitor_subroutines.h" +#include "utility.h" +#include "mem_map.h" +#include "mem_registers.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 "monitor_subroutines.h" +#include "sound.h" +#include "module_list.h" + +// External initialization requirements +#pragma require __preserve_zp +#pragma require __data_initialization_needed + +#define HSCORE_TEXT_X 27 +#define HSCORE_TEXT_Y 13 + +#define SCORE_TEXT_X 27 +#define SCORE_TEXT_Y 4 + +#define MOVES_TEXT_X 27 +#define MOVES_TEXT_Y 8 + +#define BOTTOM_TEXT_X 1 +#define BOTTOM_TEXT_Y 23 + +#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; +static uint8_t text_buf[6] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + +void main(void) { + uint16_t moves_count = 0; + uint16_t score = 0; + int8_t done = 0; + + __disable_interrupts(); // Make sure the interrupts are disabled + + // 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 = MODULE_DLOG_VDP; // Go to the dialog 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 + vdp_hide_sprites(); + vdp_clear_gamegrid(); + vdp_switch_nt(0); // Make sure VDP shows the gamegrid + + // Setup the IRQ handler + POKEW(IRQ_HANDLER_ADDRESS, (uint16_t)vdp_irq_handler); + + // 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 + vdp_print_string(0, BOTTOM_TEXT_X, BOTTOM_TEXT_Y, "hkz@social.chinwag.org 2025"); + + num_to_decbuf(state_page->hi_score, 5, text_buf); // High score + decbuf_to_ascii(5, text_buf); + vdp_print_string(0, HSCORE_TEXT_X, HSCORE_TEXT_Y, (char*)text_buf); + + num_to_decbuf(moves_count, 5, text_buf); // Moves count + decbuf_to_ascii(5, text_buf); + vdp_print_string(0, MOVES_TEXT_X, MOVES_TEXT_Y, (char*)text_buf); + + num_to_decbuf(score, 5, text_buf); // Score + decbuf_to_ascii(5, text_buf); + vdp_print_string(0, SCORE_TEXT_X, SCORE_TEXT_Y, (char*)text_buf); + + vdp_draw_joystick(JS_POS_CENTER); // Center the joystick + + vdp_redraw_tiles(get_front_grid()); + + __enable_interrupts(); + + while(1) { // Game loop + lfsr_update(); + + __disable_interrupts(); + switch(read_kb()) { + case K_UP: + SND_TAP(); + vdp_draw_joystick(JS_POS_UP); + done = step_game(GAME_STEP_UP); + break; + case K_DOWN: + SND_TAP(); + vdp_draw_joystick(JS_POS_DOWN); + done = step_game(GAME_STEP_DOWN); + break; + case K_LEFT: + SND_TAP(); + vdp_draw_joystick(JS_POS_LEFT); + done = step_game(GAME_STEP_LEFT); + break; + case K_RIGHT: + SND_TAP(); + vdp_draw_joystick(JS_POS_RIGHT); + done = step_game(GAME_STEP_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(); + + shared_page->next_module_idx = MODULE_GAME_VDP; + gad->mode = GAME_MODE_LOAD; + return; + default: + __enable_interrupts(); + continue; // Do nothing, loop again + } + + // Increase the count of moves we made (unless we lost or reset the game) + if(done >= 0) moves_count++; + + num_to_decbuf(moves_count, 5, text_buf); // Moves count + decbuf_to_ascii(5, text_buf); + vdp_print_string(0, MOVES_TEXT_X, MOVES_TEXT_Y, (char*)text_buf); + + // If we have won, or we got a reset request, break out of this loop + if(done) { + score += (done > 0) ? WIN_SCORE_BONUS : 0; + + num_to_decbuf(score, 5, text_buf); // Score + decbuf_to_ascii(5, text_buf); + vdp_print_string(0, SCORE_TEXT_X, SCORE_TEXT_Y, (char*)text_buf); + + 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 + num_to_decbuf(score, 5, text_buf); // Score + decbuf_to_ascii(5, text_buf); + vdp_print_string(0, SCORE_TEXT_X, SCORE_TEXT_Y, (char*)text_buf); + vdp_redraw_tiles(get_front_grid()); + vdp_draw_joystick(JS_POS_CENTER); + + __enable_interrupts(); + } + + dld->mode = (done > 0) ? DLOG_MODE_WIN : DLOG_MODE_LOSE; + dld->score = score; + + vdp_redraw_tiles(get_front_grid()); + + WAIT(0xFF); + + __disable_interrupts(); + + return; +} diff --git a/src/vdp.s b/src/vdp.s new file mode 100644 index 0000000..fa2a681 --- /dev/null +++ b/src/vdp.s @@ -0,0 +1,10 @@ + .rtmodel version,"1" + .rtmodel core,"6502" + +;;; IO Ports +SLOT_OFFSET:.equ 0x40 +VDP_BASE: .equ 0xC080 + SLOT_OFFSET +VDP_MEM: .equ VDP_BASE + 0x00 +VDP_REG: .equ VDP_BASE + 0x01 + + .public VDP_MEM, VDP_REG \ No newline at end of file diff --git a/src/vdp_init.h b/src/vdp_init.h new file mode 100644 index 0000000..09f0e84 --- /dev/null +++ b/src/vdp_init.h @@ -0,0 +1,8 @@ +#ifndef _VDP_INIT_HEADER_ +#define _VDP_INIT_HEADER_ + +#include + +uint8_t vdp_init(void); + +#endif /* _VDP_INIT_HEADER_ */ diff --git a/src/vdp_init.s b/src/vdp_init.s new file mode 100644 index 0000000..f7dc3a1 --- /dev/null +++ b/src/vdp_init.s @@ -0,0 +1,161 @@ + .rtmodel version,"1" + .rtmodel core,"6502" + + .extern _Zp + .extern vdp_write_vram, vdp_write_registers + .extern SpriteAttributeTable, NT_P0, NT_P1 + .extern VDP_MEM, VDP_REG + + .section code,text + +;;; vdp_init: +;;; Initialize the VDP processor +;;; Parameters: none +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, Y, X +;;; - Zp 1, 2, 3, 4, 5 +;;; +vdp_init: +T_SCRATCH_L$:.equ _Zp+0 +T_SCRATCH_H$:.equ _Zp+1 + + ; Get the board ready to accept the rest of the commands + lda #0x80 ; Disable interrupts and video, set 16K mode + sta VDP_REG + lda #0x81 + sta VDP_REG + + ; Load the pattern table int 0x0800 + lda #.byte0 PatternTable_Charset + sta zp:_Zp+0 + lda #.byte1 PatternTable_Charset + sta zp:_Zp+1 + lda #0x00 + sta zp:_Zp+2 + lda #0x08 + sta zp:_Zp+3 + lda #0x00 + sta zp:_Zp+4 + lda #0x08 + sta zp:_Zp+5 + jsr vdp_write_vram + + ; Load the Color Table into 0x0380 + lda #.byte0 ColorTable + sta zp:_Zp+0 + lda #.byte1 ColorTable + sta zp:_Zp+1 + lda #0x20 + sta zp:_Zp+2 + lda #0x00 + sta zp:_Zp+3 + lda #0x80 + sta zp:_Zp+4 + lda #0x03 + sta zp:_Zp+5 + jsr vdp_write_vram + + ; Load the Sprite attribute table at 0x0300 + lda #.byte0 SpriteAttributeTable + sta zp:_Zp+0 + lda #.byte1 SpriteAttributeTable + sta zp:_Zp+1 + lda #0x80 + sta zp:_Zp+2 + lda #0x00 + sta zp:_Zp+3 + lda #0x00 + sta zp:_Zp+4 + lda #0x03 + sta zp:_Zp+5 + jsr vdp_write_vram + + ; Load the Name Table for the Game Board at 0x0000 + lda #.byte0 NameTable_GameBoard + sta zp:_Zp+0 + lda #.byte1 NameTable_GameBoard + sta zp:_Zp+1 + lda #0x00 + sta zp:_Zp+2 + lda #0x03 + sta zp:_Zp+3 + lda #0x00 + sta zp:_Zp+4 + lda NT_P0 + sta zp:_Zp+5 + jsr vdp_write_vram + + ; Load the Name Table for the Dialog Screens at 0x1400 + lda #.byte0 NameTable_Dialog + sta zp:_Zp+0 + lda #.byte1 NameTable_Dialog + sta zp:_Zp+1 + lda #0x00 + sta zp:_Zp+2 + lda #0x03 + sta zp:_Zp+3 + lda #0x00 + sta zp:_Zp+4 + lda NT_P1 + sta zp:_Zp+5 + jsr vdp_write_vram + + ; Load the Sprite Pattern table at 0x1000 + lda #.byte0 SpritePatternTable + sta zp:_Zp+0 + lda #.byte1 SpritePatternTable + sta zp:_Zp+1 + lda #0x80 + sta zp:_Zp+2 + lda #0x01 + sta zp:_Zp+3 + lda #0x00 + sta zp:_Zp+4 + lda #0x10 + sta zp:_Zp+5 + jsr vdp_write_vram + + ; Write the register sequence + lda #.byte0 VDPRegs_M0 + sta zp:_Zp+0 + lda #.byte1 VDPRegs_M0 + sta zp:_Zp+1 + jsr vdp_write_registers + + rts + + .section data,data + +PatternTable_Charset: + .incbin "..\\data\\vdp_charset.bin" + +NameTable_GameBoard: + .incbin "..\\data\\vdp_nt_board.bin" + +NameTable_Dialog: + .incbin "..\\data\\vdp_nt_dialog.bin" + +SpritePatternTable: + .incbin "..\\data\\vdp_sprite_tiles.bin" + +ColorTable: + .incbin "..\\data\\vdp_colortable.bin" + +VDPRegs_M0: + .byte 0x00, 0x80 ; Reg. 0, Disable external video, set M3 to 0 (we'll use mode 0/Graphics I) + .byte 0xE3, 0x81 ; Reg. 1, Enable display and interrupts, 16x16 sprites and magnification + .byte 0x05, 0x82 ; Reg. 2, Place Name Table at 0x1400 (Load the Dialog) + .byte 0x0E, 0x83 ; Reg. 3, Place Color Table at 0x0380 + .byte 0x01, 0x84 ; Reg. 4, Place Pattern Table at 0x0800 + .byte 0x06, 0x85 ; Reg. 5, Place Sprite Attribute Table at 0x0300, the secondary will live at 0x1200 + .byte 0x02, 0x86 ; Reg. 6, Place Sprite Pattern Table at 0x1000 + .byte 0xF1, 0x87 ; Reg. 7, Set foreground and background color (white on black) + + ;;; + ;;; + ;;; Exported symbols + .public vdp_init + \ No newline at end of file diff --git a/src/vdp_utils.h b/src/vdp_utils.h new file mode 100644 index 0000000..a033dcc --- /dev/null +++ b/src/vdp_utils.h @@ -0,0 +1,20 @@ +#ifndef _VDP_UTILS_HEADER_ +#define _VDP_UTILS_HEADER_ + +#include + +void vdp_write_vram(uint8_t *src, uint16_t len, uint16_t vram_dest); +void vdp_fill_vram(uint8_t val, uint16_t len, uint16_t vram_dest); +uint8_t vdp_detect(void); +void vdp_init_registers(uint8_t *reg_array); +void vdp_hide_sprites(void); +void vdp_hide_sprite(uint8_t sprite_number); +void vdp_show_sprite(uint8_t sprite_number); +void vdp_set_sprite_tile(uint8_t sprite_number, uint8_t tile_idx); +void vdp_switch_nt(uint8_t nt_idx); +void vdp_print_string(uint8_t nt_idx, uint8_t x, uint8_t y, char *str); +void vdp_set_tile(uint8_t nt_idx, uint8_t x, uint8_t y, uint8_t tile_idx); +void vdp_write_interleaved_sat(void); +void vdp_irq_handler(void); + +#endif /* _VDP_UTILS_HEADER_ */ diff --git a/src/vdp_utils.s b/src/vdp_utils.s new file mode 100644 index 0000000..69b37c4 --- /dev/null +++ b/src/vdp_utils.s @@ -0,0 +1,665 @@ + .rtmodel version,"1" + .rtmodel core,"6502" + + .extern _Zp + .extern VDP_MEM, VDP_REG + + ;;; Define a space + .section intrzp, noinit, root ; root, as we always require it + .space 0xA9 + + .section code,text + + +;;; vdp_point_to_vram_xy: +;;; Readies the VDP registers to write on a tile at XY +;;; Parameters: +;;; - nametable index in vram [_Zp[4]] +;;; - X coordinate [_Zp[0]] +;;; - Y coordinate [_Zp[1]] +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, Y, X +;;; - Zp 5, 6 +;;; +vdp_point_to_vram_xy: +T_VADD_H$: .equ _Zp+6 +T_VADD_L$: .equ _Zp+5 +P_NT_IDX$: .equ _Zp+4 +P_Y$: .equ _Zp+1 +P_X$: .equ _Zp+0 + + lda #0x00 + sta zp:T_VADD_H$ + lda zp:P_Y$ + sta zp:T_VADD_L$ + + ;; Shift Y five times to the left to get the offset for the ROW in VRAM + ldx #5 +ShiftOffset$: + asl zp:T_VADD_L$ + rol zp:T_VADD_H$ + dex + bne ShiftOffset$ + + clc + + ; Calculate the VRAM address + lda zp:P_X$ + adc zp:T_VADD_L$ + sta zp:T_VADD_L$ + ldx zp:P_NT_IDX$ + lda NameTablesList,x + adc zp:T_VADD_H$ + sta zp:T_VADD_H$ + + ; Setup the VDP to write into VRAM + lda zp:T_VADD_L$ + sta VDP_REG + nop + lda zp:T_VADD_H$ + ora #0x40 + sta VDP_REG + nop + + rts + +;;; vdp_set_tile: +;;; Prints the provided ASCII string at the specified coordinates +;;; Parameters: +;;; - nametable index in vram [A] +;;; - X coordinate [_Zp[0]] +;;; - Y coordinate [_Zp[1]] +;;; - tile index [_Zp[2]] +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, Y, X +;;; - Zp 5, 6 +;;; +vdp_set_tile: +T_VADD_H$: .equ _Zp+6 +T_VADD_L$: .equ _Zp+5 +P_NT_IDX$: .equ _Zp+4 +P_TILE$: .equ _Zp+2 +P_Y$: .equ _Zp+1 +P_X$: .equ _Zp+0 + + sta zp:P_NT_IDX$ + + jsr vdp_point_to_vram_xy + + lda zp:P_TILE$ + sta VDP_MEM + nop + +EOS$: + rts + +;;; vdp_print_string: +;;; Prints the provided ASCII string at the specified coordinates (newlines and control chars are not supported) +;;; Parameters: +;;; - nametable index in vram [A] +;;; - X coordinate [_Zp[0]] +;;; - Y coordinate [_Zp[1]] +;;; - string address [_Zp[2], _Zp[3]]: Address of the beginning of data to write +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, Y, X +;;; - Zp 5, 6 +;;; +vdp_print_string: +T_VADD_H$: .equ _Zp+6 +T_VADD_L$: .equ _Zp+5 +P_NT_IDX$: .equ _Zp+4 +P_DATA_H$: .equ _Zp+3 +P_DATA_L$: .equ _Zp+2 +P_Y$: .equ _Zp+1 +P_X$: .equ _Zp+0 + + sta zp:P_NT_IDX$ + + jsr vdp_point_to_vram_xy + + ldy #0x00 +NextChar$: + lda (zp:P_DATA_L$),y + beq EOS$ + sec + sbc #0x20 + sta VDP_MEM + nop + nop + iny + bne NextChar$ + +EOS$: + rts + +;;; vdp_write_vram: +;;; Write the provided data into VRAM at specified address +;;; Parameters: +;;; - data address [_Zp[0], _Zp[1]]: Address of the beginning of data to write +;;; - data length [_Zp[2], _Zp[3]]: Length of data to write +;;; - VRAM address [_Zp[4], _Zp[5]]: Start address of data in VRAM +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, Y, X +;;; - Zp 1, 2, 3 +;;; +vdp_write_vram: +P_VADD_H$: .equ _Zp+5 +P_VADD_L$: .equ _Zp+4 +P_DLEN_H$: .equ _Zp+3 +P_DLEN_L$: .equ _Zp+2 +P_DATA_H$: .equ _Zp+1 +P_DATA_L$: .equ _Zp+0 + + ; Decrement length by one, so or the loop would do an off-by-one write + sec + lda zp:P_DLEN_L$ + sbc #1 + sta zp:P_DLEN_L$ + lda zp:P_DLEN_H$ + sbc #0 + sta zp:P_DLEN_H$ + + ; Setup the VDP to write into VRAM + lda zp:P_VADD_L$ + sta VDP_REG + lda zp:P_VADD_H$ + ora #0x40 + sta VDP_REG + + ; Actually write data into VRAM + ldx zp:P_DLEN_H$ + ldy #0x00 +CopyLoop$: + lda (zp:P_DATA_L$),y + sta VDP_MEM + nop + iny + bne SkipHIncr$ ; Check if we did overflow. In case, increment the high byte of the address + inc zp:P_DATA_H$ +SkipHIncr$: + dec zp:P_DLEN_L$ + lda zp:P_DLEN_L$ + cmp #0xFF + bne CopyLoop$ + dex + bpl CopyLoop$ + + rts + +;;; vdp_fill_vram: +;;; Fill VRAM with a specific byte +;;; Parameters: +;;; - fill value [A] +;;; - data length [_Zp[0], _Zp[1]]: Length of data to write +;;; - VRAM address [_Zp[2], _Zp[3]]: Start address of data in VRAM +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, Y, X +;;; - Zp 0, 1, 2, 4 +;;; +vdp_fill_vram: +T_FILLVAL$: .equ _Zp+4 +P_VADD_H$: .equ _Zp+3 +P_VADD_L$: .equ _Zp+2 +P_DLEN_H$: .equ _Zp+1 +P_DLEN_L$: .equ _Zp+0 + + sta zp:T_FILLVAL$ + + ; Decrement length by one, so or the loop would do an off-by-one write + sec + lda zp:P_DLEN_L$ + sbc #1 + sta zp:P_DLEN_L$ + lda zp:P_DLEN_H$ + sbc #0 + sta zp:P_DLEN_H$ + + ; Setup the VDP to write into VRAM + lda zp:P_VADD_L$ + sta VDP_REG + lda zp:P_VADD_H$ + ora #0x40 + sta VDP_REG + + ; Fill the VRAM + ldx zp:P_DLEN_H$ +CopyLoop$: + lda zp:T_FILLVAL$ + sta VDP_MEM + nop + + dec zp:P_DLEN_L$ + lda zp:P_DLEN_L$ + cmp #0xFF + bne CopyLoop$ + dex + bpl CopyLoop$ + + rts + +;;; vdp_detect: +;;; Check if a VDP is present +;;; Parameters: none +;;; +;;; Returns: 0xFF in A if VDP is present, 0 otherwise +;;; +;;; Clobbers: +;;; - A +;;; +vdp_detect: + lda #0x00 + sta VDP_REG + nop + lda #0x40 + sta VDP_REG + nop + + lda #0x55 + sta VDP_MEM + nop + lda #0xAA + sta VDP_MEM + nop + + lda #0x00 + sta VDP_REG + nop + sta VDP_REG + nop + + eor VDP_MEM + nop + eor VDP_MEM + nop + + cmp #0xFF + beq VdpFound$ + lda #0x00 +VdpFound$: + rts + +;;; vdp_hide_sprites: +;;; Marks all sprites as hidden +;;; Parameters: none +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, Y, X +;;; - Zp 1, 2, 3, 4, 5, 6, 7 +;;; +vdp_hide_sprites: + ldy #0 +HideLoop$: + tya + + jsr vdp_hide_sprite + + iny + cpy #25 + bne HideLoop$ + + ; Make sure the table gets updated in memory + jsr vdp_write_interleaved_sat + + rts + +;;; vdp_hide_sprite: +;;; Mark a single sprite as hidden +;;; Parameters: +;;; - sprite number [A] +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, Y, X +;;; +vdp_hide_sprite: + tay + ldx SAT_RowCol_Trans_Table,y + lda #0xC0 + sta SpriteAttributeTable,x + rts + +;;; vdp_show_sprite: +;;; Enable a single sprite in the game grid +;;; Parameters: +;;; - sprite number [A] +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, Y, X +;;; +vdp_show_sprite: + tay + ldx SAT_RowCol_Trans_Table,y + lda SAT_Y_Map,y + sta SpriteAttributeTable,x + rts + +;;; vdp_set_sprite_tile: +;;; Set the current tile index for a specific sprite +;;; Parameters: +;;; - sprite number [A] +;;; - tile index [_Zp [0]] +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, Y, X +;;; +vdp_set_sprite_tile: +P_TILE_IDX$:.equ _Zp+0 + asl zp:P_TILE_IDX$ ; We're using 2x2 tile sprites + asl zp:P_TILE_IDX$ + + tay + ldx SAT_RowCol_Trans_Table,y + lda zp:P_TILE_IDX$ + sta SpriteAttributeTable+2,x + rts + +;;; vdp_switch_nt: +;;; Switch shown nametable +;;; Parameters: +;;; - name table index [A] +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, X +;;; +vdp_switch_nt: + tax + lda NameTablesList,x + lsr a + lsr a + sta VDP_REG + nop + lda #0x82 + sta VDP_REG + nop + rts + +;;; vdp_write_registers: +;;; Initialize the registers of the VDP, using the provided byte array +;;; Parameters: +;;; - data address [_Zp[0], _Zp[1]]: Address of the beginning of initialization data, a 16 byte array +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, Y +;;; +vdp_write_registers: +P_REG_H$: .equ _Zp+1 +P_REG_L$: .equ _Zp+0 + + ; Write the register sequence + ldy #0x00 +RegLoop$: + lda (zp:P_REG_L$),y + sta VDP_REG + nop + iny + cpy #16 + bne RegLoop$ + + rts + +;;; vdp_write_interleaved_sat: +;;; Updates SAT at 0x300 with sprite multiplexing +;;; Parameters: none +;;; +;;; Returns: nothing +;;; +;;; Clobbers: +;;; - A, Y, X +;;; - Zp 1, 2, 3, 4, 5, 6, 7 +;;; +vdp_write_interleaved_sat: +T_COUNT$: .equ _Zp+7 +T_SATB_IDX$: .equ _Zp+6 +T_DEST_H$: .equ _Zp+5 +T_DEST_L$: .equ _Zp+4 +T_DLEN_H$: .equ _Zp+3 +T_DLEN_L$: .equ _Zp+2 +T_DATA_H$: .equ _Zp+1 +T_DATA_L$: .equ _Zp+0 + + lda #0x00 + sta zp:T_DEST_L$ + lda #0x03 + sta zp:T_DEST_H$ + + lda CurrentByteTableOffset + cmp #5 + bne NoOverflow$ + lda #0 + sta CurrentByteTableOffset +NoOverflow$: + asl a + sta zp:T_SATB_IDX$ + + lda #4 + sta zp:T_COUNT$ + +WriteLoop$: + lda #20 + sta zp:T_DLEN_L$ + lda #00 + sta zp:T_DLEN_H$ + + lda zp:T_SATB_IDX$ + tax + + lda SAT_Block_Table,x + sta zp:T_DATA_L$ + lda SAT_Block_Table+1,x + sta zp:T_DATA_H$ + + jsr vdp_write_vram + + clc + lda zp:T_DEST_L$ + adc #20 + sta zp:T_DEST_L$ + + lda zp:T_SATB_IDX$ + adc #0x02 + sta zp:T_SATB_IDX$ + dec zp:T_COUNT$ + bpl WriteLoop$ + + inc CurrentByteTableOffset + + rts + +_irq_save_zp: + ldx #0 + +SaveZp$: + lda zp:_Zp,x + sta .sectionStart intrzp,x + inx + ;cpx #.sectionSize intrzp + cpx #10 ; Save only part of the ZP, we're not using more + bne SaveZp$ + + rts + +_irq_restore_zp: + ldx #0 + +RestoreZp$: + lda .sectionStart intrzp,x + sta zp:_Zp,x + inx + ;cpx #.sectionSize intrzp + cpx #10 ; Restpre only part of the ZP, we're not using more + bne RestoreZp$ + + rts + +vdp_irq_handler: + ; Save the registers + pha + txa + pha + tya + pha + + ;;; Save ZP + jsr _irq_save_zp + + jsr vdp_write_interleaved_sat + + ;;; Restore ZP + jsr _irq_restore_zp + + ;;; Clear the IRQ + lda VDP_REG + + ; Restore the registers + pla + tay + pla + tax + pla + + rti + +;;;;;;;;;;;;;;;;;; + .section data,data + +SpriteAttributeTable: + ; Organized in columns +SAT_Col1: + .byte 0xC0, 0x08, 0x30, 0x01 ; Col 1 + .byte 0xC0, 0x08, 0x30, 0x01 + .byte 0xC0, 0x08, 0x30, 0x01 + .byte 0xC0, 0x08, 0x30, 0x01 + .byte 0xC0, 0x08, 0x30, 0x01 +SAT_Col2: + .byte 0xC0, 0x30, 0x30, 0x01 ; Col 2 + .byte 0xC0, 0x30, 0x30, 0x01 + .byte 0xC0, 0x30, 0x30, 0x01 + .byte 0xC0, 0x30, 0x30, 0x01 + .byte 0xC0, 0x30, 0x30, 0x01 +SAT_Col3: + .byte 0xC0, 0x58, 0x30, 0x01 ; Col 3 + .byte 0xC0, 0x58, 0x30, 0x01 + .byte 0xC0, 0x58, 0x30, 0x01 + .byte 0xC0, 0x58, 0x30, 0x01 + .byte 0xC0, 0x58, 0x30, 0x01 +SAT_Col4: + .byte 0xC0, 0x80, 0x30, 0x01 ; Col 4 + .byte 0xC0, 0x80, 0x30, 0x01 + .byte 0xC0, 0x80, 0x30, 0x01 + .byte 0xC0, 0x80, 0x30, 0x01 + .byte 0xC0, 0x80, 0x30, 0x01 +SAT_Col5: + .byte 0xC0, 0xA8, 0x30, 0x01 ; Col 5 + .byte 0xC0, 0xA8, 0x30, 0x01 + .byte 0xC0, 0xA8, 0x30, 0x01 + .byte 0xC0, 0xA8, 0x30, 0x01 + .byte 0xC0, 0xA8, 0x30, 0x01 + ; Unused + .byte 0xD0, 0x00, 0x30, 0x00 + .byte 0xD0, 0x00, 0x30, 0x00 + .byte 0xD0, 0x00, 0x30, 0x00 + .byte 0xD0, 0x00, 0x30, 0x00 + .byte 0xD0, 0x00, 0x30, 0x00 + .byte 0xD0, 0x00, 0x30, 0x00 + .byte 0xD0, 0x00, 0x30, 0x00 + +SAT_RowCol_Trans_Table: + .byte 0, 20, 40, 60, 80 + .byte 4, 24, 44, 64, 84 + .byte 8, 28, 48, 68, 88 + .byte 12, 32, 52, 72, 92 + .byte 16, 36, 56, 76, 96 + +SAT_Y_Map: + .byte 0x07 + .byte 0x07 + .byte 0x07 + .byte 0x07 + .byte 0x07 + .byte 0x27 + .byte 0x27 + .byte 0x27 + .byte 0x27 + .byte 0x27 + .byte 0x47 + .byte 0x47 + .byte 0x47 + .byte 0x47 + .byte 0x47 + .byte 0x67 + .byte 0x67 + .byte 0x67 + .byte 0x67 + .byte 0x67 + .byte 0x87 + .byte 0x87 + .byte 0x87 + .byte 0x87 + .byte 0x87 + +SAT_Block_Table: + .word SAT_Col1 + .word SAT_Col2 + .word SAT_Col3 + .word SAT_Col4 + .word SAT_Col5 + .word SAT_Col1 + .word SAT_Col2 + .word SAT_Col3 + .word SAT_Col4 + .word SAT_Col5 + +; Contains a list of VRAM addresses corresponding to every page +NameTablesList: +NT_P0: .byte 0x00 +NT_P1: .byte 0x14 + + +CurrentByteTableOffset: + .byte 0x00 + + ;;; + ;;; + ;;; Exported symbols + .public vdp_write_vram + .public vdp_detect + .public vdp_write_registers + .public vdp_hide_sprites + .public vdp_hide_sprite + .public vdp_show_sprite + .public vdp_switch_nt + .public vdp_set_sprite_tile + .public vdp_print_string + .public vdp_set_tile + .public vdp_point_to_vram_xy + .public vdp_write_interleaved_sat + .public vdp_irq_handler + + .public SpriteAttributeTable ; We'll need to set change visibility and values + .public NT_P0, NT_P1 \ No newline at end of file diff --git a/src/vdpin_main.c b/src/vdpin_main.c new file mode 100644 index 0000000..4d58aa7 --- /dev/null +++ b/src/vdpin_main.c @@ -0,0 +1,34 @@ +#include +#include + +#include + +#include "vdp_init.h" +#include "vdp_utils.h" + +#include "monitor_subroutines.h" +#include "utility.h" +#include "mem_map.h" +#include "shared_page.h" + +#include "module_list.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) { + + __disable_interrupts(); // Make sure the interrupts are disabled + + vdp_init(); + + vdp_print_string(1, 6, 10, "Loading..."); + + shared_page->master_command = MASTER_COMMAND_NONE; + shared_page->next_module_idx = MODULE_DLOG_VDP; + + return; +}