227 lines
8.2 KiB
JavaScript
227 lines
8.2 KiB
JavaScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { Pacman, Ghost, Bonus } from '../../utils/entities';
|
|
import { mazeVariants } from '../../utils/mazeData';
|
|
import { fillEmptySpaces, randomizeMaze, getBonusPositions, countDots } from '../../utils/gameLogic';
|
|
import GameCanvas from '../GameCanvas/GameCanvas';
|
|
import GameInfo from '../GameInfo/GameInfo';
|
|
import UserInput from '../UserInput/UserInput';
|
|
import Leaderboard from '../Leaderboard/Leaderboard';
|
|
import { useGameState } from '../../hooks/useGameState';
|
|
import { useKeyboard } from '../../hooks/useKeyboard';
|
|
import { useLeaderboard } from '../../hooks/useLeaderboard';
|
|
import { saveScore } from '../../utils/leaderboard';
|
|
|
|
function Game() {
|
|
const gameState = useGameState();
|
|
const { scores, addScore: addLeaderboardScore } = useLeaderboard();
|
|
const [username, setUsername] = useState('');
|
|
const [maze, setMaze] = useState([]);
|
|
const [pacman, setPacman] = useState(null);
|
|
const [ghosts, setGhosts] = useState([]);
|
|
const [bonuses, setBonuses] = useState([]);
|
|
|
|
const initializeGame = useCallback(() => {
|
|
const initialMaze = mazeVariants[0].map(row => [...row]);
|
|
fillEmptySpaces(initialMaze);
|
|
randomizeMaze(initialMaze);
|
|
setMaze(initialMaze);
|
|
|
|
const newPacman = new Pacman();
|
|
newPacman.speed = newPacman.baseSpeed * (1 + (gameState.level - 1) * 0.05);
|
|
setPacman(newPacman);
|
|
|
|
const newGhosts = [
|
|
new Ghost(14, 11, '#ff0000'),
|
|
new Ghost(15, 11, '#ff00ff'),
|
|
new Ghost(14, 12, '#00ffff'),
|
|
new Ghost(15, 12, '#ffa500')
|
|
];
|
|
newGhosts.forEach(ghost => ghost.updateSpeed(gameState.level));
|
|
setGhosts(newGhosts);
|
|
|
|
const bonusPositions = getBonusPositions();
|
|
const newBonuses = [];
|
|
const mazeCopy = initialMaze.map(row => [...row]);
|
|
for (let pos of bonusPositions) {
|
|
if (mazeCopy[pos.y][pos.x] === 0 || mazeCopy[pos.y][pos.x] === 2) {
|
|
mazeCopy[pos.y][pos.x] = pos.type;
|
|
newBonuses.push(new Bonus(pos.x, pos.y, pos.type));
|
|
}
|
|
}
|
|
setBonuses(newBonuses);
|
|
setMaze(mazeCopy);
|
|
}, [gameState.level]);
|
|
|
|
useEffect(() => {
|
|
initializeGame();
|
|
gameState.resetGame();
|
|
}, []);
|
|
|
|
const handleCollect = useCallback((type, points) => {
|
|
gameState.addScore(points);
|
|
|
|
if (type === 'cherry') {
|
|
const newCherriesEaten = gameState.cherriesEaten + 1;
|
|
gameState.setCherriesEaten(newCherriesEaten);
|
|
gameState.setCherryEatenRecently(true);
|
|
gameState.setCherryEatenTimer(Math.max(150, 300 - (gameState.level - 1) * 20));
|
|
}
|
|
}, [gameState]);
|
|
|
|
const handleCollision = useCallback(() => {
|
|
const newLives = gameState.lives - 1;
|
|
gameState.setLives(newLives);
|
|
|
|
if (newLives <= 0) {
|
|
gameState.setGameRunning(false);
|
|
gameState.setStatus('Game Over !');
|
|
if (username.trim() !== '') {
|
|
saveScore(username, gameState.score);
|
|
addLeaderboardScore(username, gameState.score);
|
|
}
|
|
} else {
|
|
gameState.restartLevel();
|
|
setTimeout(() => {
|
|
initializeGame();
|
|
gameState.setIsChangingLevel(false);
|
|
gameState.setGameRunning(true);
|
|
gameState.setStatus('En jeu');
|
|
}, 2000);
|
|
}
|
|
}, [gameState, username, initializeGame, addLeaderboardScore]);
|
|
|
|
const handleNextLevel = useCallback(() => {
|
|
const newLevel = gameState.level + 1;
|
|
gameState.setLevel(newLevel);
|
|
gameState.setCherriesEaten(0);
|
|
gameState.setIsChangingLevel(true);
|
|
gameState.setCherryEatenRecently(false);
|
|
gameState.setCherryEatenTimer(0);
|
|
|
|
setTimeout(() => {
|
|
const mazeIndex = (newLevel - 1) % mazeVariants.length;
|
|
const newMaze = mazeVariants[mazeIndex].map(row => [...row]);
|
|
fillEmptySpaces(newMaze);
|
|
randomizeMaze(newMaze);
|
|
setMaze(newMaze);
|
|
|
|
const newPacman = new Pacman();
|
|
newPacman.speed = newPacman.baseSpeed * (1 + (newLevel - 1) * 0.05);
|
|
setPacman(newPacman);
|
|
|
|
const newGhosts = [
|
|
new Ghost(14, 11, '#ff0000'),
|
|
new Ghost(15, 11, '#ff00ff'),
|
|
new Ghost(14, 12, '#00ffff'),
|
|
new Ghost(15, 12, '#ffa500')
|
|
];
|
|
newGhosts.forEach(ghost => ghost.updateSpeed(newLevel));
|
|
setGhosts(newGhosts);
|
|
|
|
const bonusPositions = getBonusPositions();
|
|
const newBonuses = [];
|
|
const mazeCopy = newMaze.map(row => [...row]);
|
|
for (let pos of bonusPositions) {
|
|
if (mazeCopy[pos.y][pos.x] === 0 || mazeCopy[pos.y][pos.x] === 2) {
|
|
mazeCopy[pos.y][pos.x] = pos.type;
|
|
newBonuses.push(new Bonus(pos.x, pos.y, pos.type));
|
|
}
|
|
}
|
|
setBonuses(newBonuses);
|
|
setMaze(mazeCopy);
|
|
|
|
gameState.setIsChangingLevel(false);
|
|
gameState.setGameRunning(true);
|
|
gameState.setStatus('En jeu');
|
|
}, 2000);
|
|
}, [gameState]);
|
|
|
|
const handleKeyPress = useCallback((key) => {
|
|
if (!gameState.gameRunning || !pacman) return;
|
|
|
|
switch(key) {
|
|
case 'ArrowUp':
|
|
pacman.nextDirection = 0;
|
|
break;
|
|
case 'ArrowRight':
|
|
pacman.nextDirection = 1;
|
|
break;
|
|
case 'ArrowDown':
|
|
pacman.nextDirection = 2;
|
|
break;
|
|
case 'ArrowLeft':
|
|
pacman.nextDirection = 3;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}, [gameState.gameRunning, pacman]);
|
|
|
|
useKeyboard(handleKeyPress);
|
|
|
|
const handleRestart = useCallback(() => {
|
|
initializeGame();
|
|
gameState.resetGame();
|
|
}, [initializeGame, gameState]);
|
|
|
|
useEffect(() => {
|
|
if (gameState.cherryEatenTimer > 0) {
|
|
const timer = setInterval(() => {
|
|
const newTimer = gameState.cherryEatenTimer - 1;
|
|
if (newTimer <= 0) {
|
|
gameState.setCherryEatenRecently(false);
|
|
gameState.setCherryEatenTimer(0);
|
|
} else {
|
|
gameState.setCherryEatenTimer(newTimer);
|
|
}
|
|
}, 16);
|
|
return () => clearInterval(timer);
|
|
}
|
|
}, [gameState.cherryEatenTimer, gameState]);
|
|
|
|
useEffect(() => {
|
|
if (gameState.cherriesEaten >= 4 && !gameState.isChangingLevel && gameState.gameRunning) {
|
|
handleNextLevel();
|
|
}
|
|
}, [gameState.cherriesEaten, gameState.isChangingLevel, gameState.gameRunning, handleNextLevel]);
|
|
|
|
return (
|
|
<div className="main-wrapper">
|
|
<div className="container">
|
|
<h1>OULVIC</h1>
|
|
<UserInput username={username} onUsernameChange={setUsername} />
|
|
<GameInfo
|
|
score={gameState.score}
|
|
level={gameState.level}
|
|
lives={gameState.lives}
|
|
status={gameState.status}
|
|
/>
|
|
<GameCanvas
|
|
maze={maze}
|
|
pacman={pacman}
|
|
ghosts={ghosts}
|
|
bonuses={bonuses}
|
|
gameRunning={gameState.gameRunning}
|
|
isChangingLevel={gameState.isChangingLevel}
|
|
level={gameState.level}
|
|
cherryEatenRecently={gameState.cherryEatenRecently}
|
|
onCollect={handleCollect}
|
|
onCollision={handleCollision}
|
|
/>
|
|
<div className="instructions">
|
|
<p>Utilisez les flèches directionnelles pour déplacer Oulvic</p>
|
|
{!gameState.gameRunning && (
|
|
<button id="restartBtn" onClick={handleRestart}>
|
|
Rejouer
|
|
</button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
<Leaderboard />
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default Game;
|
|
|