Files
vim-sudoku/src/useBoard/useBoard.ts
T
pastilhas 97c31ab6cc feat: implement hint system (Shift+H) with scoring penalties
- Add getHint() method to BoardManager with two-tier priority:
  1. Find incorrect user-entered cells → flash red
  2. Find empty cells → show correct digit green temporarily
- Add Shift+H key handler in useCursor (normal mode only)
- Visual feedback:
  - Red flash (hint-flash-error): incorrect cells, 1s duration, red bg + border
  - Green flash (hint-flash-correct): correct digit shown, 1s duration, green bg + border
  - Distinct from invalid-move (no background, 500ms)
- Score penalties based on difficulty:
  - Easy: 25 points per hint
  - Medium: 50 points per hint
  - Hard: 100 points per hint
- Track hints used in scoreboard display
- Correct digit appears temporarily (1s) then disappears (player still does the work)
- Update example app to show hints counter and Shift+H instruction
2025-10-22 21:24:06 +01:00

109 lines
3.1 KiB
TypeScript

import { useRef, useState } from 'react';
import { SudokuGenerator } from './SudokuGenerator';
import { SudokuRenderer } from './SudokuRenderer';
import type { Difficulty } from './SudokuGenerator';
import type { HintResult } from './types';
export function useBoard() {
const containerRef = useRef<HTMLDivElement | null>(null);
const generatorRef = useRef<SudokuGenerator | null>(null);
const rendererRef = useRef<SudokuRenderer | null>(null);
const boardRef = useRef<string>('');
const solutionRef = useRef<string>('');
const initialBoardRef = useRef<string>('');
const [boardRendered, setBoardRendered] = useState(0);
function generateBoard(difficulty: Difficulty = 'easy') {
if (!generatorRef.current) {
generatorRef.current = new SudokuGenerator();
}
generatorRef.current.generate(difficulty);
const puzzle = generatorRef.current.getPuzzle();
const solution = generatorRef.current.getSolution();
boardRef.current = puzzle;
solutionRef.current = solution;
initialBoardRef.current = puzzle;
}
function renderBoard() {
const container = containerRef.current;
if (!container) return;
if (!rendererRef.current) {
rendererRef.current = new SudokuRenderer(container);
}
const board = boardRef.current;
const initialBoard = initialBoardRef.current;
if (board) {
rendererRef.current.render(board, initialBoard);
} else {
rendererRef.current.renderEmpty();
}
setBoardRendered(prev => prev + 1);
}
function setCell(row: number, col: number, value: string): void {
const board = boardRef.current.split('');
const index = row * 9 + col;
board[index] = value;
boardRef.current = board.join('');
renderBoard();
}
function getHint(): HintResult {
const board = boardRef.current;
const solution = solutionRef.current;
const initialBoard = initialBoardRef.current;
if (!board || !solution) return null;
const incorrectCells: Array<{ row: number; col: number }> = [];
const emptyCells: Array<{ row: number; col: number }> = [];
for (let i = 0; i < 81; i++) {
const row = Math.floor(i / 9);
const col = i % 9;
const currentValue = board[i];
const correctValue = solution[i];
const initialValue = initialBoard[i];
if (currentValue !== '.' && initialValue === '.' && currentValue !== correctValue) {
incorrectCells.push({ row, col });
}
if (currentValue === '.') {
emptyCells.push({ row, col });
}
}
if (incorrectCells.length > 0) {
const randomCell = incorrectCells[Math.floor(Math.random() * incorrectCells.length)];
return { ...randomCell, type: 'error' };
}
if (emptyCells.length > 0) {
const randomCell = emptyCells[Math.floor(Math.random() * emptyCells.length)];
const index = randomCell.row * 9 + randomCell.col;
const correctValue = solution[index];
return { ...randomCell, type: 'correct', value: correctValue };
}
return null;
}
return {
containerRef,
boardRef,
boardRendered,
generateBoard,
renderBoard,
setCell,
getHint,
};
}