// Advanced solvability checker using backtracking const isAdvancedSolvable = (grid, endpoints) => { if (endpoints.length === 0) return false; const pairs = []; const sources = endpoints.filter(ep => ep.type === 'source'); sources.forEach(source => { const target = endpoints.find(ep => ep.type === 'target' && ep.id === source.id); if (target) { pairs.push({ id: source.id, source: { row: source.row, col: source.col }, target: { row: target.row, col: target.col } }); } }); if (pairs.length === 0) return false; const usedCells = new Set(); const foundPaths = new Map(); const findPath = (start, end, blocked) => { const visited = new Set(); const queue = [{ pos: start, path: [start] }]; while (queue.length > 0) { const { pos, path } = queue.shift(); const key = `${pos.row}-${pos.col}`; if (visited.has(key)) continue; visited.add(key); if (pos.row === end.row && pos.col === end.col) { return path; } for (const [dr, dc] of [[-1,0], [1,0], [0,-1], [0,1]]) { const newRow = pos.row + dr; const newCol = pos.col + dc; const newKey = `${newRow}-${newCol}`; if (newRow >= 0 && newRow < GRID_SIZE && newCol >= 0 && newCol < GRID_SIZE && grid[newRow][newCol] !== -1 && !blocked.has(newKey) && !visited.has(newKey)) { queue.push({ pos: { row: newRow, col: newCol }, path: [...path, { row: newRow, col: newCol }] }); } } } return null; }; const backtrack = (pairIndex) => { if (pairIndex >= pairs.length) { return true; } const pair = pairs[pairIndex]; const blockedCells = new Set(usedCells); const path = findPath(pair.source, pair.target, blockedCells); if (path) { const pathKeys = path.map(p => `${p.row}-${p.col}`); pathKeys.forEach(key => usedCells.add(key)); foundPaths.set(pair.id, path); if (backtrack(pairIndex + 1)) { return true; } pathKeys.forEach(key => usedCells.delete(key)); foundPaths.delete(pair.id); } return false; }; return backtrack(0); }; // Check if puzzle is solvable const isPuzzleSolvable = (grid, endpoints) => { if (endpoints.length === 0) return false; const pairs = []; const sources = endpoints.filter(ep => ep.type === 'source'); sources.forEach(source => { const target = endpoints.find(ep => ep.type === 'target' && ep.id === source.id); if (target) { pairs.push({ source, target }); } }); if (pairs.length === 0) return false; if (pairs.length <= 2) { for (const pair of pairs) { const visited = new Set(); const queue = [{ row: pair.source.row, col: pair.source.col }]; let found = false; while (queue.length > 0 && !found) { const { row, col } = queue.shift(); if (row === pair.target.row && col === pair.target.col) { found = true; break; } const key = `${row}-${col}`; if (visited.has(key)) continue; visited.add(key); for (const [dr, dc] of [[-1,0], [1,0], [0,-1], [0,1]]) { const newRow = row + dr; const newCol = col + dc; if (newRow >= 0 && newRow < GRID_SIZE && newCol >= 0 && newCol < GRID_SIZE) { if (grid[newRow][newCol] !== -1) { queue.push({ row: newRow, col: newCol }); } } } } if (!found) return false; } return true; } else { return isAdvancedSolvable(grid, endpoints); } }; // Generate guaranteed solvable puzzle layouts const generateSolvableLayout = (level) => { const grid = Array(GRID_SIZE).fill().map(() => Array(GRID_SIZE).fill(0)); if (level === 1) { return { grid, endpoints: [ { row: 0, col: 0, emoji: '⚡', type: 'source', id: 0 }, { row: 5, col: 5, emoji: '🎯', type: 'target', id: 0 } ] }; } if (level === 2) { grid[1][3] = -1; grid[3][1] = -1; grid[3][4] = -1; return { grid, endpoints: [ { row: 0, col: 0, emoji: '🔴', type: 'source', id: 0 }, { row: 2, col: 5, emoji: '🔴', type: 'target', id: 0 }, { row: 5, col: 0, emoji: '🟢', type: 'source', id: 1 }, { row: 3, col: 5, emoji: '🟢', type: 'target', id: 1 } ] }; } if (level === 3) { // Level 3: Randomized layout each time const obstacles = []; const usedPositions = new Set(); // Add 3-4 random obstacles const numObstacles = 3 + Math.floor(Math.random() * 2); for (let i = 0; i < numObstacles; i++) { let row, col; do { row = 1 + Math.floor(Math.random() * 4); // Avoid edges col = 1 + Math.floor(Math.random() * 4); } while (usedPositions.has(`${row}-${col}`)); grid[row][col] = -1; usedPositions.add(`${row}-${col}`); obstacles.push({row, col}); } // Generate random endpoint positions for 2 pairs const endpoints = []; const colors = ['🔴', '🟢']; for (let i = 0; i < 2; i++) { let sourceRow, sourceCol, targetRow, targetCol; // Find random source position do { sourceRow = Math.floor(Math.random() * GRID_SIZE); sourceCol = Math.floor(Math.random() * GRID_SIZE); } while ( grid[sourceRow][sourceCol] === -1 || usedPositions.has(`${sourceRow}-${sourceCol}`) ); usedPositions.add(`${sourceRow}-${sourceCol}`); // Find random target position do { targetRow = Math.floor(Math.random() * GRID_SIZE); targetCol = Math.floor(Math.random() * GRID_SIZE); } while ( grid[targetRow][targetCol] === -1 || usedPositions.has(`${targetRow}-${targetCol}`) || (targetRow === sourceRow && targetCol === sourceCol) || Math.abs(targetRow - sourceRow) + Math.abs(targetCol - sourceCol) < 3 ); usedPositions.add(`${targetRow}-${targetCol}`); endpoints.push( { row: sourceRow, col: sourceCol, emoji: colors[i], type: 'source', id: i }, { row: targetRow, col: targetCol, emoji: colors[i], type: 'target', id: i } ); } return { grid, endpoints }; } if (level === 4) { // Level 4: Specific pattern - green=up down forward down left down forward red=up down left down down left grid[2][2] = -1; grid[2][3] = -1; grid[3][2] = -1; return { grid, endpoints: [ // Green path: up down forward down left down forward (starting from 5,0) { row: 5, col: 0, emoji: '🟢', type: 'source', id: 0 }, { row: 0, col: 4, emoji: '🟢', type: 'target', id: 0 }, // Red path: up down left down down left (starting from 5,5) { row: 5, col: 5, emoji: '🔴', type: 'source', id: 1 }, { row: 1, col: 1, emoji: '🔴', type: 'target', id: 1 } ] }; } if (level === 5) { // Level 5: Randomized layout each time const obstacles = []; const usedPositions = new Set(); // Add 3-5 random obstacles const numObstacles = 3 + Math.floor(Math.random() * 3); for (let i = 0; i < numObstacles; i++) { let row, col; do { row = 1 + Math.floor(Math.random() * 4); // Avoid edges col = 1 + Math.floor(Math.random() * 4); } while (usedPositions.has(`${row}-${col}`)); grid[row][col] = -1; usedPositions.add(`${row}-${col}`); obstacles.push({row, col}); } // Generate random endpoint positions const endpoints = []; const colors = ['🔴', '🟢', '🔵']; const numPairs = 2 + Math.floor(Math.random() * 2); // 2-3 pairs for (let i = 0; i < numPairs; i++) { let sourceRow, sourceCol, targetRow, targetCol; // Find random source position do { sourceRow = Math.floor(Math.random() * GRID_SIZE); sourceCol = Math.floor(Math.random() * GRID_SIZE); } while ( grid[sourceRow][sourceCol] === -1 || usedPositions.has(`${sourceRow}-${sourceCol}`) ); usedPositions.add(`${sourceRow}-${sourceCol}`); // Find random target position do { targetRow = Math.floor(Math.random() * GRID_SIZE); targetCol = Math.floor(Math.random() * GRID_SIZE); } while ( grid[targetRow][targetCol] === -1 || usedPositions.has(`${targetRow}-${targetCol}`) || (targetRow === sourceRow && targetCol === sourceCol) || Math.abs(targetRow - sourceRow) + Math.abs(targetCol - sourceCol) < 2 ); usedPositions.add(`${targetRow}-${targetCol}`); endpoints.push( { row: sourceRow, col: sourceCol, emoji: colors[i], type: 'source', id: i }, { row: targetRow, col: targetCol, emoji: colors[i], type: 'target', id: i } ); } return { grid, endpoints }; } if (level === 6) { return { grid, endpoints: [ { row: 0, col: 0, emoji: '🔴', type: 'source', id: 0 }, { row: 1, col: 1, emoji: '🔴', type: 'target', id: 0 } ] }; } return { grid, endpoints: [ { row: 0, col: 0, emoji: '🔴', type: 'source', id: 0 }, { row: 5, col: 5, emoji: '🔴', type: 'target', id: 0 } ] }; }; // Initialize platformer level data const initializePlatformerLevel = (level) => { const newPlatforms = []; const newCoins = []; const newEnemies = []; let levelEndX = 2000; if (level === 1) { newPlatforms.push( { x: 0, y: 550, width: 400, height: 50, color: '#8B4513' }, { x: 500, y: 550, width: 400, height: 50, color: '#8B4513' }, { x: 1000, y: 550, width: 400, height: 50, color: '#8B4513' }, { x: 1500, y: 550, width: 500, height: 50, color: '#8B4513' }, { x: 300, y: 450, width: 100, height: 20, color: '#CD853F' }, { x: 600, y: 400, width: 120, height: 20, color: '#CD853F' }, { x: 800, y: 350, width: 100, height: 20, color: '#CD853F' }, { x: 1100, y: 450, width: 100, height: 20, color: '#CD853F' }, { x: 1600, y: 400, width: 100, height: 20, color: '#CD853F' } ); newCoins.push( { x: 350, y: 410, collected: false }, { x: 650, y: 360, collected: false }, { x: 850, y: 310, collected: false }, { x: 1150, y: 410, collected: false }, { x: 1650, y: 360, collected: false } ); newEnemies.push( { x: 520, y: 510, width: 24, height: 24, velX: -2, direction: -1, alive: true }, { x: 1150, y: 410, width: 24, height: 24, velX: -2, direction: -1, alive: true } ); levelEndX = 2000; } else if (level === 2) { newPlatforms.push( { x: 0, y: 550, width: 300, height: 50, color: '#654321' }, { x: 400, y: 500, width: 200, height: 20, color: '#8B6914' }, { x: 700, y: 450, width: 150, height: 20, color: '#8B6914' }, { x: 950, y: 400, width: 150, height: 20, color: '#8B6914' }, { x: 1200, y: 350, width: 200, height: 20, color: '#8B6914' }, { x: 1500, y: 300, width: 150, height: 20, color: '#8B6914' }, { x: 1750, y: 250, width: 200, height: 20, color: '#8B6914' }, { x: 2050, y: 550, width: 400, height: 50, color: '#654321' } ); newCoins.push( { x: 450, y: 460, collected: false }, { x: 750, y: 410, collected: false }, { x: 1000, y: 360, collected: false }, { x: 1250, y: 310, collected: false }, { x: 1550, y: 260, collected: false }, { x: 1800, y: 210, collected: false } ); newEnemies.push( { x: 450, y: 460, width: 24, height: 24, velX: 3, direction: 1, alive: true }, { x: 1000, y: 360, width: 24, height: 24, velX: -3, direction: -1, alive: true }, { x: 1800, y: 210, width: 24, height: 24, velX: 2, direction: 1, alive: true } ); levelEndX = 2450; } else { newPlatforms.push( { x: 0, y: 550, width: 250, height: 50, color: '#2F4F4F' }, { x: 350, y: 480, width: 100, height: 20, color: '#696969' }, { x: 550, y: 420, width: 100, height: 20, color: '#696969' }, { x: 750, y: 360, width: 100, height: 20, color: '#696969' }, { x: 950, y: 300, width: 100, height: 20, color: '#696969' }, { x: 1150, y: 240, width: 100, height: 20, color: '#696969' }, { x: 1350, y: 180, width: 100, height: 20, color: '#696969' }, { x: 1550, y: 120, width: 100, height: 20, color: '#696969' }, { x: 1750, y: 180, width: 150, height: 20, color: '#696969' }, { x: 2000, y: 240, width: 150, height: 20, color: '#696969' }, { x: 2250, y: 550, width: 300, height: 50, color: '#2F4F4F' } ); newCoins.push( { x: 400, y: 440, collected: false }, { x: 600, y: 380, collected: false }, { x: 800, y: 320, collected: false }, { x: 1000, y: 260, collected: false }, { x: 1200, y: 200, collected: false }, { x: 1400, y: 140, collected: false }, { x: 1600, y: 80, collected: false }, { x: 1825, y: 140, collected: false } ); newEnemies.push( { x: 400, y: 440, width: 24, height: 24, velX: -4, direction: -1, alive: true }, { x: 800, y: 320, width: 24, height: 24, velX: 4, direction: 1, alive: true }, { x: 1200, y: 200, width: 24, height: 24, velX: -3, direction: -1, alive: true }, { x: 1825, y: 140, width: 24, height: 24, velX: 3, direction: 1, alive: true } ); levelEndX = 2550; } setPlatforms(newPlatforms); setCoins(newCoins); setEnemies(newEnemies); return levelEndX; }; // Initialize platformer level const initializePlatformer = useCallback(async (level) => { setIsLoading(true); await new Promise(resolve => setTimeout(resolve, 300)); const platformLevel = level - 6; const levelEndX = initializePlatformerLevel(platformLevel); setPlatformerPlayer({ x: 100, y: 400, width: 32, height: 32, velX: 0, velY: 0, speed: 3, jumpPower: 15, onGround: false, direction: 1 }); setPlatformerGame(prev => ({ ...prev, level: platformLevel, camera: { x: 0, y: 0 }, gameOver: false, won: false, timeUp: false, levelEndX: levelEndX })); setGameWon(false); setIsLoading(false); }, []); // Initialize puzzle grid const initializePuzzle = useCallback(async (level) => { setIsLoading(true); setUnsolvableError(false); await new Promise(resolve => setTimeout(resolve, 500)); if (level <= 6) { const layout = generateSolvableLayout(level); const isSolvable = isPuzzleSolvable(layout.grid, layout.endpoints); if (!isSolvable) { setUnsolvableError(true); setIsLoading(false); return; } setGrid(layout.grid); setEndpoints(layout.endpoints); setPaths(new Map()); setActivePath(null); setGameWon(false); } setIsLoading(false); }, []); // Start new game const startGame = () => { setRequiredKey(generateRandomKey()); setGameStarted(true); setCurrentLevel(1); setGameMode('puzzle'); initializePuzzle(1); setPowerTimer(0); }; // Handle keyboard events useEffect(() => { if (!gameStarted) return; const handleKeyDown = (event) => { if (event.key.toUpperCase() === requiredKey) { setIsKeyPressed(true); } if (gameMode === 'platformer') { const key = event.key.toLowerCase(); setPlatformerKeys(prev => ({ ...prev, [key]: true })); } }; const handleKeyUp = (event) => { if (event.key.toUpperCase() === requiredKey) { setIsKeyPressed(false); } if (gameMode === 'platformer') { const key = event.key.toLowerCase(); setPlatformerKeys(prev => ({ ...prev, [key]: false })); } }; window.addEventListener('keydown', handleKeyDown); window.addEventListener('keyup', handleKeyUp); return () => { window.removeEventListener('keydown', handleKeyDown); window.removeEventListener('keyup', handleKeyUp); }; }, [gameStarted, requiredKey, gameMode]); // Power timer useEffect(() => { if (!isKeyPressed || !gameStarted || gameWon) return; const timer = setInterval(() => { setPowerTimer(prev => prev + 1); }, 100); return () => clearInterval(timer); }, [isKeyPressed, gameStarted, gameWon]); // Collision detection for platformer const checkCollision = (rect1, rect2) => { return rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x && rect1.y < rect2.y + rect2.height && rect1.y + rect1.height > rect2.y; }; // Platformer game loop useEffect(() => { if (gameMode !== 'platformer' || !gameStarted || !canvasRef.current) return; const canvas = canvasRef.current; const ctx = canvas.getContext('2d'); const gameLoop = () => { if (platformerGame.gameOver) { ctx.fillStyle = 'rgba(0, 0, 0, 0.8)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'white'; ctx.font = '48px Arial'; ctx.textAlign = 'center'; if (platformerGame.won) { ctx.fillStyle = '#FFD700'; ctx.fillText('VICTORY!', canvas.width/2, canvas.height/2 - 100); ctx.fillStyle = 'white'; ctx.font = '32px Arial'; ctx.fillText('You completed the platformer!', canvas.width/2, canvas.height/2 - 50); } else if (platformerGame.timeUp) { ctx.fillStyle = '#FF4444'; ctx.fillText('TIME\'S UP!', canvas.width/2, canvas.height/2 - 100); } else { ctx.fillText('GAME OVER', canvas.width/2, canvas.height/2 - 50); } ctx.font = '24px Arial'; ctx.fillText('Final Score: ' + platformerGame.score, canvas.width/2, canvas.height/2); ctx.fillText('Press R to Restart at Puzzle Level 1', canvas.width/2, canvas.height/2 + 80); if (platformerKeys['r']) { setCurrentLevel(1); setGameMode('puzzle'); setRequiredKey(generateRandomKey()); initializePuzzle(1); } animationFrameRef.current = requestAnimationFrame(gameLoop); return; } if (isKeyPressed) { setPlatformerPlayer(prev => { const newPlayer = { ...prev }; if (platformerKeys['a'] || platformerKeys['arrowleft']) { newPlayer.velX = -newPlayer.speed; newPlayer.direction = -1; } else if (platformerKeys['d'] || platformerKeys['arrowright']) { newPlayer.velX = newPlayer.speed; newPlayer.direction = 1; } else { newPlayer.velX *= 0.8; } if ((platformerKeys['w'] || platformerKeys['arrowup'] || platformerKeys[' ']) && newPlayer.onGround) { newPlayer.velY = -newPlayer.jumpPower; newPlayer.onGround = false; } newPlayer.velY += 0.4; if (newPlayer.velY > 15) newPlayer.velY = 15; newPlayer.x += newPlayer.velX; newPlayer.y += newPlayer.velY; newPlayer.onGround = false; platforms.forEach(platform => { if (checkCollision(newPlayer, platform)) { if (newPlayer.velY > 0 && newPlayer.y < platform.y) { newPlayer.y = platform.y - newPlayer.height; newPlayer.velY = 0; newPlayer.onGround = true; } else if (newPlayer.velY < 0 && newPlayer.y > platform.y) { newPlayer.y = platform.y + platform.height; newPlayer.velY = 0; } else if (newPlayer.velX > 0 && newPlayer.x < platform.x) { newPlayer.x = platform.x - newPlayer.width; } else if (newPlayer.velX < 0 && newPlayer.x > platform.x) { newPlayer.x = platform.x + platform.width; } } }); if (newPlayer.x < 0) newPlayer.x = 0; if (newPlayer.y > canvas.height) { // Player fell off the world - lose a life setPlatformerGame(prev => { const newLives = prev.lives - 1; if (newLives <= 0) { // All lives lost - restart at puzzle level 1 setCurrentLevel(1); setGameMode('puzzle'); setRequiredKey(generateRandomKey()); initializePuzzle(1); return prev; } else { // Still have lives - respawn at start of current platformer level return { ...prev, lives: newLives }; } }); // Reset player position return { ...prev, x: 100, y: 400, velX: 0, velY: 0 }; } if (newPlayer.x >= platformerGame.levelEndX) { if (platformerGame.level < 3) { const nextPlatformerLevel = platformerGame.level + 1; const newEndX = initializePlatformerLevel(nextPlatformerLevel); setPlatformerGame(prevGame => ({ ...prevGame, level: nextPlatformerLevel, levelEndX: newEndX })); newPlayer.x = 100; newPlayer.y = 400; newPlayer.velX = 0; newPlayer.velY = 0; } else { setPlatformerGame(prevGame => ({ ...prevGame, gameOver: true, won: true })); } } return newPlayer; }); setCoins(prevCoins => { return prevCoins.map(coin => { if (!coin.collected && Math.abs(platformerPlayer.x - coin.x) < 30 && Math.abs(platformerPlayer.y - coin.y) < 30) { setPlatformerGame(prev => ({ ...prev, score: prev.score + 100 })); if (platformerGame.score + 100 >= 500 && !platformerGame.timeLimitActive) { setPlatformerGame(prev => ({ ...prev, timeLimitActive: true, timeLimit: 60 * 60 })); } return { ...coin, collected: true }; } return coin; }); }); setEnemies(prevEnemies => { return prevEnemies.map(enemy => { if (!enemy.alive) return enemy; const newEnemy = { ...enemy }; newEnemy.x += newEnemy.velX; platforms.forEach(platform => { if (newEnemy.x >= platform.x - 20 && newEnemy.x <= platform.x + platform.width + 20 && newEnemy.y >= platform.y - newEnemy.height && newEnemy.y <= platform.y + 10) { if (newEnemy.x <= platform.x || newEnemy.x >= platform.x + platform.width - newEnemy.width) { newEnemy.velX *= -1; newEnemy.direction *= -1; } } }); if (checkCollision(platformerPlayer, newEnemy)) { if (platformerPlayer.velY > 0 && platformerPlayer.y < newEnemy.y - 10) { newEnemy.alive = false; setPlatformerPlayer(prev => ({ ...prev, velY: -8 })); setPlatformerGame(prev => ({ ...prev, score: prev.score + 200 })); } else { setPlatformerGame(prev => { const newLives = prev.lives - 1; if (newLives <= 0) { // All lives lost - restart at puzzle level 1 setCurrentLevel(1); setGameMode('puzzle'); setRequiredKey(generateRandomKey()); initializePuzzle(1); return prev; } else { // Still have lives - respawn player at start of current platformer level setPlatformerPlayer(prevPlayer => ({ ...prevPlayer, x: 100, y: 400, velX: 0, velY: 0 })); return { ...prev, lives: newLives }; } }); } } return newEnemy; }); }); if (platformerGame.timeLimitActive) { setPlatformerGame(prev => { const newTimeLimit = prev.timeLimit - 1; let newCountdown = prev.countdownStarted; if (newTimeLimit <= 30 * 60 && !newCountdown) { newCountdown = true; } if (newTimeLimit <= 0) { // Time's up - lose a life const newLives = prev.lives - 1; if (newLives <= 0) { // All lives lost - restart at puzzle level 1 setCurrentLevel(1); setGameMode('puzzle'); setRequiredKey(generateRandomKey()); initializePuzzle(1); return prev; } else { // Still have lives - respawn at start of current platformer level with reset timer setPlatformerPlayer(prevPlayer => ({ ...prevPlayer, x: 100, y: 400, velX: 0, velY: 0 })); return { ...prev, timeLimit: 60 * 60, // Reset timer to 1 minute lives: newLives, timeLimitActive: true, countdownStarted: false }; } } return { ...prev, timeLimit: newTimeLimit, countdownStarted: newCountdown }; }); } } setPlatformerGame(prev => ({ ...prev, camera: { x: Math.max(0, platformerPlayer.x - canvas.width / 2), y: 0 } })); ctx.clearRect(0, 0, canvas.width, canvas.height); const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height); gradient.addColorStop(0, '#87CEEB'); gradient.addColorStop(0.6, '#87CEEB'); gradient.addColorStop(0.6, '#90EE90'); gradient.addColorStop(1, '#90EE90'); ctx.fillStyle = gradient; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; for (let i = 0; i < 5; i++) { let x = (i * 300 + 100) - (platformerGame.camera.x * 0.3); let y = 50 + Math.sin(i) * 30; ctx.beginPath(); ctx.arc(x, y, 20, 0, Math.PI * 2); ctx.arc(x + 25, y, 30, 0, Math.PI * 2); ctx.arc(x + 50, y, 20, 0, Math.PI * 2); ctx.fill(); } ctx.save(); ctx.translate(-platformerGame.camera.x, 0); platforms.forEach(platform => { ctx.fillStyle = platform.color; ctx.fillRect(platform.x, platform.y, platform.width, platform.height); ctx.strokeStyle = '#654321'; ctx.lineWidth = 2; ctx.strokeRect(platform.x, platform.y, platform.width, platform.height); }); coins.forEach(coin => { if (coin.collected) return; ctx.fillStyle = '#FFD700'; ctx.beginPath(); ctx.arc(coin.x, coin.y, 8, 0, Math.PI * 2); ctx.fill(); ctx.strokeStyle = '#FFA500'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = '#FFFF00'; ctx.beginPath(); ctx.arc(coin.x - 3, coin.y - 3, 2, 0, Math.PI * 2); ctx.fill(); }); enemies.forEach(enemy => { if (!enemy.alive) return; ctx.fillStyle = '#8B4513'; ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height); ctx.fillStyle = 'white'; ctx.beginPath(); ctx.arc(enemy.x + 6, enemy.y + 8, 2, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(enemy.x + 18, enemy.y + 8, 2, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = 'red'; ctx.fillRect(enemy.x + 8, enemy.y + 16, 8, 2); }); ctx.fillStyle = '#FFD700'; ctx.beginPath(); ctx.arc(platformerPlayer.x + platformerPlayer.width/2, platformerPlayer.y + platformerPlayer.width/2, platformerPlayer.width/2 - 2, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#8B4513'; ctx.beginPath(); ctx.arc(platformerPlayer.x + platformerPlayer.width/2, platformerPlayer.y + platformerPlayer.width/2 - 3, platformerPlayer.width/2 - 4, Math.PI, Math.PI * 2); ctx.fill(); ctx.fillStyle = 'black'; ctx.beginPath(); ctx.arc(platformerPlayer.x + platformerPlayer.width/2 - 6, platformerPlayer.y + platformerPlayer.width/2 - 2, 2, 0, Math.PI * 2); ctx.fill(); ctx.beginPath(); ctx.arc(platformerPlayer.x + platformerPlayer.width/2 + 6, platformerPlayer.y + platformerPlayer.width/2 - 2, 2, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = '#FFA500'; ctx.beginPath(); ctx.arc(platformerPlayer.x + platformerPlayer.width/2, platformerPlayer.y + platformerPlayer.width/2 + 2, 1, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = 'red'; ctx.beginPath(); if (platformerPlayer.direction === 1) { ctx.moveTo(platformerPlayer.x + platformerPlayer.width/2 + 8, platformerPlayer.y + platformerPlayer.width/2); ctx.lineTo(platformerPlayer.x + platformerPlayer.width/2 + 12, platformerPlayer.y + platformerPlayer.width/2 - 3); ctx.lineTo(platformerPlayer.x + platformerPlayer.width/2 + 12, platformerPlayer.y + platformerPlayer.width/2 + 3); } else { ctx.moveTo(platformerPlayer.x + platformerPlayer.width/2 - 8, platformerPlayer.y + platformerPlayer.width/2); ctx.lineTo(platformerPlayer.x + platformerPlayer.width/2 - 12, platformerPlayer.y + platformerPlayer.width/2 - 3); ctx.lineTo(platformerPlayer.x + platformerPlayer.width/2 - 12, platformerPlayer.y + platformerPlayer.width/2 + 3); } ctx.fill(); ctx.restore(); if (platformerGame.countdownStarted && platformerGame.timeLimitActive) { const seconds = Math.ceil(platformerGame.timeLimit / 60); if (seconds <= 30) { const alpha = 0.1 + (Math.sin(platformerGame.timeLimit * 0.3) * 0.1); ctx.fillStyle = `rgba(255, 0, 0, ${alpha})`; ctx.fillRect(0, 0, canvas.width, canvas.height); if (seconds <= 10) { ctx.fillStyle = '#FFFFFF'; ctx.font = 'bold 64px Arial'; ctx.textAlign = 'center'; ctx.strokeStyle = '#FF0000'; ctx.lineWidth = 3; const warningText = seconds.toString(); ctx.strokeText(warningText, canvas.width/2, canvas.height/2); ctx.fillText(warningText, canvas.width/2, canvas.height/2); if (seconds <= 5) { ctx.font = 'bold 32px Arial'; ctx.fillStyle = '#FFFF00'; ctx.strokeStyle = '#FF0000'; ctx.lineWidth = 2; ctx.strokeText('HURRY!', canvas.width/2, canvas.height/2 + 80); ctx.fillText('HURRY!', canvas.width/2, canvas.height/2 + 80); } } } } animationFrameRef.current = requestAnimationFrame(gameLoop); }; animationFrameRef.current = requestAnimationFrame(gameLoop); return () => { if (animationFrameRef.current) { cancelAnimationFrame(animationFrameRef.current); } }; }, [gameMode, gameStarted, isKeyPressed, platformerKeys, platformerPlayer, platformerGame, platforms, coins, enemies]); // Handle cell clicks for flow puzzle const handleCellClick = (row, col) => { if (!isKeyPressed || gameWon || grid[row][col] === -1) return; const cellKey = `${row}-${col}`; const clickedEndpoint = endpoints.find(ep => ep.row === row && ep.col === col); if (clickedEndpoint) { if (clickedEndpoint.type === 'source') { setActivePath(clickedEndpoint.id); const newPaths = new Map(paths); newPaths.set(clickedEndpoint.id, [cellKey]); setPaths(newPaths); } else if (clickedEndpoint.type === 'target' && activePath === clickedEndpoint.id) { const currentPath = paths.get(activePath) || []; const lastCell = currentPath[currentPath.length - 1]; if (lastCell) { const [lastRow, lastCol] = lastCell.split('-').map(Number); const isAdjacent = Math.abs(row - lastRow) + Math.abs(col - lastCol) === 1; if (isAdjacent) { const newPaths = new Map(paths); newPaths.set(activePath, [...currentPath, cellKey]); setPaths(newPaths); setActivePath(null); const requiredPaths = new Set(); endpoints.forEach(ep => { if (ep.type === 'source') requiredPaths.add(ep.id); }); if (requiredPaths.size > 0 && [...requiredPaths].every(id => { const path = newPaths.get(id); return path && path.length > 1; })) { setGameWon(true); } } } } return; } if (activePath === null) return; const currentPath = paths.get(activePath) || []; const isOccupied = Array.from(paths.entries()).some(([pathId, pathCells]) => { return pathId !== activePath && pathCells.includes(cellKey); }); if (isOccupied) return; if (currentPath.includes(cellKey)) { const cellIndex = currentPath.indexOf(cellKey); const newPath = currentPath.slice(0, cellIndex); const newPaths = new Map(paths); newPaths.set(activePath, newPath); setPaths(newPaths); } else if (currentPath.length > 0) { const lastCell = currentPath[currentPath.length - 1]; const [lastRow, lastCol] = lastCell.split('-').map(Number); const isAdjacent = Math.abs(row - lastRow) + Math.abs(col - lastCol) === 1; if (isAdjacent) { const newPaths = new Map(paths); newPaths.set(activePath, [...currentPath, cellKey]); setPaths(newPaths); } } }; // Next level const nextLevel = () => { const newLevel = currentLevel + 1; setCurrentLevel(newLevel); setRequiredKey(generateRandomKey()); // Reset power timer and state for new level setPowerTimer(0); setIsKeyPressed(false); if (newLevel >= 7) { setGameMode('platformer'); initializePlatformer(newLevel); } else { initializePuzzle(newLevel); } }; // Get cell appearance const getCellClass = (row, col) => { const cellKey = `${row}-${col}`; const endpoint = endpoints.find(ep => ep.row === row && ep.col === col); const isObstacle = grid[row][col] === -1; let pathId = null; let isInActivePath = false; for (const [id, pathCells] of paths.entries()) { if (pathCells.includes(cellKey)) { pathId = id; if (id === activePath) isInActivePath = true; break; } } let classes = 'w-12 h-12 border-2 cursor-pointer transition-all duration-200 flex items-center justify-center text-lg font-bold '; if (isObstacle) { classes += 'bg-gray-800 border-gray-600 cursor-not-allowed'; } else if (endpoint) { if (endpoint.type === 'source' && activePath === endpoint.id) { classes += isKeyPressed ? 'bg-green-400 border-green-600 shadow-lg shadow-green-300 ring-2 ring-blue-400' : 'bg-green-200 border-green-400 ring-2 ring-blue-400'; } else if (endpoint.type === 'target' && activePath === endpoint.id) { classes += isKeyPressed ? 'bg-purple-400 border-purple-600 shadow-lg shadow-purple-300 ring-2 ring-blue-400' : 'bg-purple-200 border-purple-400 ring-2 ring-blue-400'; } else { const pathForEndpoint = paths.get(endpoint.id); const isComplete = pathForEndpoint && pathForEndpoint.length > 1; if (isComplete) { classes += 'bg-green-300 border-green-500 shadow-md'; } else { classes += isKeyPressed ? 'bg-gray-200 border-gray-400 hover:bg-gray-100' : 'bg-gray-100 border-gray-300 opacity-70'; } } } else if (pathId !== null) { const pathColors = [ 'bg-red-300 border-red-500', 'bg-green-300 border-green-500', 'bg-blue-300 border-blue-500', 'bg-yellow-300 border-yellow-500' ]; const colorClass = pathColors[pathId % pathColors.length]; if (isInActivePath) { classes += isKeyPressed ? `${colorClass} shadow-md ring-1 ring-blue-300` : `${colorClass.replace('300', '200')} ring-1 ring-blue-300`; } else { classes += isKeyPressed ? `${colorClass} shadow-sm` : `${colorClass.replace('300', '200')}`; } } else { classes += isKeyPressed ? 'bg-white border-gray-300 hover:bg-gray-50' : 'bg-gray-100 border-gray-300 opacity-50'; } return classes; }; const getCellContent = (row, col) => { const endpoint = endpoints.find(ep => ep.row === row && ep.col === col); const isObstacle = grid[row][col] === -1; if (isObstacle) return '■'; if (endpoint) return endpoint.emoji; return ''; }; if (!gameStarted) { return React.createElement('div', { className: "flex flex-col items-center justify-center min-h-screen bg-gradient-to-b from-blue-50 to-indigo-100 p-8" }, React.createElement('div', { className: "text-center bg-white rounded-lg shadow-lg p-8 max-w-md" }, React.createElement('h1', { className: "text-3xl font-bold text-gray-800 mb-4" }, "Cooperative Flow Puzzle"), React.createElement('div', { className: "text-gray-600 mb-6 space-y-2" }, React.createElement('p', null, React.createElement('strong', null, "Player 1 (Power):"), " Hold down the assigned key to power the grid"), React.createElement('p', null, React.createElement('strong', null, "Player 2 (Solver):"), " Click cells to create a path from ⚡ to 🎯"), React.createElement('p', { className: "text-sm text-gray-500" }, "You can only build the path while the power is on!") ), React.createElement('button', { onClick: startGame, className: "bg-blue-500 hover:bg-blue-600 text-white font-bold py-3 px-6 rounded-lg transition-colors" }, "Start Game") )); } return React.createElement('div', { className: "min-h-screen bg-gradient-to-b from-blue-50 to-indigo-100 p-4" }, React.createElement('div', { className: "max-w-4xl mx-auto" }, // Microtransaction Popup showMicrotransaction && React.createElement('div', { className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" }, React.createElement('div', { className: "bg-white rounded-lg p-6 max-w-md mx-4 shadow-xl border-4 border-yellow-400" }, React.createElement('h3', { className: "text-2xl font-bold text-yellow-600 mb-4 text-center" }, "💰 Skip Level"), React.createElement('div', { className: "text-center mb-6" }, React.createElement('div', { className: "text-4xl font-bold text-green-600 mb-2" }, "$0.45"), React.createElement('p', { className: "text-gray-700 mb-4" }, "Skip this challenging level and advance immediately!"), React.createElement('div', { className: "bg-yellow-50 border border-yellow-200 rounded-lg p-3 mb-4" }, React.createElement('p', { className: "text-sm text-yellow-800" }, React.createElement('span', null, "⚡ Instant progression"), React.createElement('br'), React.createElement('span', null, "🎯 No more frustration"), React.createElement('br'), React.createElement('span', null, "🏆 Keep the fun going!")) ) ), React.createElement('div', { className: "space-y-3" }, React.createElement('button', { onClick: () => { setShowMicrotransaction(false); setHasPurchasedSkip(true); // Simulate "purchase" and skip level setTimeout(() => { nextLevel(); setHasPurchasedSkip(false); }, 1000); }, className: "w-full bg-green-500 hover:bg-green-600 text-white font-bold py-3 px-6 rounded-lg transition-colors text-lg" }, "💳 Purchase & Skip - $0.45"), React.createElement('button', { onClick: () => setShowMicrotransaction(false), className: "w-full bg-gray-300 hover:bg-gray-400 text-gray-700 font-bold py-2 px-6 rounded-lg transition-colors" }, "Cancel") ), React.createElement('p', { className: "text-xs text-gray-500 text-center mt-4" }, "*This is a demo - no real payment will be processed") )), // Purchase Success Animation hasPurchasedSkip && React.createElement('div', { className: "fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-50" }, React.createElement('div', { className: "bg-white rounded-lg p-8 text-center shadow-xl" }, React.createElement('div', { className: "text-6xl mb-4" }, "✅"), React.createElement('h3', { className: "text-2xl font-bold text-green-600 mb-2" }, "Purchase Successful!"), React.createElement('p', { className: "text-gray-700" }, "Skipping to next level..."), React.createElement('div', { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-green-500 mx-auto mt-4" }) )), // Unsolvable Error Popup unsolvableError && React.createElement('div', { className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50" }, React.createElement('div', { className: "bg-white rounded-lg p-6 max-w-md mx-4 shadow-xl" }, React.createElement('h3', { className: "text-xl font-bold text-red-600 mb-4" }, "⚠️ Testing Alert"), React.createElement('p', { className: "text-gray-700 mb-4" }, "Puzzle cannot be solved! All pairs cannot be connected without overlap."), React.createElement('div', { className: "space-x-4" }, React.createElement('button', { onClick: () => { setUnsolvableError(false); initializePuzzle(currentLevel); }, className: "bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-4 rounded transition-colors" }, "Try Again"), React.createElement('button', { onClick: () => { setUnsolvableError(false); nextLevel(); }, className: "bg-gray-500 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded transition-colors" }, "Skip Level") ) )), // Loading Screen isLoading && React.createElement('div', { className: "fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center z-40" }, React.createElement('div', { className: "bg-white rounded-lg p-8 text-center shadow-xl" }, React.createElement('div', { className: "animate-spin rounded-full h-16 w-16 border-b-2 border-blue-500 mx-auto mb-4" }), React.createElement('h3', { className: "text-xl font-bold text-gray-800 mb-2" }, `Loading Level ${currentLevel}`), React.createElement('p', { className: "text-gray-600" }, gameMode === 'puzzle' ? 'Generating puzzle...' : 'Building platforms...') )), // Header React.createElement('div', { className: "text-center mb-6" }, React.createElement('h1', { className: "text-3xl font-bold text-gray-800 mb-2" }, gameMode === 'puzzle' ? 'Cooperative Flow Puzzle' : 'Cooperative Platformer'), React.createElement('div', { className: "flex justify-center items-center gap-8 text-sm" }, React.createElement('div', { className: "bg-white rounded-lg px-4 py-2 shadow-md" }, React.createElement('span', { className: "font-semibold" }, "Level:"), ` ${currentLevel}`), React.createElement('div', { className: "bg-white rounded-lg px-4 py-2 shadow-md" }, React.createElement('span', { className: "font-semibold" }, "Power Time:"), ` ${(powerTimer / 10).toFixed(1)}s`), React.createElement('button', { onClick: () => setShowMicrotransaction(true), className: "bg-yellow-500 hover:bg-yellow-600 text-white font-bold py-2 px-4 rounded-lg transition-colors text-sm shadow-md border-2 border-yellow-400" }, "💰 Skip Level - $0.45") ) ), // Power Status React.createElement('div', { className: "text-center mb-6" }, React.createElement('div', { className: `inline-block px-6 py-3 rounded-lg font-bold text-lg transition-all duration-200 ${ isKeyPressed ? 'bg-green-400 text-green-800 shadow-lg shadow-green-200' : 'bg-red-100 text-red-800' }` }, `Player 1: ${isKeyPressed ? `✅ Powering (${requiredKey} held)` : `❌ Hold "${requiredKey}" key`}`)), gameMode === 'puzzle' ? [ // Game Grid React.createElement('div', { className: "flex justify-center mb-6", key: "grid" }, React.createElement('div', { className: "grid grid-cols-6 gap-1 bg-gray-300 p-4 rounded-lg shadow-lg" }, grid.map((row, rowIndex) => row.map((cell, colIndex) => ( React.createElement('button', { key: `${rowIndex}-${colIndex}`, className: getCellClass(rowIndex, colIndex), onClick: () => handleCellClick(rowIndex, colIndex), disabled: !isKeyPressed || gameWon }, getCellContent(rowIndex, colIndex)) )) ))), // Instructions React.createElement('div', { className: "text-center text-gray-600 mb-4", key: "instructions" }, currentLevel === 1 ? [ React.createElement('p', { key: "p1" }, "Player 2: Click adjacent cells to build a path from ⚡ to 🎯"), React.createElement('p', { className: "text-sm", key: "p2" }, "Path only works when Player 1 provides power!") ] : [ React.createElement('p', { key: "p1" }, "Player 2: Connect matching emoji pairs with unique paths"), React.createElement('p', { className: "text-sm", key: "p2" }, "Click a source emoji to start, then build a path to its matching target"), React.createElement('p', { className: "text-sm", key: "p3" }, "Paths cannot overlap! Power required to build."), activePath !== null && React.createElement('p', { className: "text-blue-600 font-semibold", key: "active" }, `Building path for: ${endpoints.find(ep => ep.id === activePath && ep.type === 'source')?.emoji}`) ] ) ] : [ // Platformer Game React.createElement('div', { className: "flex justify-center mb-6", key: "platformer" }, React.createElement('div', { className: "relative border-4 border-gray-800 rounded-lg overflow-hidden bg-gradient-to-b from-sky-200 to-green-200" }, React.createElement('canvas', { ref: canvasRef, width: "800", height: "600", className: "block bg-gradient-to-b from-sky-200 via-sky-200 to-green-200" }), // UI Overlay React.createElement('div', { className: "absolute top-2 left-2 text-white font-bold text-shadow z-10" }, React.createElement('div', null, "Score: ", React.createElement('span', null, platformerGame.score)), React.createElement('div', null, "Lives: ", React.createElement('span', null, platformerGame.lives)), React.createElement('div', null, "Level: ", React.createElement('span', null, platformerGame.level)), platformerGame.timeLimitActive && React.createElement('div', { className: `text-lg font-bold ${platformerGame.countdownStarted ? 'text-red-500' : 'text-red-400'}` }, "Time: ", React.createElement('span', null, Math.ceil(platformerGame.timeLimit / 60))) ), // Instructions Overlay React.createElement('div', { className: "absolute bottom-2 right-2 text-white text-xs text-shadow z-10" }, React.createElement('div', null, "WASD or Arrow Keys + Space to move and jump"), React.createElement('div', null, "Collect coins and avoid enemies!"), React.createElement('div', null, "Reach 500 points to activate time limit!") ) )), // Platformer Instructions React.createElement('div', { className: "text-center text-gray-600 mb-4", key: "platformer-instructions" }, React.createElement('p', null, `Player 1: Hold "${requiredKey}" to give power`), React.createElement('p', null, "Player 2: Use WASD or Arrow Keys to move, Space to jump"), React.createElement('p', { className: "text-sm text-red-600" }, "If you fall off or lose all lives, you'll restart at puzzle level 1!") ) ], // Win State gameWon && React.createElement('div', { className: "text-center" }, React.createElement('div', { className: "bg-green-100 border border-green-400 rounded-lg p-4 mb-4 inline-block" }, React.createElement('h2', { className: "text-2xl font-bold text-green-800 mb-2" }, gameMode === 'puzzle' ? '🎉 Level Complete!' : '🎉 Platformer Level Complete!'), gameMode === 'puzzle' && currentLevel === 6 ? React.createElement('p', { className: "text-green-700" }, "That was easy! But now the real challenge begins... 🎮") : gameMode === 'puzzle' ? React.createElement('p', { className: "text-green-700" }, `Great teamwork! You completed level ${currentLevel} in ${(powerTimer / 10).toFixed(1)} seconds.`) : React.createElement('p', { className: "text-green-700" }, "Excellent coordination! You navigated the platformer challenge successfully.") ), React.createElement('div', { className: "space-x-4" }, React.createElement('button', { onClick: nextLevel, className: "bg-blue-500 hover:bg-blue-600 text-white font-bold py-2 px-6 rounded-lg transition-colors" }, gameMode === 'puzzle' && currentLevel === 6 ? 'Enter Platformer Mode!' : 'Next Level'), React.createElement('button', { onClick: () => setGameStarted(false), className: "bg-gray-500 hover:bg-gray-600 text-white font-bold py-2 px-6 rounded-lg transition-colors" }, "New Game") ) ) )); }; ReactDOM.render(React.createElement(CooperativeFlowPuzzle), document.getElementById('root'));