const BOARD_SIZE = 9; const BOX_SIZE = 3; // True RNG Utilities function getTrueRandomInt(max) { const randomBuffer = new Uint32Array(1); window.crypto.getRandomValues(randomBuffer); return randomBuffer[0] % max; } function shuffleArray(array) { for (let i = array.length - 1; i > 0; i--) { const j = getTrueRandomInt(i + 1); [array[i], array[j]] = [array[j], array[i]]; } } // Game State let solutionBoard = []; let initialBoard = []; let currentBoard = []; let notesBoard = Array.from({ length: BOARD_SIZE }, () => Array.from({ length: BOARD_SIZE }, () => [])); let history = []; // For undo let selectedCell = null; let notesMode = false; let difficulty = 'easy'; // easy, medium, hard let timerInterval = null; let secondsElapsed = 0; // Sudoku Core Logic function getEmptyBoard() { return Array.from({ length: BOARD_SIZE }, () => Array(BOARD_SIZE).fill(0)); } function isValid(board, row, col, num) { for (let x = 0; x < BOARD_SIZE; x++) { if (board[row][x] === num) return false; } for (let x = 0; x < BOARD_SIZE; x++) { if (board[x][col] === num) return false; } let startRow = row - row % BOX_SIZE, startCol = col - col % BOX_SIZE; for (let i = 0; i < BOX_SIZE; i++) { for (let j = 0; j < BOX_SIZE; j++) { if (board[i + startRow][j + startCol] === num) return false; } } return true; } function fillBoard(board) { for (let row = 0; row < BOARD_SIZE; row++) { for (let col = 0; col < BOARD_SIZE; col++) { if (board[row][col] === 0) { let nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]; shuffleArray(nums); for (let num of nums) { if (isValid(board, row, col, num)) { board[row][col] = num; if (fillBoard(board)) { return true; } board[row][col] = 0; } } return false; } } } return true; } function solveBoardCount(board, count = { value: 0 }) { let row = -1; let col = -1; let isEmpty = false; for (let i = 0; i < BOARD_SIZE; i++) { for (let j = 0; j < BOARD_SIZE; j++) { if (board[i][j] === 0) { row = i; col = j; isEmpty = true; break; } } if (isEmpty) break; } if (!isEmpty) { count.value++; return; } for (let num = 1; num <= 9; num++) { if (isValid(board, row, col, num)) { board[row][col] = num; solveBoardCount(board, count); board[row][col] = 0; } if (count.value > 1) return; // Fast exit if more than 1 solution } } function generatePuzzle(difficulty) { let board = getEmptyBoard(); fillBoard(board); solutionBoard = board.map(row => [...row]); let cellsToRemove = 40; // Default easy if (difficulty === 'medium') cellsToRemove = 50; if (difficulty === 'hard') cellsToRemove = 60; let cells = []; for (let r = 0; r < BOARD_SIZE; r++) { for (let c = 0; c < BOARD_SIZE; c++) { cells.push({ r, c }); } } shuffleArray(cells); let puzzle = solutionBoard.map(row => [...row]); let removedCount = 0; for (let cell of cells) { if (removedCount >= cellsToRemove) break; let temp = puzzle[cell.r][cell.c]; puzzle[cell.r][cell.c] = 0; let count = { value: 0 }; let copy = puzzle.map(row => [...row]); solveBoardCount(copy, count); if (count.value !== 1) { puzzle[cell.r][cell.c] = temp; // Put it back } else { removedCount++; } } return puzzle; } // Timer Logic function startTimer() { clearInterval(timerInterval); secondsElapsed = 0; updateTimerDisplay(); timerInterval = setInterval(() => { secondsElapsed++; updateTimerDisplay(); }, 1000); } function updateTimerDisplay() { let m = Math.floor(secondsElapsed / 60).toString().padStart(2, '0'); let s = (secondsElapsed % 60).toString().padStart(2, '0'); document.getElementById('timerDisplay').innerText = `${m}:${s}`; } function stopTimer() { clearInterval(timerInterval); } // UI Initialization function initGame() { history = []; selectedCell = null; notesBoard = Array.from({ length: BOARD_SIZE }, () => Array.from({ length: BOARD_SIZE }, () => [])); updateUndoBtn(); currentBoard = generatePuzzle(difficulty); initialBoard = currentBoard.map(row => [...row]); renderBoard(); startTimer(); } function renderBoard() { const boardEl = document.getElementById('board'); boardEl.innerHTML = ''; for (let r = 0; r < BOARD_SIZE; r++) { for (let c = 0; c < BOARD_SIZE; c++) { const cell = document.createElement('div'); cell.classList.add('cell'); cell.dataset.r = r; cell.dataset.c = c; if (initialBoard[r][c] !== 0) { cell.classList.add('fixed'); cell.innerText = initialBoard[r][c]; } else if (currentBoard[r][c] !== 0) { cell.classList.add('user-input'); cell.innerText = currentBoard[r][c]; // Check if it's an error relative to solution board if (currentBoard[r][c] !== solutionBoard[r][c]) { cell.classList.add('error'); } } else { // Render Notes if (notesBoard[r][c].length > 0) { const notesGrid = document.createElement('div'); notesGrid.classList.add('notes-grid'); for (let i = 1; i <= 9; i++) { const noteNum = document.createElement('div'); noteNum.classList.add('note-num'); if (notesBoard[r][c].includes(i)) { noteNum.innerText = i; } notesGrid.appendChild(noteNum); } cell.appendChild(notesGrid); } } if (selectedCell && selectedCell.r === r && selectedCell.c === c) { cell.classList.add('selected'); } else if (selectedCell && currentBoard[selectedCell.r][selectedCell.c] !== 0 && currentBoard[r][c] === currentBoard[selectedCell.r][selectedCell.c]) { cell.classList.add('highlight'); } else if (selectedCell && initialBoard[selectedCell.r][selectedCell.c] !== 0 && initialBoard[r][c] === initialBoard[selectedCell.r][selectedCell.c]) { cell.classList.add('highlight'); } cell.addEventListener('click', () => selectCell(r, c)); boardEl.appendChild(cell); } } updateNumpadState(); } function updateNumpadState() { const totalCounts = {}; for (let i = 1; i <= 9; i++) { totalCounts[i] = 0; } for (let r = 0; r < BOARD_SIZE; r++) { for (let c = 0; c < BOARD_SIZE; c++) { const val = currentBoard[r][c]; if (val >= 1 && val <= 9) { totalCounts[val]++; } } } document.querySelectorAll('.num-btn').forEach(btn => { const num = parseInt(btn.dataset.num); if (num >= 1 && num <= 9) { btn.disabled = totalCounts[num] >= 9; const counterEl = btn.querySelector('.num-counter'); if (counterEl) { const remaining = 9 - totalCounts[num]; counterEl.innerText = remaining; if (remaining > 0) { counterEl.classList.remove('hidden'); } else { counterEl.classList.add('hidden'); } } } }); } function selectCell(r, c) { if (selectedCell && selectedCell.r === r && selectedCell.c === c) { selectedCell = null; } else { selectedCell = { r, c }; } renderBoard(); } // Interaction function handleInput(num) { if (!selectedCell) return; const { r, c } = selectedCell; if (initialBoard[r][c] !== 0) return; // Cannot edit fixed cells saveHistory(); if (num === 0) { currentBoard[r][c] = 0; } else if (notesMode) { if (currentBoard[r][c] === 0) { const idx = notesBoard[r][c].indexOf(num); if (idx > -1) { notesBoard[r][c].splice(idx, 1); } else { notesBoard[r][c].push(num); notesBoard[r][c].sort((a, b) => a - b); } } } else { currentBoard[r][c] = num; notesBoard[r][c] = []; // Clear notes when filled clearRelatedNotes(r, c, num); } renderBoard(); checkWin(); } function clearRelatedNotes(r, c, num) { for (let i = 0; i < BOARD_SIZE; i++) { removeNote(r, i, num); removeNote(i, c, num); } let startR = r - r % BOX_SIZE; let startC = c - c % BOX_SIZE; for (let i = 0; i < BOX_SIZE; i++) { for (let j = 0; j < BOX_SIZE; j++) { removeNote(startR + i, startC + j, num); } } } function removeNote(r, c, num) { const idx = notesBoard[r][c].indexOf(num); if (idx > -1) notesBoard[r][c].splice(idx, 1); } // History & Undo function saveHistory() { history.push({ board: currentBoard.map(row => [...row]), notes: notesBoard.map(row => row.map(cellNotes => [...cellNotes])) }); updateUndoBtn(); } function undo() { if (history.length === 0) return; const lastState = history.pop(); currentBoard = lastState.board; notesBoard = lastState.notes; updateUndoBtn(); renderBoard(); } function updateUndoBtn() { document.getElementById('btnUndo').disabled = history.length === 0; } // Win Check function checkWin() { let complete = true; for (let r = 0; r < BOARD_SIZE; r++) { for (let c = 0; c < BOARD_SIZE; c++) { if (currentBoard[r][c] === 0 || currentBoard[r][c] !== solutionBoard[r][c]) { complete = false; break; } } } if (complete) { stopTimer(); setTimeout(() => alert('Congratulations! You solved the Sudoku!'), 100); } } // Event Listeners document.getElementById('btnNewGame').addEventListener('click', initGame); document.getElementById('btnUndo').addEventListener('click', undo); document.getElementById('btnNotes').addEventListener('click', () => { notesMode = !notesMode; document.getElementById('btnNotes').classList.toggle('active', notesMode); }); document.getElementById('btnClear').addEventListener('click', () => { handleInput(0); }); document.querySelectorAll('.num-btn').forEach(btn => { btn.addEventListener('click', (e) => { const num = parseInt(e.currentTarget.dataset.num); handleInput(num); }); }); // Difficulty Dropdown Logic const btnDifficulty = document.getElementById('btnDifficulty'); const difficultyDropdown = document.getElementById('difficultyDropdown'); btnDifficulty.addEventListener('click', (e) => { e.stopPropagation(); difficultyDropdown.classList.toggle('show'); }); document.querySelectorAll('.dropdown-item').forEach(item => { item.addEventListener('click', (e) => { document.querySelectorAll('.dropdown-item').forEach(el => el.classList.remove('active')); e.currentTarget.classList.add('active'); difficulty = e.currentTarget.dataset.diff; difficultyDropdown.classList.remove('show'); initGame(); }); }); document.addEventListener('click', (e) => { if (!difficultyDropdown.contains(e.target) && e.target !== btnDifficulty) { difficultyDropdown.classList.remove('show'); } }); // Theme Switcher Logic const btnThemeToggle = document.getElementById('btnThemeToggle'); btnThemeToggle.addEventListener('click', () => { const isDarkMode = document.body.classList.toggle('dark-mode'); const icon = btnThemeToggle.querySelector('i'); if (isDarkMode) { icon.className = 'fa-solid fa-sun'; } else { icon.className = 'fa-solid fa-moon'; } }); document.addEventListener('keydown', (e) => { if (!selectedCell) return; if (e.key >= '1' && e.key <= '9') { handleInput(parseInt(e.key)); } else if (e.key === 'Backspace' || e.key === 'Delete') { handleInput(0); } else if (e.key === 'n' || e.key === 'N') { document.getElementById('btnNotes').click(); } else if (e.key === 'ArrowUp') { if (selectedCell.r > 0) selectCell(selectedCell.r - 1, selectedCell.c); } else if (e.key === 'ArrowDown') { if (selectedCell.r < BOARD_SIZE - 1) selectCell(selectedCell.r + 1, selectedCell.c); } else if (e.key === 'ArrowLeft') { if (selectedCell.c > 0) selectCell(selectedCell.r, selectedCell.c - 1); } else if (e.key === 'ArrowRight') { if (selectedCell.c < BOARD_SIZE - 1) selectCell(selectedCell.r, selectedCell.c + 1); } }); // Start Game initGame();