147 lines
4.7 KiB
TypeScript
147 lines
4.7 KiB
TypeScript
import { motion } from 'framer-motion';
|
||
import { Link } from 'react-router-dom';
|
||
import GlitchText from '@/components/GlitchText';
|
||
import { useState, useEffect } from 'react';
|
||
|
||
const HIGHSCORE_KEYS = {
|
||
tetris: 'tetris-highscore',
|
||
pacman: 'pacman-highscore',
|
||
snake: 'snake-highscore',
|
||
breakout: 'breakout-highscore',
|
||
};
|
||
|
||
const MAX_SCORE = 4294967296;
|
||
|
||
const Leaderboard = () => {
|
||
const [scores, setScores] = useState({
|
||
tetris: 0,
|
||
pacman: 0,
|
||
snake: 0,
|
||
breakout: 0,
|
||
});
|
||
|
||
useEffect(() => {
|
||
const tetrisScore = localStorage.getItem(HIGHSCORE_KEYS.tetris);
|
||
const pacmanScore = localStorage.getItem(HIGHSCORE_KEYS.pacman);
|
||
const snakeScore = localStorage.getItem(HIGHSCORE_KEYS.snake);
|
||
const breakoutScore = localStorage.getItem(HIGHSCORE_KEYS.breakout);
|
||
|
||
setScores({
|
||
tetris: tetrisScore ? Math.min(parseInt(tetrisScore, 10), MAX_SCORE) : 0,
|
||
pacman: pacmanScore ? Math.min(parseInt(pacmanScore, 10), MAX_SCORE) : 0,
|
||
snake: snakeScore ? Math.min(parseInt(snakeScore, 10), MAX_SCORE) : 0,
|
||
breakout: breakoutScore ? Math.min(parseInt(breakoutScore, 10), MAX_SCORE) : 0,
|
||
});
|
||
}, []);
|
||
|
||
const totalScore = scores.tetris + scores.pacman + scores.snake + scores.breakout;
|
||
|
||
const gameCards = [
|
||
{
|
||
id: 'tetris',
|
||
name: 'Tetris',
|
||
score: scores.tetris,
|
||
icon: `▓▓
|
||
▓▓██
|
||
████`,
|
||
},
|
||
{
|
||
id: 'pacman',
|
||
name: 'Pac-Man',
|
||
score: scores.pacman,
|
||
icon: `◗ ᗣ
|
||
· ·`,
|
||
},
|
||
{
|
||
id: 'snake',
|
||
name: 'Snake',
|
||
score: scores.snake,
|
||
icon: `●■■
|
||
■◆`,
|
||
},
|
||
{
|
||
id: 'breakout',
|
||
name: 'Breakout',
|
||
score: scores.breakout,
|
||
icon: `████
|
||
●
|
||
═══`,
|
||
},
|
||
];
|
||
|
||
return (
|
||
<motion.div
|
||
initial={{ opacity: 0 }}
|
||
animate={{ opacity: 1 }}
|
||
transition={{ duration: 0.5 }}
|
||
className="flex flex-col h-full"
|
||
>
|
||
<div className="flex items-center gap-4 mb-4">
|
||
<Link to="/games" className="font-pixel text-sm text-foreground/50 hover:text-primary transition-colors">
|
||
{'<'} Back
|
||
</Link>
|
||
<h1 className="font-minecraft text-3xl md:text-4xl text-primary text-glow-strong">
|
||
<GlitchText text="Leaderboard" />
|
||
</h1>
|
||
</div>
|
||
|
||
{/* Total Score */}
|
||
<div className="border-2 border-primary box-glow p-4 bg-background/80 mb-4">
|
||
<p className="font-pixel text-sm text-foreground/60 mb-1">COMBINED TOTAL</p>
|
||
<p className="font-minecraft text-4xl text-primary text-glow-strong">
|
||
{totalScore.toLocaleString()}
|
||
</p>
|
||
<p className="font-pixel text-xs text-foreground/40 mt-1">
|
||
Target: 4,294,967,296 × 4 = 17,179,869,184
|
||
</p>
|
||
</div>
|
||
|
||
{/* Game Scores */}
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||
{gameCards.map((game, index) => (
|
||
<motion.div
|
||
key={game.id}
|
||
initial={{ opacity: 0, y: 20 }}
|
||
animate={{ opacity: 1, y: 0 }}
|
||
transition={{ delay: index * 0.1 }}
|
||
>
|
||
<Link to={`/games/${game.id}`} className="block">
|
||
<div className="border border-primary/50 hover:border-primary bg-background/50 hover:bg-primary/10 p-4 transition-all duration-300">
|
||
<pre className="font-mono text-lg text-primary/70 mb-3 leading-tight whitespace-pre">
|
||
{game.icon}
|
||
</pre>
|
||
<h2 className="font-minecraft text-xl text-primary text-glow mb-2">
|
||
{game.name}
|
||
</h2>
|
||
<div>
|
||
<p className="font-pixel text-xs text-foreground/60">HIGH SCORE</p>
|
||
<p className="font-minecraft text-2xl text-primary text-glow">
|
||
{game.score.toLocaleString()}
|
||
</p>
|
||
<div className="mt-2 h-2 bg-background/50 border border-primary/30">
|
||
<div
|
||
className="h-full bg-primary/60 transition-all duration-500"
|
||
style={{ width: `${Math.min((game.score / MAX_SCORE) * 100, 100)}%` }}
|
||
/>
|
||
</div>
|
||
<p className="font-pixel text-[10px] text-foreground/40 mt-1">
|
||
{((game.score / MAX_SCORE) * 100).toFixed(6)}% to max
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</Link>
|
||
</motion.div>
|
||
))}
|
||
</div>
|
||
|
||
<div className="border border-primary/30 p-2 bg-background/30 mt-4">
|
||
<p className="font-pixel text-xs text-foreground/50">
|
||
<span className="text-primary">{'>'}</span> Click a game to play and improve your score
|
||
</p>
|
||
</div>
|
||
</motion.div>
|
||
);
|
||
};
|
||
|
||
export default Leaderboard;
|