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

99 lines
2.5 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
// Bitwise Ops — demonstrates shift and mask operators on a packed
// "status" byte that holds several small flags.
//
// The byte layout:
// bit 7 — alive
// bit 6 — shield
// bit 5 — powered
// bit 4 — invulnerable
// bits 0..3 — health (0..15)
//
// The player toggles each high-bit flag with the face buttons and
// heals/damages the low nibble with the d-pad. The drawing code
// reads each flag with an `and` mask after shifting the status byte
// into place — a typical pattern on the 6502.
//
// Build: cargo run -- build examples/bitwise_ops.ne
game "Bitwise Ops" {
mapper: NROM
}
const ALIVE: u8 = 0x80
const SHIELD: u8 = 0x40
const POWERED: u8 = 0x20
const INVULN: u8 = 0x10
const HEALTH_MASK: u8 = 0x0F
var status: u8 = 0x8F // alive + full health
var debounce: u8 = 0
on frame {
if debounce > 0 {
debounce -= 1
}
// Toggle each flag on a different face button. `^` is XOR,
// which flips exactly the masked bit.
if button.a and debounce == 0 {
status = status ^ SHIELD
debounce = 15
}
if button.b and debounce == 0 {
status = status ^ POWERED
debounce = 15
}
if button.start and debounce == 0 {
status = status ^ INVULN
debounce = 15
}
if button.select and debounce == 0 {
status = status ^ ALIVE
debounce = 15
}
// D-pad tweaks the low-nibble health counter. Clear the low
// nibble, compute new health, then OR it back in — the
// canonical "update a bitfield" pattern.
var health: u8 = status & HEALTH_MASK
if button.right and debounce == 0 {
if health < 15 {
health += 1
}
debounce = 8
}
if button.left and debounce == 0 {
if health > 0 {
health -= 1
}
debounce = 8
}
status = (status & 0xF0) | health
// Read back each flag with a mask + nonzero test.
if (status & ALIVE) != 0 {
draw Player at: (120, 120)
}
if (status & SHIELD) != 0 {
draw Ring at: (120, 120)
}
if (status & POWERED) != 0 {
draw Spark at: (136, 120)
}
if (status & INVULN) != 0 {
draw Spark at: (104, 120)
}
// Draw a health bar by shifting the low nibble into a loop
// counter. `>>` divides by 2^n on the 6502 with a shift, which
// the optimizer also emits for `/ 2`.
var bars: u8 = health >> 1 // 0..7 bars
var i: u8 = 0
while i < bars {
draw Pip at: (40 + i * 10, 200)
i += 1
}
}
start Main