Mechanical sweep of all .sx sources, plan docs, and tests/expected snapshots for the sx language rename (s8/s16/s32/s64 -> i8/i16/i32/i64). Verified: tools/run_tests.sh 23/23. Note: the ios-sim build has 2 pre-existing 'restart' dot-call errors from the sx opt-in UFCS change (sx a47ea14) — independent of this rename (present pre-sweep); migrated in the follow-up commit.
147 lines
5.1 KiB
Plaintext
147 lines
5.1 KiB
Plaintext
// Collapse golden: run gravity over several HAND-CRAFTED boards and snapshot the
|
|
// post-collapse board. Unlike the clear/match goldens these boards need not be
|
|
// run-free — `collapse` never inspects matches, it only moves gems past holes —
|
|
// so each column is painted to exercise a distinct case (holes in the middle, at
|
|
// the bottom, a full column of holes, a column with none, a lone gem, an
|
|
// alternating stack). Distinct gem letters are stacked vertically so the
|
|
// top-to-bottom order is observable in the dump.
|
|
//
|
|
// For each scene the before/after boards are printed, and two facts are asserted
|
|
// independently of the dump: every column ends with its original gems (same
|
|
// top-to-bottom order) packed at the BOTTOM and all holes contiguous above, and
|
|
// the returned `moved` flag is exact.
|
|
#import "modules/std.sx";
|
|
#import "board.sx";
|
|
t :: #import "test.sx";
|
|
|
|
// Inverse of `gem_char`: map a board character back to its Gem. The hole glyph
|
|
// 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) {
|
|
if GEM_CHARS[i] == c { return cast(Gem) i; }
|
|
}
|
|
.red
|
|
}
|
|
|
|
// 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) {
|
|
line := rows[row];
|
|
for 0..BOARD_COLS (col) {
|
|
b.set(col, row, char_to_gem(line[col]));
|
|
}
|
|
}
|
|
b
|
|
}
|
|
|
|
// The collapse invariant, checked column by column against the original board:
|
|
// each column's gems (its non-hole cells, in top-to-bottom order) must reappear
|
|
// packed contiguously at the BOTTOM in that same order, with every cell above
|
|
// 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) {
|
|
gems : [BOARD_ROWS]Gem = ---;
|
|
n := 0;
|
|
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) {
|
|
if row < boundary {
|
|
if b.at(col, row) != .empty { return false; }
|
|
} else {
|
|
if b.at(col, row) != gems[row - boundary] { return false; }
|
|
}
|
|
}
|
|
}
|
|
true
|
|
}
|
|
|
|
// Collapse one scene, snapshot before/after, and assert the collapse invariant
|
|
// plus the exact `moved` flag.
|
|
scene :: (name: string, rows: []string, want_moved: bool) {
|
|
b := load_board(rows);
|
|
orig := load_board(rows); // pristine copy for the invariant check
|
|
|
|
moved := collapse(@b);
|
|
|
|
print("== {} ==\n", name);
|
|
out("before:\n");
|
|
out(board_dump(@orig));
|
|
out("after:\n");
|
|
out(board_dump(@b));
|
|
|
|
t.expect(check_collapsed(@orig, @b), concat(name, ": gems packed bottom, holes top, order preserved"));
|
|
t.expect(moved == want_moved, concat(name, ": moved flag exact"));
|
|
}
|
|
|
|
main :: () -> i32 {
|
|
print("== collapse (gravity) ==\n");
|
|
|
|
// Eight independent columns, one case each (top-to-bottom):
|
|
// col0 holes in the MIDDLE: R O Y G B straddle three holes -> all fall.
|
|
// col1 holes at the BOTTOM: R O Y sit on top -> fall to the floor.
|
|
// col2 a FULL column of holes -> stays all holes.
|
|
// col3 NO holes (eight gems) -> unchanged.
|
|
// col4 already settled (holes already at the top) -> unchanged.
|
|
// col5 a LONE gem at the very top -> drops to the floor.
|
|
// col6 an ALTERNATING gem/hole stack -> gems pack, order preserved.
|
|
// col7 three gems already resting on the floor -> unchanged.
|
|
scene("varied", .[
|
|
"RR.R.RR.",
|
|
".O.O....",
|
|
".Y.Y..O.",
|
|
"O..GR...",
|
|
"Y..BO.Y.",
|
|
"...RY..R",
|
|
"G..OG.GO",
|
|
"B..YB..Y",
|
|
], true);
|
|
|
|
// No holes anywhere: gravity has nothing to do, board is left byte-identical.
|
|
scene("no-holes", .[
|
|
"ROYGBPRO",
|
|
"YGBPROYG",
|
|
"BPROYGBP",
|
|
"ROYGBPRO",
|
|
"YGBPROYG",
|
|
"BPROYGBP",
|
|
"ROYGBPRO",
|
|
"YGBPROYG",
|
|
], false);
|
|
|
|
// Every cell a hole: an empty board collapses to itself, nothing moves.
|
|
scene("all-holes", .[
|
|
"........",
|
|
"........",
|
|
"........",
|
|
"........",
|
|
"........",
|
|
"........",
|
|
"........",
|
|
"........",
|
|
], false);
|
|
|
|
// Already settled: every column has its holes contiguous at the top and its
|
|
// gems contiguous at the bottom (this IS the post-collapse form of "varied").
|
|
// Re-collapsing must move nothing and leave the board unchanged — the
|
|
// idempotency the P2.4 cascade relies on to detect that gravity has stopped.
|
|
scene("settled", .[
|
|
"...R....",
|
|
"...O....",
|
|
"...Y....",
|
|
"R..GR...",
|
|
"O..BO.R.",
|
|
"YR.RY.OR",
|
|
"GO.OG.YO",
|
|
"BY.YBRGY",
|
|
], false);
|
|
|
|
print("ok: collapse over hand-crafted boards\n");
|
|
return 0;
|
|
}
|