mirror of
https://github.com/imjasonh/nescript
synced 2026-07-08 17:06:04 +00:00
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
100 lines
3.3 KiB
Text
100 lines
3.3 KiB
Text
// pong/play_state.ne — the Playing state and its inner phase machine.
|
|
//
|
|
// The phase machine cycles through P_SERVE → P_PLAY → P_POINT:
|
|
//
|
|
// P_SERVE — brief countdown, then serve_ball() and switch to P_PLAY
|
|
// P_PLAY — normal gameplay: update paddles, balls, powerups, draw
|
|
// P_POINT — a side just scored; short pause then re-serve (or Victory)
|
|
|
|
// Inline helper to set the phase and zero the timer atomically.
|
|
inline fun set_phase(p: u8) {
|
|
phase = p
|
|
phase_timer = 0
|
|
}
|
|
|
|
state Playing {
|
|
on enter {
|
|
// Reset scores and paddle state on a fresh game.
|
|
score[0] = 0
|
|
score[1] = 0
|
|
paddle_y[0] = 96
|
|
paddle_y[1] = 96
|
|
paddle_long[0] = 0
|
|
paddle_long[1] = 0
|
|
paddle_fast[0] = 0
|
|
paddle_fast[1] = 0
|
|
paddle_multi[0] = 0
|
|
paddle_multi[1] = 0
|
|
serving_side = SIDE_RIGHT // first serve goes toward the right paddle
|
|
set_phase(P_SERVE)
|
|
}
|
|
|
|
on frame {
|
|
global_tick += 1
|
|
phase_timer += 1
|
|
|
|
// ── Phase dispatch ───────────────────────────────
|
|
// Using `match` so at most one arm executes per frame
|
|
// (prevents one phase from advancing into the next in
|
|
// the same frame — the same rationale as war's phase
|
|
// machine).
|
|
match phase {
|
|
P_SERVE => {
|
|
// Draw the static table while the serve countdown
|
|
// ticks. After FRAMES_SERVE frames, launch the ball
|
|
// and start play.
|
|
draw_center_line()
|
|
draw_scores()
|
|
draw_paddles()
|
|
|
|
if phase_timer >= FRAMES_SERVE {
|
|
serve_ball(serving_side)
|
|
set_phase(P_PLAY)
|
|
}
|
|
}
|
|
P_PLAY => {
|
|
// Core gameplay loop.
|
|
step_paddles()
|
|
step_balls()
|
|
step_powerup()
|
|
|
|
draw_center_line()
|
|
draw_scores()
|
|
draw_paddles()
|
|
draw_balls()
|
|
draw_powerup()
|
|
}
|
|
P_POINT => {
|
|
// Short pause after a score before re-serving.
|
|
// Keep drawing the table and scores so the new
|
|
// score reads on-screen.
|
|
draw_center_line()
|
|
draw_scores()
|
|
draw_paddles()
|
|
|
|
if phase_timer >= FRAMES_POINT {
|
|
// Check for match win.
|
|
if score[SIDE_LEFT] >= WIN_SCORE {
|
|
winner = 0
|
|
transition Victory
|
|
}
|
|
if score[SIDE_RIGHT] >= WIN_SCORE {
|
|
winner = 1
|
|
transition Victory
|
|
}
|
|
// Not over yet — serve again. The serving_side
|
|
// was set in on_score() to the scoring side, so
|
|
// the ball is served TOWARD the side that lost
|
|
// the point.
|
|
if serving_side == SIDE_LEFT {
|
|
serving_side = SIDE_RIGHT
|
|
} else {
|
|
serving_side = SIDE_LEFT
|
|
}
|
|
set_phase(P_SERVE)
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|