// 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 :: () -> s32 { 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; }