clone

fifteen puzzle games for the terminal, with daily puzzles, weekly ladders and xp, built on bubble tea

test fix for sudoku covering edge case in generating ambiguous grids

Dami committed 2564c95
commit
2564c95e4f50a98d993745fb2487a63be7a1ce09
parent
f269c2c85d1b54d61a82a1e79c358efc0b5de4fe
author
Dami <dami.etoile@gmail.com>
2 files changed, +56 -48
Msudoku/generator.go15+++++-
Msudoku/sudoku_test.go89++++++++++++++----------------
M sudoku/generator.go +14 -1
M sudoku/sudoku_test.go +42 -47
@@ -101,26 +101,30 @@ func TestIsValid(t *testing.T) {
101101 })102102 103103 t.Run("all 9 boxes center placement", func(t *testing.T) {104- // Place value 1 in center of each 3x3 box, then verify no cross-box conflict.104+ // Place a distinct value in the center of each 3x3 box so that no row,105+ // column, or box conflict exists, then verify isValid agrees.105106 g := emptyGrid()106- centers := [][2]int{107- {1, 1},108- {4, 1},109- {7, 1},110- {1, 4},111- {4, 4},112- {7, 4},113- {1, 7},114- {4, 7},115- {7, 7},107+ type centerVal struct {108+ x, y, v int109+ }110+ centers := []centerVal{111+ {1, 1, 1},112+ {4, 1, 2},113+ {7, 1, 3},114+ {1, 4, 4},115+ {4, 4, 5},116+ {7, 4, 6},117+ {1, 7, 7},118+ {4, 7, 8},119+ {7, 7, 9},116120 }117121 for _, c := range centers {118- g[c[1]][c[0]].v = 1122+ g[c.y][c.x].v = c.v119123 }120- // Each center should be valid (1 appears once per box, once per row, once per column).124+ // Each center should be valid (unique per box, row, and column).121125 for _, c := range centers {122- if !isValid(&g, 1, c[0], c[1]) {123- t.Errorf("expected valid at center (%d,%d)", c[0], c[1])126+ if !isValid(&g, c.v, c.x, c.y) {127+ t.Errorf("expected valid at center (%d,%d) with value %d", c.x, c.y, c.v)124128 }125129 }126130 })
@@ -203,15 +207,27 @@ func TestFillGrid(t *testing.T) {
203207 204208 // --- countSolutions (P0) ---205209 210+// almostCompleteGrid returns a valid grid with four empty cells forming a211+// swap rectangle, giving exactly 2 solutions. The removed cells are (3,0),212+// (4,0), (3,3), (4,3) whose original values (6,7,7,6) can be swapped.213+func almostCompleteGrid() grid {214+ g := validCompleteGrid()215+ g[0][3].v = 0216+ g[0][4].v = 0217+ g[3][3].v = 0218+ g[3][4].v = 0219+ return g220+}221+206222 func TestCountSolutions(t *testing.T) {207- t.Run("complete grid", func(t *testing.T) {223+ t.Run("complete grid has 1 solution", func(t *testing.T) {208224 g := validCompleteGrid()209225 if count := countSolutions(&g, 10); count != 1 {210226 t.Errorf("countSolutions = %d, want 1", count)211227 }212228 })213229 214- t.Run("one empty cell unique", func(t *testing.T) {230+ t.Run("one empty cell has 1 solution", func(t *testing.T) {215231 g := validCompleteGrid()216232 g[0][0].v = 0217233 if count := countSolutions(&g, 10); count != 1 {
@@ -219,9 +235,8 @@ func TestCountSolutions(t *testing.T) {
219235 }220236 })221237 222- t.Run("impossible grid", func(t *testing.T) {238+ t.Run("impossible grid has 0 solutions", func(t *testing.T) {223239 g := emptyGrid()224- // Place two 1s in row 0 by forcing the grid state.225240 g[0][0].v = 1226241 g[0][1].v = 1227242 if count := countSolutions(&g, 10); count != 0 {
@@ -230,39 +245,19 @@ func TestCountSolutions(t *testing.T) {
230245 })231246 232247 t.Run("respects limit", func(t *testing.T) {233- g := emptyGrid()248+ g := almostCompleteGrid()249+ // This grid has more than 2 solutions.234250 count := countSolutions(&g, 2)235- if count < 2 {236- t.Errorf("countSolutions = %d, want >= 2", count)237- }238- if count > 2 {239- t.Errorf("countSolutions = %d, should not exceed limit 2", count)251+ if count != 2 {252+ t.Errorf("countSolutions = %d, want 2 (limit)", count)240253 }241254 })242255 243- t.Run("two empty cells ambiguous", func(t *testing.T) {244- // Start with a valid complete grid, then clear two cells that create245- // ambiguity: (0,0) and (0,1) where swapping their values still yields246- // a valid grid.247- g := validCompleteGrid()248- // g[0][0]=5, g[0][1]=3. We need to check if swapping creates a valid grid.249- // Clear both and count solutions.250- v1, v2 := g[0][0].v, g[0][1].v251- g[0][0].v = 0252- g[0][1].v = 0256+ t.Run("ambiguous grid has multiple solutions", func(t *testing.T) {257+ g := almostCompleteGrid()253258 count := countSolutions(&g, 10)254- // If swapping is valid, count >= 2; otherwise find a different pair.255- // The valid grid has (0,0)=5,(0,1)=3 — swapping: is 3 valid at (0,0)256- // and 5 valid at (0,1)?257- g[0][0].v = v2258- g[0][1].v = v1259- swapValid := isValid(&g, v2, 0, 0) && isValid(&g, v1, 1, 0)260- if swapValid && count < 2 {261- t.Errorf("countSolutions = %d, expected >= 2 for ambiguous pair", count)262- }263- if !swapValid && count != 1 {264- // The pair wasn't actually ambiguous; just verify we got 1.265- t.Logf("pair (0,0)/(0,1) not ambiguous, count=%d", count)259+ if count <= 1 {260+ t.Errorf("countSolutions = %d, want > 1 for ambiguous grid", count)266261 }267262 })268263 }