From e987f3de3b7139e49920bd4864dff876ea7f3abb Mon Sep 17 00:00:00 2001 From: JorySeverijnse Date: Mon, 8 Dec 2025 12:24:45 +0000 Subject: [PATCH] Changes --- src/contexts/AchievementsContext.tsx | 65 ++++++++++++++++++++++++++++ src/contexts/MusicContext.tsx | 2 + src/pages/Breakout.tsx | 10 +++++ src/pages/Pacman.tsx | 10 +++++ src/pages/Snake.tsx | 10 +++++ src/pages/Tetris.tsx | 10 +++++ 6 files changed, 107 insertions(+) diff --git a/src/contexts/AchievementsContext.tsx b/src/contexts/AchievementsContext.tsx index f96f762..0b5957c 100644 --- a/src/contexts/AchievementsContext.tsx +++ b/src/contexts/AchievementsContext.tsx @@ -25,6 +25,32 @@ export const getAiMessageCount = (): number => { return parseInt(localStorage.getItem(AI_MESSAGE_COUNT_KEY) || '0', 10); }; +// Game score achievement thresholds +export const GAME_SCORE_ACHIEVEMENTS = { + tetris: [ + { score: 1000, id: 'tetris_1000' }, + { score: 10000, id: 'tetris_10000' }, + { score: 50000, id: 'tetris_50000' }, + ], + pacman: [ + { score: 1000, id: 'pacman_1000' }, + { score: 5000, id: 'pacman_5000' }, + { score: 10000, id: 'pacman_10000' }, + ], + snake: [ + { score: 500, id: 'snake_500' }, + { score: 2000, id: 'snake_2000' }, + { score: 5000, id: 'snake_5000' }, + ], + breakout: [ + { score: 1000, id: 'breakout_1000' }, + { score: 5000, id: 'breakout_5000' }, + { score: 10000, id: 'breakout_10000' }, + ], +} as const; + +export type GameType = keyof typeof GAME_SCORE_ACHIEVEMENTS; + interface AchievementsContextType { achievements: Achievement[]; unlockAchievement: (id: string) => void; @@ -32,6 +58,9 @@ interface AchievementsContextType { getTotalCount: () => number; timeOnSite: number; checkAiAchievements: () => void; + checkGameScoreAchievements: (game: GameType, score: number) => void; + unlockMusicListener: () => void; + unlockMaxScore: () => void; } const defaultAchievements: Achievement[] = [ @@ -202,6 +231,19 @@ export const AchievementsProvider = ({ children }: { children: ReactNode }) => { unlockAchievement('first_visit'); }, []); + // Check for music listener achievement (set by MusicContext) + useEffect(() => { + const checkMusicListener = () => { + if (localStorage.getItem('has-listened-to-music') === 'true') { + unlockAchievement('music_listener'); + } + }; + checkMusicListener(); + // Also check periodically in case user starts playing music + const interval = setInterval(checkMusicListener, 2000); + return () => clearInterval(interval); + }, []); + const [notification, setNotification] = useState(null); const unlockAchievement = useCallback((id: string) => { @@ -236,6 +278,26 @@ export const AchievementsProvider = ({ children }: { children: ReactNode }) => { if (count >= 20) unlockAchievement('ai_long_chat'); }, [unlockAchievement]); + // Check game score achievements + const checkGameScoreAchievements = useCallback((game: GameType, score: number) => { + const thresholds = GAME_SCORE_ACHIEVEMENTS[game]; + for (const threshold of thresholds) { + if (score >= threshold.score) { + unlockAchievement(threshold.id); + } + } + }, [unlockAchievement]); + + // Unlock music listener achievement + const unlockMusicListener = useCallback(() => { + unlockAchievement('music_listener'); + }, [unlockAchievement]); + + // Unlock max score achievement + const unlockMaxScore = useCallback(() => { + unlockAchievement('max_score'); + }, [unlockAchievement]); + // Save achievements useEffect(() => { localStorage.setItem('achievements', JSON.stringify(achievements)); @@ -250,6 +312,9 @@ export const AchievementsProvider = ({ children }: { children: ReactNode }) => { getTotalCount, timeOnSite, checkAiAchievements, + checkGameScoreAchievements, + unlockMusicListener, + unlockMaxScore, }} > {children} diff --git a/src/contexts/MusicContext.tsx b/src/contexts/MusicContext.tsx index 9ce688c..d635362 100644 --- a/src/contexts/MusicContext.tsx +++ b/src/contexts/MusicContext.tsx @@ -104,6 +104,8 @@ export const MusicProvider = ({ children }: { children: ReactNode }) => { audio.onplaying = () => { setIsBuffering(false); setIsPlaying(true); + // Set flag for music listener achievement (checked in AchievementsContext) + localStorage.setItem('has-listened-to-music', 'true'); }; audio.onplay = () => { diff --git a/src/pages/Breakout.tsx b/src/pages/Breakout.tsx index 650a03e..8bc3fbf 100644 --- a/src/pages/Breakout.tsx +++ b/src/pages/Breakout.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { motion } from 'framer-motion'; import { useSettings } from '@/contexts/SettingsContext'; +import { useAchievements } from '@/contexts/AchievementsContext'; import { Link } from 'react-router-dom'; import GlitchCrash from '@/components/GlitchCrash'; import { Maximize2, Minimize2 } from 'lucide-react'; @@ -26,6 +27,7 @@ interface Brick { const Breakout = () => { const { playSound } = useSettings(); + const { checkGameScoreAchievements, unlockMaxScore } = useAchievements(); const [isFullscreen, setIsFullscreen] = useState(false); const [score, setScore] = useState(0); const [highScore, setHighScore] = useState(0); @@ -96,6 +98,14 @@ const Breakout = () => { if (saved) setHighScore(Math.min(parseInt(saved, 10), MAX_SCORE)); }, []); + // Check score achievements + useEffect(() => { + if (score > 0) { + checkGameScoreAchievements('breakout', score); + if (score >= MAX_SCORE) unlockMaxScore(); + } + }, [score, checkGameScoreAchievements, unlockMaxScore]); + const initBricks = useCallback(() => { const bricks: Brick[] = []; const brickWidth = (canvasSize.width - (BRICK_COLS + 1) * BRICK_GAP) / BRICK_COLS; diff --git a/src/pages/Pacman.tsx b/src/pages/Pacman.tsx index 06396fb..be3a881 100644 --- a/src/pages/Pacman.tsx +++ b/src/pages/Pacman.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { motion } from 'framer-motion'; import { useSettings } from '@/contexts/SettingsContext'; +import { useAchievements } from '@/contexts/AchievementsContext'; import { Link } from 'react-router-dom'; import GlitchCrash from '@/components/GlitchCrash'; import { Maximize2, Minimize2 } from 'lucide-react'; @@ -43,6 +44,7 @@ const MAZE_TEMPLATE = [ const Pacman = () => { const { playSound } = useSettings(); + const { checkGameScoreAchievements, unlockMaxScore } = useAchievements(); const [pacman, setPacman] = useState({ x: 10, y: 15 }); const [direction, setDirection] = useState('right'); const [nextDirection, setNextDirection] = useState('right'); @@ -112,6 +114,14 @@ const Pacman = () => { if (saved) setHighScore(Math.min(parseInt(saved, 10), MAX_SCORE)); }, []); + // Check score achievements + useEffect(() => { + if (score > 0) { + checkGameScoreAchievements('pacman', score); + if (score >= MAX_SCORE) unlockMaxScore(); + } + }, [score, checkGameScoreAchievements, unlockMaxScore]); + const initDots = useCallback(() => { const newDots = new Set(); const newPowerPellets = new Set(); diff --git a/src/pages/Snake.tsx b/src/pages/Snake.tsx index 61230ff..7dd55d3 100644 --- a/src/pages/Snake.tsx +++ b/src/pages/Snake.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { motion } from 'framer-motion'; import { useSettings } from '@/contexts/SettingsContext'; +import { useAchievements } from '@/contexts/AchievementsContext'; import { Link } from 'react-router-dom'; import GlitchCrash from '@/components/GlitchCrash'; import { Maximize2, Minimize2 } from 'lucide-react'; @@ -17,6 +18,7 @@ type Position = { x: number; y: number }; const Snake = () => { const { playSound } = useSettings(); + const { checkGameScoreAchievements, unlockMaxScore } = useAchievements(); const [snake, setSnake] = useState([{ x: 10, y: 10 }]); const [direction, setDirection] = useState('right'); const [food, setFood] = useState({ x: 15, y: 10 }); @@ -94,6 +96,14 @@ const Snake = () => { if (saved) setHighScore(Math.min(parseInt(saved, 10), MAX_SCORE)); }, []); + // Check score achievements + useEffect(() => { + if (score > 0) { + checkGameScoreAchievements('snake', score); + if (score >= MAX_SCORE) unlockMaxScore(); + } + }, [score, checkGameScoreAchievements, unlockMaxScore]); + const spawnFood = useCallback((currentSnake: Position[]): Position | null => { const occupied = new Set(currentSnake.map(s => `${s.x},${s.y}`)); const available: Position[] = []; diff --git a/src/pages/Tetris.tsx b/src/pages/Tetris.tsx index b22f2ab..7c8c0ba 100644 --- a/src/pages/Tetris.tsx +++ b/src/pages/Tetris.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { motion } from 'framer-motion'; import { useSettings } from '@/contexts/SettingsContext'; +import { useAchievements } from '@/contexts/AchievementsContext'; import { Link } from 'react-router-dom'; import GlitchCrash from '@/components/GlitchCrash'; import { Maximize2, Minimize2 } from 'lucide-react'; @@ -57,6 +58,7 @@ const rotate = (matrix: number[][]): number[][] => { const Tetris = () => { const { playSound } = useSettings(); + const { checkGameScoreAchievements, unlockMaxScore } = useAchievements(); const [board, setBoard] = useState(createBoard); const [piece, setPiece] = useState(randomTetromino); const [nextPiece, setNextPiece] = useState(randomTetromino); @@ -122,6 +124,14 @@ const Tetris = () => { if (saved) setHighScore(Math.min(parseInt(saved, 10), MAX_SCORE)); }, []); + // Check score achievements + useEffect(() => { + if (score > 0) { + checkGameScoreAchievements('tetris', score); + if (score >= MAX_SCORE) unlockMaxScore(); + } + }, [score, checkGameScoreAchievements, unlockMaxScore]); + const isValidMove = useCallback((newPiece: Piece, currentBoard: Board): boolean => { for (let y = 0; y < newPiece.shape.length; y++) { for (let x = 0; x < newPiece.shape[y].length; x++) {