Assunto de Direito: Sudoku Master online

Sudoku Master online

🧠 Pronto para um desafio de lógica?

O Sudoku Master é a maneira perfeita de testar seu raciocínio e concentração. Escolha o nível de dificuldade que preferir e aproveite recursos como dicas inteligentes e modo lápis para melhorar sua estratégia.

Sudoku Master

Tempo: 00:00
Erros: 0/3
Pontuação: 100%

📌 Como jogar e melhorar sua performance no Sudoku?

Agora que você começou, veja algumas dicas para dominar o Sudoku Master:

  • 🧠 Use a lógica: Evite palpites e analise cada jogada.
  • ✏️ Modo Lápis: Utilize esta função para testar possibilidades antes de confirmar um número.
  • 🔍 Olhe padrões: Algumas combinações de números se repetem frequentemente.
  • 🚫 Evite erros: Você tem um limite de erros. Jogue com estratégia!

🔄 Quer tentar outro nível? Selecione uma nova dificuldade e continue melhorando! 🎯

/** * Sudoku Game - Refatorado e Otimizado (2026) * Melhorias: Performance, legibilidade, modularidade, eliminação de bugs */ class SudokuGame { constructor() { // Cache de elementos DOM this.DOM = { board: document.getElementById('sudoku-board'), modal: document.getElementById('modal'), modalTitle: document.getElementById('modal-title'), modalMessage: document.getElementById('modal-message'), timerDisplay: document.getElementById('timer'), errorsDisplay: document.getElementById('errors'), scoreDisplay: document.getElementById('score'), hintBtn: document.getElementById('hint'), undoBtn: document.getElementById('undo'), pauseBtn: document.getElementById('pause'), togglePencilBtn: document.getElementById('toggle-pencil'), difficultySelect: document.getElementById('difficulty'), newGameBtn: document.getElementById('new-game'), modalClose: document.getElementById('modal-close') }; // Constantes do jogo this.CONSTANTS = { BOARD_SIZE: 9, BOX_SIZE: 3, MAX_ERRORS: 3, MAX_HINTS: 3, DIFFICULTIES: { easy: 35, medium: 45, hard: 52, extreme: 58 }, SCORE_PENALTIES: { TIME_PER_MINUTE: 1.5, HINT: 5, ERROR: 10 } }; // Estado do jogo this.state = { sudoku: [], solution: [], timer: null, seconds: 0, errors: 0, hintsUsed: 0, pencilMode: false, gameOver: false, paused: false, moveHistory: [], selectedNumber: null }; this.init(); } // ============= INICIALIZAÇÃO ============= init() { this.bindEvents(); this.newGame(); } bindEvents() { // Botões principais this.DOM.newGameBtn.addEventListener('click', () => this.handleNewGame()); this.DOM.pauseBtn.addEventListener('click', () => this.togglePause()); this.DOM.hintBtn.addEventListener('click', () => this.useHint()); this.DOM.undoBtn.addEventListener('click', () => this.undoMove()); this.DOM.togglePencilBtn.addEventListener('click', () => this.togglePencilMode()); this.DOM.modalClose.addEventListener('click', () => this.closeModal()); this.DOM.difficultySelect.addEventListener('change', (e) => this.handleDifficultyChange(e)); // Atalhos de teclado window.addEventListener('keydown', (e) => this.handleGlobalKeyboard(e)); } // ============= GERAÇÃO DO SUDOKU ============= generateSudoku(difficulty) { const board = this.createEmptyBoard(); this.fillBoard(board); // Clonar solução this.state.solution = board.map(row => [...row]); // Remover células baseado na dificuldade const cellsToRemove = this.CONSTANTS.DIFFICULTIES[difficulty] || 35; this.removeNumbers(board, cellsToRemove); return board; } createEmptyBoard() { return Array.from({ length: this.CONSTANTS.BOARD_SIZE }, () => Array(this.CONSTANTS.BOARD_SIZE).fill(0) ); } fillBoard(board) { return this.solveSudoku(board, true); } solveSudoku(board, randomize = false) { const emptyCell = this.findEmptyCell(board); if (!emptyCell) return true; const [row, col] = emptyCell; const numbers = randomize ? this.shuffleArray([1, 2, 3, 4, 5, 6, 7, 8, 9]) : [1, 2, 3, 4, 5, 6, 7, 8, 9]; for (const num of numbers) { if (this.isValidPlacement(board, row, col, num)) { board[row][col] = num; if (this.solveSudoku(board, randomize)) { return true; } board[row][col] = 0; // Backtrack } } return false; } findEmptyCell(board) { for (let i = 0; i < this.CONSTANTS.BOARD_SIZE; i++) { for (let j = 0; j < this.CONSTANTS.BOARD_SIZE; j++) { if (board[i][j] === 0) return [i, j]; } } return null; } isValidPlacement(board, row, col, num) { // Verificar linha e coluna for (let x = 0; x < this.CONSTANTS.BOARD_SIZE; x++) { if (board[row][x] === num || board[x][col] === num) { return false; } } // Verificar quadrante 3x3 const boxRow = Math.floor(row / this.CONSTANTS.BOX_SIZE) * this.CONSTANTS.BOX_SIZE; const boxCol = Math.floor(col / this.CONSTANTS.BOX_SIZE) * this.CONSTANTS.BOX_SIZE; for (let i = 0; i < this.CONSTANTS.BOX_SIZE; i++) { for (let j = 0; j < this.CONSTANTS.BOX_SIZE; j++) { if (board[boxRow + i][boxCol + j] === num) { return false; } } } return true; } removeNumbers(board, count) { const positions = []; // Criar lista de todas as posições for (let i = 0; i < this.CONSTANTS.BOARD_SIZE; i++) { for (let j = 0; j < this.CONSTANTS.BOARD_SIZE; j++) { positions.push([i, j]); } } // Embaralhar e remover this.shuffleArray(positions); for (let i = 0; i < Math.min(count, positions.length); i++) { const [row, col] = positions[i]; board[row][col] = 0; } } shuffleArray(array) { const shuffled = [...array]; for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]; } return shuffled; } // ============= RENDERIZAÇÃO ============= renderBoard() { // Usar DocumentFragment para melhor performance const fragment = document.createDocumentFragment(); for (let i = 0; i < this.CONSTANTS.BOARD_SIZE; i++) { for (let j = 0; j < this.CONSTANTS.BOARD_SIZE; j++) { const cell = this.createCell(i, j); fragment.appendChild(cell); } } this.DOM.board.innerHTML = ''; this.DOM.board.appendChild(fragment); // Aplicar highlight inicial se houver número selecionado if (this.state.selectedNumber) { this.highlightRelatedNumbers(this.state.selectedNumber); } } createCell(row, col) { const cell = document.createElement('div'); cell.className = 'cell'; cell.dataset.row = row; cell.dataset.col = col; // Adicionar bordas de quadrante if (col % this.CONSTANTS.BOX_SIZE === 0 && col !== 0) { cell.classList.add('quadrant-border-left'); } if (row % this.CONSTANTS.BOX_SIZE === 0 && row !== 0) { cell.classList.add('quadrant-border-top'); } const input = this.createInput(row, col); cell.appendChild(input); return cell; } createInput(row, col) { const input = document.createElement('input'); input.type = 'tel'; input.maxLength = 1; input.dataset.row = row; input.dataset.col = col; input.setAttribute('inputmode', 'numeric'); input.setAttribute('pattern', '[1-9]'); const value = this.state.sudoku[row][col]; if (value !== 0) { input.value = value; input.parentElement?.classList.add('fixed'); input.disabled = true; input.setAttribute('aria-label', `Célula fixa com valor ${value}`); } else { input.setAttribute('aria-label', `Célula vazia linha ${row + 1} coluna ${col + 1}`); } // Event listeners input.addEventListener('input', (e) => this.handleInput(e)); input.addEventListener('focus', (e) => this.handleFocus(e)); input.addEventListener('keydown', (e) => this.handleKeyDown(e)); input.addEventListener('blur', () => { // Delay para permitir cliques em outras células setTimeout(() => this.clearHighlights(), 100); }); return input; } // ============= MANIPULAÇÃO DE ENTRADA ============= handleInput(e) { if (this.state.gameOver || this.state.paused) return; const input = e.target; const row = parseInt(input.dataset.row, 10); const col = parseInt(input.dataset.col, 10); const cell = input.parentElement; // Validar entrada let value = input.value.replace(/[^1-9]/g, ''); if (value.length > 1) { value = value.slice(-1); } input.value = value; // Verificar se é célula fixa if (cell.classList.contains('fixed')) { input.value = this.state.sudoku[row][col] || ''; return; } // Salvar histórico antes de modificar const oldValue = this.state.sudoku[row][col]; const oldPencilNotes = cell.querySelector('.pencil-mode')?.innerHTML || ''; if (this.state.pencilMode) { this.handlePencilInput(cell, value); this.state.selectedNumber = value || null; } else if (value) { this.handleRegularInput(input, row, col, parseInt(value, 10), cell); this.state.selectedNumber = value; this.highlightRelatedNumbers(value); } else { // Limpar célula this.state.sudoku[row][col] = 0; cell.classList.remove('wrong'); this.state.selectedNumber = null; } // Adicionar ao histórico this.state.moveHistory.push({ row, col, oldValue, newValue: value ? parseInt(value, 10) : 0, isPencil: this.state.pencilMode, oldPencilNotes }); this.updateUndoButton(); } handlePencilInput(cell, value) { let pencilDiv = cell.querySelector('.pencil-mode'); if (!pencilDiv) { pencilDiv = document.createElement('div'); pencilDiv.className = 'pencil-mode'; pencilDiv.setAttribute('aria-label', 'Notas a lápis'); // Criar 9 spans para os números 1-9 for (let i = 0; i < this.CONSTANTS.BOARD_SIZE; i++) { const span = document.createElement('span'); span.dataset.number = i + 1; pencilDiv.appendChild(span); } cell.appendChild(pencilDiv); } const input = cell.querySelector('input'); input.value = ''; if (value) { const index = parseInt(value, 10) - 1; const spans = pencilDiv.querySelectorAll('span'); const targetSpan = spans[index]; // Toggle nota if (targetSpan.textContent) { targetSpan.textContent = ''; } else { targetSpan.textContent = value; } } } handleRegularInput(input, row, col, value, cell) { // Remover notas a lápis cell.querySelector('.pencil-mode')?.remove(); if (value === this.state.solution[row][col]) { // Resposta correta this.state.sudoku[row][col] = value; cell.classList.remove('wrong'); // Verificar vitória requestAnimationFrame(() => this.checkWin()); } else { // Resposta incorreta this.state.errors++; this.updateErrorsDisplay(); cell.classList.add('wrong'); // Feedback sonoro (se disponível) this.playErrorFeedback(); // Limpar após delay setTimeout(() => { input.value = ''; cell.classList.remove('wrong'); this.state.sudoku[row][col] = 0; }, 800); if (this.state.errors >= this.CONSTANTS.MAX_ERRORS) { this.endGame(false); } } } handleKeyDown(e) { const input = e.target; const row = parseInt(input.dataset.row, 10); const col = parseInt(input.dataset.col, 10); let nextRow = row; let nextCol = col; switch (e.key) { case 'ArrowLeft': nextCol = Math.max(0, col - 1); break; case 'ArrowRight': nextCol = Math.min(this.CONSTANTS.BOARD_SIZE - 1, col + 1); break; case 'ArrowUp': nextRow = Math.max(0, row - 1); break; case 'ArrowDown': nextRow = Math.min(this.CONSTANTS.BOARD_SIZE - 1, row + 1); break; case 'Backspace': case 'Delete': if (!input.parentElement.classList.contains('fixed')) { input.value = ''; this.state.sudoku[row][col] = 0; input.parentElement.querySelector('.pencil-mode')?.remove(); } return; default: return; } e.preventDefault(); const nextInput = this.DOM.board.querySelector( `input[data-row="${nextRow}"][data-col="${nextCol}"]` ); nextInput?.focus(); } handleFocus(e) { if (this.state.gameOver || this.state.paused) return; const input = e.target; const row = parseInt(input.dataset.row, 10); const col = parseInt(input.dataset.col, 10); const value = input.value; this.clearHighlights(); this.highlightRelatedCells(row, col); if (value) { this.highlightRelatedNumbers(value); } } handleGlobalKeyboard(e) { // Prevenir ações se modal estiver aberto if (this.DOM.modal.style.display === 'flex') return; const key = e.key.toLowerCase(); switch (key) { case 'p': e.preventDefault(); this.togglePause(); break; case 'z': if (e.ctrlKey || e.metaKey) { e.preventDefault(); this.undoMove(); } break; case 'l': e.preventDefault(); this.togglePencilMode(); break; case 'h': if (!this.state.gameOver && !this.state.paused) { e.preventDefault(); this.useHint(); } break; } } // ============= HIGHLIGHTS ============= highlightRelatedCells(row, col) { const cells = this.DOM.board.querySelectorAll('.cell'); cells.forEach(cell => { const cellRow = parseInt(cell.dataset.row, 10); const cellCol = parseInt(cell.dataset.col, 10); const sameRow = cellRow === row; const sameCol = cellCol === col; const sameBox = Math.floor(cellRow / this.CONSTANTS.BOX_SIZE) === Math.floor(row / this.CONSTANTS.BOX_SIZE) && Math.floor(cellCol / this.CONSTANTS.BOX_SIZE) === Math.floor(col / this.CONSTANTS.BOX_SIZE); if (sameRow || sameCol || sameBox) { cell.classList.add('highlight'); } }); } highlightRelatedNumbers(value) { if (!value) return; const cells = this.DOM.board.querySelectorAll('.cell'); cells.forEach(cell => { const input = cell.querySelector('input'); const pencilNotes = cell.querySelector('.pencil-mode'); const hasValue = input.value === value; const hasPencilNote = pencilNotes && Array.from(pencilNotes.querySelectorAll('span')).some( span => span.textContent === value ); if (hasValue || hasPencilNote) { cell.classList.add('highlight-number'); } }); } clearHighlights() { const cells = this.DOM.board.querySelectorAll('.cell'); cells.forEach(cell => { cell.classList.remove('highlight', 'highlight-number'); }); } // ============= FUNCIONALIDADES ============= useHint() { if (this.state.hintsUsed >= this.CONSTANTS.MAX_HINTS) { this.showTemporaryMessage('Você já usou todas as dicas disponíveis!'); return; } if (this.state.gameOver || this.state.paused) return; // Encontrar células vazias const emptyCells = []; for (let i = 0; i < this.CONSTANTS.BOARD_SIZE; i++) { for (let j = 0; j < this.CONSTANTS.BOARD_SIZE; j++) { if (this.state.sudoku[i][j] === 0) { emptyCells.push({ row: i, col: j }); } } } if (emptyCells.length === 0) return; // Escolher célula aleatória const randomIndex = Math.floor(Math.random() * emptyCells.length); const { row, col } = emptyCells[randomIndex]; const value = this.state.solution[row][col]; // Aplicar dica this.state.sudoku[row][col] = value; const cell = this.DOM.board.querySelector( `.cell[data-row="${row}"][data-col="${col}"]` ); const input = cell.querySelector('input'); input.value = value; cell.classList.add('hint-used'); // Remover highlight após animação setTimeout(() => cell.classList.remove('hint-used'), 1000); this.state.hintsUsed++; this.updateHintButton(); // Verificar vitória requestAnimationFrame(() => this.checkWin()); } undoMove() { if (this.state.moveHistory.length === 0 || this.state.gameOver || this.state.paused) { return; } const lastMove = this.state.moveHistory.pop(); const cell = this.DOM.board.querySelector( `.cell[data-row="${lastMove.row}"][data-col="${lastMove.col}"]` ); const input = cell.querySelector('input'); // Restaurar valor anterior this.state.sudoku[lastMove.row][lastMove.col] = lastMove.oldValue; if (lastMove.isPencil) { // Restaurar notas a lápis let pencilDiv = cell.querySelector('.pencil-mode'); if (lastMove.oldPencilNotes && !pencilDiv) { pencilDiv = document.createElement('div'); pencilDiv.className = 'pencil-mode'; cell.appendChild(pencilDiv); } if (pencilDiv) { pencilDiv.innerHTML = lastMove.oldPencilNotes; } input.value = ''; } else { // Restaurar valor regular input.value = lastMove.oldValue || ''; cell.querySelector('.pencil-mode')?.remove(); } cell.classList.remove('wrong'); this.state.selectedNumber = lastMove.oldValue?.toString() || null; this.updateUndoButton(); if (this.state.selectedNumber) { this.highlightRelatedNumbers(this.state.selectedNumber); } } togglePencilMode() { this.state.pencilMode = !this.state.pencilMode; const iconClass = this.state.pencilMode ? 'pencil-alt' : 'pencil-alt'; const text = this.state.pencilMode ? 'Normal' : 'Lápis'; this.DOM.togglePencilBtn.innerHTML = ` ${text}`; this.DOM.board.classList.toggle('pencil-active', this.state.pencilMode); } togglePause() { this.state.paused = !this.state.paused; const icon = this.state.paused ? 'play' : 'pause'; const text = this.state.paused ? 'Continuar' : 'Pausar'; this.DOM.pauseBtn.innerHTML = ` ${text}`; this.DOM.board.classList.toggle('paused', this.state.paused); // Desabilitar inputs durante pausa const inputs = this.DOM.board.querySelectorAll('.cell input'); inputs.forEach(input => { if (!input.parentElement.classList.contains('fixed')) { input.disabled = this.state.paused; } }); } // ============= TIMER E PONTUAÇÃO ============= startTimer() { this.stopTimer(); this.state.timer = setInterval(() => { if (!this.state.paused && !this.state.gameOver) { this.state.seconds++; this.updateDisplay(); } }, 1000); } stopTimer() { if (this.state.timer) { clearInterval(this.state.timer); this.state.timer = null; } } calculateScore() { const { TIME_PER_MINUTE, HINT, ERROR } = this.CONSTANTS.SCORE_PENALTIES; const timePenalty = Math.floor(this.state.seconds / 60) * TIME_PER_MINUTE; const hintPenalty = this.state.hintsUsed * HINT; const errorPenalty = this.state.errors * ERROR; return Math.max(0, Math.floor(100 - timePenalty - hintPenalty - errorPenalty)); } updateDisplay() { // Atualizar timer const minutes = Math.floor(this.state.seconds / 60); const seconds = this.state.seconds % 60; this.DOM.timerDisplay.textContent = `Tempo: ${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`; // Atualizar pontuação const score = this.calculateScore(); this.DOM.scoreDisplay.textContent = `Pontuação: ${score}%`; } updateErrorsDisplay() { this.DOM.errorsDisplay.textContent = `Erros: ${this.state.errors}/${this.CONSTANTS.MAX_ERRORS}`; } updateHintButton() { const remaining = this.CONSTANTS.MAX_HINTS - this.state.hintsUsed; this.DOM.hintBtn.innerHTML = ` Dica (${remaining})`; this.DOM.hintBtn.disabled = this.state.hintsUsed >= this.CONSTANTS.MAX_HINTS; } updateUndoButton() { this.DOM.undoBtn.disabled = this.state.moveHistory.length === 0; } // ============= VERIFICAÇÃO E FIM DE JOGO ============= checkWin() { for (let i = 0; i < this.CONSTANTS.BOARD_SIZE; i++) { for (let j = 0; j < this.CONSTANTS.BOARD_SIZE; j++) { if (this.state.sudoku[i][j] !== this.state.solution[i][j]) { return false; } } } this.endGame(true); return true; } endGame(win) { this.stopTimer(); this.state.gameOver = true; const finalScore = this.calculateScore(); const minutes = Math.floor(this.state.seconds / 60); const seconds = this.state.seconds % 60; this.DOM.modalTitle.textContent = win ? '🎉 Parabéns!' : '😔 Fim de Jogo'; if (win) { this.DOM.modalMessage.innerHTML = ` Você completou o Sudoku com sucesso!

Estatísticas:
Pontuação Final: ${finalScore}%
Tempo: ${minutes}m ${seconds}s
Erros: ${this.state.errors}
Dicas Usadas: ${this.state.hintsUsed}/${this.CONSTANTS.MAX_HINTS} `; } else { this.DOM.modalMessage.innerHTML = ` Você atingiu o limite de ${this.CONSTANTS.MAX_ERRORS} erros.

Não desanime! Tente novamente com um novo jogo. `; } this.showModal(); } // ============= CONTROLE DE JOGO ============= handleNewGame() { if (this.state.gameOver) { this.newGame(); return; } if (confirm('Iniciar novo jogo? O progresso atual será perdido.')) { this.newGame(); } } handleDifficultyChange(e) { const previousValue = this.DOM.difficultySelect.dataset.lastValue; if (this.state.gameOver) { this.newGame(); return; } if (confirm('Mudar a dificuldade iniciará um novo jogo. Continuar?')) { this.newGame(); } else { this.DOM.difficultySelect.value = previousValue; } } newGame() { // Parar timer anterior this.stopTimer(); this.clearHighlights(); // Gerar novo sudoku const difficulty = this.DOM.difficultySelect.value; this.state.sudoku = this.generateSudoku(difficulty); this.DOM.difficultySelect.dataset.lastValue = difficulty; // Resetar estado this.state.seconds = 0; this.state.errors = 0; this.state.hintsUsed = 0; this.state.gameOver = false; this.state.paused = false; this.state.pencilMode = false; this.state.moveHistory = []; this.state.selectedNumber = null; // Renderizar tabuleiro this.renderBoard(); // Resetar UI this.updateDisplay(); this.updateErrorsDisplay(); this.updateHintButton(); this.updateUndoButton(); this.DOM.pauseBtn.innerHTML = ' Pausar'; this.DOM.togglePencilBtn.innerHTML = ' Lápis'; this.DOM.board.classList.remove('paused', 'pencil-active'); // Iniciar timer this.startTimer(); // Focar primeira célula vazia requestAnimationFrame(() => { const firstEmpty = this.DOM.board.querySelector('input:not([disabled])'); firstEmpty?.focus(); }); } // ============= MODAL ============= showModal() { this.DOM.modal.style.display = 'flex'; this.DOM.modal.setAttribute('aria-hidden', 'false'); // Focar botão de fechar para acessibilidade requestAnimationFrame(() => { this.DOM.modalClose.focus(); }); } closeModal() { this.DOM.modal.style.display = 'none'; this.DOM.modal.setAttribute('aria-hidden', 'true'); } // ============= FEEDBACK ============= playErrorFeedback() { // Vibração (se disponível) if ('vibrate' in navigator) { navigator.vibrate(200); } } showTemporaryMessage(message) { // Implementar tooltip ou notificação temporária alert(message); } } // Inicializar jogo quando DOM estiver pronto if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', () => new SudokuGame()); } else { new SudokuGame(); }

Postar um comentário

0 Comentários