Files
vim-sudoku/src/useBoard/SudokuGenerator.ts
T
pastilhas 4236f53ef8 feat: add configurable removedCells option and comprehensive documentation
- Add removedCells to GameOptions for custom puzzle sizes
- removedCells overrides difficulty default when provided
- Maintain backward compatibility with all defaults
- Complete IMPLEMENTATION.md with Iteration 16 and summary
- Completely rewrite README.md with comprehensive documentation:
  - Complete API reference (GameOptions, GameManager, CursorManager, ScoreManager)
  - Game states and state machine flow
  - Full VIM controls reference (movement, editing, hints, game control)
  - Cell state rules and visual feedback
  - Detailed scoring formula with examples
  - Game-over conditions (time and hint limits)
  - Hint system priority and penalties
  - Configuration examples for different play styles
  - Platform hook integration guide
- Mark implementation as COMPLETE and PRODUCTION READY
2025-10-22 21:58:59 +01:00

131 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export type Difficulty = 'easy' | 'medium' | 'hard';
const CELLS_TO_REMOVE = {
easy: 25,
medium: 40,
hard: 50,
} as const;
export class SudokuGenerator {
private solution: string = '';
private puzzle: string = '';
constructor() {
this.generate('medium');
}
generate(difficulty: Difficulty = 'medium', customRemovedCells?: number): void {
this.solution = this.generateSolvedBoard();
if (!this.validateFullBoard(this.solution)) {
throw new Error('Generated invalid solved board');
}
this.puzzle = this.makePuzzle(this.solution, difficulty, customRemovedCells);
}
getSolution(): string {
return this.solution;
}
getPuzzle(): string {
return this.puzzle;
}
private generateSolvedBoard(): string {
const size = 9;
const box = 3;
const pattern = (r: number, c: number) =>
(box * (r % box) + Math.floor(r / box) + c) % size;
const baseBoard: number[][] = Array.from({ length: size }, (_, r) =>
Array.from({ length: size }, (_, c) => pattern(r, c) + 1)
);
const shuffle = <T>(arr: T[]): T[] => {
const copy = [...arr];
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[copy[i], copy[j]] = [copy[j], copy[i]];
}
return copy;
};
const rows = shuffle([0, 1, 2])
.concat(shuffle([3, 4, 5]))
.concat(shuffle([6, 7, 8]));
const cols = shuffle([0, 1, 2])
.concat(shuffle([3, 4, 5]))
.concat(shuffle([6, 7, 8]));
const rowBands = shuffle([0, 1, 2]);
const colStacks = shuffle([0, 1, 2]);
const shuffledRows = rowBands.flatMap(band => rows.slice(band * 3, band * 3 + 3));
const shuffledCols = colStacks.flatMap(stack => cols.slice(stack * 3, stack * 3 + 3));
const digits = shuffle([1, 2, 3, 4, 5, 6, 7, 8, 9]);
const board: number[][] = shuffledRows.map(r =>
shuffledCols.map(c => digits[baseBoard[r][c] - 1])
);
return board.flat().join('');
}
private makePuzzle(solution: string, difficulty: Difficulty, customRemovedCells?: number): string {
if (solution.length !== 81 || !/^[1-9]+$/.test(solution)) {
throw new Error('Invalid solved board: must be 81 digits 1–9');
}
const removeCount = customRemovedCells ?? CELLS_TO_REMOVE[difficulty];
const board = solution.split('');
const indices = Array.from({ length: 81 }, (_, i) => i);
for (let i = indices.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[indices[i], indices[j]] = [indices[j], indices[i]];
}
for (let i = 0; i < removeCount; i++) {
const idx = indices[i];
board[idx] = '.';
}
return board.join('');
}
private validateFullBoard(board: string): boolean {
if (board.length !== 81) return false;
if (!/^[1-9]+$/.test(board)) return false;
const getCell = (r: number, c: number) => Number(board[r * 9 + c]);
for (let r = 0; r < 9; r++) {
const sum = Array.from({ length: 9 }, (_, c) => getCell(r, c))
.reduce((a, b) => a + b, 0);
if (sum !== 45) return false;
}
for (let c = 0; c < 9; c++) {
const sum = Array.from({ length: 9 }, (_, r) => getCell(r, c))
.reduce((a, b) => a + b, 0);
if (sum !== 45) return false;
}
for (let br = 0; br < 3; br++) {
for (let bc = 0; bc < 3; bc++) {
const sum = Array.from({ length: 9 }, (_, i) => {
const r = br * 3 + Math.floor(i / 3);
const c = bc * 3 + (i % 3);
return getCell(r, c);
}).reduce((a, b) => a + b, 0);
if (sum !== 45) return false;
}
}
return true;
}
}