Files
m3te/tests/match_detect.sx
swipelab b0c081e397 P1.2: match detection (3+ horizontal/vertical runs)
Add a pure-sx match detector to the board model: `find_matches` walks each
row and column once in maximal same-type spans and marks every cell in a run
of length >= 3 into a `MatchMask` (a per-cell membership set mirroring
Board.cells). Overlapping shapes (L / T where a horizontal and vertical run
share a cell) collapse to the union automatically. `dump_matches` renders the
set deterministically: matched cells show their gem char, others '.'.

Detection only — no clear/collapse/refill (that is P2.1).

tests/match_detect.sx exercises hand-crafted boards (built explicitly on a
run-free checkerboard, no seeded init): a horizontal 3-run, a vertical 3-run,
multiple disjoint runs, length-4 and length-5 runs, intersecting L and T
shapes (shared cell counted once), and a no-match board. Output is locked as
tests/expected/match_detect.stdout (+ .exit) and asserts matched-cell counts.
2026-06-04 19:27:15 +03:00

122 lines
3.3 KiB
Plaintext

// Match-detection golden: run `find_matches` over several HAND-CRAFTED boards
// and snapshot the matched-cell set for each. Every board is built explicitly
// (no seeded init) on a checkerboard O/G background — which is itself run-free,
// since adjacent cells always differ — with the runs under test painted in the
// other gem colours. For each scene the board and its matched-cell dump are
// printed, and the matched-cell count is asserted independently of the dump.
#import "modules/std.sx";
#import "board.sx";
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) {
if GEM_CHARS[i] == c { return cast(Gem) i; }
}
.red
}
// Build a scene: load an 8x8 board from `rows` (top row first, each exactly
// BOARD_COLS gem characters), detect matches, print board + matched dump, and
// assert the matched-cell count.
scene :: (name: string, rows: []string, want_count: s64) {
b : Board = ---;
for 0..BOARD_ROWS: (row) {
line := rows[row];
for 0..BOARD_COLS: (col) {
b.set(col, row, char_to_gem(line[col]));
}
}
m := find_matches(@b);
print("== {} ==\n", name);
out(board_dump(@b));
out("--\n");
out(dump_matches(@b, @m));
t.expect(m.count() == want_count, name);
}
main :: () -> s32 {
// Single horizontal 3-run (row 3, cols 2-4).
scene("horizontal-3", .[
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GORRROGO",
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GOGOGOGO",
], 3);
// Single vertical 3-run (col 5, rows 2-4).
scene("vertical-3", .[
"OGOGOGOG",
"GOGOGOGO",
"OGOGOBOG",
"GOGOGBGO",
"OGOGOBOG",
"GOGOGOGO",
"OGOGOGOG",
"GOGOGOGO",
], 3);
// Multiple disjoint runs: horizontal R (row 1, cols 0-2), horizontal P
// (row 5, cols 2-4), vertical Y (col 6, rows 4-6).
scene("disjoint-runs", .[
"OGOGOGOG",
"RRROGOGO",
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGYG",
"GOPPPOYO",
"OGOGOGYG",
"GOGOGOGO",
], 9);
// A length-5 horizontal run (row 1, cols 1-5) and a length-4 vertical run
// (col 7, rows 2-5).
scene("len4-and-len5", .[
"OGOGOGOG",
"GRRRRRGO",
"OGOGOGOB",
"GOGOGOGB",
"OGOGOGOB",
"GOGOGOGB",
"OGOGOGOG",
"GOGOGOGO",
], 9);
// Intersecting shapes, shared cell counted once: an L (R horizontal row 1
// cols 1-3 meeting R vertical col 1 rows 1-3 at the corner (1,1)) and a T
// (Y horizontal row 5 cols 3-5 meeting Y vertical col 4 rows 5-7 at the
// mid/top cell (4,5)).
scene("L-and-T", .[
"OGOGOGOG",
"GRRRGOGO",
"OROGOGOG",
"GRGOGOGO",
"OGOGOGOG",
"GOGYYYGO",
"OGOGYGOG",
"GOGOYOGO",
], 10);
// No matches: the bare checkerboard, every adjacent pair differs.
scene("no-matches", .[
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GOGOGOGO",
"OGOGOGOG",
"GOGOGOGO",
], 0);
print("ok: match detection over hand-crafted boards\n");
return 0;
}