Phase 2: display the title screen (mmap, vic, screen swap)
Replace helloworld.c with main.c. Add memmap_setup/restore (banks out KERNAL+BASIC+CHAR ROMs so $E000-$FFFF is free for the 8 KB bitmap and $D000-$DFFF is I/O), show_screen(n) which copies the title .bin to $E000 and the .attr to $D000 (screen memory in multicolor bitmap mode) then flips the VIC into BMM=1 MCM=1 CSEL=1 RSEL=1 DEN=1 with bitmap base CB13=1 and screen base VM13-VM10=4 ($D018 = 0x48), and input_fire(port) which reads bit 4 of CIA1 $DC00/$DC01. build.sh default target is now main.c -> whack_hare.prg (still builds helloworld explicitly via the existing -c flag if needed). Verified: ./build.sh -e runs the title screen in oscar64's built-in emulator and exits cleanly. .map shows code at $0880-$09A6 and embedded title data at $09A7-$2CCF, well within the 38 KB main region. .prg is 9428 bytes, well under the 202-block LOAD"*",8,1 limit. Concerns for Phase 3 (score bar): the score bar will overlay the top 8 pixel rows of the bitmap ($E000-$E13F, 320 bytes). show_screen() will continue to copy the full .bin to $E000; score_render() will be called immediately after and overwrite the top 320 bytes. This keeps show_screen() dumb and lets the score bar be re-rendered on state change without re-copying the whole 8 KB bitmap.
This commit is contained in:
+15
-12
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
+22
@@ -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
|
||||
+33
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
#ifndef WHACK_HARE_MEMMAP_H
|
||||
#define WHACK_HARE_MEMMAP_H
|
||||
|
||||
#include <c64/memmap.h>
|
||||
|
||||
// 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
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
#include "screens.h"
|
||||
#include <c64/vic.h>
|
||||
#include <c64/cia.h>
|
||||
#include <string.h>
|
||||
|
||||
// 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.
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user