diff --git a/board.sx b/board.sx index e1459d8..c4d6148 100644 --- a/board.sx +++ b/board.sx @@ -199,6 +199,10 @@ mark_run :: (m: *MatchMask, vertical: bool, fixed: s64, start: s64, end: s64) { // so length-4 / length-5 runs are simply longer spans of the same walk. A cell // shared by an intersecting horizontal and vertical run is marked once per // axis into the same slot — idempotent, so the union counts it once. +// +// Only runs of an actual gem match: `.empty` holes are never matchable, so a +// line of 3+ holes (left behind by a prior clear) is not a match. Holes also +// 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; } @@ -212,7 +216,7 @@ find_matches :: (b: *Board) -> MatchMask { while run_end < BOARD_COLS and b.at(run_end, row) == g { run_end += 1; } - if run_end - col >= 3 { mark_run(@m, false, row, col, run_end); } + if g != .empty and run_end - col >= 3 { mark_run(@m, false, row, col, run_end); } col = run_end; } } @@ -226,7 +230,7 @@ find_matches :: (b: *Board) -> MatchMask { while run_end < BOARD_ROWS and b.at(col, run_end) == g { run_end += 1; } - if run_end - row >= 3 { mark_run(@m, true, col, row, run_end); } + if g != .empty and run_end - row >= 3 { mark_run(@m, true, col, row, run_end); } row = run_end; } } diff --git a/tests/clear.sx b/tests/clear.sx index 5f1f508..d9c65d4 100644 --- a/tests/clear.sx +++ b/tests/clear.sx @@ -11,8 +11,11 @@ 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. +// be written as a human-readable grid. The hole glyph maps to `.empty`, so a +// board can be hand-written with pre-existing holes (cells left by a prior +// clear) for the holes-never-match regression. 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; } } @@ -130,6 +133,23 @@ main :: () -> s32 { "GOGOGOGO", ], 0); + // Holes never match: a checkerboard carrying a horizontal 3-run of holes + // (row 3, cols 2-4) and a vertical 3-run of holes (col 1, rows 5-7), left by + // earlier clears. A line of 3+ holes is NOT a match, so detect finds nothing, + // clear removes nothing, and before/after are identical. Without this, a + // post-clear board would keep re-"matching" its own holes and the P2.4 + // cascade would never stabilise. + scene("holes-no-match", .[ + "OGOGOGOG", + "GOGOGOGO", + "OGOGOGOG", + "GO...OGO", + "OGOGOGOG", + "G.GOGOGO", + "O.OGOGOG", + "G.GOGOGO", + ], 0); + // clear_matches: the one-call detect+clear returns the same cleared count // and punches the holes itself. cm := load_board(.[ @@ -146,6 +166,38 @@ main :: () -> s32 { t.expect(cm.at(2, 3) == .empty and cm.at(3, 3) == .empty and cm.at(4, 3) == .empty, "clear_matches: matched run is now holes"); + // Holes are never matchable: a board whose only equal-adjacent runs are + // holes yields an empty match set, and clear_matches reports 0 (no change). + holes := load_board(.[ + "OGOGOGOG", + "GOGOGOGO", + "OGOGOGOG", + "GO...OGO", + "OGOGOGOG", + "G.GOGOGO", + "O.OGOGOG", + "G.GOGOGO", + ]); + hm := find_matches(@holes); + t.expect(hm.count() == 0, "holes: a line of 3+ holes is not a match"); + t.expect(clear_matches(@holes) == 0, "holes: clear_matches returns 0 on a holes-only board"); + + // Cascade base case: after a real clear punches a 3-in-a-line into holes, + // re-detecting on the cleared board must find nothing — otherwise the P2.4 + // cascade loop would re-match its own holes and never terminate. + casc := load_board(.[ + "OGOGOGOG", + "GOGOGOGO", + "OGOGOBOG", + "GOGOGBGO", + "OGOGOBOG", + "GOGOGOGO", + "OGOGOGOG", + "GOGOGOGO", + ]); + t.expect(clear_matches(@casc) == 3, "cascade: first clear removes the vertical 3-run"); + t.expect(clear_matches(@casc) == 0, "cascade: re-clear on the holed board returns 0"); + print("ok: clear over hand-crafted boards\n"); return 0; } diff --git a/tests/expected/clear.stdout b/tests/expected/clear.stdout index fef56e0..52e0480 100644 --- a/tests/expected/clear.stdout +++ b/tests/expected/clear.stdout @@ -94,4 +94,23 @@ OGOGOGOG GOGOGOGO OGOGOGOG GOGOGOGO +== holes-no-match == +before: +OGOGOGOG +GOGOGOGO +OGOGOGOG +GO...OGO +OGOGOGOG +G.GOGOGO +O.OGOGOG +G.GOGOGO +after: +OGOGOGOG +GOGOGOGO +OGOGOGOG +GO...OGO +OGOGOGOG +G.GOGOGO +O.OGOGOG +G.GOGOGO ok: clear over hand-crafted boards