feat: add red flash on invalid movement (Issue 3)

When user attempts to move out of bounds, show visual feedback:

CSS:
- Added .invalid-move class with red outline and background
- Animation: invalid-flash (500ms ease-out)
- Starts with bright red glow, fades out
- Red outline (3px solid) matches cursor style

Logic:
- flashInvalidMove() function in useCursor
- Triggers when move would go out of bounds (< 0 or > 8)
- Adds invalid-move class to current cell
- Removes class after 500ms
- Passes containerRef to useCursor for DOM access

Cleanup:
- Removed all console.log debugging statements
- Clean code ready for production

UX Flow:
- Press h at left edge → Red flash at current position
- Press j at bottom edge → Red flash at current position
- Press k at top edge → Red flash at current position
- Press l at right edge → Red flash at current position

Issue 3 complete!
This commit is contained in:
2025-10-22 19:03:05 +01:00
parent 154a2e5cc3
commit 345b273669
4 changed files with 41 additions and 33 deletions
+20 -21
View File
@@ -2,23 +2,36 @@ import { useState, useEffect } from 'react';
import type { Coord, CursorManager } from './types';
import type { GameStatusManager } from '../useGameStatus/types';
export function useCursor(gameStatusManager: GameStatusManager) {
export function useCursor(gameStatusManager: GameStatusManager, containerRef: React.RefObject<HTMLDivElement | null>) {
const [position, setPosition] = useState<Coord>({ row: 0, col: 0 });
const { gameStatus } = gameStatusManager;
function flashInvalidMove(): void {
const container = containerRef.current;
if (!container) return;
const cells = container.querySelectorAll('.sudoku-cell');
const index = position.row * 9 + position.col;
const targetCell = cells[index];
if (targetCell) {
targetCell.classList.add('invalid-move');
setTimeout(() => {
targetCell.classList.remove('invalid-move');
}, 500);
}
}
function move(dRows: number, dCols: number, count: number = 1): void {
setPosition(currentPos => {
const newRow = currentPos.row + (dRows * count);
const newCol = currentPos.col + (dCols * count);
console.log('🚀 Move attempt:', { currentPos, dRows, dCols, count, newRow, newCol });
if (newRow < 0 || newRow > 8 || newCol < 0 || newCol > 8) {
console.log('⛔ Out of bounds, movement blocked');
flashInvalidMove();
return currentPos;
}
console.log('✅ Position updated to:', { row: newRow, col: newCol });
return { row: newRow, col: newCol };
});
}
@@ -40,38 +53,24 @@ export function useCursor(gameStatusManager: GameStatusManager) {
}
useEffect(() => {
console.log('🎮 useCursor effect - gameStatus:', gameStatus);
if (gameStatus !== 'started') {
console.log('⏸️ Not started, skipping key listener');
return;
}
if (gameStatus !== 'started') return;
function handleKeyDown(e: KeyboardEvent) {
const key = e.key;
console.log('⌨️ Key pressed:', key);
if (key === 'h') {
console.log('← Move left');
moveLeft();
} else if (key === 'j') {
console.log('↓ Move down');
moveDown();
} else if (key === 'k') {
console.log('↑ Move up');
moveUp();
} else if (key === 'l') {
console.log('→ Move right');
moveRight();
}
}
console.log('✅ Adding keydown listener');
window.addEventListener('keydown', handleKeyDown);
return () => {
console.log('🗑️ Removing keydown listener');
window.removeEventListener('keydown', handleKeyDown);
};
return () => window.removeEventListener('keydown', handleKeyDown);
}, [gameStatus]);
const cursor: CursorManager = {