AeThex-OS/client/src/os/apps/ArcadeApp.tsx

134 lines
5.3 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Gamepad2, ChevronUp } from 'lucide-react';
export function ArcadeApp() {
const [snake, setSnake] = useState([{ x: 10, y: 10 }]);
const [food, setFood] = useState({ x: 15, y: 15 });
const [direction, setDirection] = useState({ x: 1, y: 0 });
const [gameOver, setGameOver] = useState(false);
const [score, setScore] = useState(0);
const [isPlaying, setIsPlaying] = useState(false);
useEffect(() => {
if (!isPlaying || gameOver) return;
const interval = setInterval(() => {
setSnake(prev => {
const newHead = { x: prev[0].x + direction.x, y: prev[0].y + direction.y };
if (newHead.x < 0 || newHead.x >= 20 || newHead.y < 0 || newHead.y >= 20) {
setGameOver(true);
setIsPlaying(false);
return prev;
}
if (prev.some(s => s.x === newHead.x && s.y === newHead.y)) {
setGameOver(true);
setIsPlaying(false);
return prev;
}
const newSnake = [newHead, ...prev];
if (newHead.x === food.x && newHead.y === food.y) {
setScore(s => s + 10);
setFood({ x: Math.floor(Math.random() * 20), y: Math.floor(Math.random() * 20) });
} else {
newSnake.pop();
}
return newSnake;
});
}, 150);
return () => clearInterval(interval);
}, [isPlaying, gameOver, direction, food]);
useEffect(() => {
const handleKey = (e: KeyboardEvent) => {
if (!isPlaying) return;
switch (e.key) {
case 'ArrowUp': if (direction.y !== 1) setDirection({ x: 0, y: -1 }); break;
case 'ArrowDown': if (direction.y !== -1) setDirection({ x: 0, y: 1 }); break;
case 'ArrowLeft': if (direction.x !== 1) setDirection({ x: -1, y: 0 }); break;
case 'ArrowRight': if (direction.x !== -1) setDirection({ x: 1, y: 0 }); break;
}
};
window.addEventListener('keydown', handleKey);
return () => window.removeEventListener('keydown', handleKey);
}, [isPlaying, direction]);
const startGame = () => {
setSnake([{ x: 10, y: 10 }]);
setFood({ x: 15, y: 15 });
setDirection({ x: 1, y: 0 });
setGameOver(false);
setScore(0);
setIsPlaying(true);
};
return (
<div className="min-h-full bg-slate-950 p-3 md:p-4 flex flex-col items-center overflow-auto">
<div className="flex items-center gap-2 mb-3 md:mb-4">
<Gamepad2 className="w-5 h-5 text-cyan-400" />
<h2 className="text-base md:text-lg font-display text-white uppercase tracking-wider">Cyber Snake</h2>
</div>
<div className="text-cyan-400 font-mono text-sm md:text-base mb-2">Score: {score}</div>
<div className="grid gap-px bg-cyan-900/20 border border-cyan-500/30 rounded" style={{ gridTemplateColumns: 'repeat(20, 12px)' }}>
{Array.from({ length: 400 }).map((_, i) => {
const x = i % 20;
const y = Math.floor(i / 20);
const isSnake = snake.some(s => s.x === x && s.y === y);
const isHead = snake[0]?.x === x && snake[0]?.y === y;
const isFood = food.x === x && food.y === y;
return (
<div
key={i}
className={`w-3 h-3 ${isHead ? 'bg-cyan-400' : isSnake ? 'bg-green-500' : isFood ? 'bg-red-500' : 'bg-slate-900'}`}
/>
);
})}
</div>
{!isPlaying && (
<button onClick={startGame} className="mt-3 md:mt-4 px-4 md:px-6 py-2 bg-cyan-500/20 hover:bg-cyan-500/30 text-cyan-400 rounded-lg border border-cyan-500/50 transition-colors font-mono text-sm md:text-base">
{gameOver ? 'Play Again' : 'Start Game'}
</button>
)}
{isPlaying && (
<div className="mt-3 md:mt-4 grid grid-cols-3 gap-2 md:hidden">
<div />
<button
onClick={() => direction.y !== 1 && setDirection({ x: 0, y: -1 })}
className="p-3 bg-cyan-500/20 active:bg-cyan-500/40 rounded border border-cyan-500/50 transition-colors"
>
<ChevronUp className="w-5 h-5 text-cyan-400 mx-auto" />
</button>
<div />
<button
onClick={() => direction.x !== 1 && setDirection({ x: -1, y: 0 })}
className="p-3 bg-cyan-500/20 active:bg-cyan-500/40 rounded border border-cyan-500/50 transition-colors"
>
<ChevronUp className="w-5 h-5 text-cyan-400 mx-auto rotate-[270deg]" />
</button>
<div />
<button
onClick={() => direction.x !== -1 && setDirection({ x: 1, y: 0 })}
className="p-3 bg-cyan-500/20 active:bg-cyan-500/40 rounded border border-cyan-500/50 transition-colors"
>
<ChevronUp className="w-5 h-5 text-cyan-400 mx-auto rotate-90" />
</button>
<div />
<button
onClick={() => direction.y !== -1 && setDirection({ x: 0, y: 1 })}
className="p-3 bg-cyan-500/20 active:bg-cyan-500/40 rounded border border-cyan-500/50 transition-colors"
>
<ChevronUp className="w-5 h-5 text-cyan-400 mx-auto rotate-180" />
</button>
<div />
</div>
)}
<div className="mt-2 text-white/40 text-xs text-center">
<span className="md:inline hidden">Use arrow keys to move</span>
<span className="md:hidden">Tap buttons to move</span>
</div>
</div>
);
}