1
0
Fork 0
mirror of https://github.com/imjasonh/nescript synced 2026-07-08 17:06:04 +00:00
nescript/examples/loop_break_continue.ne

109 lines
3.2 KiB
Text
Raw Normal View History

examples: add 5 new programs covering match/loop/logic/bitwise/scanline Fills the biggest feature-coverage gaps in the existing example set: - match_demo.ne — match statement over a Screen enum, driving a title / playing / paused / game-over flow with a debounced controller. - loop_break_continue.ne — `loop { ... }` with `break` and `continue`, scanning an enemy array for the first hit. - logic_ops.ne — keyword-based `and` / `or` / `not` gating movement and scoring on alive/paused flags. - bitwise_ops.ne — packed status-byte flags with `&` / `|` / `^` / `>>` plus a health-bar render loop. - scanline_split.ne — MMC3 `on scanline(120)` handler rewriting the scroll register mid-frame for a classic status-bar split. All 14 examples (9 existing + 5 new) pass the jsnes smoke test (`14/14 ROMs rendered successfully`) and still pass `cargo fmt`, `cargo clippy -D warnings`, and `cargo test`. Known limitations surfaced while authoring these examples, to be fixed in follow-up commits: 1. Array-literal global initializers (`var xs: u8[4] = [1,2,3,4]`) are silently dropped by `lower_program` — `eval_const` returns None for `Expr::ArrayLiteral` and no synthetic per-element init code is emitted. Affects `arrays_and_functions`, `structs_enums_for`, `loop_break_continue`, and any future array-using example. Arrays effectively boot at all-zero. 2. `draw` inside a loop body reuses one static OAM slot — `next_oam_slot` increments at IR-codegen time rather than at runtime, so N iterations all write to the same 4-byte OAM entry. Affects `arrays_and_functions`, `structs_enums_for`, `bitwise_ops` (health pips), and any loop that wants to render per-iteration sprites. Both bugs are latent and didn't surface until I tried to write examples that exercise the relevant features — the existing integration tests only check iNES header structure, and the jsnes smoke test's "at least one sprite rendered" bar is satisfied by one sprite even when several were intended. https://claude.ai/code/session_014Z5y3Q9krLcAxYpZQJhZ5V
2026-04-12 19:05:18 +00:00
// Loop / Break / Continue — demonstrates `loop`, `break`, and
// `continue` via a linear scan that finds the first "hazard" the
// player is overlapping.
//
// NEScript's sprite allocator assigns a static OAM slot per `draw`
// statement at compile time, so a `draw` inside a loop body would
// reuse the same slot on every iteration. We instead use the loop
// to COMPUTE state — "is the player touching a hazard, and which
// one?" — and then fold that result into how (and whether) the
// single player sprite is drawn. The hazards themselves get one
// unrolled `draw` per slot so each hazard gets its own OAM entry.
//
// Features shown:
// - `loop { ... }` as an infinite loop
// - `break` to exit on first match
// - `continue` to skip inactive slots
// - array indexing with a loop-carried variable
//
// Build: cargo run -- build examples/loop_break_continue.ne
game "Loop Demo" {
mapper: NROM
}
const NUM_HAZARDS: u8 = 4
const HIT_RADIUS: u8 = 12
// Four hazard slots. `active` is 1 if the slot is live.
var active: u8[4] = [1, 1, 0, 1]
var hazard_x: u8[4] = [40, 96, 160, 200]
var hazard_y: u8[4] = [60, 140, 100, 180]
var px: u8 = 120
var py: u8 = 120
// Search results, written by the loop each frame.
var hit: u8 = 0 // 1 if the player is touching any hazard
var hit_idx: u8 = 0 // which slot was hit (only meaningful if hit == 1)
// Small absolute-difference helper. Saves lines at each call site.
inline fun abs_diff(a: u8, b: u8) -> u8 {
if a > b {
return a - b
}
return b - a
}
on frame {
// Player movement.
if button.right { px += 1 }
if button.left { px -= 1 }
if button.down { py += 1 }
if button.up { py -= 1 }
// Linear scan for the first active hazard the player is
// touching. The loop exits via `break` on the first hit, or
// by running i past the end.
hit = 0
var i: u8 = 0
loop {
if i >= NUM_HAZARDS {
break
}
if active[i] == 0 {
// Skip inactive slots — this is where `continue`
// shines: the increment and re-test happen at the top.
i += 1
continue
}
var dx: u8 = abs_diff(px, hazard_x[i])
var dy: u8 = abs_diff(py, hazard_y[i])
if dx < HIT_RADIUS {
if dy < HIT_RADIUS {
hit = 1
hit_idx = i
break
}
}
i += 1
}
// One draw per hazard slot — unrolled so each sprite gets its
// own static OAM entry. Inactive slots draw off-screen ($FE)
// so they don't flicker the visible ones.
if active[0] == 1 {
draw Hazard at: (hazard_x[0], hazard_y[0])
}
if active[1] == 1 {
draw Hazard at: (hazard_x[1], hazard_y[1])
}
if active[2] == 1 {
draw Hazard at: (hazard_x[2], hazard_y[2])
}
if active[3] == 1 {
draw Hazard at: (hazard_x[3], hazard_y[3])
}
// The player sprite reflects the collision state: when hit,
// draw a "bang" marker above the player head. The `hit_idx`
// is unused visually but proves the loop ran to completion.
draw Player at: (px, py)
if hit == 1 {
draw Spark at: (px, py - 8)
}
}
start Main