Add secret auto-solver unlocked via Konami code

Logical-only solver (naked + hidden singles, no backtracking) fills one
cell every ~2-3s with a blink animation, so progress is only made when
the deduction is certain.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Eduard Duran
2026-04-26 14:00:13 +02:00
co-authored by Claude Opus 4.7
parent 2243abbb22
commit b2cc21a354
7 changed files with 222 additions and 3 deletions
+4 -1
View File
@@ -2,7 +2,7 @@ import React from 'react'
import Cell from './Cell'
import { checkValue } from '../utils/sudoku'
function Board({ board, puzzle, solution, notes, selected, highlightedNumber, showErrors, celebrating, selectCell, deselect }) {
function Board({ board, puzzle, solution, notes, selected, highlightedNumber, showErrors, celebrating, autoBlinkCell, selectCell, deselect }) {
const selectedValue = selected && board[selected.row][selected.col]
const activeNumber = selected ? selectedValue : highlightedNumber
@@ -31,6 +31,8 @@ function Board({ board, puzzle, solution, notes, selected, highlightedNumber, sh
value !== 0 &&
!checkValue(solution, r, c, value)
const isCelebrating = celebrating.has(`${r},${c}`)
const isAutoBlinking =
autoBlinkCell && autoBlinkCell.row === r && autoBlinkCell.col === c
return (
<Cell
@@ -45,6 +47,7 @@ function Board({ board, puzzle, solution, notes, selected, highlightedNumber, sh
isSameNumber={isSameNumber}
isError={isError}
isCelebrating={isCelebrating}
isAutoBlinking={isAutoBlinking}
onClick={(e) => e.ctrlKey || e.metaKey ? deselect() : selectCell(r, c)}
/>
)
+2 -1
View File
@@ -1,6 +1,6 @@
import React from 'react'
function Cell({ value, notes, isGiven, isSelected, isHighlightedRow, isHighlightedCol, isHighlightedBox, isSameNumber, isError, isCelebrating, onClick }) {
function Cell({ value, notes, isGiven, isSelected, isHighlightedRow, isHighlightedCol, isHighlightedBox, isSameNumber, isError, isCelebrating, isAutoBlinking, onClick }) {
const classes = ['cell']
if (isGiven) classes.push('given')
if (isSelected) classes.push('selected')
@@ -10,6 +10,7 @@ function Cell({ value, notes, isGiven, isSelected, isHighlightedRow, isHighlight
if (isSameNumber) classes.push('same-number')
if (isError) classes.push('error')
if (isCelebrating) classes.push('celebrating')
if (isAutoBlinking) classes.push('auto-blinking')
return (
<div className={classes.join(' ')} onClick={onClick}>
+6 -1
View File
@@ -8,7 +8,7 @@ const DIFFICULTIES = [
{ value: 'expert', label: 'Expert' },
]
function GameControls({ currentDifficulty, onNewGame, onClear, onUndo, onRedo, canUndo, canRedo, onOpenSettings }) {
function GameControls({ currentDifficulty, onNewGame, onClear, onUndo, onRedo, canUndo, canRedo, onOpenSettings, solveUnlocked, autoSolving, onToggleAutoSolve }) {
const [difficulty, setDifficulty] = useState(currentDifficulty)
const [showConfirm, setShowConfirm] = useState(false)
@@ -32,6 +32,11 @@ function GameControls({ currentDifficulty, onNewGame, onClear, onUndo, onRedo, c
<button className="btn btn-primary" onClick={() => setShowConfirm(true)}>
Nova Partida
</button>
{solveUnlocked && (
<button className="btn btn-secondary" onClick={onToggleAutoSolve}>
{autoSolving ? 'Atura' : 'Resol'}
</button>
)}
</div>
<div className="controls-row">
<button className="btn btn-secondary" onClick={onClear} title="Esborra">