Phase 3: score bar (HARE/SCOOT labels + 5 pips per side)
Draws a score bar across the top 8 rows of the multicolor bitmap at $E000-$E13F (320 bytes), with HARE on the far left, SCOOT on the far right, and 5 pips for each player between them. A pip is 8x8 pixels (2 cells wide x 1 cell tall = 16 bitmap bytes): filled = 1-pixel white border around a 6x6 black block; empty = an 8x8 white square (the border alone). Pips are packed 2 cells each with no gap, so adjacent pip borders merge into a 2-px white vertical divider. Labels use a hand-built 4x8 font (1 byte per row, top 4 bits = 4 pixels) for the 8 chars H/A/R/E/S/C/O/T (64 bytes total). Each font row is expanded 1bpp -> 2bpp via a 16-byte lookup table (expand4): each '1' becomes '11' (white, from color RAM), each '0' becomes '00' (background black, from $D021). score_render() also rewrites the top 40 cells' attributes (screen memory = 0, color RAM = 1) so the score bar's palette is exactly 2 colors. Scores are clamped to 0..5 on render to defend against double-tap bugs in Phase 4.
This commit is contained in:
+9
-3
@@ -1,11 +1,13 @@
|
||||
// main.c — Whack Hare! entry point (Phase 2: display title screen).
|
||||
// main.c — Whack Hare! entry point (Phase 3: score bar).
|
||||
//
|
||||
// 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
|
||||
// 3. score_init() — zero both player scores.
|
||||
// 4. score_render() — overlay the score bar on the top 8 rows.
|
||||
// 5. Loop: poll joystick port 1 for fire. When pressed, restore
|
||||
// the C64's default memory config and return from main() (which
|
||||
// ends the program).
|
||||
//
|
||||
@@ -16,13 +18,17 @@
|
||||
#include "memmap.h"
|
||||
#include "screens.h"
|
||||
#include "input.h"
|
||||
#include "score.h"
|
||||
|
||||
int main(void)
|
||||
{
|
||||
memmap_setup();
|
||||
show_screen(SCREEN_TITLE);
|
||||
|
||||
// Phase 2: just poll for fire on port 1 (the Hare / right joystick).
|
||||
score_init();
|
||||
score_render();
|
||||
|
||||
// Phase 3: 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))
|
||||
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
// score.c — score bar rendering for the top 8 rows of the multicolor
|
||||
// bitmap. See score.h for the layout.
|
||||
//
|
||||
// In multicolor bitmap mode the bitmap is 160 px wide × 200 px tall
|
||||
// (2 bpp) = 8000 bytes. One row of pixels = 40 bytes (160 px × 2 bpp
|
||||
// / 8 bits). The top 8 rows = 320 bytes at $E000..$E13F.
|
||||
//
|
||||
// We draw three kinds of things into those 320 bytes:
|
||||
// 1. 4×8 label characters (one cell = 8 bytes of bitmap).
|
||||
// 2. 8×8 pips (two cells = 16 bytes of bitmap).
|
||||
// 3. Background fill (00 pixel = $D021 black) for the cells outside
|
||||
// the labels and pips (the margins).
|
||||
//
|
||||
// All visible pixels are either "00" (black, from $D021) or "11" (white,
|
||||
// from color RAM). We rewrite the top 40 cells' attributes
|
||||
// (screen memory → 0, color RAM → 1) so the cell palette is exactly
|
||||
// 2 colors and we don't have to think about 01/10 mismatches across
|
||||
// the 4 cells of the bar.
|
||||
|
||||
#include "score.h"
|
||||
|
||||
// Player scores. 0..5 (clamped at the render side). Stored in zero-page-
|
||||
// adjacent BSS, default 0. Phase 4 will mutate these on WIN_P1/WIN_P2.
|
||||
byte score_p1 = 0;
|
||||
byte score_p2 = 0;
|
||||
|
||||
// --- custom 4×8 font ---------------------------------------------------
|
||||
//
|
||||
// 8 characters, each 4 px wide × 8 px tall = 8 bytes (one byte per row,
|
||||
// top 4 bits are the 4 pixels of that row, MSB = leftmost pixel).
|
||||
// 64 bytes total. Bottom 4 bits of every byte are 0.
|
||||
//
|
||||
// Index:
|
||||
// 0 = H, 1 = A, 2 = R, 3 = E, 4 = S, 5 = C, 6 = O, 7 = T
|
||||
static const char font[8][8] = {
|
||||
// H (1 0 0 1) × 3, (1 1 1 1), (1 0 0 1) × 4
|
||||
{ 0x90, 0x90, 0x90, 0xF0, 0x90, 0x90, 0x90, 0x90 },
|
||||
// A (0 1 1 0), (1 0 0 1) × 2, (1 1 1 1), (1 0 0 1) × 4
|
||||
{ 0x60, 0x90, 0x90, 0xF0, 0x90, 0x90, 0x90, 0x90 },
|
||||
// R (1 1 1 0), (1 0 0 1) × 2, (1 1 1 0), (1 0 1 0),
|
||||
// (1 0 0 1) × 3
|
||||
{ 0xE0, 0x90, 0x90, 0xE0, 0xA0, 0x90, 0x90, 0x90 },
|
||||
// E (1 1 1 1), (1 0 0 0) × 2, (1 1 1 0), (1 0 0 0) × 3, (1 1 1 1)
|
||||
{ 0xF0, 0x80, 0x80, 0xE0, 0x80, 0x80, 0x80, 0xF0 },
|
||||
// S (0 1 1 1), (1 0 0 0) × 2, (0 1 1 0), (0 0 0 1) × 3, (1 1 1 0)
|
||||
{ 0x70, 0x80, 0x80, 0x60, 0x10, 0x10, 0x10, 0xE0 },
|
||||
// C (0 1 1 0), (1 0 0 1), (1 0 0 0) × 4, (1 0 0 1), (0 1 1 0)
|
||||
{ 0x60, 0x90, 0x80, 0x80, 0x80, 0x80, 0x90, 0x60 },
|
||||
// O (0 1 1 0), (1 0 0 1) × 5, (0 1 1 0)
|
||||
{ 0x60, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x60 },
|
||||
// T (1 1 1 1), (0 0 1 0) × 7
|
||||
{ 0xF0, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20 },
|
||||
};
|
||||
|
||||
// --- pixel-pair expansion table ----------------------------------------
|
||||
//
|
||||
// For each 4-bit pixel pattern (1 = on, 0 = off), the corresponding
|
||||
// multicolor bitmap byte. Each "1" becomes "11" (white, from color RAM);
|
||||
// each "0" becomes "00" (background, from $D021 = black).
|
||||
//
|
||||
// bit pattern | multicolor byte
|
||||
// 0000 | 00 00 00 00 = 0x00
|
||||
// 0001 | 00 00 00 11 = 0x03
|
||||
// 0010 | 00 00 11 00 = 0x0C
|
||||
// 0011 | 00 00 11 11 = 0x0F
|
||||
// 0100 | 00 11 00 00 = 0x30
|
||||
// 0101 | 00 11 00 11 = 0x33
|
||||
// 0110 | 00 11 11 00 = 0x3C
|
||||
// 0111 | 00 11 11 11 = 0x3F
|
||||
// 1000 | 11 00 00 00 = 0xC0
|
||||
// 1001 | 11 00 00 11 = 0xC3
|
||||
// 1010 | 11 00 11 00 = 0xCC
|
||||
// 1011 | 11 00 11 11 = 0xCF
|
||||
// 1100 | 11 11 00 00 = 0xF0
|
||||
// 1101 | 11 11 00 11 = 0xF3
|
||||
// 1110 | 11 11 11 00 = 0xFC
|
||||
// 1111 | 11 11 11 11 = 0xFF
|
||||
static const char expand4[16] = {
|
||||
0x00, 0x03, 0x0C, 0x0F, 0x30, 0x33, 0x3C, 0x3F,
|
||||
0xC0, 0xC3, 0xCC, 0xCF, 0xF0, 0xF3, 0xFC, 0xFF
|
||||
};
|
||||
|
||||
// --- pip bitmaps -------------------------------------------------------
|
||||
//
|
||||
// 8×8 pixels = 2 cells wide × 1 cell tall = 16 bytes of bitmap
|
||||
// (8 rows × 2 bytes/row, 4 pixels per byte in 2 bpp).
|
||||
//
|
||||
// Filled pip: 1-pixel white border around a 6×6 black block.
|
||||
// Empty pip: 8×8 white square (the "border only" with no fill).
|
||||
//
|
||||
// Row 0 = top border: 8 white pixels = $FF $FF
|
||||
// Rows 1..6: 1 white + 6 black + 1 white:
|
||||
// left byte = 11 00 00 00 = 0xC0
|
||||
// right byte = 00 00 00 11 = 0x03
|
||||
// Row 7 = bottom border: $FF $FF
|
||||
static const char pip_filled[16] = {
|
||||
0xFF, 0xFF, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03,
|
||||
0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xFF, 0xFF
|
||||
};
|
||||
|
||||
static const char pip_empty[16] = {
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
|
||||
};
|
||||
|
||||
// --- cell layout constants --------------------------------------------
|
||||
//
|
||||
// The score bar fits in the top 40 cells (one character row). Each
|
||||
// cell is 4 px wide × 8 px tall = 8 bytes of bitmap. The whole top
|
||||
// row = 40 × 8 = 320 bytes at $E000..$E13F.
|
||||
|
||||
#define SCORE_BAR_BYTES 320
|
||||
#define CELLS_PER_ROW 40
|
||||
#define BYTES_PER_CELL 8
|
||||
#define SCORE_BITMAP_BASE ((char *)0xE000)
|
||||
#define SCORE_SCREEN_BASE ((char *)0xD000)
|
||||
#define SCORE_CRAM_BASE ((char *)0xD800)
|
||||
|
||||
// Cell indices for the labels and pip groups (see score.h for diagram).
|
||||
// Pips are 2 cells wide; the left pips start at cell 7, the right pips
|
||||
// at cell 22. Stride between adjacent pips is 2 cells (no gap; the
|
||||
// 1-px borders of adjacent pips merge into a 2-px white vertical line).
|
||||
#define HARE_FIRST_CELL 2
|
||||
#define LEFT_PIPS_FIRST_CELL 7
|
||||
#define RIGHT_PIPS_FIRST_CELL 22
|
||||
#define SCOOT_FIRST_CELL 33
|
||||
|
||||
// --- helpers -----------------------------------------------------------
|
||||
|
||||
static void clear_score_bar(void)
|
||||
{
|
||||
for (unsigned i = 0; i < SCORE_BAR_BYTES; i++)
|
||||
SCORE_BITMAP_BASE[i] = 0;
|
||||
}
|
||||
|
||||
static void setup_score_bar_cells(void)
|
||||
{
|
||||
// Top 40 cells: screen memory = 0, color RAM = 1.
|
||||
// Screen memory 0 means both "01" and "10" pixel codes map to black
|
||||
// (we don't use either code in the score bar). Color RAM 1 means
|
||||
// the "11" pixel code maps to white — that's the color of the
|
||||
// pip borders and the label characters.
|
||||
for (unsigned i = 0; i < CELLS_PER_ROW; i++) {
|
||||
SCORE_SCREEN_BASE[i] = 0;
|
||||
SCORE_CRAM_BASE[i] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void render_char(unsigned cell_idx, unsigned char_idx)
|
||||
{
|
||||
char *dst = SCORE_BITMAP_BASE + cell_idx * BYTES_PER_CELL;
|
||||
const char *src = font[char_idx];
|
||||
for (unsigned row = 0; row < BYTES_PER_CELL; row++) {
|
||||
unsigned nibble = ((unsigned)src[row] >> 4) & 0x0F;
|
||||
dst[row] = expand4[nibble];
|
||||
}
|
||||
}
|
||||
|
||||
static void render_pip(unsigned cell_idx, char filled)
|
||||
{
|
||||
char *dst = SCORE_BITMAP_BASE + cell_idx * BYTES_PER_CELL;
|
||||
const char *src = filled ? pip_filled : pip_empty;
|
||||
for (unsigned i = 0; i < 16; i++)
|
||||
dst[i] = src[i];
|
||||
}
|
||||
|
||||
// --- public API --------------------------------------------------------
|
||||
|
||||
void score_init(void)
|
||||
{
|
||||
score_p1 = 0;
|
||||
score_p2 = 0;
|
||||
}
|
||||
|
||||
void score_render(void)
|
||||
{
|
||||
// Clamp to 0..5 so a future bug in Phase 4 (e.g. score == 6 on a
|
||||
// double-tap) doesn't write past the pip array.
|
||||
byte p1 = score_p1;
|
||||
if (p1 > 5) p1 = 5;
|
||||
byte p2 = score_p2;
|
||||
if (p2 > 5) p2 = 5;
|
||||
|
||||
setup_score_bar_cells();
|
||||
clear_score_bar();
|
||||
|
||||
// HARE label: 4 cells, 4 unique chars.
|
||||
render_char(HARE_FIRST_CELL + 0, 0); // H
|
||||
render_char(HARE_FIRST_CELL + 1, 1); // A
|
||||
render_char(HARE_FIRST_CELL + 2, 2); // R
|
||||
render_char(HARE_FIRST_CELL + 3, 3); // E
|
||||
|
||||
// 5 pips for player 1 (left side). 2 cells per pip, no gap.
|
||||
for (unsigned p = 0; p < 5; p++)
|
||||
render_pip(LEFT_PIPS_FIRST_CELL + p * 2, (byte)p < p1);
|
||||
|
||||
// 5 pips for player 2 (right side).
|
||||
for (unsigned p = 0; p < 5; p++)
|
||||
render_pip(RIGHT_PIPS_FIRST_CELL + p * 2, (byte)p < p2);
|
||||
|
||||
// SCOOT label: 5 cells, 4 unique chars (O repeats).
|
||||
render_char(SCOOT_FIRST_CELL + 0, 4); // S
|
||||
render_char(SCOOT_FIRST_CELL + 1, 5); // C
|
||||
render_char(SCOOT_FIRST_CELL + 2, 6); // O
|
||||
render_char(SCOOT_FIRST_CELL + 3, 6); // O
|
||||
render_char(SCOOT_FIRST_CELL + 4, 7); // T
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
#ifndef WHACK_HARE_SCORE_H
|
||||
#define WHACK_HARE_SCORE_H
|
||||
|
||||
// score.h — score state and rendering for the top-of-screen score bar.
|
||||
//
|
||||
// The score bar lives in the top 8 rows of the multicolor bitmap at
|
||||
// $E000-$E13F. Those rows are one full character row of the screen
|
||||
// (40 cells × 8 px tall × 2 bpp = 320 bytes). show_screen() loads the
|
||||
// title artwork into the bitmap; score_render() overwrites the first
|
||||
// 320 bytes with the score bar.
|
||||
//
|
||||
// Layout (40 cells = 160 px, since multicolor is half-width hires):
|
||||
//
|
||||
// cell 0 1 2 3 4 5 6 7 ... 16 17 18 19 20 21 22 ... 31 32 33 34 35 36 37 38 39
|
||||
// |marg| H A R E |gap| 5 pips |marg|marg| 5 pips |gap| S C O O T |marg|
|
||||
//
|
||||
// HARE label: cells 2..5 (4 chars)
|
||||
// 5 pips P1: cells 7..16 (10 cells, 2 cells per pip, no gap)
|
||||
// 5 pips P2: cells 22..31 (10 cells)
|
||||
// SCOOT label: cells 33..37 (5 chars)
|
||||
//
|
||||
// Each pip is 8×8 pixels (2 cells wide × 1 cell tall) = 16 bytes of bitmap:
|
||||
// - filled: 1-pixel white border around a 6×6 black block
|
||||
// - empty: just the white border (an 8×8 white square with no inside)
|
||||
//
|
||||
// Each label char is 4×8 pixels (1 cell) = 8 bytes of bitmap, drawn as
|
||||
// "11" (white) on "00" (background black). Cell attributes for the top
|
||||
// row are rewritten: screen memory = 0 (both halves black), color RAM =
|
||||
// 1 (white, so the "11" pixel value maps to white).
|
||||
//
|
||||
// All 4 colors used by the bitmap (00/01/10/11) per c64-wiki:
|
||||
// 00 -> $D021 (background; set to 0 = black by show_screen)
|
||||
// 01 -> high nibble of screen mem (we set 0 = black; unused here)
|
||||
// 10 -> low nibble of screen mem (we set 0 = black; unused here)
|
||||
// 11 -> color RAM nibble (we set 1 = white for the bar's cells)
|
||||
// The score bar only uses 00 and 11, so the cell palette is effectively
|
||||
// 2 colors: black and white.
|
||||
|
||||
#include <c64/types.h>
|
||||
|
||||
// Player 1 (Hare) score, 0..5.
|
||||
extern byte score_p1;
|
||||
|
||||
// Player 2 (Scoot) score, 0..5.
|
||||
extern byte score_p2;
|
||||
|
||||
// score_init() — zero both scores. Called once at startup.
|
||||
// (The variables already default to 0 in BSS; this is for symmetry with
|
||||
// the future per-state initialization in Phase 4.)
|
||||
void score_init(void);
|
||||
|
||||
// score_render() — draw the score bar into the top 8 rows of the active
|
||||
// multicolor bitmap at $E000. Overwrites the top row of the current
|
||||
// screen's artwork; the rest of the screen is untouched.
|
||||
//
|
||||
// Call this after show_screen() to overlay the score bar on top of the
|
||||
// screen art. In Phase 4 it will be called on entry to each state.
|
||||
void score_render(void);
|
||||
|
||||
#pragma compile("score.c")
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user