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 (
);
```
};
return (
```
{score}
Level {level}
{message && (
)}
```
);
};
export default Game;
{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}
))}
👟
{moves}
🧀
{cheese}
{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}
)}
{showMerchant && (
🏪 Merchant Shop
Buy Moves
Characters ({collectedSkins} 🎨 collected)
{PLAYER_CHARS.map((char, i) => (
))}