From 345b27366906878b62682d3f6dcc1a54bfbd3883 Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Wed, 22 Oct 2025 19:03:05 +0100 Subject: [PATCH] feat: add red flash on invalid movement (Issue 3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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! --- src/game.css | 19 +++++++++++++++ src/useCursor/useCursor.ts | 41 ++++++++++++++++---------------- src/useCursor/useCursorRender.ts | 12 +--------- src/useGame.ts | 2 +- 4 files changed, 41 insertions(+), 33 deletions(-) diff --git a/src/game.css b/src/game.css index 57a9208..ceb4988 100644 --- a/src/game.css +++ b/src/game.css @@ -263,3 +263,22 @@ z-index: 10; position: relative; } + +/* Invalid move flash */ +.sudoku-cell.invalid-move { + background-color: rgba(239, 68, 68, 0.4); + outline: 3px solid rgb(239, 68, 68); + outline-offset: -3px; + animation: invalid-flash 0.5s ease-out; +} + +@keyframes invalid-flash { + 0% { + background-color: rgba(239, 68, 68, 0.6); + box-shadow: 0 0 20px rgba(239, 68, 68, 0.8); + } + 100% { + background-color: rgba(239, 68, 68, 0.4); + box-shadow: 0 0 0 rgba(239, 68, 68, 0); + } +} diff --git a/src/useCursor/useCursor.ts b/src/useCursor/useCursor.ts index 6c205d2..85091e7 100644 --- a/src/useCursor/useCursor.ts +++ b/src/useCursor/useCursor.ts @@ -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) { const [position, setPosition] = useState({ 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 = { diff --git a/src/useCursor/useCursorRender.ts b/src/useCursor/useCursorRender.ts index ad56676..ebd0f9e 100644 --- a/src/useCursor/useCursorRender.ts +++ b/src/useCursor/useCursorRender.ts @@ -12,16 +12,10 @@ export function useCursorRender( useEffect(() => { if (gameStatus !== 'started') return; - console.log('🎯 Cursor render triggered - position:', position, 'boardRendered:', boardRendered); - const container = containerRef.current; - if (!container) { - console.log('❌ No container ref'); - return; - } + if (!container) return; const cells = container.querySelectorAll('.sudoku-cell'); - console.log('📊 Found cells:', cells.length); cells.forEach(cell => { cell.classList.remove('cursor'); @@ -29,13 +23,9 @@ export function useCursorRender( const index = position.row * 9 + position.col; const targetCell = cells[index]; - console.log('🎯 Target cell index:', index, 'cell:', targetCell); if (targetCell) { targetCell.classList.add('cursor'); - console.log('✅ Added cursor class to cell'); - } else { - console.log('❌ Target cell not found'); } }, [position, containerRef, gameStatus, boardRendered]); } diff --git a/src/useGame.ts b/src/useGame.ts index fdad5b5..757c063 100644 --- a/src/useGame.ts +++ b/src/useGame.ts @@ -16,7 +16,7 @@ export function useGame(options: GameOptions = {}, platformHook?: unknown): Game const [keyLog, setKeyLog] = useState<{ key: string; timestamp: number }[]>([]); const [boardRendered, setBoardRendered] = useState(0); - const cursor = useCursor(gameStatusManager); + const cursor = useCursor(gameStatusManager, containerRef); const cursorPosition = cursor.position(); useCursorRender(containerRef, cursorPosition, gameStatus, boardRendered);