Refactoring vers React: migration complète de l'application vanilla JS vers React avec architecture modulaire
This commit is contained in:
226
src/components/Game/Game.js
Normal file
226
src/components/Game/Game.js
Normal file
@ -0,0 +1,226 @@
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user