import React, { useState, useEffect, useRef } from 'react'; import { GridCell, Position, GameState, Direction } from '../types'; import { generateGrid, GRID_SIZE } from '../utils/gameLogic'; import { Cell } from './Cell'; import { RotateCcw, ArrowUp, ArrowDown, ArrowLeft, ArrowRight, CircleHelp, BrickWall, Zap, Flag } from 'lucide-react'; const MOVE_INTERVAL_MS = 100; // Speed of movement when holding key export const Game: React.FC = () => { const [grid, setGrid] = useState([]); const [playerPos, setPlayerPos] = useState({ x: 0, y: 0 }); const [gameState, setGameState] = useState(GameState.PLAYING); const [moves, setMoves] = useState(0); const [trapsTriggered, setTrapsTriggered] = useState(0); // Refs for smooth movement loop and instant state access const gridRef = useRef([]); const playerPosRef = useRef({ x: 0, y: 0 }); const activeDirectionRef = useRef(null); const lastMoveTimeRef = useRef(0); const requestRef = useRef(); const gameStateRef = useRef(GameState.PLAYING); // Sync refs with state whenever state updates useEffect(() => { gridRef.current = grid; }, [grid]); useEffect(() => { playerPosRef.current = playerPos; }, [playerPos]); useEffect(() => { gameStateRef.current = gameState; }, [gameState]); // Initialize game useEffect(() => { startNewGame(); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const startNewGame = () => { const newGrid = generateGrid(); setGrid(newGrid); gridRef.current = newGrid; const startPos = { x: 0, y: 0 }; setPlayerPos(startPos); playerPosRef.current = startPos; setGameState(GameState.PLAYING); gameStateRef.current = GameState.PLAYING; setMoves(0); setTrapsTriggered(0); activeDirectionRef.current = null; }; const handleInteraction = (direction: Direction) => { // Access latest state directly via refs const currentGrid = gridRef.current; const currentPos = playerPosRef.current; if (!currentGrid.length || gameStateRef.current !== GameState.PLAYING) return; let nextX = currentPos.x; let nextY = currentPos.y; switch (direction) { case Direction.UP: nextY -= 1; break; case Direction.DOWN: nextY += 1; break; case Direction.LEFT: nextX -= 1; break; case Direction.RIGHT: nextX += 1; break; } // 1. Boundary Check if (nextX < 0 || nextX >= GRID_SIZE || nextY < 0 || nextY >= GRID_SIZE) { return; // Hit grid edge } // 2. Interaction Logic // Create a deep copy of the grid to ensure immutability const newGrid = currentGrid.map(row => row.map(cell => ({ ...cell }))); const targetCell = newGrid[nextY][nextX]; const targetType = targetCell.type; let gridChanged = false; let newPos = currentPos; // Reveal the cell if (!targetCell.revealed) { targetCell.revealed = true; gridChanged = true; } if (targetType === 'wall') { // Blocked: Do not move, but wall is revealed } else if (targetType === 'trap') { // Trap: Reset to start setTrapsTriggered(t => t + 1); newPos = { x: 0, y: 0 }; } else { // Safe / Start / End newPos = { x: nextX, y: nextY }; targetCell.steppedOn = true; gridChanged = true; if (targetType === 'end') { setGameState(GameState.WON); } } // Update States if (gridChanged) { setGrid(newGrid); } if (newPos.x !== currentPos.x || newPos.y !== currentPos.y) { setPlayerPos(newPos); setMoves(m => m + 1); } else if (targetType === 'trap') { // Ensure visual reset even if we were somehow already at 0,0 setPlayerPos({ x: 0, y: 0 }); } }; // Game Loop const animate = (time: number) => { if (activeDirectionRef.current && gameStateRef.current === GameState.PLAYING) { if (time - lastMoveTimeRef.current > MOVE_INTERVAL_MS) { handleInteraction(activeDirectionRef.current); lastMoveTimeRef.current = time; } } requestRef.current = requestAnimationFrame(animate); }; useEffect(() => { requestRef.current = requestAnimationFrame(animate); return () => { if (requestRef.current) cancelAnimationFrame(requestRef.current); }; }, []); // Key Listeners useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (gameStateRef.current !== GameState.PLAYING) return; let dir: Direction | null = null; switch (e.key) { case 'ArrowUp': case 'w': case 'W': dir = Direction.UP; break; case 'ArrowDown': case 's': case 'S': dir = Direction.DOWN; break; case 'ArrowLeft': case 'a': case 'A': dir = Direction.LEFT; break; case 'ArrowRight': case 'd': case 'D': dir = Direction.RIGHT; break; } if (dir) { e.preventDefault(); // Prevent scrolling if (activeDirectionRef.current !== dir) { // Initial immediate move on press activeDirectionRef.current = dir; handleInteraction(dir); lastMoveTimeRef.current = performance.now(); } } }; const handleKeyUp = (e: KeyboardEvent) => { activeDirectionRef.current = null; }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); }; }, []); return (
{/* Header */}

GRID EXPLORER

Moves: {moves}
Traps: {trapsTriggered}
{/* Main Game Area */}
{/* Overlay for Win State */} {gameState === GameState.WON && (

VICTORY!

You reached the goal in {moves} moves.

)} {/* The Grid */}
{grid.map((row, y) => ( row.map((cell, x) => ( )) ))}
{/* Controls / Legend */}
{/* Legend */}

Legend

Safe Path
Wall (Block)
Trap (Reset)
Goal
{/* Instructions */}

Instructions

Use Arrow Keys or WASD to move.
Hold key to move continuously.
Avoid hidden traps!

{/* Mobile Controls (Optional, keeping it subtle) */}
); };