import React, { useState, useEffect, useRef } from ‘react’; import { Sparkles, ShoppingCart, Star } from ‘lucide-react’; const CUBE_SIZE = 6; const INITIAL_MOVES = 15; const LEVEL_TARGET = 50; const EMOJI_SET = [‘🍎’, ‘🍊’, ‘🍋’, ‘🍌’, ‘🍉’, ‘🍇’, ‘🍓’, ‘🥝’, ‘🍒’, ‘🍑’]; const PLAYER_CHARS = [‘🐭’, ‘🐰’, ‘🐸’, ‘🐼’, ‘🐯’, ‘🦁’, ‘🐨’, ‘🐷’]; const DANGER_EMOJI = ‘💀’; const FACES = [‘front’, ‘back’, ‘left’, ‘right’, ‘top’, ‘bottom’]; const Game = () => { const [tiles, setTiles] = useState({}); const [playerPos, setPlayerPos] = useState({ face: ‘front’, x: 0, y: 0 }); const [rotation, setRotation] = useState({ x: -20, y: 20 }); const [moves, setMoves] = useState(INITIAL_MOVES); const [score, setScore] = useState(0); const [cheese, setCheese] = useState(0); const [level, setLevel] = useState(1); const [levelProgress, setLevelProgress] = useState(0); const [playerChar, setPlayerChar] = useState(0); const [unlockedChars, setUnlockedChars] = useState([0]); const [collectedSkins, setCollectedSkins] = useState(0); const [showMerchant, setShowMerchant] = useState(false); const [message, setMessage] = useState(’’); const [dragStart, setDragStart] = useState(null); const [isDragging, setIsDragging] = useState(false); const [isSpinning, setIsSpinning] = useState(false); const cubeRef = useRef(null); const initializeTiles = () => { const newTiles = {}; const roughness = Math.max(0, 10 - levelProgress / 5); ``` FACES.forEach(face => { newTiles[face] = []; for (let y = 0; y < CUBE_SIZE; y++) { for (let x = 0; x < CUBE_SIZE; x++) { const rand = Math.random(); let type = 'empty'; let emoji = ''; // 50% chance of being blank if (rand > 0.5) { type = 'emoji'; emoji = EMOJI_SET[Math.floor(Math.random() * EMOJI_SET.length)]; const specialRand = Math.random(); if (specialRand < 0.08) { type = 'danger'; emoji = DANGER_EMOJI; } else if (specialRand < 0.18) { type = 'cheese'; emoji = '🧀'; } else if (specialRand < 0.23) { type = 'skin'; emoji = '🎨'; } else if (specialRand < 0.26) { type = 'bonus'; emoji = '⭐'; } } newTiles[face].push({ x, y, face, emoji, type, rotation: (Math.random() - 0.5) * roughness, elevation: Math.random() * roughness, scale: 1 }); } } }); return newTiles; ``` }; useEffect(() => { setTiles(initializeTiles()); }, [level]); const smoothTiles = (amount) => { setTiles(prev => { const newTiles = {}; Object.keys(prev).forEach(face => { newTiles[face] = prev[face].map(tile => ({ …tile, rotation: tile.rotation * (1 - amount), elevation: tile.elevation * (1 - amount) })); }); return newTiles; }); }; const getTileAt = (face, x, y) => { if (!tiles[face]) return null; return tiles[face].find(t => t.x === x && t.y === y); }; const getEdgeTransition = (face, x, y, dx, dy) => { let newFace = face; let newX = x + dx; let newY = y + dy; ``` if (newX < 0) { newFace = face === 'front' ? 'left' : face === 'left' ? 'back' : face === 'back' ? 'right' : face === 'right' ? 'front' : face === 'top' ? 'left' : 'left'; newX = CUBE_SIZE - 1; } else if (newX >= CUBE_SIZE) { newFace = face === 'front' ? 'right' : face === 'right' ? 'back' : face === 'back' ? 'left' : face === 'left' ? 'front' : face === 'top' ? 'right' : 'right'; newX = 0; } else if (newY < 0) { newFace = face === 'front' ? 'top' : face === 'back' ? 'top' : face === 'left' ? 'top' : face === 'right' ? 'top' : face === 'top' ? 'back' : 'top'; newY = CUBE_SIZE - 1; } else if (newY >= CUBE_SIZE) { newFace = face === 'front' ? 'bottom' : face === 'back' ? 'bottom' : face === 'left' ? 'bottom' : face === 'right' ? 'bottom' : face === 'bottom' ? 'front' : 'bottom'; newY = 0; } return { face: newFace, x: newX, y: newY }; ``` }; const spinCube = () => { setIsSpinning(true); showMessage(“💀 YOU’VE BEEN SPUN! 💀”); ``` // Random crazy spins const spinDuration = 2000 + Math.random() * 1000; const spins = 2 + Math.floor(Math.random() * 3); const randomX = (Math.random() - 0.5) * 720 * spins; const randomY = (Math.random() - 0.5) * 720 * spins; setRotation({ x: randomX, y: randomY }); setTimeout(() => { // Land on a random face const faces = ['front', 'back', 'left', 'right', 'top', 'bottom']; const randomFace = faces[Math.floor(Math.random() * faces.length)]; const rotations = { front: { x: -20, y: 20 }, back: { x: -20, y: -160 }, left: { x: -20, y: 110 }, right: { x: -20, y: -70 }, top: { x: 70, y: 20 }, bottom: { x: -110, y: 20 } }; setRotation(rotations[randomFace]); setPlayerPos(prev => ({ ...prev, face: randomFace })); setIsSpinning(false); }, spinDuration); ``` }; const showMessage = (msg) => { setMessage(msg); setTimeout(() => setMessage(’’), 2000); }; const checkMatches = (affectedPositions) => { let matchCount = 0; let bonusMoves = 0; let totalPoints = 0; let cheeseCollected = 0; let skinsCollected = 0; let dangerTriggered = false; ``` affectedPositions.forEach(pos => { const tile = getTileAt(pos.face, pos.x, pos.y); if (!tile) return; // Check if danger tile was involved if (tile.type === 'danger') { dangerTriggered = true; } const neighbors = [ getTileAt(pos.face, pos.x - 1, pos.y), getTileAt(pos.face, pos.x + 1, pos.y), getTileAt(pos.face, pos.x, pos.y - 1), getTileAt(pos.face, pos.x, pos.y + 1) ].filter(Boolean); // Check if any neighbor is danger if (neighbors.some(n => n.type === 'danger')) { dangerTriggered = true; } const matches = neighbors.filter(n => n.emoji === tile.emoji && n.type === 'emoji'); if (matches.length >= 2) { matchCount++; const points = matches.length * 10 * matchCount; totalPoints += points; bonusMoves += matches.length; if (tile.type === 'cheese' || matches.some(m => m.type === 'cheese')) { cheeseCollected++; } if (tile.type === 'skin' || matches.some(m => m.type === 'skin')) { skinsCollected++; } } }); if (dangerTriggered) { setTimeout(() => spinCube(), 300); } if (matchCount > 0) { setScore(s => s + totalPoints); setMoves(m => m + bonusMoves); setCheese(c => c + cheeseCollected); setCollectedSkins(s => s + skinsCollected); setLevelProgress(p => { const newProgress = p + totalPoints; if (newProgress >= LEVEL_TARGET) { setLevel(l => l + 1); showMessage(`🎉 Level ${level + 1}!`); return 0; } return newProgress; }); smoothTiles(0.2 * matchCount); if (matchCount > 1) { showMessage(`🔥 ${matchCount}x Combo! +${totalPoints} points!`); } else { showMessage(`✨ Match! +${totalPoints} points!`); } if (skinsCollected > 0) { const nextChar = unlockedChars.length; if (nextChar < PLAYER_CHARS.length) { setUnlockedChars(prev => [...prev, nextChar]); showMessage(`🎨 New character unlocked!`); } } } return matchCount > 0; ``` }; const movePlayer = (dx, dy) => { if (moves <= 0) { showMessage(‘❌ No moves left! Visit merchant!’); return; } ``` if (isSpinning) return; const newPos = getEdgeTransition(playerPos.face, playerPos.x, playerPos.y, dx, dy); // Check if landing on danger tile const landingTile = getTileAt(newPos.face, newPos.x, newPos.y); if (landingTile && landingTile.type === 'danger') { setPlayerPos(newPos); setMoves(m => m - 1); setTimeout(() => spinCube(), 200); return; } // Chain reaction bump system const bumpedTiles = new Set(); const affectedPositions = []; const tilesToMove = []; // First, check if there's a tile in the direction we're moving const firstTile = getTileAt(newPos.face, newPos.x, newPos.y); if (firstTile && firstTile.type !== 'empty') { // Start chain reaction from this tile const queue = [{ face: newPos.face, x: newPos.x, y: newPos.y, dx, dy }]; while (queue.length > 0) { const current = queue.shift(); const key = `${current.face}-${current.x}-${current.y}`; if (bumpedTiles.has(key)) continue; const tile = getTileAt(current.face, current.x, current.y); if (!tile || tile.type === 'empty') continue; bumpedTiles.add(key); // Calculate where this tile will move const nextPos = getEdgeTransition(current.face, current.x, current.y, current.dx, current.dy); const nextTile = getTileAt(nextPos.face, nextPos.x, nextPos.y); // This tile will move if next space is empty if (!nextTile || nextTile.type === 'empty') { tilesToMove.push({ from: { face: current.face, x: current.x, y: current.y }, to: { face: nextPos.face, x: nextPos.x, y: nextPos.y } }); affectedPositions.push({ face: nextPos.face, x: nextPos.x, y: nextPos.y }); // Check all adjacent tiles for chain reaction const neighbors = [ { dx: 1, dy: 0 }, { dx: -1, dy: 0 }, { dx: 0, dy: 1 }, { dx: 0, dy: -1 } ]; neighbors.forEach(dir => { const neighborPos = getEdgeTransition(current.face, current.x, current.y, dir.dx, dir.dy); const neighborTile = getTileAt(neighborPos.face, neighborPos.x, neighborPos.y); const neighborKey = `${neighborPos.face}-${neighborPos.x}-${neighborPos.y}`; if (neighborTile && neighborTile.type !== 'empty' && !bumpedTiles.has(neighborKey)) { // This neighbor gets bumped in the same direction queue.push({ face: neighborPos.face, x: neighborPos.x, y: neighborPos.y, dx: current.dx, dy: current.dy }); } }); } else { affectedPositions.push({ face: current.face, x: current.x, y: current.y }); } } } if (tilesToMove.length > 0) { setTiles(prev => { const newTiles = { ...prev }; // Move all tiles that should move tilesToMove.forEach(move => { newTiles[move.from.face] = newTiles[move.from.face].map(t => t.x === move.from.x && t.y === move.from.y ? { ...t, x: move.to.x, y: move.to.y, face: move.to.face, scale: 1.2 } : t ); }); return newTiles; }); setTimeout(() => { setTiles(prev => { const newTiles = {}; Object.keys(prev).forEach(face => { newTiles[face] = prev[face].map(t => ({ ...t, scale: 1 })); }); return newTiles; }); checkMatches(affectedPositions); }, 200); } setPlayerPos(newPos); setMoves(m => m - 1); const rotations = { front: { x: -20, y: 20 }, back: { x: -20, y: -160 }, left: { x: -20, y: 110 }, right: { x: -20, y: -70 }, top: { x: 70, y: 20 }, bottom: { x: -110, y: 20 } }; setRotation(rotations[newPos.face]); ``` }; const handleTouchStart = (e) => { e.preventDefault(); const touch = e.touches[0]; setDragStart({ x: touch.clientX, y: touch.clientY }); setIsDragging(true); }; const handleTouchMove = (e) => { e.preventDefault(); if (!isDragging || !dragStart) return; }; const handleTouchEnd = (e) => { e.preventDefault(); if (!isDragging || !dragStart) return; ``` const touch = e.changedTouches[0]; const dx = touch.clientX - dragStart.x; const dy = touch.clientY - dragStart.y; const threshold = 20; if (Math.abs(dx) > Math.abs(dy) && Math.abs(dx) > threshold) { movePlayer(dx > 0 ? 1 : -1, 0); } else if (Math.abs(dy) > threshold) { movePlayer(0, dy > 0 ? 1 : -1); } setDragStart(null); setIsDragging(false); ``` }; const buyMoves = () => { if (cheese >= 3) { setCheese(c => c - 3); setMoves(m => m + 10); showMessage(‘✅ Bought 10 moves!’); } else { showMessage(‘❌ Need 3 cheese!’); } }; const changeCharacter = (charIndex) => { setPlayerChar(charIndex); setShowMerchant(false); showMessage(`Changed to ${PLAYER_CHARS[charIndex]}`); }; useEffect(() => { const handleKey = (e) => { if (showMerchant) return; ``` switch(e.key) { case 'ArrowUp': movePlayer(0, -1); break; case 'ArrowDown': movePlayer(0, 1); break; case 'ArrowLeft': movePlayer(-1, 0); break; case 'ArrowRight': movePlayer(1, 0); break; } }; window.addEventListener('keydown', handleKey); return () => window.removeEventListener('keydown', handleKey); ``` }, [playerPos, moves, tiles, showMerchant]); const renderFace = (face, transform) => { if (!tiles[face]) return null; ``` // Only show current face clearly, others are hints const isCurrentFace = playerPos.face === face; return (
{tiles[face].map((tile, i) => (
2 ? '0 8px 16px rgba(0,0,0,0.4)' : 'none', backgroundColor: tile.type === 'empty' ? 'rgba(17, 24, 39, 0.3)' : tile.type === 'danger' ? '#ef4444' : tile.type === 'bonus' ? '#fbbf24' : tile.type === 'cheese' ? '#fcd34d' : '#1f2937' }} > {playerPos.face === face && playerPos.x === tile.x && playerPos.y === tile.y ? ( {PLAYER_CHARS[playerChar]} ) : tile.type !== 'empty' ? ( {tile.emoji} ) : null}
))}
); ``` }; return (
```
{score}
👟 {moves}
🧀 {cheese}
Level {level}
{renderFace('front', 'translateZ(150px)')} {renderFace('back', 'rotateY(180deg) translateZ(150px)')} {renderFace('left', 'rotateY(-90deg) translateZ(150px)')} {renderFace('right', 'rotateY(90deg) translateZ(150px)')} {renderFace('top', 'rotateX(90deg) translateZ(150px)')} {renderFace('bottom', 'rotateX(-90deg) translateZ(150px)')}
Desktop: Arrow keys • Mobile: Swipe • Push tiles into empty spaces!
Current face: {playerPos.face}
⚠️ Avoid 💀 danger tiles - they spin the cube!
{message && (
{message}
)} {showMerchant && (

🏪 Merchant Shop

Buy Moves

Characters ({collectedSkins} 🎨 collected)

{PLAYER_CHARS.map((char, i) => ( ))}
)}
``` ); }; export default Game;