Sudoku generator and solver for TypeScript. You pass a 9×9 grid (null for blanks); it generates puzzles, checks moves, solves, and has helpers for hints and imports. No UI included.
npm: @reetesh/sudoku-engine · GitHub: rishureetesh/sudoku-engine
Runs on Node 18+, in the browser, or in a bundler. No runtime dependencies.
npm install @reetesh/sudoku-engineimport { generatePuzzle, solve } from "@reetesh/sudoku-engine";
// Or only the sudoku module:
import { generatePuzzle } from "@reetesh/sudoku-engine/sudoku";import { generatePuzzle, solve, isSolvedCorrectly } from "@reetesh/sudoku-engine";
const { puzzle, solution, difficulty, clueCount } = generatePuzzle("medium");
const playerBoard = puzzle.map((row) => [...row]);
const { solved, board: solvedBoard } = solve(puzzle);
console.log(solved, difficulty, clueCount);
isSolvedCorrectly(solvedBoard, solution);puzzle— what you show at the start (givens + blanks)solution— full answer; keep server-side if you care about cheatingplayerBoard— copy ofpuzzlethat the player edits
Generated puzzles have one solution.
The API is a Board: (1–9 | null)[][].
Inside, row/column/box masks track which digits are used (9 bits each). Candidates come from those masks. The solver uses MRV (pick the cell with fewest options) and backtracking. Generation builds a full grid, removes cells, and checks uniqueness with the same solver.
You only work with Board; conversion to bitmasks is internal.
type Board = (1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | null)[][];import { createEmptyBoard, boardToString, stringToBoard } from "@reetesh/sudoku-engine";
createEmptyBoard();
boardToString(board);
stringToBoard("530070000600195000...");Givens per level:
| Level | Givens |
|---|---|
| easy | 40–45 |
| medium | 32–39 |
| hard | 26–31 |
| expert | 22–25 |
generatePuzzle("expert");
generatePuzzle("medium", { symmetric: true });
generateOne({ difficulty: "hard", seed: 20260530 });
dailyPuzzle("2026-05-30", "hard");By clue count: rateDifficulty(board)
By how far naked singles get you: rateDifficultyByTechniques(board), analyzeTechniques(board)
import {
validateBoard,
isValidMove,
getCandidates,
solve,
countSolutions,
hasUniqueSolution,
} from "@reetesh/sudoku-engine";
validateBoard(board);
isValidMove(board, row, column, value);
getCandidates(board, row, column);
const { solved, board } = solve(board);
countSolutions(board);
hasUniqueSolution(board);isValidMove only checks row/column/box conflicts, not whether the puzzle is still solvable.
import { applyMove, isBoardComplete, isSolvedCorrectly } from "@reetesh/sudoku-engine";
const result = applyMove(board, row, column, 5, puzzle);
if (result.success) {
board = result.board;
}
isBoardComplete(board);
isSolvedCorrectly(board, solution);Pass puzzle into applyMove so givens stay fixed.
isGiven(puzzle, row, column);
getGivenCells(puzzle);
getCellDisplayState(puzzle, board, solution, row, column);
revealCell(board, solution, row, column, puzzle);
revealNext(board, solution, puzzle);
revealRandom(board, solution, puzzle);getCellDisplayState: "given" | "empty" | "player" | "incorrect"
import { puzzleFromString, validateImportedPuzzle } from "@reetesh/sudoku-engine";
const board = puzzleFromString("530070000600195000...");
const result = validateImportedPuzzle(board);
if (result.valid) {
result.puzzle;
result.solution;
}Rows and columns are 0–8. Bad indices throw SudokuEngineError.
isInBounds(row, column);
assertInBounds(row, column);Up to 1000 per call. Default: split evenly across easy/medium/hard/expert.
generateBatch({ count: 100 });
generateBatch({
count: 50,
distribution: { easy: 20, medium: 15, hard: 10, expert: 5 },
});cd examples/react
npm install
npm run devSmall React app: new game, daily puzzle, hints, cell states.
docs/API.md. Types in dist/index.d.ts after npm run build.
Roughly 18 KB minified (ESM entry), tree-shakeable.
npm install
npm test
npm run typecheck
npm run lint
npm run buildTests: src/games/sudoku/engine/tests/
CHANGELOG.md — 1.0.0
MIT © Reetesh Kumar