1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 08:55:38 +00:00
nescript/examples/war.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

100 lines
4.4 KiB
Text

// War — a full NES port of the card game.
//
// This is a production-quality example: a title screen with a menu,
// an animated deal, cards that slide between two decks and a central
// play area with a running card count, a dedicated "WAR!" tie-break
// with buried cards, and a victory screen with a fanfare. Every
// piece of the game (logic, art, sound, states) is authored in
// NEScript itself — no external assets, no external tooling.
//
// The source is split across examples/war/*.ne files. This top-level
// file declares the hardware config, the reset-time palette and
// background, the one big Tileset sprite block that carries every
// custom CHR tile, the audio includes, the state includes, and the
// `start` declaration. Every include is processed by the parser's
// preprocess pass before lexing, so it's exactly as if the whole
// game lived in one .ne file.
//
// The file layout mirrors how a real NES game would be organised:
//
// examples/war.ne — this file, the top-level game
// examples/war/PLAN.md — living design doc
// examples/war/constants.ne — layout + gameplay constants
// examples/war/audio.ne — sfx + music declarations
// examples/war/state.ne — global variables
// examples/war/rng.ne — 8-bit LFSR PRNG
// examples/war/deck.ne — queue operations on the decks
// examples/war/compare.ne — card rank/suit extraction
// examples/war/render.ne — card + digit drawing helpers
// examples/war/title_state.ne — Title state + menu
// examples/war/deal_state.ne — Deal state + dealing animation
// examples/war/play_state.ne — Playing state + phase machine
// examples/war/victory_state.ne — Victory state
//
// Controls (human players):
// D-pad up/down — move the title menu cursor
// A / Start — confirm a menu choice / draw the next card
// / return to the title from the victory screen
//
// In 0-player mode both players are CPU and the game plays itself;
// in 1-player mode you are player A and the CPU is player B; in
// 2-player mode both sides are human. The headless jsnes golden
// harness boots straight into 0-player mode (title auto-confirms
// after ~45 frames with no input) so the captured frame is always a
// scene from actual gameplay.
//
// Build: cargo run --release -- build examples/war.ne
// Output: examples/war.nes
game "War" {
mapper: NROM
mirroring: horizontal
}
// ── Palette ─────────────────────────────────────────────────
//
// Grouped form with a shared `universal:` so the $3F10 mirror
// trap is handled automatically. The sprite sub-palette 0 is the
// one the NEScript codegen hardwires into the OAM attribute byte,
// so every sprite in the game renders through it. Four colours:
//
// 0 = transparent (universal — dk_green felt)
// 1 = red (hearts, diamonds, accents)
// 2 = white (card face, text)
// 3 = black (outlines, spades, clubs)
//
// The three bg sub-palettes carry the felt, the cream banner, and
// a warm accent for the "WAR!" flash; bg3 stays reserved for
// future tweaks.
palette Main {
universal: dk_green // green felt table colour
bg0: [forest, green, mint] // felt table base + subtle pattern
bg1: [dk_red, red, white] // cream / red banners
bg2: [black, lt_gray, white] // card body on the nametable
bg3: [dk_olive, olive, yellow] // warm accent, "WAR!" flash
sp0: [red, white, black] // every sprite uses this
sp1: [dk_red, red, peach] // reserved
sp2: [dk_blue, blue, sky_blue] // reserved
sp3: [dk_gray, lt_gray, white] // reserved
}
// Pull in everything else. Order matters only for symbol visibility:
// constants before the state variables that use them, state variables
// before helper functions, helper functions before state handlers.
include "war/constants.ne"
include "war/assets.ne"
include "war/background.ne"
include "war/audio.ne"
include "war/state.ne"
include "war/rng.ne"
include "war/deck.ne"
include "war/compare.ne"
include "war/render.ne"
include "war/title_state.ne"
include "war/deal_state.ne"
include "war/play_state.ne"
include "war/victory_state.ne"
start Title