Générateur de Sudoku en JavaScript
Apprenez à programmer un générateur de Sudoku complet en JavaScript. Code source, algorithmes et techniques avancées.
Programmation d'un Générateur Sudoku JavaScript
Créer un générateur de Sudoku en JavaScript combine algorithmes mathématiques, techniques de programmation avancées et optimisations de performance. Ce guide complet vous accompagne dans le développement d'un générateur professionnel capable de créer des puzzles de qualité.
🎯 Ce que vous allez apprendre
- • Algorithmes de génération de grilles Sudoku
- • Techniques de backtracking et optimisation
- • Validation et assurance de solution unique
- • Gestion de la difficulté et des patterns
- • Interface utilisateur interactive
🏗️ Architecture du Générateur
Structure Modulaire Recommandée
Organisez votre code en modules spécialisés pour maintenir la clarté et permettre la réutilisation :
// Structure de projet recommandée
sudoku-generator/
├── src/
│ ├── core/
│ │ ├── SudokuGrid.js // Classe grille principale
│ │ ├── Generator.js // Algorithmes de génération
│ │ ├── Solver.js // Résolveur et validation
│ │ └── Validator.js // Vérification contraintes
│ ├── utils/
│ │ ├── arrayUtils.js // Utilitaires tableaux
│ │ ├── mathUtils.js // Fonctions mathématiques
│ │ └── patterns.js // Patterns et symétries
│ └── ui/
│ ├── GridRenderer.js // Affichage interface
│ ├── Controls.js // Contrôles utilisateur
│ └── Export.js // Export/sauvegarde
└── tests/
└── generator.test.js // Tests unitaires💻 Implémentation de Base
Classe SudokuGrid Principale
Commençons par implémenter la classe fondamentale qui représente la grille Sudoku :
class SudokuGrid {
constructor() {
this.grid = Array(9).fill().map(() => Array(9).fill(0));
this.size = 9;
this.boxSize = 3;
}
// Vérifie si un nombre peut être placé à une position
isValid(row, col, num) {
return this.isValidRow(row, num) &&
this.isValidCol(col, num) &&
this.isValidBox(row, col, num);
}
// Validation ligne
isValidRow(row, num) {
return !this.grid[row].includes(num);
}
// Validation colonne
isValidCol(col, num) {
for (let row = 0; row < this.size; row++) {
if (this.grid[row][col] === num) return false;
}
return true;
}
// Validation région 3x3
isValidBox(row, col, num) {
const startRow = Math.floor(row / this.boxSize) * this.boxSize;
const startCol = Math.floor(col / this.boxSize) * this.boxSize;
for (let r = 0; r < this.boxSize; r++) {
for (let c = 0; c < this.boxSize; c++) {
if (this.grid[startRow + r][startCol + c] === num) {
return false;
}
}
}
return true;
}
// Place un nombre dans la grille
setCell(row, col, value) {
if (this.isValid(row, col, value)) {
this.grid[row][col] = value;
return true;
}
return false;
}
// Copie profonde de la grille
clone() {
const newGrid = new SudokuGrid();
newGrid.grid = this.grid.map(row => [...row]);
return newGrid;
}
}Algorithme de Génération (Backtracking)
L'algorithme de backtracking est la méthode standard pour générer des solutions Sudoku complètes :
class SudokuGenerator {
constructor() {
this.grid = new SudokuGrid();
}
// Génère une solution complète
generateSolution() {
this.grid = new SudokuGrid();
this.fillGrid(0, 0);
return this.grid.clone();
}
// Remplissage récursif avec backtracking
fillGrid(row, col) {
// Fin de grille atteinte
if (row === 9) return true;
// Passage à la ligne suivante
if (col === 9) return this.fillGrid(row + 1, 0);
// Cellule déjà remplie
if (this.grid.grid[row][col] !== 0) {
return this.fillGrid(row, col + 1);
}
// Essai de chaque nombre dans un ordre aléatoire
const numbers = this.getShuffledNumbers();
for (const num of numbers) {
if (this.grid.isValid(row, col, num)) {
this.grid.grid[row][col] = num;
if (this.fillGrid(row, col + 1)) {
return true;
}
// Backtrack
this.grid.grid[row][col] = 0;
}
}
return false;
}
// Mélange les nombres pour la randomisation
getShuffledNumbers() {
const numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9];
return this.shuffle(numbers);
}
// Algorithme de mélange Fisher-Yates
shuffle(array) {
const result = [...array];
for (let i = result.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[result[i], result[j]] = [result[j], result[i]];
}
return result;
}
}🔧 Création de Puzzles Jouables
Retrait Stratégique de Chiffres
Transformer une solution complète en puzzle jouable nécessite de retirer des chiffres tout en maintenant une solution unique :
// Extension de la classe SudokuGenerator
generatePuzzle(difficulty = 'medium') {
// Générer solution complète
const solution = this.generateSolution();
const puzzle = solution.clone();
// Configuration difficulté
const difficultySettings = {
easy: { minClues: 45, maxClues: 50 },
medium: { minClues: 35, maxClues: 44 },
hard: { minClues: 25, maxClues: 34 },
expert: { minClues: 17, maxClues: 24 }
};
const settings = difficultySettings[difficulty];
const targetClues = Math.floor(Math.random() *
(settings.maxClues - settings.minClues + 1)) + settings.minClues;
// Créer liste de positions à tenter
const positions = this.generateRandomPositions();
let cluesRemaining = 81;
for (const pos of positions) {
if (cluesRemaining <= targetClues) break;
const [row, col] = pos;
if (puzzle.grid[row][col] === 0) continue;
// Sauvegarder valeur originale
const originalValue = puzzle.grid[row][col];
puzzle.grid[row][col] = 0;
// Vérifier solution unique
if (this.hasUniqueSolution(puzzle)) {
cluesRemaining--;
} else {
// Restaurer si multiples solutions
puzzle.grid[row][col] = originalValue;
}
}
return {
puzzle: puzzle,
solution: solution,
difficulty: difficulty,
clues: cluesRemaining
};
}
// Génère positions aléatoires avec préférence symétrique
generateRandomPositions() {
const positions = [];
// Ajouter toutes les positions
for (let row = 0; row < 9; row++) {
for (let col = 0; col < 9; col++) {
positions.push([row, col]);
}
}
return this.shuffle(positions);
}✅ Validation et Résolution
Résolveur avec Comptage de Solutions
Pour garantir l'unicité de la solution, implémentez un résolveur qui compte le nombre de solutions possibles :
class SudokuSolver {
constructor(grid) {
this.grid = grid.clone();
}
// Compte le nombre de solutions possibles
countSolutions(maxSolutions = 2) {
return this.countSolutionsRecursive(0, 0, maxSolutions);
}
countSolutionsRecursive(row, col, maxSolutions) {
if (row === 9) return 1; // Solution trouvée
if (col === 9) return this.countSolutionsRecursive(row + 1, 0, maxSolutions);
if (this.grid.grid[row][col] !== 0) {
return this.countSolutionsRecursive(row, col + 1, maxSolutions);
}
let solutionCount = 0;
for (let num = 1; num <= 9; num++) {
if (this.grid.isValid(row, col, num)) {
this.grid.grid[row][col] = num;
solutionCount += this.countSolutionsRecursive(row, col + 1, maxSolutions);
// Optimisation : arrêt précoce si trop de solutions
if (solutionCount >= maxSolutions) {
this.grid.grid[row][col] = 0;
return solutionCount;
}
this.grid.grid[row][col] = 0;
}
}
return solutionCount;
}
// Vérifie si la grille a exactement une solution
hasUniqueSolution() {
return this.countSolutions(2) === 1;
}
// Résout complètement le puzzle
solve() {
return this.solveRecursive(0, 0);
}
solveRecursive(row, col) {
if (row === 9) return true;
if (col === 9) return this.solveRecursive(row + 1, 0);
if (this.grid.grid[row][col] !== 0) {
return this.solveRecursive(row, col + 1);
}
for (let num = 1; num <= 9; num++) {
if (this.grid.isValid(row, col, num)) {
this.grid.grid[row][col] = num;
if (this.solveRecursive(row, col + 1)) {
return true;
}
this.grid.grid[row][col] = 0;
}
}
return false;
}
}🚀 Optimisations de Performance
Techniques d'Accélération
Pre-remplissage Intelligent
Commencez par placer des patterns de base pour réduire l'espace de recherche
Ordre de Remplissage
Priorisez les cellules avec le moins de possibilités (heuristique MRV)
Cache des Contraintes
Maintenez des structures de données pour éviter les recalculs
Web Workers
Déportez la génération en arrière-plan pour éviter le blocage UI
Implémentation avec Web Workers
// worker.js - Web Worker pour génération
self.onmessage = function(e) {
const { difficulty, count } = e.data;
const generator = new SudokuGenerator();
const puzzles = [];
for (let i = 0; i < count; i++) {
const puzzle = generator.generatePuzzle(difficulty);
puzzles.push(puzzle);
// Progression
self.postMessage({
type: 'progress',
completed: i + 1,
total: count
});
}
// Résultat final
self.postMessage({
type: 'complete',
puzzles: puzzles
});
};
// Utilisation côté principal
class AsyncSudokuGenerator {
generatePuzzlesAsync(difficulty, count) {
return new Promise((resolve, reject) => {
const worker = new Worker('worker.js');
worker.postMessage({ difficulty, count });
worker.onmessage = function(e) {
if (e.data.type === 'complete') {
resolve(e.data.puzzles);
worker.terminate();
} else if (e.data.type === 'progress') {
// Callback de progression
this.onProgress?.(e.data.completed, e.data.total);
}
};
worker.onerror = reject;
});
}
}🎨 Interface Utilisateur Interactive
Rendu de Grille HTML/CSS
Créez une interface utilisateur moderne et responsive pour votre générateur :
class SudokuRenderer {
constructor(containerSelector) {
this.container = document.querySelector(containerSelector);
this.grid = null;
this.selectedCell = null;
}
renderGrid(sudokuGrid, isEditable = false) {
this.grid = sudokuGrid;
this.container.innerHTML = '';
this.container.className = 'sudoku-grid';
for (let row = 0; row < 9; row++) {
for (let col = 0; col < 9; col++) {
const cell = this.createCell(row, col, isEditable);
this.container.appendChild(cell);
}
}
}
createCell(row, col, isEditable) {
const cell = document.createElement('div');
cell.className = this.getCellClasses(row, col);
cell.dataset.row = row;
cell.dataset.col = col;
const value = this.grid.grid[row][col];
cell.textContent = value === 0 ? '' : value;
if (isEditable && value === 0) {
cell.contentEditable = true;
cell.addEventListener('input', this.handleCellInput.bind(this));
cell.addEventListener('click', this.handleCellClick.bind(this));
}
return cell;
}
getCellClasses(row, col) {
let classes = ['sudoku-cell'];
// Bordures épaisses pour régions 3x3
if (row % 3 === 0) classes.push('border-top');
if (col % 3 === 0) classes.push('border-left');
if (row === 8) classes.push('border-bottom');
if (col === 8) classes.push('border-right');
return classes.join(' ');
}
handleCellInput(event) {
const cell = event.target;
const row = parseInt(cell.dataset.row);
const col = parseInt(cell.dataset.col);
const value = parseInt(cell.textContent) || 0;
if (value >= 1 && value <= 9) {
if (this.grid.isValid(row, col, value)) {
this.grid.grid[row][col] = value;
cell.classList.remove('error');
} else {
cell.classList.add('error');
setTimeout(() => {
cell.textContent = '';
cell.classList.remove('error');
}, 1000);
}
}
}
}📱 Application Complète
Interface de Contrôle
class SudokuApp {
constructor() {
this.generator = new SudokuGenerator();
this.renderer = new SudokuRenderer('#sudoku-container');
this.currentPuzzle = null;
this.currentSolution = null;
this.initializeUI();
}
initializeUI() {
// Boutons de génération
document.getElementById('generate-easy').addEventListener('click',
() => this.generatePuzzle('easy'));
document.getElementById('generate-medium').addEventListener('click',
() => this.generatePuzzle('medium'));
document.getElementById('generate-hard').addEventListener('click',
() => this.generatePuzzle('hard'));
// Boutons d'action
document.getElementById('solve').addEventListener('click',
() => this.showSolution());
document.getElementById('validate').addEventListener('click',
() => this.validateCurrentGrid());
document.getElementById('export').addEventListener('click',
() => this.exportPuzzle());
}
async generatePuzzle(difficulty) {
this.showLoading(true);
try {
const result = this.generator.generatePuzzle(difficulty);
this.currentPuzzle = result.puzzle;
this.currentSolution = result.solution;
this.renderer.renderGrid(this.currentPuzzle, true);
this.updateStats(result);
} catch (error) {
console.error('Erreur génération:', error);
} finally {
this.showLoading(false);
}
}
showSolution() {
if (this.currentSolution) {
this.renderer.renderGrid(this.currentSolution, false);
}
}
validateCurrentGrid() {
const solver = new SudokuSolver(this.renderer.grid);
const isComplete = this.isGridComplete();
const isValid = this.isGridValid();
this.showValidationResult(isComplete && isValid);
}
exportPuzzle() {
if (this.currentPuzzle) {
const data = {
puzzle: this.currentPuzzle.grid,
solution: this.currentSolution.grid,
timestamp: new Date().toISOString()
};
this.downloadJSON(data, 'sudoku-puzzle.json');
}
}
}⚡ Optimisations Importantes
- • Utilisez des Web Workers pour éviter le blocage UI
- • Implémentez un cache pour les grilles génération
- • Optimisez la validation avec des bitmasks
- • Utilisez requestAnimationFrame pour les animations
Fonctionnalités Avancées
Une fois les bases maîtrisées, explorez des fonctionnalités comme la génération de variantes (Killer Sudoku, X-Sudoku), l'analyse de difficulté sophistiquée, et l'intégration d'intelligence artificielle pour optimiser la qualité des puzzles.
Félicitations ! Vous avez maintenant toutes les connaissances pour créer un générateur Sudoku professionnel en JavaScript !