From da50d8f30139296568e2deeae7c60f4e80c8d165 Mon Sep 17 00:00:00 2001 From: Daniel Covington Date: Thu, 28 May 2026 04:49:34 -0400 Subject: [PATCH] skirmish --- .claude/settings.local.json | 4 +- skirmish-game/IMPLEMENTATION.md | 118 ++++++ skirmish-game/Makefile | 30 ++ skirmish-game/README.md | 48 +++ skirmish-game/build.bat | 13 + skirmish-game/data.o | Bin 0 -> 4307 bytes skirmish-game/debug-vice.bat | 6 + skirmish-game/docs/C64-CHEATSHEET.md | 333 +++++++++++++++ skirmish-game/docs/GDD.md | 158 +++++++ skirmish-game/docs/TECHNICAL-PLAN.md | 300 +++++++++++++ skirmish-game/enemy-ai.o | Bin 0 -> 5677 bytes skirmish-game/game-logic.o | Bin 0 -> 6890 bytes skirmish-game/input.o | Bin 0 -> 2592 bytes skirmish-game/render.o | Bin 0 -> 10462 bytes skirmish-game/run-vice.bat | 6 + skirmish-game/skirmish.o | Bin 0 -> 3316 bytes skirmish-game/skirmish.prg | Bin 0 -> 2625 bytes skirmish-game/src/c64.cfg | 19 + skirmish-game/src/data.asm | 132 ++++++ skirmish-game/src/enemy-ai.asm | 303 +++++++++++++ skirmish-game/src/game-logic.asm | 391 +++++++++++++++++ skirmish-game/src/input.asm | 112 +++++ skirmish-game/src/render.asm | 613 +++++++++++++++++++++++++++ skirmish-game/src/skirmish.asm | 136 ++++++ skirmish-game/src/skirmish.inc | 40 ++ 25 files changed, 2761 insertions(+), 1 deletion(-) create mode 100644 skirmish-game/IMPLEMENTATION.md create mode 100644 skirmish-game/Makefile create mode 100644 skirmish-game/README.md create mode 100644 skirmish-game/build.bat create mode 100644 skirmish-game/data.o create mode 100644 skirmish-game/debug-vice.bat create mode 100644 skirmish-game/docs/C64-CHEATSHEET.md create mode 100644 skirmish-game/docs/GDD.md create mode 100644 skirmish-game/docs/TECHNICAL-PLAN.md create mode 100644 skirmish-game/enemy-ai.o create mode 100644 skirmish-game/game-logic.o create mode 100644 skirmish-game/input.o create mode 100644 skirmish-game/render.o create mode 100644 skirmish-game/run-vice.bat create mode 100644 skirmish-game/skirmish.o create mode 100644 skirmish-game/skirmish.prg create mode 100644 skirmish-game/src/c64.cfg create mode 100644 skirmish-game/src/data.asm create mode 100644 skirmish-game/src/enemy-ai.asm create mode 100644 skirmish-game/src/game-logic.asm create mode 100644 skirmish-game/src/input.asm create mode 100644 skirmish-game/src/render.asm create mode 100644 skirmish-game/src/skirmish.asm create mode 100644 skirmish-game/src/skirmish.inc diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 4984c7c..c973f6e 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -13,7 +13,9 @@ "Bash(ca65 \"c:/Development/Commodore/os Experiments/c64os/samples/kernal-os-skeleton/kernal_os.asm\" -o \"c:/Development/Commodore/os Experiments/c64os/samples/kernal-os-skeleton/kernal_os.o\")", "Bash(cmd /c build_all.bat)", "Bash(cmd /c \"build_all.bat && echo BUILD_OK || echo BUILD_FAILED\")", - "Bash(echo \"EXIT: $?\")" + "Bash(echo \"EXIT: $?\")", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\skirmish-game\\\\\" -Recurse -File | Select-Object FullName | ForEach-Object { $_.FullName })", + "PowerShell(Test-Path \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\skirmish-game\\\\skirmish.prg\" && \\(Get-Item \"c:\\\\Development\\\\Commodore\\\\os Experiments\\\\c64os\\\\skirmish-game\\\\skirmish.prg\"\\).Length)" ] } } diff --git a/skirmish-game/IMPLEMENTATION.md b/skirmish-game/IMPLEMENTATION.md new file mode 100644 index 0000000..41606a0 --- /dev/null +++ b/skirmish-game/IMPLEMENTATION.md @@ -0,0 +1,118 @@ +# Implementation Progress — Skirmish (C64) + +## Status: BUILDABLE MVP BASELINE + +Last updated: 2026-05-28 + +--- + +## What Exists Now + +### Project structure + +``` +skirmish-game/ +├── src/ +│ ├── skirmish.asm # Entry point, raster sync, shared state +│ ├── input.asm # Keyboard + joystick polling +│ ├── game-logic.asm # Turn flow, movement, combat, occupancy +│ ├── enemy-ai.asm # Closest-target AI and movement +│ ├── render.asm # Sprites, HUD, board markers +│ ├── data.asm # Tables, text, sprite bitmaps +│ ├── skirmish.inc # Shared constants +│ └── c64.cfg # ld65 memory layout +├── docs/ +│ ├── GDD.md +│ ├── TECHNICAL-PLAN.md +│ └── C64-CHEATSHEET.md +├── build.bat +├── run-vice.bat +├── debug-vice.bat +├── Makefile +├── README.md +└── skirmish.prg +``` + +### Core gameplay implemented + +- 8x8 tactical board with coordinate markers +- 3 player units and 3 enemy units +- Single-step turn flow: + - player selects a unit + - player moves or attacks + - enemy team takes its turn + - win/lose is checked +- Adjacent melee combat for both teams +- Unit HP tracking and death removal +- Greedy enemy AI using Manhattan distance +- Selected-unit highlight via sprite color +- Restart flow after win or loss + +### Controls implemented + +- `Q` or joystick fire: cycle selected player unit +- `W`, `A`, `S`, `D` or joystick directions: move or attack +- `E` or space: end turn +- `R`: restart after game over + +### Engine and rendering implemented + +- BASIC stub starts game with `RUN` -> `SYS 2061` +- Raster-driven frame sync installed through IRQ vector +- Main loop updates once per IRQ tick +- 6 hardware sprites used for units +- HUD shows current phase, turn count, selected unit, and all unit HP +- Sprite bitmaps and lookup tables are linked into C64-visible RAM +- Occupancy grid rebuilt from unit state to avoid stale collision state + +--- + +## Build Verification + +### Confirmed + +- `build.bat` assembles and links successfully with local `ca65` and `ld65` +- Output artifact currently builds as `skirmish.prg` + +### Current artifact + +- `skirmish.prg` size: 2617 bytes +- Most recent successful local build: 2026-05-28 + +### Not yet verified + +- Full gameplay smoke test inside VICE +- Sprite alignment and board readability in the live emulator +- Joystick port 2 behavior in VICE input mapping +- End-to-end win/lose flow by playtesting + +--- + +## Known Gaps + +### High priority + +1. Run the build in `x64sc` and verify the boot path, controls, and turn flow. +2. Confirm the raster-driven loop behaves correctly on a real VICE session. +3. Tune board readability if the current dot-grid is too sparse under sprites. + +### Medium priority + +1. Add attack and death feedback such as flash or simple SID beeps. +2. Add a stronger title/opening screen. +3. Add a clearer wait indicator and maybe a per-unit action prompt. + +### Low priority + +1. Add obstacles and richer pathing. +2. Add ranged attacks or special abilities. +3. Add difficulty scaling or multiple waves. + +--- + +## Notes + +- The previous tracker text described files that did not actually exist yet. +- The source tree and build scripts now exist and match this document. +- The PRG boot path was corrected so the BASIC stub jumps to the real contiguous load address. +- The next milestone should be emulator validation and gameplay polish, not project scaffolding. diff --git a/skirmish-game/Makefile b/skirmish-game/Makefile new file mode 100644 index 0000000..4f355a7 --- /dev/null +++ b/skirmish-game/Makefile @@ -0,0 +1,30 @@ +CA65 ?= ca65 +LD65 ?= ld65 + +OBJS = skirmish.o input.o game-logic.o enemy-ai.o render.o data.o + +all: skirmish.prg + +skirmish.prg: $(OBJS) + $(LD65) $(OBJS) -C src/c64.cfg -o $@ + +skirmish.o: src/skirmish.asm src/skirmish.inc + $(CA65) src/skirmish.asm -o $@ + +input.o: src/input.asm src/skirmish.inc + $(CA65) src/input.asm -o $@ + +game-logic.o: src/game-logic.asm src/skirmish.inc + $(CA65) src/game-logic.asm -o $@ + +enemy-ai.o: src/enemy-ai.asm src/skirmish.inc + $(CA65) src/enemy-ai.asm -o $@ + +render.o: src/render.asm src/skirmish.inc + $(CA65) src/render.asm -o $@ + +data.o: src/data.asm src/skirmish.inc + $(CA65) src/data.asm -o $@ + +clean: + rm -f $(OBJS) skirmish.prg diff --git a/skirmish-game/README.md b/skirmish-game/README.md new file mode 100644 index 0000000..84ab389 --- /dev/null +++ b/skirmish-game/README.md @@ -0,0 +1,48 @@ +# Skirmish + +`Skirmish` is a single-screen C64 tactical prototype built in 6510 assembly with `ca65`/`ld65`. + +## Controls + +- `Q` or joystick fire: cycle the selected player unit +- `W`, `A`, `S`, `D` or joystick directions: move or attack +- `E` or space: end your turn without moving +- `R`: restart after a win or loss + +## Build + +### Windows + +```bat +build.bat +``` + +### Cross-platform + +```bash +make +``` + +## Run + +### Normal + +```bat +run-vice.bat +``` + +If you load it manually in BASIC, use `RUN`. The built PRG contains a BASIC stub that executes `SYS 2061`. + +### With monitor + +```bat +debug-vice.bat +``` + +## Current MVP + +- 3 player units and 3 enemy units on an 8x8 board +- Keyboard and joystick input +- Single-step movement or adjacent melee attacks +- Greedy enemy AI using Manhattan distance +- Win/lose flow with restart diff --git a/skirmish-game/build.bat b/skirmish-game/build.bat new file mode 100644 index 0000000..108f196 --- /dev/null +++ b/skirmish-game/build.bat @@ -0,0 +1,13 @@ +@echo off +setlocal +cd /d "%~dp0" + +ca65 src\skirmish.asm -o skirmish.o || exit /b 1 +ca65 src\input.asm -o input.o || exit /b 1 +ca65 src\game-logic.asm -o game-logic.o || exit /b 1 +ca65 src\enemy-ai.asm -o enemy-ai.o || exit /b 1 +ca65 src\render.asm -o render.o || exit /b 1 +ca65 src\data.asm -o data.o || exit /b 1 +ld65 skirmish.o input.o game-logic.o enemy-ai.o render.o data.o -C src\c64.cfg -o skirmish.prg || exit /b 1 + +echo Built skirmish.prg diff --git a/skirmish-game/data.o b/skirmish-game/data.o new file mode 100644 index 0000000000000000000000000000000000000000..b60b0afe1757414f55a1f86edf60bfd8a2a92d06 GIT binary patch literal 4307 zcmaKuU2Ie58ONW;#PN3=@(xU!6AgAr)zx)SFG+&92e3Nl09*7n8zB07w8;;A7xZ{)TjZr0i4MAboxm_K z58UP%tX!}LIDsHA&AnK;;1l2h@E8d2Ijmf;2;2eg0e|LwUxw>w5IlSqVPqTl46247 z%H8^mpU=PW6!G)W%IAqafD32=e4AEVov;Kn^S4EKm8|Si+LbNJfU-^5z9nx`O-eg| zSH@{lTH2?&OBqnMDV<7}(yI4-A#rFz$}T=b><8L`0MG%rfF^(d2e--B4e$YWAOQG* z9h+GT6am|L93_HQzz%pfvkoW*w6$Tg!3X6Bb^zKPc!2;wKr_3c1a`gxbpn)rK?JW*PjIg3EB0&L@Lu!4JlgSW#9 z)&M`BfE7FfcJVB%;4v`7-_pUsb=54CMc)^UcQX^3xa@`Q?P;{(9H&l z1yx{(UnbrQtOJL+A6D>t;AQ#U5G(iqV2%>s2UY-itHBCX$*{yDfGW9DP%mu7Ha?6V zf_s32PZIwNSOffAz`6yGfNtJ}RSK%WAiuE{Rlb2`3A%wtyo?hG{J5R!}1{GE&NS+ciWPyQnSEw zB28Z9oB!0tA21)0v0I(STi2?W^G5Wm8wB^XDL&|r}R z5{w}@qQOVZrzA0ubZe5~hyOqkR3qr3&2-^II;nhECL4XyHMD3QakV>>ORmlRM0X9Tm)7$+=KSR@cW@cBB&4c zO+cP_*@Rk3>~W|CIY{=dL_M(Y0C@o0M!CxJk@q&yPN=V;Ap#}0i|9OT70`S6meoyc zKa^Vz8iU%6;vE2-ELJ{8ATvM*>=~$GAO^e&bfbq8H~`xPH49Y&_QA@gnCPppy-@PX zuy*9MgzQCRStt`Y0V_YXL|0(10{D8<>rerxH=r&9*EIw70P>o{;~*ag)P7|8ke`DZ zu}VbY=Zr>S+fjEK>V4p*-k+1AXb^q|mk=hzeq{9QfM}zgVPaf&p;`;gz1?%VP z>mN@Bd+UB`pkGc__hOA;uM=~_l2l;+E+o1T&xVpUxGjz8*9z)NCatBl`Kqo>RY%1v zRbYZTkTo}z*0gHFV@BR`DSP((Y-P4*wtT5GJ3G)HIMv&8=vd%jU_4(5WV45}eZg$A zRJ~rlm@i$*m*;!(3t4wM7D;8o@pLTZO+-U8hG`j(5i=%dEbTTX6DiR~H0ntip~;L@ z=vJ+D-efA2GHSWUGTKnn;Rz#r!e!-Ck(k?3(Rk8uMq-I{Dw9sQEioFOiq&M)7)yDq z)QpTzq-yb4#58I$W=wCSQ=v%8S1UGTcr)crhE2nWWz5i|D;$r;O(;(?VMZ|1l$msm z#6#w&EHPSBW_-$LWr@hN5zS0LZXr`DE=h;2Za(kL77N8vt!ayy&$}!6N+Fl2w!t7kGV4cYtxE}awCDwAh wR~q>TthfMYBZYh(NHS1Z6uj&fN-8R%y=R+ZZyQw(Wt|^)=8rO0U)NYWdHyG literal 0 HcmV?d00001 diff --git a/skirmish-game/debug-vice.bat b/skirmish-game/debug-vice.bat new file mode 100644 index 0000000..3650a15 --- /dev/null +++ b/skirmish-game/debug-vice.bat @@ -0,0 +1,6 @@ +@echo off +setlocal +cd /d "%~dp0" + +call build.bat || exit /b 1 +"C:\Program Files\GTK3VICE-3.10-win64\bin\x64sc.exe" -autostart skirmish.prg -autostart-warp -nativemonitor -keepmonopen -refreshonbreak diff --git a/skirmish-game/docs/C64-CHEATSHEET.md b/skirmish-game/docs/C64-CHEATSHEET.md new file mode 100644 index 0000000..01ff47a --- /dev/null +++ b/skirmish-game/docs/C64-CHEATSHEET.md @@ -0,0 +1,333 @@ +# C64 6502 Assembly Cheat Sheet + +--- + +## 1. Memory Map + +``` +$0000–$00FF Zero page (fast access, 2-byte instructions) +$0100–$01FF Stack (grows downward from $01FF) +$0200–$1FFF System/free area +$2000–$3FFF User RAM (40 KB available) +$D000–$DFFF I/O ports (VIC-II, SID, CIA1, CIA2) +$E000–$FFFF Kernal ROM (can be banked out for more RAM) +``` + +--- + +## 2. Key VIC-II Registers + +All addresses relative to $D000 (e.g., $D010 = $D000 + $10). + +### Sprite Coordinates + +``` +$D000–$D00F Sprite X positions (16 bytes, 2 per sprite) + $D000 = Sprite 0 X (low byte) + $D010 = X high bits (bit 0 = Sprite 0 X bit 8, etc.) + +$D001–$D00F Sprite Y positions (16 bytes, 2 per sprite) + $D001 = Sprite 0 Y + $D003 = Sprite 1 Y + ... (every other byte) +``` + +### Sprite Control + +``` +$D015 Sprite enable ($00 = all off, $3F = all 6 on) +$D017 Sprite Y expand (1 = double height) +$D01D Sprite X expand (1 = double width) +$D025 Sprite 0 color +$D026 Sprite 1 color +... (one byte per sprite) + +$D027 Sprite 0 multicolor (0–3 for multicolor mode) +$D028 Sprite 1 multicolor +... +$D01C Multicolor mode enable (bit = sprite index) +``` + +### Screen Control + +``` +$D011 VIC control register 1 + Bit 7: Raster interrupt request + Bit 6: Extended color text mode + Bit 5: Bitmap mode + Bit 4: Blank screen (1 = blank) + Bits 3–0: Scroll Y + +$D012 Raster line (read for current, write for interrupt trigger) + +$D016 VIC control register 2 (scroll X, multicolor, etc.) + +$D018 Memory control (screen addr, bitmap addr) + Default: $14 (screen at $0400, bitmap at $2000) +``` + +### Colors + +``` +$D020 Border color +$D021 Background color 0 +$D022 Background color 1 (multicolor mode) +$D023 Background color 2 (multicolor mode) +$D024 Background color 3 (multicolor mode) +``` + +--- + +## 3. CIA (Complex Interface Adapter) — Input + +### CIA1 ($DC00–$DC0F) + +``` +$DC00 Data direction register A (keyboard columns) +$DC01 Keyboard matrix input register + Bit 0 = Key Row + Bit 1 = Key Row, etc. + +$DC02 Data direction register B +$DC03 Joystick / paddle input + +$DC04–$DC05 Timer A (low/high) +$DC06–$DC07 Timer B (low/high) +$DC0E Control register A (timer, interrupt) +$DC0F Interrupt enable register +``` + +### Simple Joystick Read + +``` +LDA #$00 ; Set port B for input +STA $DC02 +LDA $DC01 ; Read joystick (port 2) or keyboard +; Bit pattern: 7=up, 6=down, 5=left, 4=right, 3=fire +``` + +--- + +## 4. Common 6502 Instructions + +### Load/Store + +```asm +LDA #$42 ; Load immediate +LDA $42 ; Load from zero page +LDA $4200 ; Load from absolute address +LDA $4200,X ; Indexed +LDA ($42,X) ; Indirect indexed +STA $42 ; Store +``` + +### Arithmetic + +```asm +ADC #$10 ; Add with carry +SBC #$10 ; Subtract with carry +INC $42 ; Increment memory +DEC $42 ; Decrement memory +INX, INY, DEX, DEY ; Increment/decrement registers +``` + +### Branching + +```asm +BNE $4000 ; Branch if not equal (after CMP, etc.) +BEQ $4000 ; Branch if equal +BCS $4000 ; Branch if carry set +BCC $4000 ; Branch if carry clear +BPL $4000 ; Branch if plus (MSB = 0) +BMI $4000 ; Branch if minus (MSB = 1) +JMP $4000 ; Unconditional jump +JSR $4000 ; Jump to subroutine (push return address) +RTS ; Return from subroutine +``` + +### Comparisons + +```asm +CMP #$10 ; Compare A with immediate (sets flags) +CMP $42 ; Compare A with memory +CPX #$10 ; Compare X +CPY #$10 ; Compare Y +; Flags: Z=zero (equal), C=carry (unsigned <), N=minus +``` + +### Bit Operations + +```asm +ASL ; Arithmetic shift left (A or M) +LSR ; Logical shift right +ROL ; Rotate left +ROR ; Rotate right +AND #$0F ; Bitwise AND +ORA #$F0 ; Bitwise OR +EOR #$FF ; Bitwise XOR +BIT $42 ; Test bits (sets N, V, Z) +``` + +### Stack + +```asm +PHA ; Push A +PLA ; Pull A +PHP ; Push status +PLP ; Pull status +``` + +--- + +## 5. Interrupt Handling + +### Setup IRQ Handler + +```asm +; In initialization: +LDA #IRQ_HANDLER ; High byte +STA $0315 + +CLI ; Clear interrupt disable flag +``` + +### IRQ Handler Structure + +```asm +IRQ_HANDLER: + PHA ; Save registers + TXA + PHA + TYA + PHA + + ; Check if it's our interrupt (raster or timer) + LDA $D019 ; VIC interrupt status + BIT #$01 ; Test raster interrupt + BEQ NOT_VIC + + ; Handle VIC/raster interrupt + ; (your game logic here) + + LDA #$01 ; Acknowledge interrupt + STA $D019 + +NOT_VIC: + PLA ; Restore registers + TAY + PLA + TAX + PLA + RTI +``` + +--- + +## 6. Sprite Data Format + +C64 sprites are 24 pixels wide × 21 pixels high, stored as 63 bytes (3 bytes/line). + +**Location:** Default at $3000–$3FFF (can be changed via $D018) + +**Format:** +``` +Bytes 0–2: Line 0 (pixel data) +Bytes 3–5: Line 1 +... +Bytes 60–62: Line 20 +``` + +Each 3-byte line = 24 pixels (bit pattern). + +**Example: Simple square** +``` +$38 $38 $00 (11000000 11000000 00000000 = two vertical bars) +$38 $38 $00 +... +``` + +--- + +## 7. Useful Zero-Page Patterns + +```asm +; Temporary registers (safe to overwrite) +TEMP0 = $F0 +TEMP1 = $F1 +TEMP2 = $F2 +TEMP3 = $F3 + +; Loop counters +LOOP_X = $F4 +LOOP_Y = $F5 +LOOP_C = $F6 + +; Pointers (16-bit address) +PTR0 = $F8 ; PTR0_L = $F8, PTR0_H = $F9 +PTR1 = $FA +``` + +--- + +## 8. Quick Patterns + +### Wait for Raster (Sync to VIC) + +```asm +WAIT_RASTER: + LDA # Player input → select/move/attack + 1 => Animate move, resolve collision/damage + 2 => Enemy AI, animate enemy moves + 3 => Check win/lose, advance state + + ; 3. Render sprites & text + JSR SPRITE_UPDATE + JSR TEXT_UPDATE + + Restore registers + RTI +``` + +--- + +## 6. Subroutine Checklist + +### Input & Selection + +| Name | In | Out | Cost | +|------|----|----|------| +| `INPUT_READ` | — | `$30` (key code) | 10 cyc | +| `UNIT_SELECT` | `$30` (key) | `$32` (unit) | 20 cyc | +| `UNIT_MOVE` | `$32`, `$58`, `$59` | `$5A` (valid) | 40 cyc | +| `UNIT_ATTACK` | `$32`, target unit | HP update | 30 cyc | + +### Game Logic + +| Name | In | Out | Cost | +|------|----|----|------| +| `FIND_CLOSEST_ENEMY` | `$32` (player unit) | `$F0` (enemy idx) | 50 cyc | +| `ENEMY_MOVE` | `$F0` (enemy idx) | Update unit pos | 80 cyc | +| `ENEMY_AI` | — | Execute all enemies | 300 cyc | +| `CHECK_WIN` | — | Set `GAME_STATE` | 20 cyc | + +### Rendering + +| Name | In | Out | Cost | +|------|----|----|------| +| `SPRITE_UPDATE` | Unit state | VIC-II regs | 100 cyc | +| `TEXT_UPDATE` | Game state | Screen RAM | 50 cyc | +| `REBUILD_OCCUPANCY` | Unit state | Occupancy array | 60 cyc | + +--- + +## 7. Key Algorithms + +### Manhattan Distance + +```asm +; Input: X in $F0, Y in $F1 (target); A (own X), $F2 (own Y) +; Output: A (distance) +MANHATTAN: + SEC + SBC $F0 ; A = own_x - target_x + BPL + ; If positive, skip + EOR #$FF + ADC #$01 ; Negate (two's complement) ++ STA $F3 ; Save |dx| + + LDA $F2 + SEC + SBC $F1 ; A = own_y - target_y + BPL + ; If positive, skip + EOR #$FF + ADC #$01 ; Negate ++ CLC + ADC $F3 ; A = |dx| + |dy| + RTS +``` + +### Grid Occupancy Check + +```asm +; Input: X in $F0, Y in $F1 +; Output: A = unit index (or $FF if empty) +GRID_OCCUPIED: + LDA $F1 + ASL + ASL + ASL ; A = Y * 8 + CLC + ADC $F0 ; A = Y*8 + X + TAX + LDA $6000,X ; Lookup in occupancy array + RTS +``` + +--- + +## 8. State Transitions + +``` +WAITING_FOR_INPUT + ↓ [Player selects unit] + ↓ [Player moves/attacks] +EXECUTING_MOVE + ↓ [Move resolves, damage applied] +ENEMY_TURN + ↓ [Each enemy acts] + ↓ [Wait for animations] +CHECK_WIN + ↓ [If enemy count = 0 or player count = 0] +GAME_OVER (win/lose) + ↓ [Display result, wait for restart key] +WAITING_FOR_INPUT [loop] +``` + +--- + +## 9. Build & Run + +**Development tools:** +- **cc65** (C-to-6502 compiler, includes assembler `ca65` and linker `ld65`) +- **VICE** (Commodore 64 emulator, with debugger) +- **Makefile** or shell script to assemble & link + +**Command sequence:** +```bash +ca65 main.asm -o main.o # Assemble +ld65 main.o -C c64.cfg -o game.prg # Link +x64sc game.prg # Run in VICE +``` + +--- + +## 10. Implementation Order (MVP Priority) + +1. **Kernel & Interrupt Handler** — Get IRQ handler ticking +2. **Sprite Rendering** — Display 6 units on screen +3. **Grid & Occupancy** — Manage unit positions +4. **Input & Unit Selection** — Player can select a unit +5. **Movement & Collision** — Player can move units (no combat yet) +6. **Combat & Damage** — Attack resolves HP +7. **Enemy AI** — Simple greedy pathfinding +8. **Win/Lose Detection** — Game ends correctly +9. **Polish** — Text output, feedback, cleanup + +--- + +## 11. Known Pitfalls & Mitigations + +| Risk | Mitigation | +|------|-----------| +| Interrupt handler exceeds raster line | Use raster-stable jitter removal; keep subroutines cycle-predictable | +| Sprite overlap (>8 on screen) | Multiplex later; for now, 6 sprites fit without overlap | +| Occupancy array corruption | Rebuild every frame from unit state; don't write directly | +| AI hangs/slowdown | Limit enemy AI to 1 move each; use lookup tables for distance | +| Collision bugs | Test grid occupancy before allowing move; confirm unit dies at 0 HP | + +--- + +## 12. File Structure + +``` +skirmish-game/ + src/ + main.asm # Entry point, IRQ handler, main loop + input.asm # Input polling, key handling + game-logic.asm # Unit selection, movement, combat + enemy-ai.asm # Pathfinding, enemy turn + render.asm # Sprite updates, text output + data.asm # Lookup tables, sprite/tile data + assets/ + sprites.chr # C64 sprite design (binary) + tiles.chr # Tile data (if used) + docs/ + GDD.md # Game design (this file) + TECHNICAL-PLAN.md # Technical details (this file) + C64-CHEATSHEET.md # Quick register/memory reference + Makefile # Build script + c64.cfg # cc65 linker config (if using cc65) + README.md # Build & run instructions +``` + +--- + +## 13. Next Steps + +1. **Choose assembler:** cc65 + ca65/ld65 (recommended) or ACME +2. **Set up build environment** on your machine +3. **Write skeleton main.asm** (entry point + IRQ stub) +4. **Create sprite test** (render 6 colored squares) +5. **Implement zero-page variables** +6. **Add input handler** (keyboard or joystick) +7. **Iterate gameplay logic** + diff --git a/skirmish-game/enemy-ai.o b/skirmish-game/enemy-ai.o new file mode 100644 index 0000000000000000000000000000000000000000..923c31a5662ebf7ec330b705111bfa7be70385d2 GIT binary patch literal 5677 zcmZ8k33yc18NL4t3A2!ZA`-DeP*AHvLkKbO*<~_$BmM7OvBAtxGfJa1Ud~q#ASM z<;Do~Y0!DSDcQHIzQ5>bIdUk?y2^L9_p}fC_RH+VB{l5{B%f*arHJ*7Wvp!}(kaF| zk|tS3PRdp>(p#}Z9Ih4-2i5y6BN>YK$a-n08o-$hV4c@taVmC5jB1Q&u4J7pSV9+& zb-qfhUNsASg^+w%1O2QlV8{X^nbOlpOg_|VA8O^%EIgVsXxACEV;>w$kqtV%*R(7# zU%FutQ{-BvTc;ym2iY&LCQ2?~$ye3!K4kbJMgE9RtITq?q{%F%-=+6u7gieSm%zA3 zMsiYse3_xiyE6F(_1mDy({V)C2N9 zxD#k(mHga@gK$XVMtTt1r}r0bAZbX8x` zGRw8ha@C*H1$j>O`7AWwVDD^&M=H?B^Rizf_p8Sa=v`%p>LYmDM{t0fpr!#i>_X0< zkc)FvCw3HZdt^Io4#hSZ%M@d|;APxl3*qJj@^nduX)`w2WU_yg#C0k)} zD6W#1!BdFVV-@09JzuLn1>TKO7ayJWLYHp|qrNC9=k3C)V2u zVb7G`BU?<`^p>|lX_Y2ENKHC`DY{*Z^kwQcc>{w>1M($ULAYD#t<3Wv zVq-FgHRkYTXLA*gV88~5WSvsVN9`R%WGJ>vHAh*^!~+>N(8wm4&f!j{c36uYhW{q~ zapIf6^O1wg`Zh2VcZd8tY(ZmcF+|Id)tzVc=h^LGqSgl0x?5x99>LJ8pcGk-;Z{JL z>5F7QY{4#kYo3!4(-v88VWBpORU+l-ALGlu&f0;m;X1sL_sghWo5eRBOaHLA*5jmd z=UVGsY}M@JSoLZdEni4###YP!nbgZDnUp?jrR1HF?whRsUrX;5R?{1po%rM_Nnei7 z?&xj!nC!5Q%GjY>#64%A6yuZO8goh}%*TtbZX+JWHcN^m(>4Bmd}Nkeg$uF3&1bP? zWTZSRY0t>SE!Gn21M!G01!?nIaLo7!uf7vv6=JVSAImGNExf?20x7Y+!~=&JTAqbi ziBxIjscM(C-s*zu5h4CgafvFp+h|u1-cflfJ^B)QQk+S+UtK3qoJLrzo_CObr?gdZ zp<)?Sb6Dx$6?+)ecuc9AHtZy01~IslfOk61xoWcx9ozq{h>Vy(r+MYRn?-$LV|<#p#4Cn&AP( z{pxTHeTUMkUHdB(BNT=ZuA}{74e+qPQSG^;pDD$U9%Eip+Ks+3nz~Wksp2ToGfKZ# ztffO&ssqTrQt_nXUgyxLE6td{Y5%v1FDT;Rj5e7bPw1LHsWQ$nXb&qtr1%@fpHk+JLW6S%+As#<_n$E% z$*yLR`_%Jt(ifE;RUAl%n+bTmq8(86bH!zfQ5x@8dMjz0(g5j0I=dT4|DY-ky~a2Q z*F!UF)V@^lSBkq7ub{_vrIieRN`s$JY@l#09iGwtZpBB{c`R-G@)@&Rg;$l|L}9B6 zIrMm3L)I(evkaS3oDR@FRsOspe_xx08aRi6zgG21s?RD7QN?*44JR05zEE{G)wzTn z+Q<3cm@L8win9n$YSsm$e^sY<6>)?|>qGe(0={x+{TTZvb@)axg=$x-|5b;#RmBe{ zwCfmmR)yt?lc+A!daa5D^yo+62hxNIOVo9{Vj)d9Ng11>s3BibaU>~^^o8ss4cOz(spTGDBt7YDBYi013VTDg9f*cJB~EY99}JPX z!=)h~M;Zx*odrQps1Q^1Cs`<1o}SQ3edBC@bleYm6+O?zo?9m5?3L8Qw%_(fY|R!7 zU7A$Stsxwp#brsO`+V78$Wc(l`oU1Qf?%jn*B+hpM4ZPLjg*A~mxSzsGM~RN8uWV0 zN<9H@gldVT=3rN(13>$4SyDCQ^*Rm@c!rkJOAbK0!hns~G+ zacSGdJea4HunJS682;lj~|@uG&~r zJgTOiBYgqPnRhuHN$!pGhFD{~t|6B2a3&|}jr6Flshu2+S2j$E#Wkf{^CX1y+hXx} ztRZQ3&8(Z9jCXrIes6SKG#s%@6TBk9;|`smzPL)|$(BUT-EH($`5Nj|II xsRfv@J8QEtZhuKhFwn&tEVR=?!9q{OlTr{4cOh|%w?n~FPm$dvQ0Dg=^FIR?Fv?vF4Q2G>gPuh%AnJ3BY zu(M;CjqK2wiR|VeQ;^bOk2(dkL-tcYm^6nT3_3*y=}22NuVWeu*rE$8vWoJRn%*&u zC9Jd;Zmb66l4c#bO!E!p&7@gOGY9<~F^i2nhmscIPZw_yl%3L>EA}Ax4kl>{s9%7XiAUF&Qe>s?3PNty2VqQ&hWr`+Hkr?n=Icm929tTmvmEW7lus-YQ7W9cS7@tY-PR!h)ffpwfu}cicYIsuL0K^@yl2C zR8H!){fZfmaT3RL5|#|psUHHLrl{MoTH{MKzEtC%xFG%s#z%DxqKI}%l}0b-@IW1Y zCx_pFAzZQna=+ZbW#RZY=mc6gffk)Wx-5p|i1g9n`*8FRLA1&m#;;-gX?x2s!D(c# zl9j+5`I}wX6?%>S24$NpMCLT0rhFWXQ&FdMFFLJqKayqvHAPG_RJdEZNQIGuG8e84 zAk>yTOLe`9>s9+4hudbCu}p{C2BrO#Jal2U*SWLyVuAm*=AJVfsy*lt+@y<0{qT%x(20vng(P^1EJZ^@sa zctx&qw-~t!`Oe5~TL0hS>rPxIxND(!8Ba6;LL=zvVy{E!CB&t2oi5CEx`=n#*Qqz2 zSFm~cf3HQqfeEAmbzyGimi&WW%A0jvduVhIEq)bLsX%>*uEvl~pz`m4PXQ|brlo81 zroFX(rnw`UI-(cuX|Rip{7ao@;Tue*1=&a0m;!UI?Y4^Y)_AUZ|STJDs) zsh$aUmdu1*B_F86k_B|ErDH7}(>ZET7d(h->5wMf#lJ9;s_=|luG79&m*QF-W-o%e zDe5wPiB7w`k2SG?>V6dHCU(lJI!}i@hygm}JvdG(>ScHyomM%3Dl&muUMFydJdb^t z2~_4g*5%O&dh{Cn6aA}+Z8Dd-xyX-t-$v*hc?f7JYW^$rVl0JSBQlYq->Jr-68l)S z71>&41^4|5YPjEB!MYE3w2;gp=@CczSVCzI=H z-6S7aj;oyCid89Y*MyI)uchlc?2K*J%c#bxkj>)ShkH6LeC_+zK}qZqt6Z*jFOeyO zx5!E>w>kYy>!4+glxHmW!#Gs#7T+nH2X|NjeJ+fUnc{dGPrP?5DV4JHqT_gUtiV-W zAlcI`2}nlD7Hgqw$73fVCGuP6F6;Wzc1wzJzH)ZkV6Cw>TA27oajcbF=2>#1v{}wa ztOqR5CXwGromDv3k|Oy?+}_RBbv=Rytmm;A56kT`T%Hv-j_I@TeOBKo#p7pMYb`01 z|4E;XxS5{_BX7CaQJVHmg~RKEv_2Ylf`XpPo?-AG)U}Q5XCy9B;zbgRN#GVRCRc-t6_(O< zi<&L;-$krdVz>fZp<4G)`YG`x;xTG(Q0IL#A0z%qW3E)TK>^PgV`ekpdhO4n=%;GF zPxAvJZVY3(s&)aj>&fb?rg@L%eBvmLdx47X>f20q4v7P_%c-D=<1LzAe5@Zb|BLKm zimy@GRGJG(FCbo|;#D*sB;pMbZLq3$QN4(GhZ3Jt;a3x9B4e^(m^CDSuKnH$?^BOQ zlQFAl{+k%kDBMfHK??8D+{Dr5)AlKS#HN|^1QEwiw5N&sMsA)Uy^6vX()eyi`!o6D zM7(_)gHsbWB6W9?{e<>(eL*q_9qqRYV-?<^>v7_P#J?cenAd1eW%!RZ-vCwbCHpK{ zyy&9MrX5meKkefXV$7qId6j&G{()+KN)x9nv}={ls_c#N$lK7mslK2$y7b;mt^GVjzUx^h=^$_t=9pOoupAd2UN7En3%ytG&Cr)4{ zDt@TG7iq?n4JzEIuz}Lkbm48ym_szjt8cOb&g^JUk;RV=*pwu4)NEG4^UUyjH9w;2 zDDidTkCpzCibW)Fo;79%&67kfTAH%ovHuS78R8iR{!DXp((E99OJ^^z<{p|=s+dI| zet<)Jopw8Ms5&oKR*y-hOyO>ZEg=@DA3sOZCn)efNWC630 z_5-x<z8|etbuujWx4&&_sa+6VX*UkbbYz~)3r985)G~2r3@$H zIcs4KZrj%KlY;XWil#JKFYRQh%&Jw=&;psru8pQ$Rx-U-bWu}d?B>zYx_F=3hS4># zzR{+-Ua{z)yquB!`}Vsgr%z5{O(G{28xR|q8|wn?FPp~JG}hHLjpmn2QUZ&=7dUkX@D@cv;b4iUrhZ&=A~LErI=acN`z6wph$iBhP4U`zED^7cG}qT8 zG7`;=^^sUZbA2M-m@%$4Iw{^rN0TRBAFrEa+i7(T zO{65^b>ni0{fPaE1Be5Od8s;BWTO2sDUH1`<6Qhq#G`dtqZ@0gBMq@w^SEezY*I?! zrkcrdPpmc`O_q~3W?ZDUp? zbwe^m#GXqw7dI5FjCk`yMS+qm?%@b_Z$$TPrd?^{q)2r`ecZ$KuuI6g;HpF_D&2O? zIvp41q(x`d$0sHt>}sBFu`vw|P4UR6n#QI?vJ5PDQ*$F08R@%@s;RGzL~CoX=*fAG z!WF7ccCzamBC$yrCz6OZj*cfX>`oKbDmKPrcc}M76NzYStX*<-JbE^PGe1z^OAQ9F b*SwCQWo1q^(r@ww1Et-S7P_6YF}^FhB5#PwIqaXsT` zMH!s@sqyD4k23JjzrCwp2hQ}p?p^)8!}A-5r^UBK%9kXR?*!cz^aaiO8bq_g^@I3> z9-xr%H3Ve@HE9TJNGO+yT!LQF89^RElOhO3gyJK*iA)K)gpNRl7kpWvO=KP-!_cH4 zJVDMVG=q!+t)TN1nWvm_(-bnj$S^di4o{skZb~9E2R(<*b7Y=##!F>n`jBC0(h@vN zobeLxlS9iWETfe|D8-==ts~Th5JQtD;F;i1h*pq^LC1tE{MTzT1pQ4{1mzi&mqogW z=y^mLnzRAy21ki*Ale`l8vKY~nsgf8lkhS$X#uST_9}E8-W}*2cB=FloLAvwXp)JJ z$xb(&fU^g>hn*>!gmVo}h9(W8Gt5peh2UI-US#Ji1>pRgAA-lmCat2k%HA-23U3@b z&fYLp;SIye(4;+k7kj)5-z2@TpX07w+Qr5sxnVyBJ42HsCX%+fh>5qMx7le>6wVr) z3{BF|(b&05x8NLv9z-YYYbcZ!O6E0n@VTQgbjIN6_BCA84UdYNidsM1{ete&2!9YF z!ykbn!0sHdnFE$(Ms_!6*W4sY_hg2|)8{ElQDR^=QVJ^dxuxZa*hYgEcFdmP#nt#8 z?ULNq2FFxN(kDu~pAvMIy6Hz!)E7LFm!9zrzoLFOUmo%hgJ3;?CBQ16z(eq>5h4Jl zfL#I0qs!-9h_}FwfPh9)jPA5ceUE1B!qS z9=rl>T!&0Tc7t65WLp;QUV`NmUb^a1_&Y+0`r3nfbomxHvzc}>4khB5QK~Y zrU2();g68F1z8640v36QNiYrYHh|9~ree4R$;V8H3KM?1%;r#g7Z3#upwx{JVEz-VwZesk98jp=e+V>idrOimRt&8y(->j`!%1G3UwP8y(!TNou z*c%D#Gv0&0#j$+H&}O`7+v=>>=FU>>S+Po+V1f2Tt+m;-wJc|PtSDIDtS;5oH=b>c zRO*lWs+D*wJQp1qy&k?2PS>`=)#_MvJW>s@?P`6kw)wbLe>hTGulfsG#wsWEf@XE* zv+BGwS|T7dnVWBMzs%(=<}#ZNSW?ZETSmXlZC|Hpsg`u=ftIiv4o^XZIfy&w0YB znKCPrmb7Qnjc%v9{|wpan=Uxbz`<>G!<$Is{<_hb(2bP+>{&RwGo+dd*srvF|54>Z zJ@lwj-zpy}bZo6W-iX*Z+A&hE?W_dXR<`dwscbHnm!GV!xRZKHdJH|KTB<8ynr;y9 Rf;9BJnwD;@kmY|={{hjhwJ`ty literal 0 HcmV?d00001 diff --git a/skirmish-game/render.o b/skirmish-game/render.o new file mode 100644 index 0000000000000000000000000000000000000000..be89bbde42ff59d7343f5dc63b4213587f5da979 GIT binary patch literal 10462 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 f@ZLXz`(SCREEN_RAM + 40 * 0), >(SCREEN_RAM + 40 * 1), >(SCREEN_RAM + 40 * 2), >(SCREEN_RAM + 40 * 3), >(SCREEN_RAM + 40 * 4) + .byte >(SCREEN_RAM + 40 * 5), >(SCREEN_RAM + 40 * 6), >(SCREEN_RAM + 40 * 7), >(SCREEN_RAM + 40 * 8), >(SCREEN_RAM + 40 * 9) + .byte >(SCREEN_RAM + 40 * 10), >(SCREEN_RAM + 40 * 11), >(SCREEN_RAM + 40 * 12), >(SCREEN_RAM + 40 * 13), >(SCREEN_RAM + 40 * 14) + .byte >(SCREEN_RAM + 40 * 15), >(SCREEN_RAM + 40 * 16), >(SCREEN_RAM + 40 * 17), >(SCREEN_RAM + 40 * 18), >(SCREEN_RAM + 40 * 19) + .byte >(SCREEN_RAM + 40 * 20), >(SCREEN_RAM + 40 * 21), >(SCREEN_RAM + 40 * 22), >(SCREEN_RAM + 40 * 23), >(SCREEN_RAM + 40 * 24) + +color_row_lo: + .byte <(COLOR_RAM + 40 * 0), <(COLOR_RAM + 40 * 1), <(COLOR_RAM + 40 * 2), <(COLOR_RAM + 40 * 3), <(COLOR_RAM + 40 * 4) + .byte <(COLOR_RAM + 40 * 5), <(COLOR_RAM + 40 * 6), <(COLOR_RAM + 40 * 7), <(COLOR_RAM + 40 * 8), <(COLOR_RAM + 40 * 9) + .byte <(COLOR_RAM + 40 * 10), <(COLOR_RAM + 40 * 11), <(COLOR_RAM + 40 * 12), <(COLOR_RAM + 40 * 13), <(COLOR_RAM + 40 * 14) + .byte <(COLOR_RAM + 40 * 15), <(COLOR_RAM + 40 * 16), <(COLOR_RAM + 40 * 17), <(COLOR_RAM + 40 * 18), <(COLOR_RAM + 40 * 19) + .byte <(COLOR_RAM + 40 * 20), <(COLOR_RAM + 40 * 21), <(COLOR_RAM + 40 * 22), <(COLOR_RAM + 40 * 23), <(COLOR_RAM + 40 * 24) +color_row_hi: + .byte >(COLOR_RAM + 40 * 0), >(COLOR_RAM + 40 * 1), >(COLOR_RAM + 40 * 2), >(COLOR_RAM + 40 * 3), >(COLOR_RAM + 40 * 4) + .byte >(COLOR_RAM + 40 * 5), >(COLOR_RAM + 40 * 6), >(COLOR_RAM + 40 * 7), >(COLOR_RAM + 40 * 8), >(COLOR_RAM + 40 * 9) + .byte >(COLOR_RAM + 40 * 10), >(COLOR_RAM + 40 * 11), >(COLOR_RAM + 40 * 12), >(COLOR_RAM + 40 * 13), >(COLOR_RAM + 40 * 14) + .byte >(COLOR_RAM + 40 * 15), >(COLOR_RAM + 40 * 16), >(COLOR_RAM + 40 * 17), >(COLOR_RAM + 40 * 18), >(COLOR_RAM + 40 * 19) + .byte >(COLOR_RAM + 40 * 20), >(COLOR_RAM + 40 * 21), >(COLOR_RAM + 40 * 22), >(COLOR_RAM + 40 * 23), >(COLOR_RAM + 40 * 24) + +title_text: + .byte 19, 11, 9, 18, 13, 9, 19, 8, 0 +player_turn_text: + .byte 16, 12, 1, 25, 5, 18, 32, 20, 21, 18, 14, 0 +enemy_turn_text: + .byte 5, 14, 5, 13, 25, 32, 20, 8, 9, 14, 11, 9, 14, 7, 0 +win_text: + .byte 25, 15, 21, 32, 23, 9, 14, 0 +lose_text: + .byte 7, 1, 13, 5, 32, 15, 22, 5, 18, 0 +controls_text: + .byte 17, 32, 19, 5, 12, 32, 23, 1, 19, 4, 32, 13, 15, 22, 5, 32, 5, 32, 23, 1, 9, 20, 32, 18, 32, 18, 5, 19, 20, 1, 18, 20, 0 +turn_text: + .byte 20, 21, 18, 14, 32, 0 +select_text: + .byte 19, 5, 12, 32, 0 +player_label_text: + .byte 16, 0 +enemy_label_text: + .byte 5, 0 + +.segment "SPRITES" +.align 64 +player_sprite: + .byte $00, $18, $00 + .byte $00, $3C, $00 + .byte $00, $7E, $00 + .byte $00, $FF, $00 + .byte $01, $FF, $80 + .byte $03, $FF, $C0 + .byte $07, $FF, $E0 + .byte $0F, $FF, $F0 + .byte $1F, $FF, $F8 + .byte $3F, $FF, $FC + .byte $3F, $7E, $FC + .byte $1E, $3C, $78 + .byte $0C, $18, $30 + .byte $0C, $18, $30 + .byte $1E, $3C, $78 + .byte $3F, $24, $FC + .byte $33, $24, $CC + .byte $21, $24, $84 + .byte $01, $C3, $80 + .byte $00, $C3, $00 + .byte $00, $42, $00 + .byte $00 + +enemy_sprite: + .byte $00, $00, $00 + .byte $03, $C3, $C0 + .byte $07, $E7, $E0 + .byte $0F, $FF, $F0 + .byte $1E, $7E, $78 + .byte $3C, $3C, $3C + .byte $78, $18, $1E + .byte $F0, $00, $0F + .byte $E0, $00, $07 + .byte $C3, $81, $C3 + .byte $C7, $FF, $E3 + .byte $EF, $FF, $F7 + .byte $7F, $FF, $FE + .byte $3F, $FF, $FC + .byte $1F, $FF, $F8 + .byte $0F, $7E, $F0 + .byte $06, $24, $60 + .byte $0C, $3C, $30 + .byte $18, $24, $18 + .byte $30, $00, $0C + .byte $00, $00, $00 + .byte $00 diff --git a/skirmish-game/src/enemy-ai.asm b/skirmish-game/src/enemy-ai.asm new file mode 100644 index 0000000..4ec3f5a --- /dev/null +++ b/skirmish-game/src/enemy-ai.asm @@ -0,0 +1,303 @@ +.include "skirmish.inc" + +.export ENEMY_AI_TURN + +.import REBUILD_OCCUPANCY +.import UNIT_ATTACK +.import ENSURE_SELECTED_UNIT + +.importzp temp0 +.importzp temp1 +.importzp temp2 +.importzp temp3 +.importzp temp4 +.importzp temp5 +.importzp temp6 +.importzp temp7 + +.import unit_x +.import unit_y +.import unit_hp +.import unit_team +.import grid_occupancy + +.segment "CODE" +ENEMY_AI_TURN: + ldx #PLAYER_COUNT +enemy_loop: + cpx #UNIT_COUNT + beq enemy_done + lda unit_hp,x + beq next_enemy + + stx temp7 + jsr FIND_CLOSEST_PLAYER + cpy #$FF + beq restore_enemy + jsr ENEMY_ACT +restore_enemy: + ldx temp7 +next_enemy: + inx + jmp enemy_loop + +enemy_done: + jsr ENSURE_SELECTED_UNIT + rts + +FIND_CLOSEST_PLAYER: + ldx temp7 + lda unit_x,x + sta temp0 + lda unit_y,x + sta temp1 + + lda #$FF + sta temp2 + sta temp3 + ldy #$FF + + ldx #0 +find_target_loop: + lda unit_hp,x + beq next_target + + lda temp0 + cmp unit_x,x + bcs :+ + lda unit_x,x + sec + sbc temp0 + jmp :++ +: sec + sbc unit_x,x +: sta temp4 + + lda temp1 + cmp unit_y,x + bcs :+ + lda unit_y,x + sec + sbc temp1 + jmp :++ +: sec + sbc unit_y,x +: clc + adc temp4 + sta temp4 + + lda unit_x,x + cmp #3 + bcs :+ + sec + lda #3 + sbc unit_x,x + jmp :++ +: sec + sbc #3 +: sta temp5 + + lda unit_y,x + cmp #3 + bcs :+ + sec + lda #3 + sbc unit_y,x + jmp :++ +: sec + sbc #3 +: clc + adc temp5 + sta temp5 + + lda temp4 + cmp temp2 + bcc better_target + bne next_target + lda temp5 + cmp temp3 + bcs next_target + +better_target: + lda temp4 + sta temp2 + lda temp5 + sta temp3 + txa + tay + +next_target: + inx + cpx #PLAYER_COUNT + bne find_target_loop + rts + +ENEMY_ACT: + sty temp6 + ldx temp7 + + lda unit_x,x + sta temp2 + lda unit_y,x + sta temp3 + + ldy temp6 + lda unit_x,y + sta temp4 + lda unit_y,y + sta temp5 + + lda temp2 + cmp temp4 + bcs :+ + lda temp4 + sec + sbc temp2 + jmp :++ +: sec + sbc temp4 +: sta temp0 + + lda temp3 + cmp temp5 + bcs :+ + lda temp5 + sec + sbc temp3 + jmp :++ +: sec + sbc temp5 +: sta temp1 + + lda temp0 + clc + adc temp1 + cmp #1 + bne move_enemy + + ldx temp7 + ldy temp6 + jsr UNIT_ATTACK + jsr REBUILD_OCCUPANCY + rts + +move_enemy: + jsr CALC_X_STEP + sta temp2 + jsr CALC_Y_STEP + sta temp3 + + lda temp0 + cmp temp1 + bcc try_y_first + +try_x_first: + lda temp2 + beq try_y_after_x + jsr TRY_X_MOVE + bcs enemy_move_done +try_y_after_x: + lda temp3 + beq enemy_move_fail + jsr TRY_Y_MOVE + bcs enemy_move_done + jmp enemy_move_fail + +try_y_first: + lda temp3 + beq try_x_after_y + jsr TRY_Y_MOVE + bcs enemy_move_done +try_x_after_y: + lda temp2 + beq enemy_move_fail + jsr TRY_X_MOVE + bcs enemy_move_done + +enemy_move_fail: + clc + rts + +enemy_move_done: + jsr REBUILD_OCCUPANCY + sec + rts + +CALC_X_STEP: + ldx temp7 + ldy temp6 + lda unit_x,y + cmp unit_x,x + beq x_step_zero + bcc x_step_left + lda #1 + rts +x_step_left: + lda #$FF + rts +x_step_zero: + lda #0 + rts + +CALC_Y_STEP: + ldx temp7 + ldy temp6 + lda unit_y,y + cmp unit_y,x + beq y_step_zero + bcc y_step_up + lda #1 + rts +y_step_up: + lda #$FF + rts +y_step_zero: + lda #0 + rts + +TRY_X_MOVE: + ldx temp7 + lda unit_x,x + clc + adc temp2 + sta temp4 + cmp #8 + bcs move_fail + lda unit_y,x + sta temp5 + jmp TRY_MOVE_COMMON + +TRY_Y_MOVE: + ldx temp7 + lda unit_x,x + sta temp4 + lda unit_y,x + clc + adc temp3 + sta temp5 + cmp #8 + bcs move_fail + +TRY_MOVE_COMMON: + lda temp5 + asl + asl + asl + clc + adc temp4 + tay + lda grid_occupancy,y + cmp #EMPTY_CELL + bne move_fail + + ldx temp7 + lda temp4 + sta unit_x,x + lda temp5 + sta unit_y,x + sec + rts + +move_fail: + clc + rts diff --git a/skirmish-game/src/game-logic.asm b/skirmish-game/src/game-logic.asm new file mode 100644 index 0000000..178908b --- /dev/null +++ b/skirmish-game/src/game-logic.asm @@ -0,0 +1,391 @@ +.include "skirmish.inc" + +.export INIT_GAME +.export UPDATE_GAME +.export REBUILD_OCCUPANCY +.export UNIT_ATTACK +.export ENSURE_SELECTED_UNIT + +.import ENEMY_AI_TURN + +.importzp input_flags +.importzp game_state +.importzp selected_unit +.importzp turn_counter +.importzp player_units +.importzp enemy_units +.importzp move_dx +.importzp move_dy +.importzp action_result +.importzp temp0 +.importzp temp1 +.importzp temp2 +.importzp temp3 +.importzp temp4 +.importzp temp5 + +.import unit_x +.import unit_y +.import unit_hp +.import unit_team +.import grid_occupancy + +.segment "CODE" +INIT_GAME: + lda #STATE_PLAYER + sta game_state + lda #0 + sta turn_counter + sta action_result + lda #0 + sta selected_unit + + lda #PLAYER_COUNT + sta player_units + lda #ENEMY_COUNT + sta enemy_units + + ldx #0 +clear_units: + lda #0 + sta unit_x,x + sta unit_y,x + sta unit_hp,x + sta unit_team,x + inx + cpx #UNIT_COUNT + bne clear_units + + lda #0 + sta unit_x+0 + sta unit_x+1 + sta unit_x+2 + lda #7 + sta unit_x+3 + sta unit_x+4 + sta unit_x+5 + + lda #1 + sta unit_y+0 + lda #3 + sta unit_y+1 + lda #5 + sta unit_y+2 + lda #1 + sta unit_y+3 + lda #3 + sta unit_y+4 + lda #5 + sta unit_y+5 + + lda #3 + ldx #0 +hp_loop: + sta unit_hp,x + inx + cpx #UNIT_COUNT + bne hp_loop + + lda #TEAM_PLAYER + sta unit_team+0 + sta unit_team+1 + sta unit_team+2 + lda #TEAM_ENEMY + sta unit_team+3 + sta unit_team+4 + sta unit_team+5 + + jsr REBUILD_OCCUPANCY + rts + +UPDATE_GAME: + jsr REBUILD_OCCUPANCY + + lda game_state + cmp #STATE_PLAYER + beq update_player + cmp #STATE_ENEMY + beq update_enemy + cmp #STATE_CHECK + beq update_check + jmp update_end + +update_player: + jsr ENSURE_SELECTED_UNIT + + lda input_flags + and #INPUT_NEXT + beq :+ + jsr CYCLE_SELECTED_UNIT + rts +: + lda input_flags + and #INPUT_FIRE + beq :+ + jsr CYCLE_SELECTED_UNIT + rts +: + lda input_flags + and #INPUT_WAIT + beq :+ + lda #STATE_ENEMY + sta game_state + rts +: + lda #0 + sta move_dx + sta move_dy + + lda input_flags + and #INPUT_UP + beq :+ + lda #$FF + sta move_dy + jmp do_player_action +: + lda input_flags + and #INPUT_DOWN + beq :+ + lda #1 + sta move_dy + jmp do_player_action +: + lda input_flags + and #INPUT_LEFT + beq :+ + lda #$FF + sta move_dx + jmp do_player_action +: + lda input_flags + and #INPUT_RIGHT + beq :+ + lda #1 + sta move_dx + jmp do_player_action +: + rts + +do_player_action: + jsr UNIT_TRY_ACTION + lda action_result + beq :+ + lda #STATE_ENEMY + sta game_state +: + rts + +update_enemy: + jsr ENEMY_AI_TURN + inc turn_counter + lda #STATE_CHECK + sta game_state + rts + +update_check: + jsr CHECK_WIN_LOSE + lda game_state + cmp #STATE_CHECK + bne :+ + lda #STATE_PLAYER + sta game_state +: + rts + +update_end: + lda input_flags + and #INPUT_RESTART + beq :+ + jsr INIT_GAME +: + rts + +UNIT_TRY_ACTION: + lda #0 + sta action_result + + ldx selected_unit + cpx #PLAYER_COUNT + bcs unit_try_done + lda unit_hp,x + beq unit_try_done + + lda unit_x,x + clc + adc move_dx + sta temp0 + cmp #8 + bcs unit_try_done + + lda unit_y,x + clc + adc move_dy + sta temp1 + cmp #8 + bcs unit_try_done + + lda temp1 + asl + asl + asl + clc + adc temp0 + tay + lda grid_occupancy,y + cmp #EMPTY_CELL + beq move_player + + tay + lda unit_team,y + cmp #TEAM_ENEMY + bne unit_try_done + + ldx selected_unit + jsr UNIT_ATTACK_XY + lda #1 + sta action_result + jmp unit_try_done + +move_player: + ldx selected_unit + lda temp0 + sta unit_x,x + lda temp1 + sta unit_y,x + lda #1 + sta action_result + +unit_try_done: + rts + +REBUILD_OCCUPANCY: + ldx #0 +clear_grid: + lda #EMPTY_CELL + sta grid_occupancy,x + inx + cpx #64 + bne clear_grid + + ldx #0 +grid_loop: + lda unit_hp,x + beq next_grid_unit + lda unit_y,x + asl + asl + asl + clc + adc unit_x,x + tay + txa + sta grid_occupancy,y +next_grid_unit: + inx + cpx #UNIT_COUNT + bne grid_loop + rts + +CHECK_WIN_LOSE: + lda enemy_units + bne :+ + lda #STATE_WIN + sta game_state + rts +: + lda player_units + bne :+ + lda #STATE_LOSE + sta game_state + rts +: + lda #STATE_CHECK + sta game_state + rts + +ENSURE_SELECTED_UNIT: + lda selected_unit + cmp #PLAYER_COUNT + bcs choose_first_player + tax + lda unit_hp,x + bne ensure_done + +choose_first_player: + ldx #0 +find_alive_player: + lda unit_hp,x + bne found_player + inx + cpx #PLAYER_COUNT + bne find_alive_player + lda #$FF + sta selected_unit + rts + +found_player: + stx selected_unit +ensure_done: + rts + +CYCLE_SELECTED_UNIT: + lda player_units + beq no_cycle_target + + lda selected_unit + cmp #PLAYER_COUNT + bcc :+ + lda #$FF +: + clc + adc #1 + cmp #PLAYER_COUNT + bcc :+ + lda #0 +: + sta temp0 + ldx #PLAYER_COUNT +cycle_search: + ldy temp0 + lda unit_hp,y + bne cycle_found + inc temp0 + lda temp0 + cmp #PLAYER_COUNT + bcc :+ + lda #0 + sta temp0 +: + dex + bne cycle_search + +no_cycle_target: + lda #$FF + sta selected_unit + rts + +cycle_found: + sty selected_unit + rts + +UNIT_ATTACK: + jsr UNIT_ATTACK_XY + rts + +UNIT_ATTACK_XY: + lda unit_hp,y + beq attack_done + sec + sbc #1 + sta unit_hp,y + bne attack_done + lda unit_team,y + beq dead_player + dec enemy_units + jmp attack_done + +dead_player: + dec player_units + +attack_done: + rts diff --git a/skirmish-game/src/input.asm b/skirmish-game/src/input.asm new file mode 100644 index 0000000..2121cf9 --- /dev/null +++ b/skirmish-game/src/input.asm @@ -0,0 +1,112 @@ +.include "skirmish.inc" + +.export INPUT_READ + +.importzp input_flags +.importzp last_input_flags +.importzp temp0 +.importzp temp1 + +.segment "CODE" +INPUT_READ: + lda #0 + sta temp0 + + lda $DC00 + sta temp1 + + lda temp1 + and #$01 + bne :+ + lda temp0 + ora #INPUT_UP + sta temp0 +: + lda temp1 + and #$02 + bne :+ + lda temp0 + ora #INPUT_DOWN + sta temp0 +: + lda temp1 + and #$04 + bne :+ + lda temp0 + ora #INPUT_LEFT + sta temp0 +: + lda temp1 + and #$08 + bne :+ + lda temp0 + ora #INPUT_RIGHT + sta temp0 +: + lda temp1 + and #$10 + bne :+ + lda temp0 + ora #INPUT_FIRE + sta temp0 +: + jsr $FFE4 + beq keyboard_done + + cmp #'W' + bne :+ + lda temp0 + ora #INPUT_UP + sta temp0 +: + cmp #'S' + bne :+ + lda temp0 + ora #INPUT_DOWN + sta temp0 +: + cmp #'A' + bne :+ + lda temp0 + ora #INPUT_LEFT + sta temp0 +: + cmp #'D' + bne :+ + lda temp0 + ora #INPUT_RIGHT + sta temp0 +: + cmp #'Q' + bne :+ + lda temp0 + ora #INPUT_NEXT + sta temp0 +: + cmp #' ' + bne :+ + lda temp0 + ora #INPUT_WAIT + sta temp0 +: + cmp #'E' + bne :+ + lda temp0 + ora #INPUT_WAIT + sta temp0 +: + cmp #'R' + bne keyboard_done + lda temp0 + ora #INPUT_RESTART + sta temp0 + +keyboard_done: + lda last_input_flags + eor #$FF + and temp0 + sta input_flags + + lda temp0 + sta last_input_flags + rts diff --git a/skirmish-game/src/render.asm b/skirmish-game/src/render.asm new file mode 100644 index 0000000..d6040bf --- /dev/null +++ b/skirmish-game/src/render.asm @@ -0,0 +1,613 @@ +.include "skirmish.inc" + +.export INIT_RENDER +.export SPRITE_UPDATE +.export TEXT_UPDATE + +.importzp game_state +.importzp selected_unit +.importzp turn_counter +.importzp player_units +.importzp enemy_units +.importzp temp0 +.importzp temp1 +.importzp temp2 +.importzp temp3 +.importzp temp4 +.importzp temp5 + +.import unit_x +.import unit_y +.import unit_hp + +.import grid_x_low +.import grid_x_msb +.import grid_y_pos +.import sprite_bits +.import screen_row_lo +.import screen_row_hi +.import color_row_lo +.import color_row_hi + +.import title_text +.import player_turn_text +.import enemy_turn_text +.import win_text +.import lose_text +.import controls_text +.import turn_text +.import select_text +.import player_label_text +.import enemy_label_text +.import player_sprite +.import enemy_sprite + +.segment "CODE" +INIT_RENDER: + lda #BORDER_COLOR + sta $D020 + lda #BG_COLOR + sta $D021 + + lda #0 + sta $D015 + sta $D017 + sta $D01B + sta $D01C + sta $D01D + sta $D010 + + jsr CLEAR_SCREEN_AND_COLOR + jsr DRAW_STATIC_UI + jsr SETUP_SPRITES + rts + +SPRITE_UPDATE: + lda #0 + sta temp0 + sta temp1 + + ldx #0 +sprite_loop: + lda unit_hp,x + beq hide_sprite + + lda temp0 + ora sprite_bits,x + sta temp0 + + stx temp4 + txa + asl + tay + + lda unit_x,x + tax + lda grid_x_low,x + sta $D000,y + lda grid_x_msb,x + beq :+ + ldx temp4 + lda temp1 + ora sprite_bits,x + sta temp1 +: + ldx temp4 + lda unit_y,x + tax + lda grid_y_pos,x + sta $D001,y + + ldx temp4 + cpx #PLAYER_COUNT + bcs enemy_color + cpx selected_unit + bne player_color + lda #SPRITE_SELECTED_COLOR + bne store_color +player_color: + lda #SPRITE_PLAYER_COLOR + bne store_color +enemy_color: + lda #SPRITE_ENEMY_COLOR +store_color: + sta $D027,x +hide_sprite_after_pos: + jmp next_sprite + +hide_sprite: + stx temp4 + txa + asl + tay + lda #250 + sta $D001,y + ldx temp4 + +next_sprite: + inx + cpx #UNIT_COUNT + bne sprite_loop + + lda temp0 + sta $D015 + lda temp1 + sta $D010 + rts + +TEXT_UPDATE: + ldx #1 + jsr CLEAR_ROW + lda game_state + cmp #STATE_PLAYER + beq text_player + cmp #STATE_ENEMY + beq text_enemy + cmp #STATE_WIN + beq text_win + lda #lose_text + sta temp3 + ldx #1 + lda #14 + jmp draw_state_line + +text_player: + lda #player_turn_text + sta temp3 + ldx #1 + lda #13 + jmp draw_state_line + +text_enemy: + lda #enemy_turn_text + sta temp3 + ldx #1 + lda #7 + jmp draw_state_line + +text_win: + lda #win_text + sta temp3 + ldx #1 + lda #5 + +draw_state_line: + sta temp4 + jsr SET_SCREEN_PTR + lda #14 + jsr ADD_TO_SCREEN_PTR + jsr WRITE_STRING + ldx #1 + lda temp4 + jsr COLOR_FULL_ROW + + ldx #22 + jsr CLEAR_ROW + ldx #22 + jsr SET_SCREEN_PTR + lda #2 + jsr ADD_TO_SCREEN_PTR + lda #turn_text + sta temp3 + jsr WRITE_STRING + lda turn_counter + jsr WRITE_DECIMAL2 + + lda #14 + jsr ADD_TO_SCREEN_PTR + lda #select_text + sta temp3 + jsr WRITE_STRING + lda selected_unit + cmp #$FF + beq no_selection + clc + adc #1 + clc + adc #48 + ldy #0 + sta (temp0),y + jmp status_done +no_selection: + lda #45 + ldy #0 + sta (temp0),y +status_done: + ldx #22 + lda #HUD_COLOR + jsr COLOR_FULL_ROW + + ldx #23 + jsr CLEAR_ROW + ldx #23 + jsr SET_SCREEN_PTR + lda #2 + jsr ADD_TO_SCREEN_PTR + lda #16 + ldy #0 + sta (temp0),y + iny + lda #49 + sta (temp0),y + iny + lda #58 + sta (temp0),y + iny + ldx #0 + lda unit_hp,x + clc + adc #48 + sta (temp0),y + + iny + iny + lda #16 + sta (temp0),y + iny + lda #50 + sta (temp0),y + iny + lda #58 + sta (temp0),y + iny + ldx #1 + lda unit_hp,x + clc + adc #48 + sta (temp0),y + + iny + iny + lda #16 + sta (temp0),y + iny + lda #51 + sta (temp0),y + iny + lda #58 + sta (temp0),y + iny + ldx #2 + lda unit_hp,x + clc + adc #48 + sta (temp0),y + + iny + iny + lda #5 + sta (temp0),y + iny + lda #49 + sta (temp0),y + iny + lda #58 + sta (temp0),y + iny + ldx #3 + lda unit_hp,x + clc + adc #48 + sta (temp0),y + + iny + iny + lda #5 + sta (temp0),y + iny + lda #50 + sta (temp0),y + iny + lda #58 + sta (temp0),y + iny + ldx #4 + lda unit_hp,x + clc + adc #48 + sta (temp0),y + + iny + iny + lda #5 + sta (temp0),y + iny + lda #51 + sta (temp0),y + iny + lda #58 + sta (temp0),y + iny + ldx #5 + lda unit_hp,x + clc + adc #48 + sta (temp0),y + + ldx #23 + lda #HUD_COLOR + jsr COLOR_FULL_ROW + rts + +CLEAR_SCREEN_AND_COLOR: + ldx #0 +clear_pages: + lda #32 + sta SCREEN_RAM + $0000,x + sta SCREEN_RAM + $0100,x + sta SCREEN_RAM + $0200,x + sta SCREEN_RAM + $02E8,x + + lda #HUD_COLOR + sta COLOR_RAM + $0000,x + sta COLOR_RAM + $0100,x + sta COLOR_RAM + $0200,x + sta COLOR_RAM + $02E8,x + inx + cpx #$E8 + bne clear_pages + rts + +DRAW_STATIC_UI: + ldx #0 + jsr SET_SCREEN_PTR + lda #15 + jsr ADD_TO_SCREEN_PTR + lda #title_text + sta temp3 + jsr WRITE_STRING + ldx #0 + lda #7 + jsr COLOR_FULL_ROW + + ldx #24 + jsr CLEAR_ROW + ldx #24 + jsr SET_SCREEN_PTR + lda #1 + jsr ADD_TO_SCREEN_PTR + lda #controls_text + sta temp3 + jsr WRITE_STRING + ldx #24 + lda #3 + jsr COLOR_FULL_ROW + + jsr DRAW_BOARD + rts + +DRAW_BOARD: + ldx #0 +board_top_labels: + lda screen_row_lo + (BOARD_ROW - 1) + sta temp0 + lda screen_row_hi + (BOARD_ROW - 1) + sta temp1 + txa + asl + asl + clc + adc #BOARD_COL + tay + txa + clc + adc #48 + sta (temp0),y + + lda color_row_lo + (BOARD_ROW - 1) + sta temp2 + lda color_row_hi + (BOARD_ROW - 1) + sta temp3 + lda #BOARD_COLOR + sta (temp2),y + + txa + pha + txa + asl + clc + adc #BOARD_ROW + tax + jsr DRAW_BOARD_ROW + pla + tax + inx + cpx #8 + bne board_top_labels + rts + +DRAW_BOARD_ROW: + stx temp5 + lda screen_row_lo,x + sta temp0 + lda screen_row_hi,x + sta temp1 + lda color_row_lo,x + sta temp2 + lda color_row_hi,x + sta temp3 + + ldy #BOARD_COL + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + iny + iny + iny + lda #46 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + + lda temp5 + sec + sbc #BOARD_ROW + lsr + clc + adc #48 + ldy #2 + sta (temp0),y + lda #BOARD_COLOR + sta (temp2),y + rts + +SETUP_SPRITES: + lda #<(player_sprite / 64) + sta SPRITE_PTRS + 0 + sta SPRITE_PTRS + 1 + sta SPRITE_PTRS + 2 + lda #<(enemy_sprite / 64) + sta SPRITE_PTRS + 3 + sta SPRITE_PTRS + 4 + sta SPRITE_PTRS + 5 + rts + +CLEAR_ROW: + jsr SET_SCREEN_PTR + ldy #0 +clear_row_loop: + lda #32 + sta (temp0),y + iny + cpy #40 + bne clear_row_loop + rts + +SET_SCREEN_PTR: + lda screen_row_lo,x + sta temp0 + lda screen_row_hi,x + sta temp1 + rts + +ADD_TO_SCREEN_PTR: + clc + adc temp0 + sta temp0 + bcc :+ + inc temp1 +: rts + +WRITE_STRING: + ldy #0 +write_string_loop: + lda (temp2),y + beq write_string_done + sta (temp0),y + iny + bne write_string_loop +write_string_done: + tya + clc + adc temp0 + sta temp0 + bcc :+ + inc temp1 +: rts + +WRITE_DECIMAL2: + sta temp4 + ldx #0 +decimal_tens_loop: + lda temp4 + cmp #10 + bcc decimal_store + sec + sbc #10 + sta temp4 + inx + jmp decimal_tens_loop + +decimal_store: + ldy #0 + txa + clc + adc #48 + sta (temp0),y + iny + lda temp4 + clc + adc #48 + sta (temp0),y + iny + tya + clc + adc temp0 + sta temp0 + bcc :+ + inc temp1 +: rts + +COLOR_FULL_ROW: + sta temp4 + lda color_row_lo,x + sta temp2 + lda color_row_hi,x + sta temp3 + ldy #0 +color_loop: + lda temp4 + sta (temp2),y + iny + cpy #40 + bne color_loop + rts diff --git a/skirmish-game/src/skirmish.asm b/skirmish-game/src/skirmish.asm new file mode 100644 index 0000000..9238496 --- /dev/null +++ b/skirmish-game/src/skirmish.asm @@ -0,0 +1,136 @@ +.include "skirmish.inc" + +.exportzp input_flags +.exportzp last_input_flags +.exportzp game_state +.exportzp selected_unit +.exportzp turn_counter +.exportzp player_units +.exportzp enemy_units +.exportzp move_dx +.exportzp move_dy +.exportzp action_result +.exportzp frame_ready +.exportzp temp0 +.exportzp temp1 +.exportzp temp2 +.exportzp temp3 +.exportzp temp4 +.exportzp temp5 +.exportzp temp6 +.exportzp temp7 + +.export unit_x +.export unit_y +.export unit_hp +.export unit_team +.export old_irq +.export grid_occupancy + +.import INPUT_READ +.import INIT_GAME +.import UPDATE_GAME +.import INIT_RENDER +.import SPRITE_UPDATE +.import TEXT_UPDATE + +.segment "LOADADDR" + .word $0801 + +.segment "EXEHDR" + .byte $0B, $08, $0A, $00, $9E + .byte "2061", $00, $00, $00 + +.segment "ZEROPAGE" +input_flags: .res 1 +last_input_flags: .res 1 +game_state: .res 1 +selected_unit: .res 1 +turn_counter: .res 1 +player_units: .res 1 +enemy_units: .res 1 +move_dx: .res 1 +move_dy: .res 1 +action_result: .res 1 +frame_ready: .res 1 +temp0: .res 1 +temp1: .res 1 +temp2: .res 1 +temp3: .res 1 +temp4: .res 1 +temp5: .res 1 +temp6: .res 1 +temp7: .res 1 + +.segment "BSS" +unit_x: .res UNIT_COUNT +unit_y: .res UNIT_COUNT +unit_hp: .res UNIT_COUNT +unit_team:.res UNIT_COUNT +old_irq: .res 2 + +.segment "OCCUPANCY" +grid_occupancy: .res 64 + +.segment "CODE" +start: + sei + jsr $FF81 + + lda #0 + sta frame_ready + sta last_input_flags + + lda $0314 + sta old_irq + lda $0315 + sta old_irq+1 + + jsr INIT_RENDER + jsr INIT_GAME + jsr install_irq + cli + +main_loop: + lda frame_ready + beq main_loop + lda #0 + sta frame_ready + + jsr INPUT_READ + jsr UPDATE_GAME + jsr SPRITE_UPDATE + jsr TEXT_UPDATE + jmp main_loop + +install_irq: + lda #irq_handler + sta $0315 + + lda $D011 + and #$7F + sta $D011 + lda #$80 + sta $D012 + + lda #$01 + sta $D01A + + lda $DC0D + lda $DD0D + lda #$01 + sta $D019 + rts + +irq_handler: + lda $D019 + and #$01 + beq chain_irq + lda #$01 + sta $D019 + inc frame_ready + +chain_irq: + jmp (old_irq) diff --git a/skirmish-game/src/skirmish.inc b/skirmish-game/src/skirmish.inc new file mode 100644 index 0000000..20002ef --- /dev/null +++ b/skirmish-game/src/skirmish.inc @@ -0,0 +1,40 @@ +UNIT_COUNT = 6 +PLAYER_COUNT = 3 +ENEMY_COUNT = 3 + +EMPTY_CELL = $FF + +TEAM_PLAYER = 0 +TEAM_ENEMY = 1 + +STATE_PLAYER = 0 +STATE_ENEMY = 1 +STATE_CHECK = 2 +STATE_WIN = 3 +STATE_LOSE = 4 + +INPUT_UP = $01 +INPUT_DOWN = $02 +INPUT_LEFT = $04 +INPUT_RIGHT = $08 +INPUT_FIRE = $10 +INPUT_NEXT = $20 +INPUT_WAIT = $40 +INPUT_RESTART = $80 + +SCREEN_RAM = $0400 +COLOR_RAM = $D800 +SPRITE_PTRS = SCREEN_RAM + $03F8 + +BOARD_COL = 4 +BOARD_ROW = 4 +BOARD_PIXEL_X = 56 +BOARD_PIXEL_Y = 60 + +SPRITE_PLAYER_COLOR = 13 +SPRITE_SELECTED_COLOR = 7 +SPRITE_ENEMY_COLOR = 10 +HUD_COLOR = 1 +BOARD_COLOR = 15 +BG_COLOR = 6 +BORDER_COLOR = 14