package solver import "time" // Grid matches generator's grid representation type Grid [9][9]uint8 // Solve attempts to fill the grid in-place using backtracking. // Returns whether a solution was found before timeout. func Solve(g *Grid, timeout time.Duration) bool { deadline := time.Now().Add(timeout) return solveBacktrack(g, deadline) } // CountSolutions counts up to maxCount solutions for uniqueness check. func CountSolutions(g Grid, timeout time.Duration, maxCount int) int { deadline := time.Now().Add(timeout) count := 0 var dfs func(*Grid) bool dfs = func(cur *Grid) bool { if time.Now().After(deadline) { return true } // Use most-constrained heuristic for faster counting row, col, ok := findMostConstrained(*cur) if !ok { count++ return count >= maxCount } cands := candidates(*cur, row, col) if len(cands) == 0 { return false } for _, v := range cands { cur[row][col] = v if dfs(cur) { return true } cur[row][col] = 0 } return false } copyGrid := g dfs(©Grid) return count } func solveBacktrack(g *Grid, deadline time.Time) bool { if time.Now().After(deadline) { return false } // Use most-constrained-first heuristic for better performance row, col, ok := findMostConstrained(*g) if !ok { return true } cands := candidates(*g, row, col) // If no candidates available, this path is invalid if len(cands) == 0 { return false } for _, v := range cands { g[row][col] = v if solveBacktrack(g, deadline) { return true } g[row][col] = 0 } return false } func findEmpty(g Grid) (int, int, bool) { for r := 0; r < 9; r++ { for c := 0; c < 9; c++ { if g[r][c] == 0 { return r, c, true } } } return 0, 0, false } // findMostConstrained finds the empty cell with the fewest candidates. // This heuristic significantly improves solving performance. func findMostConstrained(g Grid) (int, int, bool) { minCands := 10 bestRow, bestCol := -1, -1 found := false for r := 0; r < 9; r++ { for c := 0; c < 9; c++ { if g[r][c] == 0 { cands := candidates(g, r, c) if len(cands) < minCands { minCands = len(cands) bestRow = r bestCol = c found = true // If we find a cell with only one candidate, use it immediately if minCands == 1 { return bestRow, bestCol, true } } } } } return bestRow, bestCol, found } func candidates(g Grid, row, col int) []uint8 { used := [10]bool{} for i := 0; i < 9; i++ { used[g[row][i]] = true used[g[i][col]] = true } r0 := (row / 3) * 3 c0 := (col / 3) * 3 for r := r0; r < r0+3; r++ { for c := c0; c < c0+3; c++ { used[g[r][c]] = true } } var out []uint8 for v := 1; v <= 9; v++ { if !used[v] { out = append(out, uint8(v)) } } return out } // IsValid checks if the grid is in a valid state (no conflicts). func IsValid(g Grid) bool { // Check rows for r := 0; r < 9; r++ { seen := [10]bool{} for c := 0; c < 9; c++ { v := g[r][c] if v == 0 { continue } if seen[v] { return false } seen[v] = true } } // Check columns for c := 0; c < 9; c++ { seen := [10]bool{} for r := 0; r < 9; r++ { v := g[r][c] if v == 0 { continue } if seen[v] { return false } seen[v] = true } } // Check 3x3 blocks for br := 0; br < 3; br++ { for bc := 0; bc < 3; bc++ { seen := [10]bool{} for r := br * 3; r < br*3+3; r++ { for c := bc * 3; c < bc*3+3; c++ { v := g[r][c] if v == 0 { continue } if seen[v] { return false } seen[v] = true } } } } return true } // IsSolved checks if the grid is completely filled and valid. func IsSolved(g Grid) bool { if !IsValid(g) { return false } for r := 0; r < 9; r++ { for c := 0; c < 9; c++ { if g[r][c] == 0 { return false } } } return true } // SolveStep represents a single step in the solving process. type SolveStep struct { Row int Col int Value uint8 Candidates []uint8 StepNumber int } // SolveWithSteps solves the puzzle and returns all steps taken. func SolveWithSteps(g Grid, timeout time.Duration) ([]SolveStep, bool) { deadline := time.Now().Add(timeout) var steps []SolveStep stepNum := 0 var solve func(*Grid) bool solve = func(cur *Grid) bool { if time.Now().After(deadline) { return false } row, col, ok := findMostConstrained(*cur) if !ok { return true } cands := candidates(*cur, row, col) if len(cands) == 0 { return false } for _, v := range cands { stepNum++ steps = append(steps, SolveStep{ Row: row, Col: col, Value: v, Candidates: cands, StepNumber: stepNum, }) cur[row][col] = v if solve(cur) { return true } cur[row][col] = 0 } return false } copyGrid := g success := solve(©Grid) return steps, success } // GetAllSolutions returns all possible solutions for a puzzle (up to maxSolutions). func GetAllSolutions(g Grid, timeout time.Duration, maxSolutions int) []Grid { deadline := time.Now().Add(timeout) var solutions []Grid var dfs func(*Grid) dfs = func(cur *Grid) { if time.Now().After(deadline) || len(solutions) >= maxSolutions { return } row, col, ok := findMostConstrained(*cur) if !ok { // Found a solution, save a copy var solution Grid for r := 0; r < 9; r++ { for c := 0; c < 9; c++ { solution[r][c] = cur[r][c] } } solutions = append(solutions, solution) return } cands := candidates(*cur, row, col) for _, v := range cands { cur[row][col] = v dfs(cur) cur[row][col] = 0 } } copyGrid := g dfs(©Grid) return solutions }