1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00
nescript/examples/pong/state.ne
Claude 21b91f6398
examples/pong: production-quality Pong game with powerups and multi-ball
A complete, playable Pong game split across examples/pong/*.ne files
and pulled in from a top-level examples/pong.ne. Features:

- **Title screen** with a 3-option menu (CPU VS CPU / 1 PLAYER /
  2 PLAYERS), a cursor sprite, blinking "PRESS A" prompt, brisk
  title march on pulse 2, and autopilot that auto-confirms CPU VS
  CPU after 45 frames of no input so the headless jsnes golden
  harness reaches gameplay by frame 180.

- **Ball physics** with signed-magnitude velocity (u8 magnitude +
  sign bit per axis), wall bounce at top/bottom, paddle AABB
  collision with push-out, and score-out detection at left/right
  exits.

- **Multi-ball** via parallel ball_* arrays (MAX_BALLS = 3). Each
  ball scores a point independently; the round continues until the
  last ball exits the playfield.

- **CPU AI** that tracks the nearest active ball heading toward its
  side with a per-frame step, 4 px dead zone, and CPU_SPEED = 1 so
  rallies can end naturally.

- **Three powerup types** that spawn every ~4 seconds, bounce off
  all four walls, and are caught by paddle AABB overlap:
  1. LONG — extends the catching paddle from 24 → 40 px for 5 hits
  2. FAST — doubles ball x-velocity on the catcher's next hit
  3. MULTI — spawns two extra balls on the catcher's next hit

- **Victory** at first-to-7 with a "PLAYER N WINS" banner and the
  builtin fanfare, auto-returning to Title.

- **Audio**: 5 user-declared sfx (WallBounce, PaddleHit, Score,
  PowerSpawn, PowerCatch) plus a title march and the builtin
  fanfare for victory.

Source layout mirrors examples/war:

    examples/pong.ne               top-level game shell
    examples/pong/PLAN.md          living design doc
    examples/pong/constants.ne     layout + gameplay constants
    examples/pong/assets.ne        45-tile Tileset (paddles, ball, alphabet,
                                   digits, cursor, center-line, powerup icons)
    examples/pong/audio.ne         sfx + music declarations
    examples/pong/state.ne         all mutable globals
    examples/pong/rng.ne           8-bit Galois LFSR
    examples/pong/render.ne        draw helpers
    examples/pong/input.ne         paddle step (human + CPU AI)
    examples/pong/ball.ne          multi-ball physics + paddle collision
    examples/pong/powerup.ne       powerup entity (spawn, bounce, catch, apply)
    examples/pong/title_state.ne   state Title + menu
    examples/pong/play_state.ne    state Playing (P_SERVE/P_PLAY/P_POINT)
    examples/pong/victory_state.ne state Victory

Verification:
- 616 compiler unit tests pass (cargo test --all-targets)
- cargo fmt / cargo clippy --all-targets -- -D warnings clean
- 33/33 emulator harness goldens match
- examples/pong.nes builds byte-identically from source

https://claude.ai/code/session_0134F5uwDEVTes2Ee9S7JeXy
2026-04-16 01:25:29 +00:00

88 lines
4.3 KiB
Text

// pong/state.ne — every mutable variable the game touches.
//
// As in the war example, keeping these as globals means helper
// functions can read and write without taking parameters and
// returning values — the 6502's 8-register ABI makes every extra
// parameter an extra LDA/STA pair, so the shorter call signatures
// pay for themselves in the hot loop.
// ── Paddles ──────────────────────────────────────────────
//
// Two parallel u8 arrays indexed by `side` (SIDE_LEFT = 0, SIDE_RIGHT = 1).
// `paddle_y[i]` — top-edge y of paddle i
// `paddle_long[i]` — remaining LONG-paddle hits (0 = normal)
// `paddle_fast[i]` — 1 if catcher's next hit doubles ball x velocity
// `paddle_multi[i]` — 1 if catcher's next hit spawns two extra balls
// `score[i]` — current score (0..WIN_SCORE)
// `is_cpu[i]` — 1 if this side is CPU-controlled
var paddle_y: u8[2] = [96, 96]
var paddle_long: u8[2] = [0, 0]
var paddle_fast: u8[2] = [0, 0]
var paddle_multi: u8[2] = [0, 0]
var score: u8[2] = [0, 0]
var is_cpu: u8[2] = [1, 1]
// ── Balls ────────────────────────────────────────────────
//
// Parallel u8 arrays, one slot per ball up to MAX_BALLS. Each
// ball's velocity is stored as a (magnitude, sign) pair so the
// existing u8-only arithmetic suffices — sign 0 means +x right
// / +y down, sign 1 means -x left / -y up.
//
// `ball_active[i]` — 1 if this slot is in play, 0 if free
// `ball_x[i]` — top-left x in px
// `ball_y[i]` — top-left y in px
// `ball_dx[i]` — x speed in px/frame
// `ball_dy[i]` — y speed in px/frame
// `ball_dx_sign[i]` — 0 = moving right, 1 = moving left
// `ball_dy_sign[i]` — 0 = moving down, 1 = moving up
var ball_active: u8[3] = [0, 0, 0]
var ball_x: u8[3] = [0, 0, 0]
var ball_y: u8[3] = [0, 0, 0]
var ball_dx: u8[3] = [0, 0, 0]
var ball_dy: u8[3] = [0, 0, 0]
var ball_dx_sign: u8[3] = [0, 0, 0]
var ball_dy_sign: u8[3] = [0, 0, 0]
// ── Powerup ──────────────────────────────────────────────
//
// Single-slot powerup entity. `powerup_kind == PWR_NONE` means
// nothing is on screen; any other value is the current kind plus
// its position and velocity. `powerup_timer` counts frames since
// spawn so we can despawn after POWERUP_LIFE_FRAMES.
var powerup_kind: u8 = 0
var powerup_x: u8 = 0
var powerup_y: u8 = 0
var powerup_dx_sign: u8 = 0
var powerup_dy_sign: u8 = 0
var powerup_timer: u16 = 0
var powerup_cooldown:u16 = 0 // counts down to the next spawn
// ── Game mode + phase machine ────────────────────────────
var mode: u8 = 0 // 0/1/2 — CPU vs CPU / human vs CPU / human vs human
var phase: u8 = 0 // one of the P_* constants
var phase_timer: u8 = 0 // frames spent in the current phase
var serving_side:u8 = 0 // which side serves next (alternates)
// ── Title menu ────────────────────────────────────────────
var title_cursor: u8 = 0
var title_timer: u8 = 0
var title_blink: u8 = 0
var title_debounce: u8 = 0
// ── Victory state ─────────────────────────────────────────
var winner: u8 = 0
var victory_timer: u16 = 0
// ── RNG ──────────────────────────────────────────────────
//
// 8-bit Galois LFSR state. Seeded from the title frame counter at
// the moment the title transitions to gameplay so the shuffle of
// the first serve direction and powerup kinds is deterministic
// once a user commits. The jsnes headless harness always hits the
// title autopilot at the same frame, so the seed is stable there
// too.
var rng_state: u8 = 0xA7
// ── Global free-running frame counter ────────────────────
var global_tick: u16 = 0