1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-10 01:37:45 +00:00
nescript/examples/war/victory_state.ne
Claude 8ababdcec4
examples/war: working end-to-end War card game
A complete, playable port of the card game War: title screen with
0/1/2 player menu, animated deal, sliding cards, deck-count HUD, a
"WAR!" tie-break with buried cards, and a victory screen with a
fanfare. Source split across examples/war/*.ne (constants, assets,
audio, deck/queue logic, RNG, render helpers, and one state file
per game state) and pulled in via examples/war.ne.

Drives nearly every NEScript subsystem at once: custom 88-tile
sprite sheet (card frames, ranks, suits, font, BIG WAR letters);
felt background nametable; pulse-1 / pulse-2 / noise sfx; looping
march on pulse 2; an 8-bit Galois LFSR PRNG; queue-based decks
that conserve cards across rounds; a phase machine inside the
Playing state that handles draw/reveal/win/war/check; and an
autopilot that boots straight into 0-PLAYERS mode so the headless
jsnes harness captures real gameplay at frame 180.

While building this I uncovered five compiler bugs / limitations
in the v0.1 implementation; each is documented with a minimal
reproduction, root cause, current workaround, and proposed fix in
examples/war/COMPILER_BUGS.md. The most painful was the
parameter-VarId aliasing one (#1b) — two functions sharing a
parameter NAME end up sharing a single zero-page slot mapping
across the whole program. Once those compiler bugs are fixed, the
workarounds in war/*.ne should be reverted in the same PR.

https://claude.ai/code/session_0143dTgh3UeRrtfHgQwzcv5z
2026-04-15 15:22:20 +00:00

73 lines
2.5 KiB
Text

// war/victory_state.ne — the Victory state.
//
// Big "PLAYER A WINS" or "PLAYER B WINS" banner, the builtin
// `fanfare` music, an auto-return to Title after
// VICTORY_LINGER_FRAMES frames, and a manual "press A to skip"
// path.
state Victory {
on enter {
victory_timer = 0
start_music fanfare
}
on frame {
global_tick += 1
victory_timer += 1
// ── Banner ──────────────────────────────────────
// "PLAYER X" on row 1, "WINS" on row 2. Splitting the
// banner across two y rows keeps each scanline under the
// NES's 8-sprite-per-scanline limit (the "PLAYER X WINS"
// single-line version was 11 sprites tall and dropped
// letters past the 8th).
draw_word_player(64, 80)
if winner == 0 {
draw_letter(120, 80, 0) // A
} else {
draw_letter(120, 80, 1) // B
}
draw_word_wins(96, 96)
// Flourish: draw the winning deck's top card in the
// middle of the screen as a victory showcase. If the
// deck is empty (shouldn't happen — we only arrive
// here because the *loser* is empty) fall back to a
// card back so the layout is stable.
if winner == 0 {
if deck_a_count > 0 {
var vic_top_a: u8 = deck_a[deck_a_front]
draw_card_face(120, 128, vic_top_a)
} else {
draw_card_back(120, 128)
}
} else {
if deck_b_count > 0 {
var vic_top_b: u8 = deck_b[deck_b_front]
draw_card_face(120, 128, vic_top_b)
} else {
draw_card_back(120, 128)
}
}
// Row of tiny hearts under the banner.
draw Tileset at: (80, 168) frame: TILE_HEART_TINY
draw Tileset at: (96, 168) frame: TILE_HEART_TINY
draw Tileset at: (112, 168) frame: TILE_HEART_TINY
draw Tileset at: (128, 168) frame: TILE_HEART_TINY
draw Tileset at: (144, 168) frame: TILE_HEART_TINY
draw Tileset at: (160, 168) frame: TILE_HEART_TINY
// ── Input + auto-return ─────────────────────────
if button.a or button.start {
transition Title
}
if victory_timer >= VICTORY_LINGER_FRAMES {
transition Title
}
}
on exit {
stop_music
}
}