migrate to the new for-loop syntax
Drop the ':' before captures (for xs (x) / for 0..n (i)); the index capture becomes the trailing open range (for xs, 0.. (x, i)). 136 headers across 26 files, mechanical. Five headless tests (banner_layout, hit_test, swipe_commit, swipe_intent, swipe_reshuffle) also gain a direct #import "modules/ui/types.sx" — they named Point/Frame through a transitive import, which bare visibility no longer permits. Gates: sx build --target ios-sim main.sx links; tools/run_tests.sh 23/23.
This commit is contained in:
56
board.sx
56
board.sx
@@ -155,8 +155,8 @@ Board :: struct {
|
||||
self.moves_made = 0;
|
||||
self.move_limit = DEFAULT_MOVE_LIMIT;
|
||||
self.target_score = DEFAULT_TARGET_SCORE;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
self.set(col, row, pick_gem(self, @self.rng, col, row));
|
||||
}
|
||||
}
|
||||
@@ -167,7 +167,7 @@ Board :: struct {
|
||||
// upward. Pure given the board's already-placed prefix and the RNG state.
|
||||
pick_gem :: (board: *Board, rng: *Rng, col: s64, row: s64) -> Gem {
|
||||
forbidden : [GEM_COUNT]bool = ---;
|
||||
for 0..GEM_COUNT: (t) { forbidden[t] = false; }
|
||||
for 0..GEM_COUNT (t) { forbidden[t] = false; }
|
||||
|
||||
// Two same gems immediately to the left → a third of that type matches.
|
||||
if col >= 2 {
|
||||
@@ -185,11 +185,11 @@ pick_gem :: (board: *Board, rng: *Rng, col: s64, row: s64) -> Gem {
|
||||
}
|
||||
|
||||
allowed := 0;
|
||||
for 0..GEM_COUNT: (t) { if !forbidden[t] { allowed += 1; } }
|
||||
for 0..GEM_COUNT (t) { if !forbidden[t] { allowed += 1; } }
|
||||
|
||||
// Pick the k-th still-allowed type; single RNG draw, always terminates.
|
||||
k := rng.next_range(allowed);
|
||||
for 0..GEM_COUNT: (t) {
|
||||
for 0..GEM_COUNT (t) {
|
||||
if !forbidden[t] {
|
||||
if k == 0 { return cast(Gem) t; }
|
||||
k -= 1;
|
||||
@@ -203,9 +203,9 @@ pick_gem :: (board: *Board, rng: *Rng, col: s64, row: s64) -> Gem {
|
||||
board_dump :: (self: *Board) -> string {
|
||||
line_w := BOARD_COLS + 1; // 8 gem chars + newline
|
||||
buf := cstring(BOARD_ROWS * line_w);
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
base := row * line_w;
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
buf[base + col] = gem_char(self.at(col, row));
|
||||
}
|
||||
buf[base + BOARD_COLS] = 10; // '\n'
|
||||
@@ -229,7 +229,7 @@ MatchMask :: struct {
|
||||
|
||||
count :: (self: *MatchMask) -> s64 {
|
||||
n : s64 = 0;
|
||||
for 0..BOARD_CELLS: (i) { if self.cells[i] { n += 1; } }
|
||||
for 0..BOARD_CELLS (i) { if self.cells[i] { n += 1; } }
|
||||
n
|
||||
}
|
||||
}
|
||||
@@ -238,7 +238,7 @@ MatchMask :: struct {
|
||||
// is the constant coordinate (the row for a horizontal span, the column for a
|
||||
// vertical one) and the span covers `start..end` of the moving coordinate.
|
||||
mark_run :: (m: *MatchMask, vertical: bool, fixed: s64, start: s64, end: s64) {
|
||||
for start..end: (i) {
|
||||
for start..end (i) {
|
||||
if vertical {
|
||||
m.cells[Board.idx(fixed, i)] = true;
|
||||
} else {
|
||||
@@ -259,10 +259,10 @@ mark_run :: (m: *MatchMask, vertical: bool, fixed: s64, start: s64, end: s64) {
|
||||
// break runs of real gems, since a hole differs from every gem type.
|
||||
find_matches :: (b: *Board) -> MatchMask {
|
||||
m : MatchMask = ---;
|
||||
for 0..BOARD_CELLS: (i) { m.cells[i] = false; }
|
||||
for 0..BOARD_CELLS (i) { m.cells[i] = false; }
|
||||
|
||||
// Horizontal: walk each row left-to-right in maximal same-type spans.
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
col := 0;
|
||||
while col < BOARD_COLS {
|
||||
g := b.at(col, row);
|
||||
@@ -276,7 +276,7 @@ find_matches :: (b: *Board) -> MatchMask {
|
||||
}
|
||||
|
||||
// Vertical: walk each column top-to-bottom in maximal same-type spans.
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
row := 0;
|
||||
while row < BOARD_ROWS {
|
||||
g := b.at(col, row);
|
||||
@@ -299,9 +299,9 @@ find_matches :: (b: *Board) -> MatchMask {
|
||||
dump_matches :: (b: *Board, m: *MatchMask) -> string {
|
||||
line_w := BOARD_COLS + 1; // 8 cells + newline
|
||||
buf := cstring(BOARD_ROWS * line_w);
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
base := row * line_w;
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
if m.at(col, row) {
|
||||
buf[base + col] = gem_char(b.at(col, row));
|
||||
} else {
|
||||
@@ -371,8 +371,8 @@ Swap :: struct {
|
||||
// the snapshot can depend on it.
|
||||
legal_swaps :: (board: *Board) -> List(Swap) {
|
||||
result := List(Swap).{};
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
here := Cell.{ col = col, row = row };
|
||||
if col + 1 < BOARD_COLS {
|
||||
right := Cell.{ col = col + 1, row = row };
|
||||
@@ -397,7 +397,7 @@ legal_swaps :: (board: *Board) -> List(Swap) {
|
||||
// as just "0 legal swaps", which reads unambiguously. Suitable for snapshotting.
|
||||
dump_swaps :: (swaps: *List(Swap)) -> string {
|
||||
result := format("{} legal swaps\n", swaps.len);
|
||||
for 0..swaps.len: (i) {
|
||||
for 0..swaps.len (i) {
|
||||
s := swaps.items[i];
|
||||
result = concat(result, format("({},{})-({},{})\n", s.a.col, s.a.row, s.b.col, s.b.row));
|
||||
}
|
||||
@@ -415,7 +415,7 @@ dump_swaps :: (swaps: *List(Swap)) -> string {
|
||||
// `true` per shared cell) clear as one set with no double-counting.
|
||||
clear_cells :: (board: *Board, mask: *MatchMask) -> s64 {
|
||||
cleared : s64 = 0;
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if mask.cells[i] {
|
||||
board.cells[i] = .empty;
|
||||
cleared += 1;
|
||||
@@ -449,7 +449,7 @@ clear_matches :: (board: *Board) -> s64 {
|
||||
// this to know when gravity has stopped.
|
||||
collapse :: (board: *Board) -> bool {
|
||||
moved := false;
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
// Pack this column's gems toward the bottom: scan it bottom-to-top and
|
||||
// write each gem at the falling cursor `w`, which also descends from the
|
||||
// bottom. A gem whose source row differs from `w` actually fell. `w`
|
||||
@@ -492,8 +492,8 @@ collapse :: (board: *Board) -> bool {
|
||||
refill :: (board: *Board) -> s64 {
|
||||
rng := @board.rng;
|
||||
filled : s64 = 0;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
if board.at(col, row) == .empty {
|
||||
board.set(col, row, cast(Gem) rng.next_range(GEM_COUNT));
|
||||
filled += 1;
|
||||
@@ -628,7 +628,7 @@ run_score :: (len: s64) -> s64 {
|
||||
find_runs :: (b: *Board) -> List(Run) {
|
||||
runs := List(Run).{};
|
||||
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
col := 0;
|
||||
while col < BOARD_COLS {
|
||||
g := b.at(col, row);
|
||||
@@ -643,7 +643,7 @@ find_runs :: (b: *Board) -> List(Run) {
|
||||
}
|
||||
}
|
||||
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
row := 0;
|
||||
while row < BOARD_ROWS {
|
||||
g := b.at(col, row);
|
||||
@@ -669,7 +669,7 @@ find_runs :: (b: *Board) -> List(Run) {
|
||||
score_round :: (board: *Board) -> s64 {
|
||||
runs := find_runs(board);
|
||||
total : s64 = 0;
|
||||
for 0..runs.len: (i) {
|
||||
for 0..runs.len (i) {
|
||||
total += run_score(runs.items[i].len);
|
||||
}
|
||||
total
|
||||
@@ -724,7 +724,7 @@ SpecialCounts :: struct {
|
||||
count_specials :: (board: *Board) -> SpecialCounts {
|
||||
runs := find_runs(board);
|
||||
counts := SpecialCounts.{ len4 = 0, len5_plus = 0 };
|
||||
for 0..runs.len: (i) {
|
||||
for 0..runs.len (i) {
|
||||
len := runs.items[i].len;
|
||||
if len == 4 {
|
||||
counts.len4 += 1;
|
||||
@@ -741,7 +741,7 @@ count_specials :: (board: *Board) -> SpecialCounts {
|
||||
// "0 runs". Suitable for snapshotting.
|
||||
dump_runs :: (runs: *List(Run)) -> string {
|
||||
result := format("{} runs\n", runs.len);
|
||||
for 0..runs.len: (i) {
|
||||
for 0..runs.len (i) {
|
||||
r := runs.items[i];
|
||||
axis := if r.vertical then "V" else "H";
|
||||
result = concat(result, format("{} len {} at fixed {} start {}\n", axis, r.len, r.fixed, r.start));
|
||||
@@ -829,8 +829,8 @@ level_status :: (board: *Board) -> Status {
|
||||
// a throwaway list each call. The trial swaps inside `swap_legal` are reverted,
|
||||
// so the board is left unchanged.
|
||||
has_legal_swap :: (board: *Board) -> bool {
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
here := Cell.{ col = col, row = row };
|
||||
if col + 1 < BOARD_COLS {
|
||||
right := Cell.{ col = col + 1, row = row };
|
||||
|
||||
@@ -163,7 +163,7 @@ ClearDiag :: struct { lo: s64; hi: s64; }
|
||||
clear_diag_span :: (m: *MatchMask) -> ClearDiag {
|
||||
lo : s64 = (BOARD_COLS - 1) + (BOARD_ROWS - 1) + 1;
|
||||
hi : s64 = -1;
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if m.cells[i] {
|
||||
d := (i % BOARD_COLS) + (i / BOARD_COLS);
|
||||
if d < lo { lo = d; }
|
||||
@@ -277,7 +277,7 @@ plan_and_commit :: (board: *Board, a: Cell, b: Cell) -> AnimMove {
|
||||
// came from source row `r`. The rows left above the survivors (0..w) are
|
||||
// refilled, so they drop in from above: a dest row `j` there starts at
|
||||
// `j - n_refill`, i.e. stacked just off the top edge.
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
w := BOARD_ROWS - 1;
|
||||
r := BOARD_ROWS - 1;
|
||||
while r >= 0 {
|
||||
@@ -362,7 +362,7 @@ BoardAnim :: struct {
|
||||
return AnimPhase.{ kind = .swap, round = 0, t = e / SWAP_ANIM_DUR };
|
||||
}
|
||||
e -= SWAP_ANIM_DUR;
|
||||
for 0..self.move.rounds.len: (k) {
|
||||
for 0..self.move.rounds.len (k) {
|
||||
if e < CLEAR_ANIM_DUR {
|
||||
return AnimPhase.{ kind = .clear, round = k, t = e / CLEAR_ANIM_DUR };
|
||||
}
|
||||
|
||||
10
board_fx.sx
10
board_fx.sx
@@ -123,7 +123,7 @@ BoardFxAssets :: struct {
|
||||
loaded: bool;
|
||||
|
||||
init :: (self: *BoardFxAssets) {
|
||||
for 0..GEM_COUNT: (t) { self.tex[t] = 0; }
|
||||
for 0..GEM_COUNT (t) { self.tex[t] = 0; }
|
||||
self.loaded = false;
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ BoardFxAssets :: struct {
|
||||
// (sx codegen), so the per-pixel tint loop only ASSIGNS pre-declared vars.
|
||||
i : s64 = 0;
|
||||
o : s64 = 0;
|
||||
for 0..GEM_COUNT: (t) {
|
||||
for 0..GEM_COUNT (t) {
|
||||
col := fx_tint(t);
|
||||
i = 0;
|
||||
while i < n {
|
||||
@@ -219,7 +219,7 @@ BoardFx :: struct {
|
||||
// Whole-move depth boost: a deeper cascade makes every burst bigger from
|
||||
// its first round, escalating in lockstep with the cascade SFX cue.
|
||||
depth_boost := FX_BURST_DEPTH * cast(f32) fx_combo_level(mv.rounds.len);
|
||||
for 0..mv.rounds.len: (k) {
|
||||
for 0..mv.rounds.len (k) {
|
||||
rd := @mv.rounds.items[k];
|
||||
t0 := SWAP_ANIM_DUR + cast(f32) k * (CLEAR_ANIM_DUR + FALL_ANIM_DUR);
|
||||
extra := depth_boost + FX_BURST_COMBO * cast(f32) min(k, 2);
|
||||
@@ -227,7 +227,7 @@ BoardFx :: struct {
|
||||
// bursts ripple in lockstep with the staggered pops (P18.2) instead of
|
||||
// one simultaneous flash. The round's audio cue still fires once at t0.
|
||||
span := clear_diag_span(@rd.matched);
|
||||
for 0..BOARD_CELLS: (idx) {
|
||||
for 0..BOARD_CELLS (idx) {
|
||||
if rd.matched.cells[idx] {
|
||||
g := rd.before[idx];
|
||||
if g != .empty {
|
||||
@@ -253,7 +253,7 @@ BoardFx :: struct {
|
||||
sc : s64 = 0;
|
||||
sr : s64 = 0;
|
||||
cnt : s64 = 0;
|
||||
for 0..BOARD_CELLS: (idx) {
|
||||
for 0..BOARD_CELLS (idx) {
|
||||
if rd0.matched.cells[idx] {
|
||||
sc += idx % BOARD_COLS;
|
||||
sr += idx / BOARD_COLS;
|
||||
|
||||
@@ -357,8 +357,8 @@ BoardView :: struct {
|
||||
// Settled-board gems: one sprite per non-empty cell, drawn with its live
|
||||
// per-gem animation pose. Used whenever no move is animating.
|
||||
render_gems :: (self: *BoardView, ctx: *RenderContext, dim: f32) {
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
g := self.board.at(col, row);
|
||||
if g != .empty {
|
||||
pose := self.gem_pose_at(col, row);
|
||||
@@ -431,7 +431,7 @@ BoardView :: struct {
|
||||
// (which resumes the same back-dated stamp). tick() normally clears
|
||||
// `active` before this is reached.
|
||||
last := mv.rounds.len - 1;
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
g := mv.final[i];
|
||||
if g != .empty {
|
||||
sq := self.rest_squash(i, last, e);
|
||||
@@ -452,7 +452,7 @@ BoardView :: struct {
|
||||
ai := Board.idx(mv.a.col, mv.a.row);
|
||||
bi := Board.idx(mv.b.col, mv.b.row);
|
||||
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if i == ai or i == bi { continue; }
|
||||
g := mv.pre[i];
|
||||
if g != .empty {
|
||||
@@ -495,7 +495,7 @@ BoardView :: struct {
|
||||
// satisfying pop, composing with the particle burst); the rest hold position.
|
||||
render_clear :: (self: *BoardView, ctx: *RenderContext, rd: *AnimRound, k: s64, e: f32, dim: f32, t: f32) {
|
||||
span := clear_diag_span(@rd.matched);
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
g := rd.before[i];
|
||||
if g == .empty { continue; }
|
||||
col := i % BOARD_COLS;
|
||||
@@ -532,7 +532,7 @@ BoardView :: struct {
|
||||
cs * cast(f32) BOARD_COLS, cs * cast(f32) BOARD_ROWS
|
||||
);
|
||||
ctx.push_clip(grid);
|
||||
for 0..self.fx.particles.len: (i) {
|
||||
for 0..self.fx.particles.len (i) {
|
||||
p := self.fx.particles.items[i];
|
||||
lt := (p.age - p.delay) / p.life;
|
||||
env := fx_pop_env(lt);
|
||||
@@ -556,7 +556,7 @@ BoardView :: struct {
|
||||
render_fx_popups :: (self: *BoardView, ctx: *RenderContext) {
|
||||
if self.fx == null or self.fx.popups.len == 0 { return; }
|
||||
cs := self.layout.cell_size;
|
||||
for 0..self.fx.popups.len: (i) {
|
||||
for 0..self.fx.popups.len (i) {
|
||||
q := self.fx.popups.items[i];
|
||||
lt := (q.age - q.delay) / q.life;
|
||||
if lt >= 0.0 {
|
||||
@@ -594,7 +594,7 @@ BoardView :: struct {
|
||||
// guarantees every column reaches 1 by t==1, so each gem lands exactly on its
|
||||
// cell and the seam to the next round / settled board stays invisible.
|
||||
render_fall :: (self: *BoardView, ctx: *RenderContext, rd: *AnimRound, k: s64, e: f32, dim: f32, t: f32) {
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
g := rd.after[i];
|
||||
if g == .empty { continue; }
|
||||
col := i % BOARD_COLS;
|
||||
@@ -731,8 +731,8 @@ impl View for BoardView {
|
||||
gem_inset := self.layout.cell_size * (1.0 - GEM_FILL_FRAC) * 0.5;
|
||||
gem_dim := self.layout.cell_size * GEM_FILL_FRAC;
|
||||
if self.assets.cell_tex != 0 {
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
ctx.add_image(self.layout.cell_frame(col, row), self.assets.cell_tex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ GemMotion :: struct {
|
||||
// its resting pose instead of replaying the prior move's landing wobble; the
|
||||
// idle clock keeps running, so the always-on idle simply resumes from rest.
|
||||
reset_landings :: (self: *GemMotion) {
|
||||
for 0..BOARD_CELLS: (i) { self.land_at[i] = -1000.0; }
|
||||
for 0..BOARD_CELLS (i) { self.land_at[i] = -1000.0; }
|
||||
}
|
||||
|
||||
stamp_land :: (self: *GemMotion, i: s64) {
|
||||
|
||||
8
main.sx
8
main.sx
@@ -183,8 +183,8 @@ parse_s64 :: (s: string) -> s64 {
|
||||
// trial swaps inside `swap_legal` are reverted, so the board is left unchanged.
|
||||
illegal_swaps :: (board: *Board) -> List(Swap) {
|
||||
result := List(Swap).{};
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
here := Cell.{ col = col, row = row };
|
||||
if col + 1 < BOARD_COLS {
|
||||
right := Cell.{ col = col + 1, row = row };
|
||||
@@ -222,7 +222,7 @@ frame :: () {
|
||||
g_pipeline.resize(fc.viewport_w, fc.viewport_h);
|
||||
}
|
||||
|
||||
for g_plat.poll_events(): (*ev) {
|
||||
for g_plat.poll_events() (*ev) {
|
||||
inline if OS != .ios {
|
||||
if ev == {
|
||||
case .key_up: (e) {
|
||||
@@ -277,7 +277,7 @@ frame :: () {
|
||||
mv := @g_anim.move;
|
||||
total := g_anim.total();
|
||||
last := mv.rounds.len - 1;
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
m := delivering_round(mv, i, last);
|
||||
if m >= 0 {
|
||||
col := i % BOARD_COLS;
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
SEED :: 1337;
|
||||
|
||||
boards_equal :: (x: *Board, y: *Board) -> bool {
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if !(x.cells[i] == y.cells[i]) { return false; }
|
||||
}
|
||||
true
|
||||
@@ -61,7 +61,7 @@ main :: () -> s32 {
|
||||
|
||||
// move.final equals the model board.
|
||||
final_eq := true;
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if !(move.final[i] == bm.cells[i]) { final_eq = false; }
|
||||
}
|
||||
if !final_eq { fails += 1; }
|
||||
@@ -74,21 +74,21 @@ main :: () -> s32 {
|
||||
ai := Board.idx(a.col, a.row);
|
||||
bi := Board.idx(b.col, b.row);
|
||||
r0 := @move.rounds.items[0];
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
expect : Gem = move.pre[i];
|
||||
if i == ai { expect = move.pre[bi]; }
|
||||
else if i == bi { expect = move.pre[ai]; }
|
||||
if !(r0.before[i] == expect) { contiguous = false; }
|
||||
}
|
||||
for 1..move.rounds.len: (k) {
|
||||
for 1..move.rounds.len (k) {
|
||||
prev := @move.rounds.items[k - 1];
|
||||
cur := @move.rounds.items[k];
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if !(cur.before[i] == prev.after[i]) { contiguous = false; }
|
||||
}
|
||||
}
|
||||
last := @move.rounds.items[move.rounds.len - 1];
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if !(last.after[i] == move.final[i]) { contiguous = false; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// shape and rationale as tests/hit_test.sx. Failure is signalled via a non-zero
|
||||
// exit code (the runner checks exit code AND stdout).
|
||||
#import "modules/std.sx";
|
||||
#import "modules/ui/types.sx";
|
||||
#import "board.sx";
|
||||
#import "board_layout.sx";
|
||||
|
||||
|
||||
@@ -12,14 +12,14 @@ SEED :: 1337;
|
||||
// independently of the placement logic, so it's a real check, not a tautology.
|
||||
count_three_runs :: (b: *Board) -> s32 {
|
||||
runs : s32 = 0;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..(BOARD_COLS - 2): (col) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..(BOARD_COLS - 2) (col) {
|
||||
g := b.at(col, row);
|
||||
if g == b.at(col + 1, row) and g == b.at(col + 2, row) { runs += 1; }
|
||||
}
|
||||
}
|
||||
for 0..(BOARD_ROWS - 2): (row) {
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..(BOARD_ROWS - 2) (row) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
g := b.at(col, row);
|
||||
if g == b.at(col, row + 1) and g == b.at(col, row + 2) { runs += 1; }
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ EXPECTED_DEPTH :: 2;
|
||||
// board can be written as a human-readable grid. The hole glyph maps to `.empty`.
|
||||
char_to_gem :: (c: u8) -> Gem {
|
||||
if c == EMPTY_CHAR { return .empty; }
|
||||
for 0..GEM_COUNT: (i) {
|
||||
for 0..GEM_COUNT (i) {
|
||||
if GEM_CHARS[i] == c { return cast(Gem) i; }
|
||||
}
|
||||
.red
|
||||
@@ -34,9 +34,9 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// The RNG is left unseeded — callers seed it before resolving.
|
||||
load_board :: (rows: []string) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
b.set(col, row, char_to_gem(line[col]));
|
||||
}
|
||||
}
|
||||
@@ -44,7 +44,7 @@ load_board :: (rows: []string) -> Board {
|
||||
}
|
||||
|
||||
boards_equal :: (a: *Board, b: *Board) -> bool {
|
||||
for 0..BOARD_CELLS: (i) { if a.cells[i] != b.cells[i] { return false; } }
|
||||
for 0..BOARD_CELLS (i) { if a.cells[i] != b.cells[i] { return false; } }
|
||||
true
|
||||
}
|
||||
|
||||
@@ -120,7 +120,7 @@ main :: () -> s32 {
|
||||
t.expect(c.depth == depth, "cascade: resolve depth matches manual loop");
|
||||
same_counts := c.cleared.len == counts.len;
|
||||
if same_counts {
|
||||
for 0..counts.len: (i) {
|
||||
for 0..counts.len (i) {
|
||||
if c.cleared.items[i] != counts.items[i] { same_counts = false; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ main :: () -> s32 {
|
||||
// middle are visible: depths 0,1 pin to the first cue; depths >= 5 pin to
|
||||
// the last; 2,3,4 step up one cue at a time.
|
||||
prev : s64 = -1;
|
||||
for 0..10: (depth) {
|
||||
for 0..10 (depth) {
|
||||
idx := cascade_cue_index(depth);
|
||||
print("depth {} -> idx {} ({})\n", depth, idx, cascade_cue_name(idx));
|
||||
// The mapping must never step down as depth grows.
|
||||
|
||||
@@ -57,7 +57,7 @@ main :: () -> s32 {
|
||||
|
||||
// Deep chain: the cue tail clamps at combo5 for round >= 5 (cascade_cue_index).
|
||||
print("-- deep-chain cue clamp --\n");
|
||||
for 1..8: (r) { print("round {} -> {}\n", r, cascade_cue_name(cascade_cue_index(r))); }
|
||||
for 1..8 (r) { print("round {} -> {}\n", r, cascade_cue_name(cascade_cue_index(r))); }
|
||||
|
||||
print("ok: one ascending combo cue per cascade round, clamped at combo5\n");
|
||||
return 0;
|
||||
|
||||
@@ -16,7 +16,7 @@ t :: #import "test.sx";
|
||||
// clear) for the holes-never-match regression.
|
||||
char_to_gem :: (c: u8) -> Gem {
|
||||
if c == EMPTY_CHAR { return .empty; }
|
||||
for 0..GEM_COUNT: (i) {
|
||||
for 0..GEM_COUNT (i) {
|
||||
if GEM_CHARS[i] == c { return cast(Gem) i; }
|
||||
}
|
||||
.red
|
||||
@@ -26,9 +26,9 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// characters).
|
||||
load_board :: (rows: []string) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
b.set(col, row, char_to_gem(line[col]));
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ scene :: (name: string, rows: []string, want_cleared: s64) {
|
||||
|
||||
cleared_holes := true; // every matched cell is now a hole
|
||||
others_intact := true; // every other cell is byte-identical
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if m.cells[i] {
|
||||
if !(b.cells[i] == .empty) { cleared_holes = false; }
|
||||
} else {
|
||||
|
||||
@@ -18,7 +18,7 @@ t :: #import "test.sx";
|
||||
// maps to `.empty`, so a board can be hand-written with holes in any position.
|
||||
char_to_gem :: (c: u8) -> Gem {
|
||||
if c == EMPTY_CHAR { return .empty; }
|
||||
for 0..GEM_COUNT: (i) {
|
||||
for 0..GEM_COUNT (i) {
|
||||
if GEM_CHARS[i] == c { return cast(Gem) i; }
|
||||
}
|
||||
.red
|
||||
@@ -27,9 +27,9 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// Load an 8x8 board from `rows` (top row first, each exactly BOARD_COLS chars).
|
||||
load_board :: (rows: []string) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
b.set(col, row, char_to_gem(line[col]));
|
||||
}
|
||||
}
|
||||
@@ -42,15 +42,15 @@ load_board :: (rows: []string) -> Board {
|
||||
// them a hole. This single check covers holes-bubble-to-top, gems-settle-to-
|
||||
// bottom, order-preservation, and the all-holes / no-holes edge columns at once.
|
||||
check_collapsed :: (orig: *Board, b: *Board) -> bool {
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
gems : [BOARD_ROWS]Gem = ---;
|
||||
n := 0;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
g := orig.at(col, row);
|
||||
if g != .empty { gems[n] = g; n += 1; }
|
||||
}
|
||||
boundary := BOARD_ROWS - n; // first row that must hold a gem
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
if row < boundary {
|
||||
if b.at(col, row) != .empty { return false; }
|
||||
} else {
|
||||
|
||||
@@ -23,7 +23,7 @@ t :: #import "test.sx";
|
||||
// be written as a human-readable grid. The hole glyph maps to `.empty`.
|
||||
char_to_gem :: (c: u8) -> Gem {
|
||||
if c == EMPTY_CHAR { return .empty; }
|
||||
for 0..GEM_COUNT: (i) {
|
||||
for 0..GEM_COUNT (i) {
|
||||
if GEM_CHARS[i] == c { return cast(Gem) i; }
|
||||
}
|
||||
.red
|
||||
@@ -33,9 +33,9 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// seeded RNG, running score zeroed so `board.score` ends equal to the payout.
|
||||
load_board :: (rows: []string, seed: s64) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
b.set(col, row, char_to_gem(line[col]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ main :: () -> s32 {
|
||||
p_io := ease_in_out_cubic(0.0);
|
||||
p_oc := ease_out_cubic(0.0);
|
||||
p_iq := ease_in_quad(0.0);
|
||||
for 1..21: (i) {
|
||||
for 1..21 (i) {
|
||||
t := cast(f32) i / 20.0;
|
||||
v_in := ease_in_cubic(t); if v_in < p_in - 0.000001 { mono_in = false; } p_in = v_in;
|
||||
v_io := ease_in_out_cubic(t); if v_io < p_io - 0.000001 { mono_io = false; } p_io = v_io;
|
||||
@@ -72,7 +72,7 @@ main :: () -> s32 {
|
||||
back_mx := ease_out_back(0.0); back_mn := ease_out_back(0.0);
|
||||
spr_mx := spring(0.0); spr_mn := spring(0.0);
|
||||
spr_wobble := false;
|
||||
for 1..21: (i) {
|
||||
for 1..21 (i) {
|
||||
t := cast(f32) i / 20.0;
|
||||
b := ease_out_back(t);
|
||||
if b > back_mx { back_mx = b; }
|
||||
@@ -98,7 +98,7 @@ main :: () -> s32 {
|
||||
// squash (positive) and a stretch (negative) lobe, and stays bounded.
|
||||
print("== squash envelope bounded ==\n");
|
||||
sq_mx : f32 = 0.0; sq_mn : f32 = 0.0; sq_moves := false;
|
||||
for 0..21: (i) {
|
||||
for 0..21 (i) {
|
||||
t := cast(f32) i / 20.0;
|
||||
s := squash_envelope(t);
|
||||
if s > sq_mx { sq_mx = s; }
|
||||
@@ -121,7 +121,7 @@ main :: () -> s32 {
|
||||
print("== illegal-swap bounce ==\n");
|
||||
bb_ends := bad_swap_bounce(0.0) == 0.0 and bad_swap_bounce(1.0) == 0.0;
|
||||
bb_mx : f32 = 0.0; bb_mx_t : f32 = 0.0; bb_mn : f32 = 0.0;
|
||||
for 0..101: (i) {
|
||||
for 0..101 (i) {
|
||||
t := cast(f32) i / 100.0;
|
||||
v := bad_swap_bounce(t);
|
||||
if v > bb_mx { bb_mx = v; bb_mx_t = t; }
|
||||
@@ -151,18 +151,18 @@ main :: () -> s32 {
|
||||
// starts later), the opposite of a flat lockstep row sharing one progress.
|
||||
print("== fall stagger bounded ==\n");
|
||||
stg_t0 := true; stg_t1 := true;
|
||||
for 0..BOARD_COLS: (c) {
|
||||
for 0..BOARD_COLS (c) {
|
||||
if fall_stagger_t(0.0, c) != 0.0 { stg_t0 = false; }
|
||||
if fall_stagger_t(1.0, c) != 1.0 { stg_t1 = false; }
|
||||
}
|
||||
stg_cascade := true;
|
||||
for 1..BOARD_COLS: (c) {
|
||||
for 1..BOARD_COLS (c) {
|
||||
if !(fall_stagger_t(0.5, c) < fall_stagger_t(0.5, c - 1)) { stg_cascade = false; }
|
||||
}
|
||||
stg_mono := true;
|
||||
for 0..BOARD_COLS: (c) {
|
||||
for 0..BOARD_COLS (c) {
|
||||
pp := fall_stagger_t(0.0, c);
|
||||
for 1..21: (i) {
|
||||
for 1..21 (i) {
|
||||
tt := cast(f32) i / 20.0;
|
||||
vv := fall_stagger_t(tt, c);
|
||||
if vv < pp - 0.000001 { stg_mono = false; }
|
||||
@@ -190,14 +190,14 @@ main :: () -> s32 {
|
||||
lf_last := approx(fall_landing_frac(BOARD_COLS - 1), 1.0);
|
||||
lf_mono := true;
|
||||
lf_seam := true;
|
||||
for 0..BOARD_COLS: (c) {
|
||||
for 0..BOARD_COLS (c) {
|
||||
if c >= 1 and !(fall_landing_frac(c) > fall_landing_frac(c - 1)) { lf_mono = false; }
|
||||
lf := fall_landing_frac(c);
|
||||
if !approx(fall_stagger_t(lf, c), 1.0) { lf_seam = false; } // landed at lf
|
||||
if fall_stagger_t(lf - 0.05, c) >= 1.0 { lf_seam = false; } // still in air just before
|
||||
}
|
||||
rlt_col_mono := true;
|
||||
for 1..BOARD_COLS: (c) {
|
||||
for 1..BOARD_COLS (c) {
|
||||
if !(round_land_time(0, c) > round_land_time(0, c - 1)) { rlt_col_mono = false; }
|
||||
}
|
||||
rlt_round_after := round_land_time(1, 0) > round_land_time(0, BOARD_COLS - 1);
|
||||
@@ -222,22 +222,22 @@ main :: () -> s32 {
|
||||
// gem 0..1 by diagonal across the round (lowest-diagonal = 0, the first to pop).
|
||||
print("== clear ripple bounded ==\n");
|
||||
rip_t0 := true; rip_t1 := true;
|
||||
for 0..6: (j) {
|
||||
for 0..6 (j) {
|
||||
u := cast(f32) j / 5.0;
|
||||
if clear_ripple_t(0.0, u) != 0.0 { rip_t0 = false; }
|
||||
if clear_ripple_t(1.0, u) != 1.0 { rip_t1 = false; }
|
||||
}
|
||||
rip_ripple := true;
|
||||
for 1..6: (j) {
|
||||
for 1..6 (j) {
|
||||
u := cast(f32) j / 5.0;
|
||||
up := cast(f32) (j - 1) / 5.0;
|
||||
if !(clear_ripple_t(0.5, u) < clear_ripple_t(0.5, up)) { rip_ripple = false; }
|
||||
}
|
||||
rip_mono := true;
|
||||
for 0..6: (j) {
|
||||
for 0..6 (j) {
|
||||
u := cast(f32) j / 5.0;
|
||||
pp := clear_ripple_t(0.0, u);
|
||||
for 1..21: (i) {
|
||||
for 1..21 (i) {
|
||||
tt := cast(f32) i / 20.0;
|
||||
vv := clear_ripple_t(tt, u);
|
||||
if vv < pp - 0.000001 { rip_mono = false; }
|
||||
@@ -245,7 +245,7 @@ main :: () -> s32 {
|
||||
}
|
||||
}
|
||||
mm : MatchMask = ---;
|
||||
for 0..BOARD_CELLS: (i) { mm.cells[i] = false; }
|
||||
for 0..BOARD_CELLS (i) { mm.cells[i] = false; }
|
||||
mm.cells[Board.idx(5, 0)] = true; // diagonal 5 — first to pop
|
||||
mm.cells[Board.idx(5, 1)] = true; // diagonal 6
|
||||
mm.cells[Board.idx(5, 2)] = true; // diagonal 7 — last to pop
|
||||
|
||||
@@ -21,7 +21,7 @@ main :: () -> s32 {
|
||||
expect_level : [10]s64 = .{ 0, 0, 1, 2, 3, 4, 4, 4, 4, 4 };
|
||||
|
||||
prev : s64 = -1;
|
||||
for 0..10: (depth) {
|
||||
for 0..10 (depth) {
|
||||
lvl := fx_combo_level(depth);
|
||||
font := fx_popup_font(depth);
|
||||
combo := depth > 1;
|
||||
@@ -45,7 +45,7 @@ main :: () -> s32 {
|
||||
// larger and the font never shrinks as the cascade deepens.
|
||||
if fx_popup_font(1) != FX_POPUP_FONT { print("FAIL: single-clear popup not plain font\n"); return 1; }
|
||||
pf : f32 = 0.0;
|
||||
for 2..10: (depth) {
|
||||
for 2..10 (depth) {
|
||||
f := fx_popup_font(depth);
|
||||
if f <= FX_POPUP_FONT { print("FAIL: combo popup not larger than plain at depth {}\n", depth); return 1; }
|
||||
if depth > 2 and f < pf { print("FAIL: popup font shrank at depth {}\n", depth); return 1; }
|
||||
|
||||
@@ -24,8 +24,8 @@ main :: () -> s32 {
|
||||
// 1. t==0 idle pose is EXACTLY rest for every cell (the determinism invariant).
|
||||
print("== idle t=0 is rest for all cells ==\n");
|
||||
rest_ok := true;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
p := idle_pose(0.0, col, row);
|
||||
if !(p.scale_x == 1.0 and p.scale_y == 1.0 and p.dx == 0.0 and p.dy == 0.0) {
|
||||
rest_ok = false;
|
||||
@@ -39,8 +39,8 @@ main :: () -> s32 {
|
||||
print("== idle mid-phase deforms, bounded ==\n");
|
||||
moved := false;
|
||||
bounded := true;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
p := idle_pose(0.6, col, row);
|
||||
if fabs(p.scale_x - 1.0) > 0.0005 { moved = true; }
|
||||
if fabs(p.scale_x - 1.0) > 0.05 { bounded = false; }
|
||||
@@ -82,7 +82,7 @@ main :: () -> s32 {
|
||||
c_peak := clear_pop_scale(0.30) > 1.1;
|
||||
c_collapse := true;
|
||||
pc := clear_pop_scale(CLEAR_POP_RISE);
|
||||
for 1..21: (i) {
|
||||
for 1..21 (i) {
|
||||
tt := CLEAR_POP_RISE + (1.0 - CLEAR_POP_RISE) * cast(f32) i / 20.0;
|
||||
vv := clear_pop_scale(tt);
|
||||
if vv > pc + 0.000001 { c_collapse = false; }
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
// second `Frame` struct that collides with the UI `Frame`. Failure is signalled
|
||||
// via a non-zero exit code (the runner checks exit code AND stdout).
|
||||
#import "modules/std.sx";
|
||||
#import "modules/ui/types.sx";
|
||||
#import "board.sx";
|
||||
#import "board_layout.sx";
|
||||
|
||||
@@ -26,8 +27,8 @@ main :: () -> s32 {
|
||||
|
||||
// Every cell center must map back to its own cell.
|
||||
hits : s64 = 0;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
cf := lay.cell_frame(col, row);
|
||||
center := Point.{ x = cf.mid_x(), y = cf.mid_y() };
|
||||
if h := lay.point_to_cell(center) {
|
||||
|
||||
@@ -25,7 +25,7 @@ RESHUFFLE_SEED :: 1337;
|
||||
// be written as a human-readable grid. The hole glyph maps to `.empty`.
|
||||
char_to_gem :: (c: u8) -> Gem {
|
||||
if c == EMPTY_CHAR { return .empty; }
|
||||
for 0..GEM_COUNT: (i) {
|
||||
for 0..GEM_COUNT (i) {
|
||||
if GEM_CHARS[i] == c { return cast(Gem) i; }
|
||||
}
|
||||
.red
|
||||
@@ -36,9 +36,9 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// the per-level goal set.
|
||||
load_board :: (rows: []string, seed: s64, move_limit: s64, target_score: s64) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
b.set(col, row, char_to_gem(line[col]));
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ load_board :: (rows: []string, seed: s64, move_limit: s64, target_score: s64) ->
|
||||
}
|
||||
|
||||
boards_equal :: (a: *Board, b: *Board) -> bool {
|
||||
for 0..BOARD_CELLS: (i) { if a.cells[i] != b.cells[i] { return false; } }
|
||||
for 0..BOARD_CELLS (i) { if a.cells[i] != b.cells[i] { return false; } }
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ t :: #import "test.sx";
|
||||
// Inverse of `gem_char`: map a gem character back to its Gem so each board can
|
||||
// be written as a human-readable grid of GEM_CHARS.
|
||||
char_to_gem :: (c: u8) -> Gem {
|
||||
for 0..GEM_COUNT: (i) {
|
||||
for 0..GEM_COUNT (i) {
|
||||
if GEM_CHARS[i] == c { return cast(Gem) i; }
|
||||
}
|
||||
.red
|
||||
@@ -22,9 +22,9 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// assert the matched-cell count.
|
||||
scene :: (name: string, rows: []string, want_count: s64) {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
b.set(col, row, char_to_gem(line[col]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ SEED :: 1337;
|
||||
// board can be written as a human-readable grid. The hole glyph maps to `.empty`.
|
||||
char_to_gem :: (c: u8) -> Gem {
|
||||
if c == EMPTY_CHAR { return .empty; }
|
||||
for 0..GEM_COUNT: (i) {
|
||||
for 0..GEM_COUNT (i) {
|
||||
if GEM_CHARS[i] == c { return cast(Gem) i; }
|
||||
}
|
||||
.red
|
||||
@@ -32,9 +32,9 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// The RNG is left unseeded — callers seed it before drawing.
|
||||
load_board :: (rows: []string) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
b.set(col, row, char_to_gem(line[col]));
|
||||
}
|
||||
}
|
||||
@@ -43,12 +43,12 @@ load_board :: (rows: []string) -> Board {
|
||||
|
||||
count_empties :: (b: *Board) -> s64 {
|
||||
n : s64 = 0;
|
||||
for 0..BOARD_CELLS: (i) { if b.cells[i] == .empty { n += 1; } }
|
||||
for 0..BOARD_CELLS (i) { if b.cells[i] == .empty { n += 1; } }
|
||||
n
|
||||
}
|
||||
|
||||
boards_equal :: (a: *Board, b: *Board) -> bool {
|
||||
for 0..BOARD_CELLS: (i) { if a.cells[i] != b.cells[i] { return false; } }
|
||||
for 0..BOARD_CELLS (i) { if a.cells[i] != b.cells[i] { return false; } }
|
||||
true
|
||||
}
|
||||
|
||||
@@ -101,7 +101,7 @@ main :: () -> s32 {
|
||||
distinct := false;
|
||||
have_first := false;
|
||||
first : Gem = .empty;
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if pre.cells[i] == .empty {
|
||||
want := cast(Gem) v.next_range(GEM_COUNT);
|
||||
if b.cells[i] != want { stream_ok = false; }
|
||||
@@ -125,17 +125,17 @@ main :: () -> s32 {
|
||||
holes_n := 0;
|
||||
hole_idx : [BOARD_CELLS]s64 = ---;
|
||||
fill1 : [BOARD_CELLS]Gem = ---;
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if pre.cells[i] == .empty {
|
||||
hole_idx[holes_n] = i;
|
||||
fill1[holes_n] = b.cells[i];
|
||||
holes_n += 1;
|
||||
}
|
||||
}
|
||||
for 0..holes_n: (k) { b.cells[hole_idx[k]] = .empty; }
|
||||
for 0..holes_n (k) { b.cells[hole_idx[k]] = .empty; }
|
||||
refill(@b);
|
||||
differs := false;
|
||||
for 0..holes_n: (k) {
|
||||
for 0..holes_n (k) {
|
||||
if b.cells[hole_idx[k]] != fill1[k] { differs = true; }
|
||||
}
|
||||
t.expect(differs, "refill: a second refill of the same holes draws new gems (RNG threads, no reseed)");
|
||||
|
||||
@@ -20,7 +20,7 @@ t :: #import "test.sx";
|
||||
// be written as a human-readable grid. The hole glyph maps to `.empty`.
|
||||
char_to_gem :: (c: u8) -> Gem {
|
||||
if c == EMPTY_CHAR { return .empty; }
|
||||
for 0..GEM_COUNT: (i) {
|
||||
for 0..GEM_COUNT (i) {
|
||||
if GEM_CHARS[i] == c { return cast(Gem) i; }
|
||||
}
|
||||
.red
|
||||
@@ -30,9 +30,9 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// with the running score zeroed so the accumulation check starts from a known base.
|
||||
load_board :: (rows: []string) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
b.set(col, row, char_to_gem(line[col]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ SEED :: 1337;
|
||||
// Inverse of `gem_char`: map a gem character back to its Gem so each board can
|
||||
// be written as a human-readable grid of GEM_CHARS.
|
||||
char_to_gem :: (c: u8) -> Gem {
|
||||
for 0..GEM_COUNT: (i) {
|
||||
for 0..GEM_COUNT (i) {
|
||||
if GEM_CHARS[i] == c { return cast(Gem) i; }
|
||||
}
|
||||
.red
|
||||
@@ -26,9 +26,9 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// characters).
|
||||
load_board :: (rows: []string) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
b.set(col, row, char_to_gem(line[col]));
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ load_board :: (rows: []string) -> Board {
|
||||
// Whole-board equality, cell by cell — used to prove a trial swap leaves the
|
||||
// board untouched.
|
||||
boards_equal :: (x: *Board, y: *Board) -> bool {
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if !(x.cells[i] == y.cells[i]) { return false; }
|
||||
}
|
||||
true
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
// its trace.sx pulls in a second `Frame` that collides with the UI one. Failure
|
||||
// is signalled via a non-zero exit code (the runner checks exit code AND stdout).
|
||||
#import "modules/std.sx";
|
||||
#import "modules/ui/types.sx";
|
||||
#import "board.sx";
|
||||
#import "board_layout.sx";
|
||||
#import "swipe.sx";
|
||||
@@ -25,7 +26,7 @@ cell_center :: (lay: *BoardLayout, col: s64, row: s64) -> Point {
|
||||
}
|
||||
|
||||
boards_equal :: (x: *Board, y: *Board) -> bool {
|
||||
for 0..BOARD_CELLS: (i) {
|
||||
for 0..BOARD_CELLS (i) {
|
||||
if !(x.cells[i] == y.cells[i]) { return false; }
|
||||
}
|
||||
true
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
// clears it; a 10px drag does not. Failure is signalled via a non-zero exit code
|
||||
// (the runner checks exit code AND stdout).
|
||||
#import "modules/std.sx";
|
||||
#import "modules/ui/types.sx";
|
||||
#import "board.sx";
|
||||
#import "board_layout.sx";
|
||||
#import "swipe.sx";
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// avoids tests/test.sx (its trace.sx pulls in a second `Frame` that collides with
|
||||
// the UI one). Failure is a non-zero exit code (the runner checks exit + stdout).
|
||||
#import "modules/std.sx";
|
||||
#import "modules/ui/types.sx";
|
||||
#import "board.sx";
|
||||
#import "board_anim.sx";
|
||||
#import "board_layout.sx";
|
||||
@@ -27,7 +28,7 @@ SEED :: 1337;
|
||||
// board can be written as a human-readable grid (mirrors tests/level.sx).
|
||||
char_to_gem :: (c: u8) -> Gem {
|
||||
if c == EMPTY_CHAR { return .empty; }
|
||||
for 0..GEM_COUNT: (i) {
|
||||
for 0..GEM_COUNT (i) {
|
||||
if GEM_CHARS[i] == c { return cast(Gem) i; }
|
||||
}
|
||||
.red
|
||||
@@ -37,9 +38,9 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// counters reset to a fresh game, and the per-level goal set.
|
||||
load_board :: (rows: []string, seed: s64, move_limit: s64, target_score: s64) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
b.set(col, row, char_to_gem(line[col]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ LIMIT :: 5;
|
||||
// be written as a human-readable grid. The hole glyph maps to `.empty`.
|
||||
char_to_gem :: (c: u8) -> Gem {
|
||||
if c == EMPTY_CHAR { return .empty; }
|
||||
for 0..GEM_COUNT: (i) {
|
||||
for 0..GEM_COUNT (i) {
|
||||
if GEM_CHARS[i] == c { return cast(Gem) i; }
|
||||
}
|
||||
.red
|
||||
@@ -39,9 +39,9 @@ char_to_gem :: (c: u8) -> Gem {
|
||||
// (no moves made, the given move budget).
|
||||
load_board :: (rows: []string, seed: s64, move_limit: s64) -> Board {
|
||||
b : Board = ---;
|
||||
for 0..BOARD_ROWS: (row) {
|
||||
for 0..BOARD_ROWS (row) {
|
||||
line := rows[row];
|
||||
for 0..BOARD_COLS: (col) {
|
||||
for 0..BOARD_COLS (col) {
|
||||
b.set(col, row, char_to_gem(line[col]));
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ load_board :: (rows: []string, seed: s64, move_limit: s64) -> Board {
|
||||
}
|
||||
|
||||
boards_equal :: (a: *Board, b: *Board) -> bool {
|
||||
for 0..BOARD_CELLS: (i) { if a.cells[i] != b.cells[i] { return false; } }
|
||||
for 0..BOARD_CELLS (i) { if a.cells[i] != b.cells[i] { return false; } }
|
||||
true
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user