Sudoku Gen
A web-based Sudoku generator and solver supporting 9×9, 12×12, and 16×16 grids including jigsaw variants. Uses Web Workers to keep the UI responsive during puzzle generation, with batch-print and database features.




sudoku-gen generates and solves Sudoku puzzles across 9×9, 12×12, and 16×16 grids, including jigsaw (irregular-region) variants, with a constraint-propagation solver fast enough to validate uniqueness while a puzzle is still being carved out of a filled grid. The web frontend runs generation in a Web Worker so dragging the difficulty slider or requesting a fresh batch never blocks the UI thread, with a database of generated puzzles and a batch-print view for producing a stack of puzzles at once. Alongside the web app, a separate CLI client drives the same generation core directly from the command line: it fans the search across goroutines, one per worker, to brute-force-generate and validate large batches of puzzles locally far faster than a single-threaded generator — useful for pre-seeding the puzzle database or stress-testing the solver against pathological grids.
Architecture
graph TD CLI["sudoku-gen CLI"] -->|size, type, difficulty| Engine["sudoku_gen_go
(submodule)"] Engine --> Workers["N worker goroutines
(threadID 0..threads-1)"] Workers -->|backtracking + MRV heuristic| Solve["solve() per worker"] Solve -->|first valid grid wins| ResultChan[("resultChan")] Workers -.close on success.-> StopChan[("stopChan")] ResultChan --> Output["Puzzle + Solution grid"]
Under the hood
Generation fans out across a worker pool — every goroutine races to solve its own random grid, and the first one to succeed closes stopChan to cancel the rest.
for i := 0; i < g.threads; i++ {
wg.Add(1)
go func(threadID int) {
defer wg.Done()
for attempt := 0; attempt < attemptsPerThread; attempt++ {
select {
case <-stopChan:
return
default:
}
grid := types.NewGrid(g.size, g.sudokuType)
// ... build subgrids, then solve ...
if solved := g.solve(grid, startTime, maxTime); solved {
g.removeNumbers(grid)
select {
case resultChan <- grid:
close(stopChan) // Signal other goroutines to stop
return
case <-stopChan:
return
}
}
}
}(i)
}