1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 17:06:04 +00:00
nescript/examples/pong/input.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

117 lines
3.9 KiB
Text

// pong/input.ne — paddle update (human + CPU).
//
// `step_paddles()` is called every frame by the Playing state
// phase machine. Humans move their paddle up/down based on the
// D-pad on their controller; CPU paddles (is_cpu[side] == 1)
// are driven by `step_cpu_paddle(side)` which tracks the nearest
// active ball's y position with a one-frame lag and a small miss
// zone so the AI feels beatable.
//
// Clamping: paddles can never leave the playfield vertically.
// `move_paddle_up` / `move_paddle_down` handle the clamp so the
// caller just has to ask for motion in one direction at a time.
// Move a paddle upward by PADDLE_SPEED, clamped to PLAYFIELD_TOP.
fun move_paddle_up(side: u8) {
if paddle_y[side] >= PLAYFIELD_TOP + PADDLE_SPEED {
paddle_y[side] -= PADDLE_SPEED
} else {
paddle_y[side] = PLAYFIELD_TOP
}
}
// Move a paddle downward by PADDLE_SPEED, clamped to
// (PLAYFIELD_BOTTOM - paddle_height). The paddle height depends
// on whether this side is currently in long-paddle mode.
fun move_paddle_down(side: u8) {
var h: u8 = PADDLE_H
if paddle_long[side] > 0 {
h = PADDLE_H_LONG
}
var max_y: u8 = PLAYFIELD_BOTTOM - h
if paddle_y[side] + PADDLE_SPEED <= max_y {
paddle_y[side] += PADDLE_SPEED
} else {
paddle_y[side] = max_y
}
}
// ── CPU AI ────────────────────────────────────────────────
//
// The CPU tracks the y-centre of the nearest active ball that
// is heading toward its side. It moves CPU_SPEED px per frame
// toward the ball's y, but only if the ball is more than 4 px
// away from the paddle centre (the "dead zone" that makes the
// AI imperfect and lets fast balls score occasionally).
//
// If no ball is heading toward this side, the CPU drifts toward
// the playfield center so it isn't caught flat-footed on the
// next serve.
fun step_cpu_paddle(side: u8) {
// Find the nearest ball heading toward this side.
var target_y: u8 = 112 // default: field centre
var found: u8 = 0
var j: u8 = 0
while j < MAX_BALLS {
if ball_active[j] == 1 {
// Ball heading left → of interest to the left paddle
// Ball heading right → of interest to the right paddle
if side == SIDE_LEFT and ball_dx_sign[j] == 1 {
target_y = ball_y[j] + (BALL_SIZE >> 1)
found = 1
}
if side == SIDE_RIGHT and ball_dx_sign[j] == 0 {
target_y = ball_y[j] + (BALL_SIZE >> 1)
found = 1
}
}
j += 1
}
// Aim the paddle centre at target_y.
var ph: u8 = PADDLE_H
if paddle_long[side] > 0 {
ph = PADDLE_H_LONG
}
var centre: u8 = paddle_y[side] + (ph >> 1)
// Dead zone: don't jitter if we're already close.
if centre + 4 < target_y {
// Need to move down.
if paddle_y[side] + CPU_SPEED + ph <= PLAYFIELD_BOTTOM {
paddle_y[side] += CPU_SPEED
}
}
if centre > target_y + 4 {
// Need to move up.
if paddle_y[side] >= PLAYFIELD_TOP + CPU_SPEED {
paddle_y[side] -= CPU_SPEED
}
}
}
// Drive both paddles from controller input (humans) or the CPU
// AI (bots) this frame. The per-player button reads are hand-
// rolled because `p1` / `p2` are compile-time syntactic prefixes.
fun step_paddles() {
if is_cpu[SIDE_LEFT] == 0 {
if button.up {
move_paddle_up(SIDE_LEFT)
}
if button.down {
move_paddle_down(SIDE_LEFT)
}
} else {
step_cpu_paddle(SIDE_LEFT)
}
if is_cpu[SIDE_RIGHT] == 0 {
if p2.button.up {
move_paddle_up(SIDE_RIGHT)
}
if p2.button.down {
move_paddle_down(SIDE_RIGHT)
}
} else {
step_cpu_paddle(SIDE_RIGHT)
}
}