Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Sudoku Master</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet"> | |
| <script src="https://unpkg.com/aos@2.3.1/dist/aos.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <style> | |
| .sudoku-cell { | |
| width: 100%; | |
| height: 100%; | |
| text-align: center; | |
| font-size: 1.5rem; | |
| font-weight: bold; | |
| border: none; | |
| outline: none; | |
| background-color: white; | |
| transition: all 0.2s ease; | |
| } | |
| .sudoku-cell:focus { | |
| background-color: rgba(59, 130, 246, 0.1); | |
| box-shadow: inset 0 0 0 2px #3b82f6; | |
| z-index: 10; | |
| } | |
| .sudoku-grid { | |
| display: grid; | |
| grid-template-columns: repeat(9, 1fr); | |
| grid-template-rows: repeat(9, 1fr); | |
| gap: 1px; | |
| width: 450px; | |
| height: 450px; | |
| border: 2px solid #1e40af; | |
| } | |
| .sudoku-cell:nth-child(3n) { | |
| border-right: 3px solid #1e40af; | |
| } | |
| .sudoku-cell:nth-child(9n) { | |
| border-right: none; | |
| } | |
| .sudoku-row:nth-child(3n) { | |
| border-bottom: 3px solid #1e40af; | |
| } | |
| .sudoku-row:last-child { | |
| border-bottom: none; | |
| } | |
| .fixed-number { | |
| color: #1e40af; | |
| font-weight: 800; | |
| } | |
| .error { | |
| color: #ef4444; | |
| background-color: #fee2e2; | |
| animation: shake 0.5s; | |
| } | |
| @keyframes shake { | |
| 0%, 100% { transform: translateX(0); } | |
| 25% { transform: translateX(-5px); } | |
| 75% { transform: translateX(5px); } | |
| } | |
| @media (max-width: 640px) { | |
| .sudoku-grid { | |
| width: 90vw; | |
| height: 90vw; | |
| } | |
| .sudoku-cell { | |
| font-size: 1rem; | |
| } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gradient-to-br from-blue-50 to-indigo-50 min-h-screen flex flex-col items-center justify-center p-4"> | |
| <div class="max-w-4xl w-full"> | |
| <header class="text-center mb-8" data-aos="fade-down"> | |
| <h1 class="text-5xl font-bold bg-gradient-to-r from-blue-600 to-indigo-600 bg-clip-text text-transparent mb-2">Sudoku Master</h1> | |
| <p class="text-gray-600 text-lg">Challenge your mind with this classic puzzle game</p> | |
| </header> | |
| <div class="flex flex-col lg:flex-row items-center justify-center gap-8"> | |
| <div class="bg-white rounded-2xl shadow-2xl p-6 border-2 border-blue-100" data-aos="zoom-in"> | |
| <div class="sudoku-grid bg-gray-200"> | |
| <!-- Sudoku grid will be generated by JavaScript --> | |
| </div> | |
| </div> | |
| <div class="bg-white rounded-2xl shadow-2xl p-8 w-full max-w-md border-2 border-blue-100" data-aos="fade-left"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <div> | |
| <h2 class="text-xl font-semibold text-gray-800">Game Controls</h2> | |
| <p class="text-gray-500 text-sm">Difficulty: <span id="difficulty-level" class="font-medium">Medium</span></p> | |
| </div> | |
| <div class="flex items-center space-x-2"> | |
| <span id="timer" class="text-lg font-mono bg-gray-100 px-3 py-1 rounded">00:00</span> | |
| <button id="pause-btn" class="p-2 rounded-full bg-gray-100 hover:bg-gray-200"> | |
| <i data-feather="pause"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-3 gap-3 mb-6"> | |
| <button id="easy-btn" class="btn-difficulty py-3 px-4 rounded-xl bg-green-50 text-green-700 hover:bg-green-100 border-2 border-green-200 font-medium transition-all" data-level="easy">Easy</button> | |
| <button id="medium-btn" class="btn-difficulty py-3 px-4 rounded-xl bg-blue-50 text-blue-700 hover:bg-blue-100 border-2 border-blue-200 font-medium transition-all" data-level="medium">Medium</button> | |
| <button id="hard-btn" class="btn-difficulty py-3 px-4 rounded-xl bg-red-50 text-red-700 hover:bg-red-100 border-2 border-red-200 font-medium transition-all" data-level="hard">Hard</button> | |
| </div> | |
| <div class="grid grid-cols-3 gap-3 mb-6"> | |
| <button id="new-game-btn" class="col-span-2 py-3 px-6 rounded-xl bg-indigo-600 hover:bg-indigo-700 text-white flex items-center justify-center shadow-md hover:shadow-lg transition-all"> | |
| <i data-feather="refresh-cw" class="mr-2"></i> New Game | |
| </button> | |
| <button id="hint-btn" class="py-3 px-6 rounded-xl bg-amber-500 hover:bg-amber-600 text-white flex items-center justify-center shadow-md hover:shadow-lg transition-all"> | |
| <i data-feather="help-circle" class="mr-2"></i> Hint | |
| </button> | |
| </div> | |
| <div class="grid grid-cols-2 gap-3"> | |
| <button id="check-btn" class="py-3 px-6 rounded-xl bg-gray-800 hover:bg-gray-900 text-white flex items-center justify-center shadow-md hover:shadow-lg transition-all"> | |
| <i data-feather="check" class="mr-2"></i> Check | |
| </button> | |
| <button id="solve-btn" class="py-3 px-6 rounded-xl bg-purple-600 hover:bg-purple-700 text-white flex items-center justify-center shadow-md hover:shadow-lg transition-all"> | |
| <i data-feather="eye" class="mr-2"></i> Solution | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-8 text-center text-gray-500 text-sm" data-aos="fade-up"> | |
| <p>Use numbers 1-9 to fill the grid. Each row, column, and 3x3 box must contain all digits from 1 to 9.</p> | |
| </div> | |
| </div> | |
| <script> | |
| // Initialize AOS animations | |
| AOS.init({ | |
| duration: 800, | |
| once: true | |
| }); | |
| // Initialize feather icons | |
| feather.replace(); | |
| // Sudoku game logic | |
| document.addEventListener('DOMContentLoaded', function() { | |
| const sudokuGrid = document.querySelector('.sudoku-grid'); | |
| const newGameBtn = document.getElementById('new-game-btn'); | |
| const hintBtn = document.getElementById('hint-btn'); | |
| const checkBtn = document.getElementById('check-btn'); | |
| const solveBtn = document.getElementById('solve-btn'); | |
| const difficultyBtns = document.querySelectorAll('.btn-difficulty'); | |
| const difficultyLevel = document.getElementById('difficulty-level'); | |
| const timerElement = document.getElementById('timer'); | |
| const pauseBtn = document.getElementById('pause-btn'); | |
| let board = Array(9).fill().map(() => Array(9).fill(0)); | |
| let solution = Array(9).fill().map(() => Array(9).fill(0)); | |
| let fixedCells = Array(9).fill().map(() => Array(9).fill(false)); | |
| let selectedCell = null; | |
| let timerInterval; | |
| let seconds = 0; | |
| let isPaused = false; | |
| let currentDifficulty = 'medium'; | |
| // Initialize the game | |
| function initGame() { | |
| createGrid(); | |
| generatePuzzle(currentDifficulty); | |
| startTimer(); | |
| addEventListeners(); | |
| } | |
| // Create the Sudoku grid | |
| function createGrid() { | |
| sudokuGrid.innerHTML = ''; | |
| for (let i = 0; i < 9; i++) { | |
| for (let j = 0; j < 9; j++) { | |
| const cell = document.createElement('input'); | |
| cell.type = 'text'; | |
| cell.maxLength = 1; | |
| cell.className = 'sudoku-cell'; | |
| cell.dataset.row = i; | |
| cell.dataset.col = j; | |
| sudokuGrid.appendChild(cell); | |
| } | |
| } | |
| } | |
| // Generate a Sudoku puzzle | |
| function generatePuzzle(difficulty) { | |
| // Reset the board | |
| board = Array(9).fill().map(() => Array(9).fill(0)); | |
| fixedCells = Array(9).fill().map(() => Array(9).fill(false)); | |
| // Generate a complete solution | |
| generateSolution(0, 0); | |
| solution = JSON.parse(JSON.stringify(board)); | |
| // Remove numbers based on difficulty | |
| let cellsToRemove; | |
| switch(difficulty) { | |
| case 'easy': | |
| cellsToRemove = 40; | |
| break; | |
| case 'hard': | |
| cellsToRemove = 60; | |
| break; | |
| default: // medium | |
| cellsToRemove = 50; | |
| } | |
| // Remove numbers while keeping the puzzle solvable | |
| let cellsRemoved = 0; | |
| while (cellsRemoved < cellsToRemove) { | |
| const row = Math.floor(Math.random() * 9); | |
| const col = Math.floor(Math.random() * 9); | |
| if (board[row][col] !== 0) { | |
| const temp = board[row][col]; | |
| board[row][col] = 0; | |
| // Check if the puzzle still has a unique solution | |
| const tempBoard = JSON.parse(JSON.stringify(board)); | |
| if (solveSudoku(tempBoard)) { | |
| cellsRemoved++; | |
| } else { | |
| board[row][col] = temp; | |
| } | |
| } | |
| } | |
| // Mark fixed cells and update the UI | |
| updateUI(); | |
| } | |
| // Generate a complete Sudoku solution | |
| function generateSolution(row, col) { | |
| if (row === 9) { | |
| return true; | |
| } | |
| if (col === 9) { | |
| return generateSolution(row + 1, 0); | |
| } | |
| if (board[row][col] !== 0) { | |
| return generateSolution(row, col + 1); | |
| } | |
| const nums = [1, 2, 3, 4, 5, 6, 7, 8, 9]; | |
| shuffleArray(nums); | |
| for (let num of nums) { | |
| if (isValidPlacement(row, col, num)) { | |
| board[row][col] = num; | |
| if (generateSolution(row, col + 1)) { | |
| return true; | |
| } | |
| board[row][col] = 0; | |
| } | |
| } | |
| return false; | |
| } | |
| // Shuffle an array | |
| function shuffleArray(array) { | |
| for (let i = array.length - 1; i > 0; i--) { | |
| const j = Math.floor(Math.random() * (i + 1)); | |
| [array[i], array[j]] = [array[j], array[i]]; | |
| } | |
| return array; | |
| } | |
| // Check if a number can be placed in a cell | |
| function isValidPlacement(row, col, num) { | |
| // Check row | |
| for (let i = 0; i < 9; i++) { | |
| if (board[row][i] === num) return false; | |
| } | |
| // Check column | |
| for (let i = 0; i < 9; i++) { | |
| if (board[i][col] === num) return false; | |
| } | |
| // Check 3x3 box | |
| 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 (board[boxRow + i][boxCol + j] === num) return false; | |
| } | |
| } | |
| return true; | |
| } | |
| // Update the UI based on the current board state | |
| function updateUI() { | |
| const cells = document.querySelectorAll('.sudoku-cell'); | |
| cells.forEach(cell => { | |
| const row = parseInt(cell.dataset.row); | |
| const col = parseInt(cell.dataset.col); | |
| if (board[row][col] !== 0) { | |
| cell.value = board[row][col]; | |
| if (fixedCells[row][col]) { | |
| cell.classList.add('fixed-number'); | |
| cell.readOnly = true; | |
| } else { | |
| cell.classList.remove('fixed-number'); | |
| cell.readOnly = false; | |
| } | |
| } else { | |
| cell.value = ''; | |
| cell.classList.remove('fixed-number'); | |
| cell.readOnly = false; | |
| } | |
| cell.classList.remove('error'); | |
| }); | |
| } | |
| // Solve the Sudoku puzzle | |
| function 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 (isValidPlacementForGrid(grid, row, col, num)) { | |
| grid[row][col] = num; | |
| if (solveSudoku(grid)) { | |
| return true; | |
| } | |
| grid[row][col] = 0; | |
| } | |
| } | |
| return false; | |
| } | |
| } | |
| } | |
| return true; | |
| } | |
| // Check if a number can be placed in a cell for a given grid | |
| function isValidPlacementForGrid(grid, row, col, num) { | |
| // Check row | |
| for (let i = 0; i < 9; i++) { | |
| if (grid[row][i] === num) return false; | |
| } | |
| // Check column | |
| for (let i = 0; i < 9; i++) { | |
| if (grid[i][col] === num) return false; | |
| } | |
| // Check 3x3 box | |
| 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; | |
| } | |
| // Start the game timer | |
| function startTimer() { | |
| clearInterval(timerInterval); | |
| seconds = 0; | |
| updateTimerDisplay(); | |
| timerInterval = setInterval(() => { | |
| if (!isPaused) { | |
| seconds++; | |
| updateTimerDisplay(); | |
| } | |
| }, 1000); | |
| } | |
| // Update the timer display | |
| function updateTimerDisplay() { | |
| const minutes = Math.floor(seconds / 60); | |
| const remainingSeconds = seconds % 60; | |
| timerElement.textContent = `${minutes.toString().padStart(2, '0')}:${remainingSeconds.toString().padStart(2, '0')}`; | |
| } | |
| // Add event listeners | |
| function addEventListeners() { | |
| // Cell input | |
| const cells = document.querySelectorAll('.sudoku-cell'); | |
| cells.forEach(cell => { | |
| cell.addEventListener('input', function(e) { | |
| const row = parseInt(this.dataset.row); | |
| const col = parseInt(this.dataset.col); | |
| const value = parseInt(this.value) || 0; | |
| if (value >= 1 && value <= 9) { | |
| board[row][col] = value; | |
| this.classList.remove('error'); | |
| } else if (this.value === '') { | |
| board[row][col] = 0; | |
| this.classList.remove('error'); | |
| } else { | |
| this.value = ''; | |
| } | |
| }); | |
| cell.addEventListener('click', function() { | |
| cells.forEach(c => c.classList.remove('bg-blue-100')); | |
| this.classList.add('bg-blue-100'); | |
| selectedCell = this; | |
| }); | |
| cell.addEventListener('keydown', function(e) { | |
| if (e.key === 'ArrowUp' || e.key === 'ArrowDown' || | |
| e.key === 'ArrowLeft' || e.key === 'ArrowRight') { | |
| e.preventDefault(); | |
| moveSelection(e.key); | |
| } | |
| }); | |
| }); | |
| // Keyboard number input | |
| document.addEventListener('keydown', function(e) { | |
| if (selectedCell && !selectedCell.readOnly && e.key >= '1' && e.key <= '9') { | |
| selectedCell.value = e.key; | |
| const row = parseInt(selectedCell.dataset.row); | |
| const col = parseInt(selectedCell.dataset.col); | |
| board[row][col] = parseInt(e.key); | |
| selectedCell.classList.remove('error'); | |
| } else if (selectedCell && e.key === 'Backspace') { | |
| selectedCell.value = ''; | |
| const row = parseInt(selectedCell.dataset.row); | |
| const col = parseInt(selectedCell.dataset.col); | |
| board[row][col] = 0; | |
| selectedCell.classList.remove('error'); | |
| } | |
| }); | |
| // New game button | |
| newGameBtn.addEventListener('click', function() { | |
| generatePuzzle(currentDifficulty); | |
| startTimer(); | |
| }); | |
| // Hint button | |
| hintBtn.addEventListener('click', function() { | |
| if (selectedCell && selectedCell.value === '') { | |
| const row = parseInt(selectedCell.dataset.row); | |
| const col = parseInt(selectedCell.dataset.col); | |
| selectedCell.value = solution[row][col]; | |
| board[row][col] = solution[row][col]; | |
| selectedCell.classList.add('fixed-number'); | |
| selectedCell.readOnly = true; | |
| fixedCells[row][col] = true; | |
| } | |
| }); | |
| // Check button | |
| checkBtn.addEventListener('click', function() { | |
| const cells = document.querySelectorAll('.sudoku-cell'); | |
| let hasErrors = false; | |
| cells.forEach(cell => { | |
| if (!cell.readOnly && cell.value !== '') { | |
| const row = parseInt(cell.dataset.row); | |
| const col = parseInt(cell.dataset.col); | |
| if (parseInt(cell.value) !== solution[row][col]) { | |
| cell.classList.add('error'); | |
| hasErrors = true; | |
| } else { | |
| cell.classList.remove('error'); | |
| } | |
| } | |
| }); | |
| if (!hasErrors) { | |
| alert('Congratulations! Your solution is correct!'); | |
| } | |
| }); | |
| // Solve button | |
| solveBtn.addEventListener('click', function() { | |
| if (confirm('Are you sure you want to see the solution?')) { | |
| board = JSON.parse(JSON.stringify(solution)); | |
| updateUI(); | |
| clearInterval(timerInterval); | |
| } | |
| }); | |
| // Difficulty buttons | |
| difficultyBtns.forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| currentDifficulty = this.dataset.level; | |
| difficultyLevel.textContent = this.dataset.level.charAt(0).toUpperCase() + this.dataset.level.slice(1); | |
| difficultyBtns.forEach(b => b.classList.remove('ring-2', 'ring-offset-2', 'ring-blue-500')); | |
| this.classList.add('ring-2', 'ring-offset-2', 'ring-blue-500'); | |
| generatePuzzle(currentDifficulty); | |
| startTimer(); | |
| }); | |
| }); | |
| // Pause button | |
| pauseBtn.addEventListener('click', function() { | |
| isPaused = !isPaused; | |
| if (isPaused) { | |
| this.innerHTML = feather.icons['play'].toSvg(); | |
| } else { | |
| this.innerHTML = feather.icons['pause'].toSvg(); | |
| } | |
| }); | |
| } | |
| // Move selection with arrow keys | |
| function moveSelection(direction) { | |
| if (!selectedCell) return; | |
| const row = parseInt(selectedCell.dataset.row); | |
| const col = parseInt(selectedCell.dataset.col); | |
| let newRow = row; | |
| let newCol = col; | |
| switch(direction) { | |
| case 'ArrowUp': newRow = Math.max(0, row - 1); break; | |
| case 'ArrowDown': newRow = Math.min(8, row + 1); break; | |
| case 'ArrowLeft': newCol = Math.max(0, col - 1); break; | |
| case 'ArrowRight': newCol = Math.min(8, col + 1); break; | |
| } | |
| if (newRow !== row || newCol !== col) { | |
| const cells = document.querySelectorAll('.sudoku-cell'); | |
| cells.forEach(c => c.classList.remove('bg-blue-100')); | |
| const newCell = document.querySelector(`.sudoku-cell[data-row="${newRow}"][data-col="${newCol}"]`); | |
| newCell.classList.add('bg-blue-100'); | |
| newCell.focus(); | |
| selectedCell = newCell; | |
| } | |
| } | |
| // Start the game | |
| initGame(); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |