diff --git a/src/build.sh b/src/build.sh index c74489c..1c785b4 100755 --- a/src/build.sh +++ b/src/build.sh @@ -2,17 +2,17 @@ # build.sh — compile and optionally run a single C64 program with Oscar64. # # Usage: -# ./build.sh # compile helloworld.c → build/helloworld.prg +# ./build.sh # compile main.c → build/whack_hare.prg # ./build.sh -e # run in oscar64's built-in emulator (headless, fast) # ./build.sh -v # run in VICE x64 (interactive, needs a display) # ./build.sh -V # run in VICE x64sc (interactive, cycle-exact, slow) # ./build.sh -c # just compile # # Output (in ./build/): -# helloworld.prg — loadable C64 program (run with x64, VICE, or real hw) -# helloworld.asm — full 6502 listing -# helloworld.map — region/section/object placement -# helloworld.lbl — VICE monitor label commands +# whack_hare.prg — loadable C64 program (run with x64, VICE, or real hw) +# whack_hare.asm — full 6502 listing +# whack_hare.map — region/section/object placement +# whack_hare.lbl — VICE monitor label commands # # For the development loop, use -e (oscar64's built-in emulator). It runs # without a display, needs no ROMs, and is faster than VICE. VICE is for @@ -46,7 +46,10 @@ if [ ! -x "$OSCAR64_BIN" ]; then fi # --- compile + optionally run -------------------------------------------- -SRC=helloworld.c +# Phase 2+ entry point. helloworld.c is kept around as a minimal sanity +# check; build it explicitly with `./build.sh -c helloworld` (TODO if +# needed). Default is the real game. +SRC=main.c EMU_CMD="" for arg in "$@"; do @@ -62,15 +65,15 @@ done echo "compiling $SRC with $OSCAR64_BIN -> $BUILD_DIR/" # -o puts the .prg in the build dir; the other artifacts (.asm, .map, .lbl) # follow automatically since they share the base name. -"$OSCAR64_BIN" -i="$OSCAR64_DIR/include" -o="$BUILD_DIR/helloworld.prg" "$SRC" +"$OSCAR64_BIN" -i="$OSCAR64_DIR/include" -o="$BUILD_DIR/whack_hare.prg" "$SRC" case "$EMU_CMD" in "") # compile only ;; "oscar64") - echo "running helloworld.prg in oscar64's built-in emulator" - "$OSCAR64_BIN" -i="$OSCAR64_DIR/include" -o="$BUILD_DIR/helloworld.prg" -e "$SRC" + echo "running whack_hare.prg in oscar64's built-in emulator" + "$OSCAR64_BIN" -i="$OSCAR64_DIR/include" -o="$BUILD_DIR/whack_hare.prg" -e "$SRC" ;; "x64"|"x64sc") if ! command -v "$EMU_CMD" >/dev/null 2>&1; then @@ -81,9 +84,9 @@ case "$EMU_CMD" in echo "warning: no \$DISPLAY set; VICE may not render correctly" >&2 echo " for headless testing use -e (oscar64's built-in emulator)" >&2 fi - echo "running helloworld.prg in VICE ($EMU_CMD)" - "$EMU_CMD" "$BUILD_DIR/helloworld.prg" + echo "running whack_hare.prg in VICE ($EMU_CMD)" + "$EMU_CMD" "$BUILD_DIR/whack_hare.prg" ;; esac -echo "done: $BUILD_DIR/helloworld.prg" +echo "done: $BUILD_DIR/whack_hare.prg" diff --git a/src/input.c b/src/input.c new file mode 100644 index 0000000..2c7b10f --- /dev/null +++ b/src/input.c @@ -0,0 +1,9 @@ +#include "input.h" + +static const unsigned short cia_port_base[2] = { 0xDC00, 0xDC01 }; + +char input_fire(int port) +{ + volatile char *p = (volatile char *)cia_port_base[port & 1]; + return ((*p) & 0x10) == 0; +} diff --git a/src/input.h b/src/input.h new file mode 100644 index 0000000..a3c45ff --- /dev/null +++ b/src/input.h @@ -0,0 +1,22 @@ +#ifndef WHACK_HARE_INPUT_H +#define WHACK_HARE_INPUT_H + +// input_fire(port) — returns 1 if the fire button on the given joystick +// port is currently pressed, 0 otherwise. +// +// port = 0 → CIA1 PRA at $DC00 (left joystick, "Scoot", player 2) +// port = 1 → CIA1 PRB at $DC01 (right joystick, "Hare", player 1) +// +// Active low: the button is "pressed" when bit 4 of the CIA port is 0. +// The direction bits (0..3) are read but masked off — only the fire +// bit is examined. Bits 5..7 of the CIA ports are timer A/B outputs +// and the paddles, which we ignore here. +// +// No debounce — this is a raw poll. Call it once per frame from a +// raster IRQ or game loop. + +char input_fire(int port); + +#pragma compile("input.c") + +#endif diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..9fe96eb --- /dev/null +++ b/src/main.c @@ -0,0 +1,33 @@ +// main.c — Whack Hare! entry point (Phase 2: display title screen). +// +// Flow: +// 1. memmap_setup() — bank out KERNAL/BASIC/CHAR ROM. +// 2. show_screen(TITLE) — copy the title bitmap into $E000 and +// configure the VIC for multicolor bitmap +// mode. +// 3. Loop: poll joystick port 1 for fire. When pressed, restore +// the C64's default memory config and return from main() (which +// ends the program). +// +// Build: ./build.sh (compile to src/build/whack_hare.prg) +// Run: ./build.sh -e (headless, in oscar64's built-in emulator) +// ./build.sh -v (VICE x64, interactive, needs a display) + +#include "memmap.h" +#include "screens.h" +#include "input.h" + +int main(void) +{ + memmap_setup(); + show_screen(SCREEN_TITLE); + + // Phase 2: just poll for fire on port 1 (the Hare / right joystick). + // In Phase 4 this becomes a 50 Hz raster IRQ handler that drives the + // full state machine. + while (!input_fire(1)) + ; + + memmap_restore(); + return 0; +} diff --git a/src/memmap.c b/src/memmap.c new file mode 100644 index 0000000..116b0e3 --- /dev/null +++ b/src/memmap.c @@ -0,0 +1,13 @@ +#include "memmap.h" + +void memmap_setup(void) +{ + mmap_trampoline(); + mmap_set(MMAP_RAM); + mmap_set(MMAP_NO_ROM); +} + +void memmap_restore(void) +{ + mmap_set(MMAP_ROM); +} diff --git a/src/memmap.h b/src/memmap.h new file mode 100644 index 0000000..ed2fd29 --- /dev/null +++ b/src/memmap.h @@ -0,0 +1,25 @@ +#ifndef WHACK_HARE_MEMMAP_H +#define WHACK_HARE_MEMMAP_H + +#include + +// memmap_setup() — bank out the KERNAL + BASIC + CHAR ROMs so we get +// the $E000-$FFFF region as free RAM for the multicolor bitmap, and +// $D000-$DFFF shows I/O (not the CHAR ROM) so the VIC can be configured +// and color RAM is accessible. +// +// Idempotent: call once at startup, before any VIC config or bitmap +// copy. After this returns, the program code is running out of the +// 38 KB contiguous region at $0900-$A000 (the default Oscar64 main +// region; verified in the .map file). +// +// memmap_restore() puts the C64 back in its power-on memory config. +// Call it just before exit, so a soft-reset / second-run of the +// program behaves the way the user expects (BASIC prompt etc.). + +void memmap_setup(void); +void memmap_restore(void); + +#pragma compile("memmap.c") + +#endif diff --git a/src/screens.c b/src/screens.c new file mode 100644 index 0000000..7842c39 --- /dev/null +++ b/src/screens.c @@ -0,0 +1,114 @@ +#include "screens.h" +#include +#include +#include + +// Embedded screen data. In Phase 2 only the title screen is wired in; +// the other 4 will be added in Phase 4 (state machine) when we need +// them. The arrays are const so they live in the ROM/load-image part +// of the .prg, not in BSS. +// +// Screen memory layout (in multicolor bitmap mode) per +// cebix-vic-article §3.7.3.4: +// "00" pixel -> $D021 (background, per-screen) +// "01" pixel -> high nibble of screen memory byte +// "10" pixel -> low nibble of screen memory byte +// "11" pixel -> color RAM nibble (we set to 0 = black) +// +// convert_screens.py's .attr file stores the screen memory byte: +// bits 0-3 = "10" color +// bits 4-7 = "01" color +// (the "11" color is not stored; color RAM defaults to black at boot). + +const char ScreenTitleBin[] = { + #embed "data/processed/title.bin" +}; + +const char ScreenTitleAttr[] = { + #embed "data/processed/title.attr" +}; + +// The $D021 background color for the title screen. Generated by +// tools/convert_screens.py as a separate .d021 sidecar, but the value +// is a single small integer (0..15) and we hardcode it here rather +// than read the file at runtime. When we add the other 4 screens in +// Phase 4 we'll fold the d021 value into a per-screen descriptor +// struct so the compiler can't constant-fold it. +#define SCREEN_TITLE_D021 0 + +// Copy `len` bytes from `src` to `dst`. +// +// Both source and destination are 16-bit-addressed memory regions in +// the C64. Using a simple byte-by-byte copy (rather than the c64 +// memcpy helper, which is in the runtime and pulls in more code) keeps +// the .prg small. The compiler turns this into a tight loop; for +// 8000 bytes that's well under a frame at 1 MHz. +static void copy_bytes(const char *src, char *dst, unsigned len) +{ + for (unsigned i = 0; i < len; i++) + dst[i] = src[i]; +} + +void show_screen(int n) +{ + // 1. Write the new pixel/attribute data into the destination + // while the VIC is still in its old mode. This avoids a + // visible garbage frame during the mode switch. + // + // 2. Then flip the VIC into multicolor bitmap mode pointing at + // the new data. The VIC's line buffer absorbs the transition + // cleanly enough for our purposes. + if (n == SCREEN_TITLE) + { + copy_bytes(ScreenTitleBin, (char *)0xE000, sizeof(ScreenTitleBin)); + copy_bytes(ScreenTitleAttr, (char *)0xD000, sizeof(ScreenTitleAttr)); + + // Color RAM (the "11" color per cell). We don't have per-cell + // "11" data in the .attr file, so zero it (= black). This + // reduces each cell from 4 to 3 distinct colors but matches + // what convert_screens.py produces. + __asm + { + lda #0 + ldx #4 + ldy #0 + L0: sta $d800, y + sta $d900, y + sta $da00, y + sta $db00, y + iny + bne L0 + dex + bne L0 + } + + // Background color ($D021, the "00" color). The title screen + // uses color 0 (black); per-screen .d021 files are + // documentation, the value is inlined here. + vic.color_back = SCREEN_TITLE_D021; + vic.color_border = 0; + + // 3. Now flip the VIC into multicolor bitmap mode. + // + // We set the registers explicitly rather than calling + // vic_setmode() so the yscroll/xscroll bits stay 0 + // (vic_setmode() hardcodes yscroll=3, which we don't want + // for a non-scrolling display). + // + // ctrl1 = BMM(1) | DEN(1) | RSEL(1) | yscroll(0) = 0x38 + // ctrl2 = MCM(1) | CSEL(1) | xscroll(0) = 0x18 + // + // CIA2 PRA low 2 bits = 0 -> VIC bank 0 = CPU $C000-$FFFF + // (both our $D000 screen and $E000 bitmap live there). + // + // memptr (D018) = 0x48 + // bits 7-4 = (0xD000 >> 6) & 0xF0 = 0x40 -> screen at $D000 + // bits 3-1 = (0xE000 >> 10) & 0x0E = 0x08 -> bitmap upper 8K + // bit 0 = 0 -> unused in BMM + vic.ctrl1 = VIC_CTRL1_BMM | VIC_CTRL1_DEN | VIC_CTRL1_RSEL; + vic.ctrl2 = VIC_CTRL2_MCM | VIC_CTRL2_CSEL; + cia2.pra = (cia2.pra & 0xfc) | 0x00; + vic.memptr = 0x48; + } + // Other SCREEN_* IDs: reserved for Phase 4. +} diff --git a/src/screens.h b/src/screens.h new file mode 100644 index 0000000..7a5fa3e --- /dev/null +++ b/src/screens.h @@ -0,0 +1,54 @@ +#ifndef WHACK_HARE_SCREENS_H +#define WHACK_HARE_SCREENS_H + +// Screen IDs. Pass to show_screen() to switch to a new screen. +#define SCREEN_TITLE 0 +#define SCREEN_WAITING1 1 +#define SCREEN_WAITING2 2 +#define SCREEN_WIN_HARE 3 +#define SCREEN_WIN_SCOOT 4 + +// show_screen(n) — load screen `n` into the VIC. +// +// The 5 game screens (title, waiting1, waiting2, win_hare, win_scoot) +// are 160x200 multicolor bitmaps, 8000 bytes of pixel data each. Each +// screen has a 1000-byte color attribute table (one byte per 4x8 cell) +// and a single-byte $D021 background color value. +// +// The pixel data and color attribute table are produced by +// tools/convert_screens.py (see its docstring for the exact format). +// This function copies the appropriate ones into place and configures +// the VIC to display them. +// +// Memory layout used here (after memmap_setup()): +// $D000-$D3E7 — screen memory (1000 bytes; the "color attributes") +// Per cebix-vic-article §3.7.3.4, in multicolor bitmap +// mode the screen memory byte holds the "01" color in +// its high nibble and the "10" color in its low +// nibble. The "11" color comes from color RAM at +// $D800+cell; we leave color RAM zeroed (black) for +// now since the .attr files don't store it (see +// tools/convert_screens.py for why). +// $D800-$DBE7 — color RAM (1000 nibbles). Cleared to 0 here. +// $E000-$FFFF — 8 KB bitmap (the .bin data). +// $D021 — background color (the "00" color in the multicolor +// scheme). Set to the per-screen value from the +// .d021 sidecar file. +// +// VIC config written here: +// bank = 0 (CIA2 PRA low 2 bits = 0, selects CPU $C000-$FFFF) +// ctrl1 = BMM | DEN | RSEL (multicolor bitmap, display on, 25 rows, +// no vertical scroll) +// ctrl2 = MCM | CSEL (multicolor, 40 columns, no horiz scroll) +// memptr (D018) = 0x48 (screen at $D000, bitmap at $E000 within +// the selected 16K VIC bank) +// +// For Phase 2 only SCREEN_TITLE has embedded data; the other IDs are +// reserved for Phase 4 (state machine). Calling show_screen() with an +// unsupported ID is a no-op. + +void show_screen(int n); + +#pragma compile("screens.c") + +#endif