Sudoku mit JavaScript erstellen

Von den Grundlagen zur funktionsfähigen Sudoku-Anwendung

💻 Warum JavaScript für Sudoku?

JavaScript ist die perfekte Wahl für Sudoku-Entwicklung, da es sowohl frontend- als auch backend-Fähigkeiten bietet, eine große Community hat und sofortige visuelle Ergebnisse im Browser ermöglicht. Mit modernem JavaScript können Sie komplexe Algorithmen implementieren und gleichzeitig eine ansprechende Benutzeroberfläche erstellen.

Einfacher Einstieg

Keine Installation erforderlich

Sofortige Ergebnisse

Direkt im Browser testen

Web-ready

Einfache Veröffentlichung

🏗️ Projekt-Setup

Grundlegende HTML-Struktur

Beginnen Sie mit einer einfachen HTML-Datei, die das Sudoku-Gitter und die notwendigen JavaScript-Dateien einbindet:

<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <title>Mein Sudoku Spiel</title>
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div class="container">
        <h2>Sudoku Spiel</h2>
        <div class="game-controls">
            <button id="newGame">Neues Spiel</button>
            <button id="checkSolution">Lösung prüfen</button>
            <button id="showHint">Hinweis</button>
        </div>
        <div id="sudoku-grid" class="sudoku-grid"></div>
        <div class="game-info">
            <div id="timer">Zeit: 00:00</div>
            <div id="difficulty">Schwierigkeit: Leicht</div>
        </div>
    </div>
    <script src="sudoku.js"></script>
</body>
</html>

CSS-Styling für das Sudoku-Gitter

.container {
    max-width: 600px;
    margin: 0 auto;
    padding: 20px;
    font-family: 'Arial', sans-serif;
}

.sudoku-grid {
    display: grid;
    grid-template-columns: repeat(9, 1fr);
    gap: 1px;
    background-color: #333;
    border: 3px solid #333;
    margin: 20px 0;
    width: 450px;
    height: 450px;
}

.cell {
    background-color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 20px;
    font-weight: bold;
    cursor: pointer;
    transition: background-color 0.2s;
}

.cell:hover {
    background-color: #e6f3ff;
}

.cell.selected {
    background-color: #cce7ff;
}

.cell.error {
    background-color: #ffe6e6;
}

.cell.given {
    background-color: #f0f0f0;
    color: #333;
    cursor: not-allowed;
}

/* Dickere Grenzen für 3x3-Boxen */
.cell:nth-child(3n) {
    border-right: 2px solid #333;
}

.cell:nth-child(n+19):nth-child(-n+27),
.cell:nth-child(n+46):nth-child(-n+54) {
    border-bottom: 2px solid #333;
}

🧠 JavaScript-Kern: Die Sudoku-Klasse

Grundstruktur der Sudoku-Klasse

Sudoku-Klassen-Architektur:

class SudokuGame {
    constructor() {
        this.grid = this.createEmptyGrid();
        this.solution = null;
        this.difficulty = 'medium';
        this.timer = 0;
        this.timerInterval = null;
        this.selectedCell = null;
        
        this.initializeGame();
    }
    
    createEmptyGrid() {
        return Array(9).fill(null).map(() => Array(9).fill(0));
    }
    
    initializeGame() {
        this.createGridHTML();
        this.setupEventListeners();
        this.generateNewPuzzle();
        this.startTimer();
    }
    
    // Weitere Methoden folgen...
}

Gitter-Validierung implementieren

isValidMove(grid, row, col, num) {
    // Zeile prüfen
    for (let x = 0; x < 9; x++) {
        if (grid[row][x] === num) return false;
    }
    
    // Spalte prüfen
    for (let x = 0; x < 9; x++) {
        if (grid[x][col] === num) return false;
    }
    
    // 3x3 Box prüfen
    const boxRow = Math.floor(row / 3) * 3;
    const boxCol = Math.floor(col / 3) * 3;
    
    for (let i = 0; i < 3; i++) {
        for (let j = 0; j < 3; j++) {
            if (grid[boxRow + i][boxCol + j] === num) return false;
        }
    }
    
    return true;
}

validateGrid() {
    for (let row = 0; row < 9; row++) {
        for (let col = 0; col < 9; col++) {
            if (this.grid[row][col] !== 0) {
                const num = this.grid[row][col];
                this.grid[row][col] = 0; // Temporär entfernen für Test
                
                if (!this.isValidMove(this.grid, row, col, num)) {
                    this.grid[row][col] = num; // Zurücksetzen
                    return false;
                }
                
                this.grid[row][col] = num; // Zurücksetzen
            }
        }
    }
    return true;
}

🎲 Sudoku-Generierung: Der Algorithmus

Backtracking-Löser

Der Herzstück jeder Sudoku-Anwendung ist der Lösungsalgorithmus. Backtracking ist die bewährte Methode:

solveSudoku(grid) {
    for (let row = 0; row < 9; row++) {
        for (let col = 0; col < 9; col++) {
            if (grid[row][col] === 0) {
                for (let num = 1; num <= 9; num++) {
                    if (this.isValidMove(grid, row, col, num)) {
                        grid[row][col] = num;
                        
                        if (this.solveSudoku(grid)) {
                            return true;
                        }
                        
                        grid[row][col] = 0; // Backtrack
                    }
                }
                return false;
            }
        }
    }
    return true;
}

generateCompleteSolution() {
    const grid = this.createEmptyGrid();
    
    // Zufällige Zahlen in erste Zeile
    const numbers = [1,2,3,4,5,6,7,8,9];
    for (let i = numbers.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [numbers[i], numbers[j]] = [numbers[j], numbers[i]];
    }
    
    for (let col = 0; col < 9; col++) {
        grid[0][col] = numbers[col];
    }
    
    this.solveSudoku(grid);
    return grid;
}

Puzzle-Generierung durch Zellenentfernung

Schwierigkeitsgrad durch strategische Entfernung:

generatePuzzle(difficulty = 'medium') {
    const solution = this.generateCompleteSolution();
    const puzzle = solution.map(row => [...row]);
    
    const cellsToRemove = this.getCellsToRemove(difficulty);
    
    for (let i = 0; i < cellsToRemove; i++) {
        let row, col;
        do {
            row = Math.floor(Math.random() * 9);
            col = Math.floor(Math.random() * 9);
        } while (puzzle[row][col] === 0);
        
        const backup = puzzle[row][col];
        puzzle[row][col] = 0;
        
        // Prüfen ob Puzzle noch eindeutig lösbar
        if (!this.hasUniqueSolution(puzzle)) {
            puzzle[row][col] = backup; // Rückgängig machen
        }
    }
    
    this.grid = puzzle;
    this.solution = solution;
}

getCellsToRemove(difficulty) {
    const levels = {
        'easy': 35,
        'medium': 45,
        'hard': 55,
        'expert': 65
    };
    return levels[difficulty] || levels['medium'];
}

🎮 Benutzerinteraktion und Event-Handling

Event-Listeners einrichten

setupEventListeners() {
    // Zellen-Klicks handhaben
    document.addEventListener('click', (e) => {
        if (e.target.classList.contains('cell')) {
            this.selectCell(e.target);
        }
    });
    
    // Tastatur-Eingaben
    document.addEventListener('keydown', (e) => {
        if (this.selectedCell) {
            const num = parseInt(e.key);
            if (num >= 1 && num <= 9) {
                this.enterNumber(num);
            } else if (e.key === 'Delete' || e.key === 'Backspace') {
                this.clearCell();
            }
        }
    });
    
    // Spielsteuerungs-Buttons
    document.getElementById('newGame').addEventListener('click', () => {
        this.startNewGame();
    });
    
    document.getElementById('checkSolution').addEventListener('click', () => {
        this.checkCurrentSolution();
    });
    
    document.getElementById('showHint').addEventListener('click', () => {
        this.showHint();
    });
}

selectCell(cellElement) {
    // Vorherige Auswahl entfernen
    document.querySelectorAll('.cell.selected')
        .forEach(cell => cell.classList.remove('selected'));
    
    // Neue Zelle auswählen
    cellElement.classList.add('selected');
    this.selectedCell = {
        element: cellElement,
        row: parseInt(cellElement.dataset.row),
        col: parseInt(cellElement.dataset.col)
    };
}

Zahlen-Eingabe und Validierung

enterNumber(num) {
    if (!this.selectedCell) return;
    
    const { row, col, element } = this.selectedCell;
    
    // Prüfen ob Zelle editierbar
    if (element.classList.contains('given')) return;
    
    // Zahl setzen
    this.grid[row][col] = num;
    element.textContent = num;
    
    // Validierung und visuelles Feedback
    if (this.isValidMove(this.removeCurrentNumber(this.grid), row, col, num)) {
        element.classList.remove('error');
        this.checkWinCondition();
    } else {
        element.classList.add('error');
    }
    
    this.updateHighlights();
}

removeCurrentNumber(grid) {
    const { row, col } = this.selectedCell;
    const newGrid = grid.map(r => [...r]);
    newGrid[row][col] = 0;
    return newGrid;
}

updateHighlights() {
    if (!this.selectedCell) return;
    
    const selectedValue = this.grid[this.selectedCell.row][this.selectedCell.col];
    
    document.querySelectorAll('.cell').forEach((cell, index) => {
        const row = Math.floor(index / 9);
        const col = index % 9;
        
        cell.classList.remove('highlighted', 'same-number');
        
        // Highlight-Zeile, Spalte und Box
        if (row === this.selectedCell.row || col === this.selectedCell.col ||
            (Math.floor(row / 3) === Math.floor(this.selectedCell.row / 3) &&
             Math.floor(col / 3) === Math.floor(this.selectedCell.col / 3))) {
            cell.classList.add('highlighted');
        }
        
        // Gleiche Zahlen markieren
        if (selectedValue && this.grid[row][col] === selectedValue) {
            cell.classList.add('same-number');
        }
    });
}

⚡ Erweiterte Features

Timer und Statistiken

Game-State-Management:

startTimer() {
    this.startTime = Date.now();
    this.timerInterval = setInterval(() => {
        const elapsed = Date.now() - this.startTime;
        const minutes = Math.floor(elapsed / 60000);
        const seconds = Math.floor((elapsed % 60000) / 1000);
        
        document.getElementById('timer').textContent = 
            `Zeit: ${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
    }, 1000);
}

saveGameState() {
    const gameState = {
        grid: this.grid,
        timer: Date.now() - this.startTime,
        difficulty: this.difficulty,
        timestamp: Date.now()
    };
    
    localStorage.setItem('sudokuGameState', JSON.stringify(gameState));
}

loadGameState() {
    const saved = localStorage.getItem('sudokuGameState');
    if (saved) {
        const gameState = JSON.parse(saved);
        this.grid = gameState.grid;
        this.difficulty = gameState.difficulty;
        this.startTime = Date.now() - gameState.timer;
        this.updateDisplay();
        return true;
    }
    return false;
}

Hint-System

showHint() {
    // Einfachsten leeren Zelle finden
    let bestCell = null;
    let minOptions = 10;
    
    for (let row = 0; row < 9; row++) {
        for (let col = 0; col < 9; col++) {
            if (this.grid[row][col] === 0) {
                const options = this.getPossibleNumbers(row, col);
                if (options.length < minOptions && options.length > 0) {
                    minOptions = options.length;
                    bestCell = { row, col, options };
                }
            }
        }
    }
    
    if (bestCell) {
        if (bestCell.options.length === 1) {
            // Direkte Lösung zeigen
            this.highlightCell(bestCell.row, bestCell.col);
            this.showMessage(`Zelle (${bestCell.row + 1}, ${bestCell.col + 1}) kann nur ${bestCell.options[0]} sein!`);
        } else {
            // Mögliche Zahlen anzeigen
            this.highlightCell(bestCell.row, bestCell.col);
            this.showMessage(`Zelle (${bestCell.row + 1}, ${bestCell.col + 1}) kann ${bestCell.options.join(', ')} sein`);
        }
    } else {
        this.showMessage('Keine Hinweise verfügbar - prüfen Sie Ihre bisherigen Eingaben!');
    }
}

getPossibleNumbers(row, col) {
    const possible = [];
    for (let num = 1; num <= 9; num++) {
        if (this.isValidMove(this.grid, row, col, num)) {
            possible.push(num);
        }
    }
    return possible;
}

🏆 Optimierung und Best Practices

Performance-Optimierungen

  • Memoization: Cache für wiederholte Berechnungen
  • Efficient DOM Updates: Minimieren Sie DOM-Manipulationen
  • Web Workers: Schwere Berechnungen in Background-Threads
  • Local Storage: Spiel-Zustand persistent speichern

Code-Organisation

Modulare Struktur:

  • sudoku-core.js: Kernlogik und Algorithmen
  • sudoku-ui.js: Benutzeroberflächen-Management
  • sudoku-generator.js: Puzzle-Generierung
  • sudoku-solver.js: Lösungsalgorithmen
  • sudoku-utils.js: Hilfsfunktionen und Konstanten

Testing-Strategien

// Beispiel Unit-Test mit Jest
describe('Sudoku Validation', () => {
    let sudoku;
    
    beforeEach(() => {
        sudoku = new SudokuGame();
    });
    
    test('should validate correct row', () => {
        const grid = sudoku.createEmptyGrid();
        grid[0] = [1,2,3,4,5,6,7,8,9];
        expect(sudoku.isValidMove(grid, 1, 0, 1)).toBe(false);
        expect(sudoku.isValidMove(grid, 1, 0, 2)).toBe(false);
    });
    
    test('should generate valid complete solution', () => {
        const solution = sudoku.generateCompleteSolution();
        expect(sudoku.isValidSolution(solution)).toBe(true);
    });
    
    test('should solve puzzle correctly', () => {
        const puzzle = /* test puzzle */;
        const solved = sudoku.solveSudoku([...puzzle]);
        expect(solved).toBe(true);
        expect(sudoku.isValidSolution(puzzle)).toBe(true);
    });
});

🚀 Deployment und Veröffentlichung

Web-Deployment-Optionen

Kostenlose Optionen

  • GitHub Pages: Direkt von Repository
  • Netlify: Drag-and-Drop Deployment
  • Vercel: Automatische Git-Integration
  • Firebase Hosting: Google's Platform

Premium-Optionen

  • Custom Domain: Ihre eigene URL
  • CDN-Integration: Globale Performance
  • Analytics: Nutzer-Tracking
  • Progressive Web App: App-ähnliche Erfahrung

Progressive Web App (PWA) Features

  • Service Worker: Offline-Funktionalität
  • Manifest.json: App-Installation ermöglichen
  • Responsive Design: Alle Bildschirmgrößen
  • Push Notifications: Tägliche Sudoku-Erinnerungen

💻 Bereit für JavaScript-Sudoku?

JavaScript bietet alle Tools, die Sie für ein professionelles Sudoku-Spiel benötigen. Von einfachen Implementierungen bis zu fortgeschrittenen Features - Ihrer Kreativität sind keine Grenzen gesetzt!