mirror of
https://github.com/AeThex-Corporation/AeThex-OS.git
synced 2026-04-17 22:27:19 +00:00
134 lines
5.3 KiB
TypeScript
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>
|
|
);
|
|
}
|