Compare commits
1 Commits
main
...
dev-vuerea
| Author | SHA1 | Date | |
|---|---|---|---|
|
54ebd58e5a
|
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@ -0,0 +1,33 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# Testing
|
||||
/coverage
|
||||
|
||||
# Production
|
||||
/build
|
||||
/dist
|
||||
|
||||
# Misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Vite
|
||||
.vite/
|
||||
|
||||
32
index.html
32
index.html
@ -4,37 +4,9 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Jeu Oulvic</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="main-wrapper">
|
||||
<div class="container">
|
||||
<h1>OULVIC</h1>
|
||||
<div class="user-input-section">
|
||||
<label for="username">Nom d'utilisateur:</label>
|
||||
<input type="text" id="username" placeholder="Entrez votre nom" maxlength="15">
|
||||
</div>
|
||||
<div class="game-info">
|
||||
<div class="score">Score: <span id="score">0</span></div>
|
||||
<div class="level">Niveau: <span id="level">1</span></div>
|
||||
<div class="lives">Vies: <span id="lives"><span class="heart">♥</span><span class="heart">♥</span><span class="heart">♥</span></span></div>
|
||||
<div class="status" id="status">Prêt à jouer</div>
|
||||
</div>
|
||||
<canvas id="gameCanvas" width="600" height="600"></canvas>
|
||||
<div class="instructions">
|
||||
<p>Utilisez les flèches directionnelles pour déplacer Oulvic</p>
|
||||
<button id="restartBtn" style="display: none;">Rejouer</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="leaderboard-container">
|
||||
<h2>Classement</h2>
|
||||
<div id="leaderboard"></div>
|
||||
</div>
|
||||
</div>
|
||||
<footer>
|
||||
<p>By Ludo and Syoul</p>
|
||||
</footer>
|
||||
<script src="game.js"></script>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
20
package.json
Normal file
20
package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "oulvic-game",
|
||||
"version": "1.0.0",
|
||||
"description": "Jeu Oulvic - Refactoring React",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"vite": "^5.0.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
}
|
||||
}
|
||||
|
||||
16
src/App.js
Normal file
16
src/App.js
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
import Game from './components/Game/Game';
|
||||
import Footer from './components/Footer/Footer';
|
||||
import './styles/App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<Game />
|
||||
<Footer />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
||||
12
src/components/Footer/Footer.js
Normal file
12
src/components/Footer/Footer.js
Normal file
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
|
||||
function Footer() {
|
||||
return (
|
||||
<footer>
|
||||
<p>By Ludo and Syoul</p>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
|
||||
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;
|
||||
|
||||
81
src/components/GameCanvas/GameCanvas.js
Normal file
81
src/components/GameCanvas/GameCanvas.js
Normal file
@ -0,0 +1,81 @@
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
import { drawMaze } from '../../utils/maze';
|
||||
import { COLS, ROWS, CELL_SIZE } from '../../utils/constants';
|
||||
|
||||
function GameCanvas({
|
||||
maze,
|
||||
pacman,
|
||||
ghosts,
|
||||
bonuses,
|
||||
gameRunning,
|
||||
isChangingLevel,
|
||||
level,
|
||||
cherryEatenRecently,
|
||||
onCollect,
|
||||
onCollision
|
||||
}) {
|
||||
const canvasRef = useRef(null);
|
||||
const animationFrameRef = useRef(null);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const canvas = canvasRef.current;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
canvas.width = COLS * CELL_SIZE;
|
||||
canvas.height = ROWS * CELL_SIZE;
|
||||
|
||||
const gameLoop = () => {
|
||||
if (isChangingLevel || !gameRunning) {
|
||||
return;
|
||||
}
|
||||
|
||||
drawMaze(ctx, maze, bonuses);
|
||||
|
||||
if (pacman) {
|
||||
pacman.update(maze, gameRunning);
|
||||
pacman.collectDot(maze, (type, points) => {
|
||||
onCollect(type, points);
|
||||
});
|
||||
pacman.draw(ctx);
|
||||
}
|
||||
|
||||
if (ghosts && ghosts.length > 0) {
|
||||
ghosts.forEach(ghost => {
|
||||
ghost.update(maze, gameRunning, level, pacman, cherryEatenRecently);
|
||||
ghost.draw(ctx);
|
||||
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(pacman.pixelX - ghost.pixelX, 2) +
|
||||
Math.pow(pacman.pixelY - ghost.pixelY, 2)
|
||||
);
|
||||
|
||||
if (distance < CELL_SIZE * 0.6) {
|
||||
onCollision();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (gameRunning && !isChangingLevel) {
|
||||
animationFrameRef.current = requestAnimationFrame(gameLoop);
|
||||
}
|
||||
};
|
||||
|
||||
if (gameRunning && !isChangingLevel) {
|
||||
animationFrameRef.current = requestAnimationFrame(gameLoop);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (animationFrameRef.current) {
|
||||
cancelAnimationFrame(animationFrameRef.current);
|
||||
}
|
||||
};
|
||||
}, [maze, bonuses, pacman, ghosts, gameRunning, isChangingLevel, level, cherryEatenRecently, onCollect, onCollision]);
|
||||
|
||||
|
||||
return <canvas ref={canvasRef} id="gameCanvas" />;
|
||||
}
|
||||
|
||||
export default GameCanvas;
|
||||
|
||||
18
src/components/GameInfo/GameInfo.js
Normal file
18
src/components/GameInfo/GameInfo.js
Normal file
@ -0,0 +1,18 @@
|
||||
import React from 'react';
|
||||
import Lives from '../Lives/Lives';
|
||||
|
||||
function GameInfo({ score, level, lives, status }) {
|
||||
return (
|
||||
<div className="game-info">
|
||||
<div className="score">Score: <span>{score}</span></div>
|
||||
<div className="level">Niveau: <span>{level}</span></div>
|
||||
<Lives lives={lives} />
|
||||
<div className="status" style={{ color: status === 'En jeu' ? '#ffd700' : '#ff6b6b' }}>
|
||||
{status}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GameInfo;
|
||||
|
||||
52
src/components/Leaderboard/Leaderboard.js
Normal file
52
src/components/Leaderboard/Leaderboard.js
Normal file
@ -0,0 +1,52 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { getLeaderboard } from '../../utils/leaderboard';
|
||||
|
||||
function Leaderboard() {
|
||||
const [scores, setScores] = useState([]);
|
||||
|
||||
useEffect(() => {
|
||||
updateLeaderboard();
|
||||
}, []);
|
||||
|
||||
const updateLeaderboard = () => {
|
||||
const leaderboard = getLeaderboard();
|
||||
setScores(leaderboard);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
updateLeaderboard();
|
||||
}, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
if (scores.length === 0) {
|
||||
return (
|
||||
<div className="leaderboard-container">
|
||||
<h2>Classement</h2>
|
||||
<div className="empty-leaderboard">Aucun score enregistré</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="leaderboard-container">
|
||||
<h2>Classement</h2>
|
||||
<div>
|
||||
{scores.map((entry, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`leaderboard-item ${index < 3 ? 'top' : ''}`}
|
||||
>
|
||||
<div className="leaderboard-rank">{index + 1}</div>
|
||||
<div className="leaderboard-name">{entry.name}</div>
|
||||
<div className="leaderboard-score">{entry.score}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Leaderboard;
|
||||
|
||||
16
src/components/Lives/Lives.js
Normal file
16
src/components/Lives/Lives.js
Normal file
@ -0,0 +1,16 @@
|
||||
import React from 'react';
|
||||
|
||||
function Lives({ lives }) {
|
||||
return (
|
||||
<div className="lives">
|
||||
Vies: <span>
|
||||
<span className="heart" style={{ opacity: lives >= 1 ? 1 : 0.3 }}>♥</span>
|
||||
<span className="heart" style={{ opacity: lives >= 2 ? 1 : 0.3 }}>♥</span>
|
||||
<span className="heart" style={{ opacity: lives >= 3 ? 1 : 0.3 }}>♥</span>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Lives;
|
||||
|
||||
20
src/components/UserInput/UserInput.js
Normal file
20
src/components/UserInput/UserInput.js
Normal file
@ -0,0 +1,20 @@
|
||||
import React from 'react';
|
||||
|
||||
function UserInput({ username, onUsernameChange }) {
|
||||
return (
|
||||
<div className="user-input-section">
|
||||
<label htmlFor="username">Nom d'utilisateur:</label>
|
||||
<input
|
||||
type="text"
|
||||
id="username"
|
||||
placeholder="Entrez votre nom"
|
||||
maxLength="15"
|
||||
value={username}
|
||||
onChange={(e) => onUsernameChange(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default UserInput;
|
||||
|
||||
89
src/hooks/useGameState.js
Normal file
89
src/hooks/useGameState.js
Normal file
@ -0,0 +1,89 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
export function useGameState() {
|
||||
const [score, setScore] = useState(0);
|
||||
const [level, setLevel] = useState(1);
|
||||
const [lives, setLives] = useState(3);
|
||||
const [gameRunning, setGameRunning] = useState(true);
|
||||
const [status, setStatus] = useState('En jeu');
|
||||
const [cherriesEaten, setCherriesEaten] = useState(0);
|
||||
const [isChangingLevel, setIsChangingLevel] = useState(false);
|
||||
const [cherryEatenRecently, setCherryEatenRecently] = useState(false);
|
||||
const [cherryEatenTimer, setCherryEatenTimer] = useState(0);
|
||||
|
||||
const addScore = useCallback((points) => {
|
||||
setScore(prev => prev + points);
|
||||
}, []);
|
||||
|
||||
const resetGame = useCallback(() => {
|
||||
setScore(0);
|
||||
setLevel(1);
|
||||
setLives(3);
|
||||
setGameRunning(true);
|
||||
setStatus('En jeu');
|
||||
setCherriesEaten(0);
|
||||
setIsChangingLevel(false);
|
||||
setCherryEatenRecently(false);
|
||||
setCherryEatenTimer(0);
|
||||
}, []);
|
||||
|
||||
const loseLife = useCallback(() => {
|
||||
setLives(prev => prev - 1);
|
||||
}, []);
|
||||
|
||||
const eatCherry = useCallback(() => {
|
||||
setCherriesEaten(prev => prev + 1);
|
||||
setCherryEatenRecently(true);
|
||||
setCherryEatenTimer(300);
|
||||
}, []);
|
||||
|
||||
const nextLevel = useCallback(() => {
|
||||
setLevel(prev => prev + 1);
|
||||
setCherriesEaten(0);
|
||||
setIsChangingLevel(true);
|
||||
setCherryEatenRecently(false);
|
||||
setCherryEatenTimer(0);
|
||||
}, []);
|
||||
|
||||
const restartLevel = useCallback(() => {
|
||||
setCherriesEaten(0);
|
||||
setIsChangingLevel(true);
|
||||
setCherryEatenRecently(false);
|
||||
setCherryEatenTimer(0);
|
||||
}, []);
|
||||
|
||||
const updateCherryTimer = useCallback(() => {
|
||||
if (cherryEatenTimer > 0) {
|
||||
setCherryEatenTimer(prev => prev - 1);
|
||||
} else {
|
||||
setCherryEatenRecently(false);
|
||||
}
|
||||
}, [cherryEatenTimer]);
|
||||
|
||||
return {
|
||||
score,
|
||||
level,
|
||||
lives,
|
||||
gameRunning,
|
||||
status,
|
||||
cherriesEaten,
|
||||
isChangingLevel,
|
||||
cherryEatenRecently,
|
||||
cherryEatenTimer,
|
||||
setScore,
|
||||
setLevel,
|
||||
setLives,
|
||||
setGameRunning,
|
||||
setStatus,
|
||||
setCherriesEaten,
|
||||
setIsChangingLevel,
|
||||
addScore,
|
||||
resetGame,
|
||||
loseLife,
|
||||
eatCherry,
|
||||
nextLevel,
|
||||
restartLevel,
|
||||
updateCherryTimer
|
||||
};
|
||||
}
|
||||
|
||||
15
src/hooks/useKeyboard.js
Normal file
15
src/hooks/useKeyboard.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export function useKeyboard(onKeyPress) {
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e) => {
|
||||
onKeyPress(e.key);
|
||||
};
|
||||
|
||||
window.addEventListener('keydown', handleKeyDown);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', handleKeyDown);
|
||||
};
|
||||
}, [onKeyPress]);
|
||||
}
|
||||
|
||||
25
src/hooks/useLeaderboard.js
Normal file
25
src/hooks/useLeaderboard.js
Normal file
@ -0,0 +1,25 @@
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { getLeaderboard, saveScore } from '../utils/leaderboard';
|
||||
|
||||
export function useLeaderboard() {
|
||||
const [scores, setScores] = useState([]);
|
||||
|
||||
const updateLeaderboard = useCallback(() => {
|
||||
const leaderboard = getLeaderboard();
|
||||
setScores(leaderboard);
|
||||
}, []);
|
||||
|
||||
const addScore = useCallback((username, score) => {
|
||||
const updatedScores = saveScore(username, score);
|
||||
setScores(updatedScores);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
updateLeaderboard();
|
||||
const interval = setInterval(updateLeaderboard, 1000);
|
||||
return () => clearInterval(interval);
|
||||
}, [updateLeaderboard]);
|
||||
|
||||
return { scores, addScore, updateLeaderboard };
|
||||
}
|
||||
|
||||
12
src/index.js
Normal file
12
src/index.js
Normal file
@ -0,0 +1,12 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom/client';
|
||||
import App from './App';
|
||||
import './styles/App.css';
|
||||
|
||||
const root = ReactDOM.createRoot(document.getElementById('root'));
|
||||
root.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
307
src/styles/App.css
Normal file
307
src/styles/App.css
Normal file
@ -0,0 +1,307 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Arial', sans-serif;
|
||||
background: #0a0a0a;
|
||||
background-image:
|
||||
radial-gradient(circle at 20% 30%, rgba(255, 0, 0, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 70%, rgba(0, 0, 255, 0.1) 0%, transparent 50%),
|
||||
radial-gradient(circle at 50% 50%, rgba(128, 0, 128, 0.1) 0%, transparent 50%);
|
||||
color: #fff;
|
||||
min-height: 100vh;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image:
|
||||
url("data:image/svg+xml,%3Csvg width='100' height='100' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M50 20 Q60 10 70 20 Q80 30 70 40 Q60 50 50 40 Q40 50 30 40 Q20 30 30 20 Q40 10 50 20 Z' fill='%23ffffff' opacity='0.05'/%3E%3Ccircle cx='45' cy='30' r='3' fill='%23000000' opacity='0.3'/%3E%3Ccircle cx='55' cy='30' r='3' fill='%23000000' opacity='0.3'/%3E%3Cpath d='M50 40 Q45 45 50 50 Q55 45 50 40' fill='%23000000' opacity='0.2'/%3E%3C/svg%3E");
|
||||
background-size: 200px 200px;
|
||||
background-repeat: repeat;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background:
|
||||
repeating-linear-gradient(
|
||||
0deg,
|
||||
transparent,
|
||||
transparent 2px,
|
||||
rgba(255, 255, 255, 0.02) 2px,
|
||||
rgba(255, 255, 255, 0.02) 4px
|
||||
);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.App {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
gap: 30px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.container {
|
||||
text-align: center;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
padding: 30px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.8), 0 0 50px rgba(255, 0, 0, 0.2);
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
border: 2px solid rgba(255, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3em;
|
||||
margin-bottom: 20px;
|
||||
text-shadow: 3px 3px 6px rgba(0, 0, 0, 0.5);
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.game-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
padding: 10px 20px;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
border-radius: 10px;
|
||||
gap: 15px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.score {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.level {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.lives {
|
||||
font-size: 1.5em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.lives .heart {
|
||||
color: #ff0000;
|
||||
font-size: 1.2em;
|
||||
margin: 0 2px;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
#status {
|
||||
font-size: 1.2em;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
#gameCanvas {
|
||||
border: 3px solid #ffd700;
|
||||
border-radius: 10px;
|
||||
background: #000;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.user-input-section {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.user-input-section label {
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
#username {
|
||||
padding: 8px 15px;
|
||||
font-size: 1em;
|
||||
border: 2px solid #ffd700;
|
||||
border-radius: 8px;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
color: #fff;
|
||||
outline: none;
|
||||
max-width: 200px;
|
||||
}
|
||||
|
||||
#username:focus {
|
||||
border-color: #ffed4e;
|
||||
box-shadow: 0 0 10px rgba(255, 215, 0, 0.5);
|
||||
}
|
||||
|
||||
.instructions {
|
||||
margin-top: 20px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.leaderboard-container {
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
padding: 25px;
|
||||
border-radius: 15px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.8), 0 0 50px rgba(255, 0, 0, 0.2);
|
||||
min-width: 300px;
|
||||
max-height: 700px;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
border: 2px solid rgba(255, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.leaderboard-container h2 {
|
||||
color: #ffd700;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
font-size: 2em;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
#leaderboard {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.leaderboard-item {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 12px;
|
||||
margin-bottom: 10px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-left: 4px solid #ffd700;
|
||||
}
|
||||
|
||||
.leaderboard-item.top {
|
||||
background: rgba(255, 215, 0, 0.2);
|
||||
border-left-color: #ffed4e;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.leaderboard-rank {
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: #ffd700;
|
||||
min-width: 30px;
|
||||
}
|
||||
|
||||
.leaderboard-name {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
margin-left: 15px;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
||||
.leaderboard-score {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
color: #ffd700;
|
||||
min-width: 80px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.empty-leaderboard {
|
||||
text-align: center;
|
||||
color: #aaa;
|
||||
padding: 20px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
#restartBtn {
|
||||
margin-top: 15px;
|
||||
padding: 12px 30px;
|
||||
font-size: 1.2em;
|
||||
background: #ffd700;
|
||||
color: #000;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
#restartBtn:hover {
|
||||
background: #ffed4e;
|
||||
}
|
||||
|
||||
#restartBtn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
color: #ffd700;
|
||||
font-size: 1.1em;
|
||||
font-weight: bold;
|
||||
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.main-wrapper {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.leaderboard-container {
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
#gameCanvas {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.game-info {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.user-input-section {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
11
src/utils/constants.js
Normal file
11
src/utils/constants.js
Normal file
@ -0,0 +1,11 @@
|
||||
export const CELL_SIZE = 20;
|
||||
export const COLS = 30;
|
||||
export const ROWS = 30;
|
||||
|
||||
export const WALL = 1;
|
||||
export const DOT = 2;
|
||||
export const EMPTY = 0;
|
||||
export const TUNNEL = 3;
|
||||
export const BONUS_CHERRY = 4;
|
||||
export const BONUS_LUDO = 5;
|
||||
|
||||
342
src/utils/entities.js
Normal file
342
src/utils/entities.js
Normal file
@ -0,0 +1,342 @@
|
||||
import { CELL_SIZE, COLS, ROWS, WALL, DOT, EMPTY, BONUS_CHERRY, BONUS_LUDO } from './constants';
|
||||
|
||||
export class Pacman {
|
||||
constructor() {
|
||||
this.x = 14;
|
||||
this.y = 23;
|
||||
this.direction = 0;
|
||||
this.nextDirection = 0;
|
||||
this.mouthAngle = 0;
|
||||
this.mouthOpen = true;
|
||||
this.baseSpeed = 0.15;
|
||||
this.speed = this.baseSpeed;
|
||||
this.pixelX = this.x * CELL_SIZE + CELL_SIZE / 2;
|
||||
this.pixelY = this.y * CELL_SIZE + CELL_SIZE / 2;
|
||||
this.colorAnimation = 0;
|
||||
}
|
||||
|
||||
canMove(direction, maze) {
|
||||
const dx = [0, 1, 0, -1];
|
||||
const dy = [-1, 0, 1, 0];
|
||||
|
||||
const nextX = Math.floor((this.pixelX + dx[direction] * CELL_SIZE * 0.5) / CELL_SIZE);
|
||||
const nextY = Math.floor((this.pixelY + dy[direction] * CELL_SIZE * 0.5) / CELL_SIZE);
|
||||
|
||||
if (nextX < 0 || nextX >= COLS || nextY < 0 || nextY >= ROWS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return maze[nextY][nextX] !== WALL;
|
||||
}
|
||||
|
||||
update(maze, gameRunning) {
|
||||
if (!gameRunning) return;
|
||||
|
||||
this.mouthAngle += 0.2;
|
||||
if (this.mouthAngle > Math.PI * 2) {
|
||||
this.mouthAngle = 0;
|
||||
this.mouthOpen = !this.mouthOpen;
|
||||
}
|
||||
|
||||
this.colorAnimation += 0.3;
|
||||
if (this.colorAnimation > Math.PI * 2) {
|
||||
this.colorAnimation = 0;
|
||||
}
|
||||
|
||||
const gridX = Math.floor(this.pixelX / CELL_SIZE);
|
||||
const gridY = Math.floor(this.pixelY / CELL_SIZE);
|
||||
|
||||
if (this.canMove(this.nextDirection, maze)) {
|
||||
this.direction = this.nextDirection;
|
||||
}
|
||||
|
||||
if (this.canMove(this.direction, maze)) {
|
||||
const dx = [0, 1, 0, -1];
|
||||
const dy = [-1, 0, 1, 0];
|
||||
|
||||
this.pixelX += dx[this.direction] * this.speed * CELL_SIZE;
|
||||
this.pixelY += dy[this.direction] * this.speed * CELL_SIZE;
|
||||
|
||||
if (this.pixelX < 0) {
|
||||
this.pixelX = COLS * CELL_SIZE;
|
||||
} else if (this.pixelX > COLS * CELL_SIZE) {
|
||||
this.pixelX = 0;
|
||||
}
|
||||
} else {
|
||||
this.pixelX = gridX * CELL_SIZE + CELL_SIZE / 2;
|
||||
this.pixelY = gridY * CELL_SIZE + CELL_SIZE / 2;
|
||||
}
|
||||
|
||||
this.x = Math.floor(this.pixelX / CELL_SIZE);
|
||||
this.y = Math.floor(this.pixelY / CELL_SIZE);
|
||||
}
|
||||
|
||||
collectDot(maze, onCollect) {
|
||||
if (maze[this.y][this.x] === DOT) {
|
||||
maze[this.y][this.x] = EMPTY;
|
||||
onCollect('dot', 10);
|
||||
} else if (maze[this.y][this.x] === BONUS_CHERRY) {
|
||||
maze[this.y][this.x] = EMPTY;
|
||||
onCollect('cherry', 100);
|
||||
} else if (maze[this.y][this.x] === BONUS_LUDO) {
|
||||
maze[this.y][this.x] = EMPTY;
|
||||
onCollect('ludo', 15);
|
||||
}
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
ctx.save();
|
||||
ctx.translate(this.pixelX, this.pixelY);
|
||||
|
||||
const rotation = [Math.PI * 1.5, 0, Math.PI * 0.5, Math.PI];
|
||||
ctx.rotate(rotation[this.direction]);
|
||||
|
||||
const hue = (this.colorAnimation * 180 / Math.PI) % 360;
|
||||
ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
|
||||
ctx.beginPath();
|
||||
|
||||
if (this.mouthOpen) {
|
||||
ctx.arc(0, 0, CELL_SIZE * 0.4, 0.2, Math.PI * 2 - 0.2);
|
||||
} else {
|
||||
ctx.arc(0, 0, CELL_SIZE * 0.4, 0, Math.PI * 2);
|
||||
}
|
||||
|
||||
ctx.lineTo(0, 0);
|
||||
ctx.fill();
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
export class Ghost {
|
||||
constructor(x, y, color) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.color = color;
|
||||
this.direction = Math.floor(Math.random() * 4);
|
||||
this.baseSpeed = 0.1;
|
||||
this.speed = this.baseSpeed;
|
||||
this.pixelX = this.x * CELL_SIZE + CELL_SIZE / 2;
|
||||
this.pixelY = this.y * CELL_SIZE + CELL_SIZE / 2;
|
||||
this.moveCounter = 0;
|
||||
this.moveInterval = 30;
|
||||
}
|
||||
|
||||
updateSpeed(level) {
|
||||
this.speed = this.baseSpeed * (1 + (level - 1) * 0.2);
|
||||
}
|
||||
|
||||
canMove(direction, maze) {
|
||||
const dx = [0, 1, 0, -1];
|
||||
const dy = [-1, 0, 1, 0];
|
||||
|
||||
const nextX = Math.floor((this.pixelX + dx[direction] * CELL_SIZE * 0.5) / CELL_SIZE);
|
||||
const nextY = Math.floor((this.pixelY + dy[direction] * CELL_SIZE * 0.5) / CELL_SIZE);
|
||||
|
||||
if (nextX < 0 || nextX >= COLS || nextY < 0 || nextY >= ROWS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return maze[nextY][nextX] !== WALL;
|
||||
}
|
||||
|
||||
getDirectionToPacman(possibleDirections, pacman) {
|
||||
const dx = [0, 1, 0, -1];
|
||||
const dy = [-1, 0, 1, 0];
|
||||
|
||||
let bestDirection = possibleDirections[0];
|
||||
let minDistance = Infinity;
|
||||
|
||||
for (let dir of possibleDirections) {
|
||||
const nextX = this.x + dx[dir];
|
||||
const nextY = this.y + dy[dir];
|
||||
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(pacman.x - nextX, 2) +
|
||||
Math.pow(pacman.y - nextY, 2)
|
||||
);
|
||||
|
||||
if (distance < minDistance) {
|
||||
minDistance = distance;
|
||||
bestDirection = dir;
|
||||
}
|
||||
}
|
||||
|
||||
return bestDirection;
|
||||
}
|
||||
|
||||
update(maze, gameRunning, level, pacman, cherryEatenRecently) {
|
||||
if (!gameRunning) return;
|
||||
|
||||
this.moveInterval = Math.max(15, 30 - (level - 1) * 2);
|
||||
|
||||
this.moveCounter++;
|
||||
|
||||
if (this.moveCounter > this.moveInterval || !this.canMove(this.direction, maze)) {
|
||||
const possibleDirections = [];
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (this.canMove(i, maze)) {
|
||||
possibleDirections.push(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (possibleDirections.length > 0) {
|
||||
if (cherryEatenRecently) {
|
||||
this.direction = this.getDirectionToPacman(possibleDirections, pacman);
|
||||
} else {
|
||||
this.direction = possibleDirections[Math.floor(Math.random() * possibleDirections.length)];
|
||||
}
|
||||
}
|
||||
this.moveCounter = 0;
|
||||
}
|
||||
|
||||
const dx = [0, 1, 0, -1];
|
||||
const dy = [-1, 0, 1, 0];
|
||||
|
||||
this.pixelX += dx[this.direction] * this.speed * CELL_SIZE;
|
||||
this.pixelY += dy[this.direction] * this.speed * CELL_SIZE;
|
||||
|
||||
if (this.pixelX < 0) {
|
||||
this.pixelX = COLS * CELL_SIZE;
|
||||
} else if (this.pixelX > COLS * CELL_SIZE) {
|
||||
this.pixelX = 0;
|
||||
}
|
||||
|
||||
this.x = Math.floor(this.pixelX / CELL_SIZE);
|
||||
this.y = Math.floor(this.pixelY / CELL_SIZE);
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
ctx.save();
|
||||
ctx.translate(this.pixelX, this.pixelY);
|
||||
|
||||
const size = CELL_SIZE * 0.75;
|
||||
|
||||
ctx.fillStyle = this.color;
|
||||
ctx.strokeStyle = '#000000';
|
||||
ctx.lineWidth = 2;
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, -size * 0.3, size * 0.5, Math.PI, 0, false);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillRect(-size * 0.5, -size * 0.3, size * 1.0, size * 0.7);
|
||||
ctx.strokeRect(-size * 0.5, -size * 0.3, size * 1.0, size * 0.7);
|
||||
|
||||
const waveHeight = size * 0.15;
|
||||
const waveWidth = size * 0.2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-size * 0.5, size * 0.4);
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const x = -size * 0.5 + i * waveWidth;
|
||||
const y = size * 0.4 + (i % 2 === 0 ? 0 : waveHeight);
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
ctx.lineTo(size * 0.5, size * 0.4);
|
||||
ctx.lineTo(size * 0.5, size * 0.7);
|
||||
ctx.lineTo(-size * 0.5, size * 0.7);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.beginPath();
|
||||
ctx.arc(-size * 0.2, -size * 0.1, size * 0.12, 0, Math.PI * 2);
|
||||
ctx.arc(size * 0.2, -size * 0.1, size * 0.12, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = '#000000';
|
||||
ctx.beginPath();
|
||||
ctx.arc(-size * 0.2, -size * 0.1, size * 0.06, 0, Math.PI * 2);
|
||||
ctx.arc(size * 0.2, -size * 0.1, size * 0.06, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
export class Bonus {
|
||||
constructor(x, y, type) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.type = type;
|
||||
this.animation = 0;
|
||||
}
|
||||
|
||||
update() {
|
||||
this.animation += 0.1;
|
||||
if (this.animation > Math.PI * 2) {
|
||||
this.animation = 0;
|
||||
}
|
||||
}
|
||||
|
||||
draw(ctx) {
|
||||
const cellX = this.x * CELL_SIZE + CELL_SIZE / 2;
|
||||
const cellY = this.y * CELL_SIZE + CELL_SIZE / 2;
|
||||
const scale = 1 + Math.sin(this.animation) * 0.2;
|
||||
|
||||
ctx.save();
|
||||
ctx.translate(cellX, cellY);
|
||||
ctx.scale(scale, scale);
|
||||
|
||||
if (this.type === BONUS_CHERRY) {
|
||||
ctx.fillStyle = '#ff0000';
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, CELL_SIZE * 0.25, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = '#00ff00';
|
||||
ctx.beginPath();
|
||||
ctx.arc(-CELL_SIZE * 0.15, -CELL_SIZE * 0.2, CELL_SIZE * 0.1, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.strokeStyle = '#00aa00';
|
||||
ctx.lineWidth = 2;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(-CELL_SIZE * 0.15, -CELL_SIZE * 0.3);
|
||||
ctx.lineTo(-CELL_SIZE * 0.25, -CELL_SIZE * 0.4);
|
||||
ctx.stroke();
|
||||
} else if (this.type === BONUS_LUDO) {
|
||||
const size = CELL_SIZE * 0.45;
|
||||
|
||||
ctx.fillStyle = '#ffd700';
|
||||
ctx.strokeStyle = '#ffaa00';
|
||||
ctx.lineWidth = 3;
|
||||
|
||||
ctx.beginPath();
|
||||
const spikes = 5;
|
||||
const outerRadius = size;
|
||||
const innerRadius = size * 0.4;
|
||||
|
||||
for (let i = 0; i < spikes * 2; i++) {
|
||||
const angle = (i * Math.PI) / spikes;
|
||||
const radius = i % 2 === 0 ? outerRadius : innerRadius;
|
||||
const x = Math.cos(angle) * radius;
|
||||
const y = Math.sin(angle) * radius;
|
||||
|
||||
if (i === 0) {
|
||||
ctx.moveTo(x, y);
|
||||
} else {
|
||||
ctx.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = '#ffff00';
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, size * 0.3, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
|
||||
ctx.fillStyle = '#ffd700';
|
||||
ctx.font = `bold ${size * 0.6}px Arial`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText('L', 0, 0);
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
196
src/utils/gameLogic.js
Normal file
196
src/utils/gameLogic.js
Normal file
@ -0,0 +1,196 @@
|
||||
import { ROWS, COLS, CELL_SIZE, WALL, DOT, EMPTY, BONUS_CHERRY, BONUS_LUDO } from './constants';
|
||||
|
||||
export function countDots(maze) {
|
||||
let totalDots = 0;
|
||||
if (!maze || maze.length === 0) {
|
||||
console.error('countDots() - maze est vide ou undefined');
|
||||
return 0;
|
||||
}
|
||||
for (let y = 0; y < ROWS; y++) {
|
||||
if (!maze[y]) {
|
||||
console.error(`countDots() - maze[${y}] est undefined`);
|
||||
continue;
|
||||
}
|
||||
for (let x = 0; x < COLS; x++) {
|
||||
if (maze[y][x] === DOT) {
|
||||
totalDots++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return totalDots;
|
||||
}
|
||||
|
||||
export function fillEmptySpaces(maze) {
|
||||
console.log('Remplissage de tous les espaces vides avec des chemins');
|
||||
|
||||
const centerX = Math.floor(COLS / 2);
|
||||
const centerY = Math.floor(ROWS / 2);
|
||||
|
||||
for (let y = 1; y < ROWS - 1; y++) {
|
||||
for (let x = 1; x < COLS - 1; x++) {
|
||||
if (maze[y][x] === EMPTY) {
|
||||
maze[y][x] = DOT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let iteration = 0; iteration < 6; iteration++) {
|
||||
for (let y = 1; y < ROWS - 1; y++) {
|
||||
for (let x = 1; x < COLS - 1; x++) {
|
||||
if (maze[y][x] === WALL) {
|
||||
const neighbors = [
|
||||
maze[y-1][x], maze[y+1][x], maze[y][x-1], maze[y][x+1]
|
||||
];
|
||||
const emptyCount = neighbors.filter(c => c === EMPTY).length;
|
||||
const dotCount = neighbors.filter(c => c === DOT).length;
|
||||
const wallCount = neighbors.filter(c => c === WALL).length;
|
||||
|
||||
if (emptyCount >= 1) {
|
||||
maze[y][x] = DOT;
|
||||
} else if (wallCount <= 1 && dotCount >= 2) {
|
||||
if (Math.random() < 0.9) {
|
||||
maze[y][x] = DOT;
|
||||
}
|
||||
} else if (wallCount === 0) {
|
||||
maze[y][x] = DOT;
|
||||
}
|
||||
} else if (maze[y][x] === EMPTY) {
|
||||
maze[y][x] = DOT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let y = 2; y < ROWS - 2; y++) {
|
||||
for (let x = 2; x < COLS - 2; x++) {
|
||||
if (maze[y][x] === EMPTY) {
|
||||
let emptyArea = 0;
|
||||
const visited = new Set();
|
||||
const stack = [[x, y]];
|
||||
|
||||
while (stack.length > 0 && emptyArea < 20) {
|
||||
const [cx, cy] = stack.pop();
|
||||
const key = `${cy},${cx}`;
|
||||
if (visited.has(key)) continue;
|
||||
visited.add(key);
|
||||
|
||||
if (maze[cy][cx] === EMPTY) {
|
||||
emptyArea++;
|
||||
if (cy > 1) stack.push([cx, cy-1]);
|
||||
if (cy < ROWS-2) stack.push([cx, cy+1]);
|
||||
if (cx > 1) stack.push([cx-1, cy]);
|
||||
if (cx < COLS-2) stack.push([cx+1, cy]);
|
||||
}
|
||||
}
|
||||
|
||||
if (emptyArea >= 5) {
|
||||
for (const key of visited) {
|
||||
const [cy, cx] = key.split(',').map(Number);
|
||||
if (maze[cy][cx] === EMPTY) {
|
||||
maze[cy][cx] = DOT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const centerX = Math.floor(COLS / 2);
|
||||
const centerY = Math.floor(ROWS / 2);
|
||||
|
||||
if (maze[centerY] && maze[centerY][centerX] !== WALL) {
|
||||
maze[centerY][centerX] = EMPTY;
|
||||
}
|
||||
|
||||
console.log('Tous les grands espaces réduits, chemins denses créés');
|
||||
}
|
||||
|
||||
export function randomizeMaze(maze) {
|
||||
const modificationRate = 0.05;
|
||||
const changes = Math.floor(ROWS * COLS * modificationRate);
|
||||
|
||||
console.log('Modification aléatoire du labyrinthe,', changes, 'changements');
|
||||
|
||||
const centerX = Math.floor(COLS / 2);
|
||||
const centerY = Math.floor(ROWS / 2);
|
||||
|
||||
for (let i = 0; i < changes; i++) {
|
||||
const x = Math.floor(Math.random() * COLS);
|
||||
const y = Math.floor(Math.random() * ROWS);
|
||||
|
||||
if (x === 0 || x === COLS - 1 || y === 0 || y === ROWS - 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const distX = Math.abs(x - centerX);
|
||||
const distY = Math.abs(y - centerY);
|
||||
|
||||
if (distX <= 1 && distY <= 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const currentCell = maze[y][x];
|
||||
|
||||
if (currentCell === WALL) {
|
||||
if (Math.random() < 0.8) {
|
||||
maze[y][x] = DOT;
|
||||
}
|
||||
} else if (currentCell === EMPTY) {
|
||||
maze[y][x] = DOT;
|
||||
}
|
||||
}
|
||||
|
||||
for (let y = 1; y < ROWS - 1; y++) {
|
||||
for (let x = 1; x < COLS - 1; x++) {
|
||||
const distX = Math.abs(x - centerX);
|
||||
const distY = Math.abs(y - centerY);
|
||||
|
||||
if (distX > 1 || distY > 1) {
|
||||
if (maze[y][x] === EMPTY) {
|
||||
maze[y][x] = DOT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let iteration = 0; iteration < 3; iteration++) {
|
||||
for (let y = 1; y < ROWS - 1; y++) {
|
||||
for (let x = 1; x < COLS - 1; x++) {
|
||||
const distX = Math.abs(x - centerX);
|
||||
const distY = Math.abs(y - centerY);
|
||||
|
||||
if (distX <= 1 || distY <= 1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (maze[y][x] === WALL) {
|
||||
const neighbors = [
|
||||
maze[y-1][x], maze[y+1][x], maze[y][x-1], maze[y][x+1]
|
||||
];
|
||||
const wallCount = neighbors.filter(c => c === WALL).length;
|
||||
const dotCount = neighbors.filter(c => c === DOT).length;
|
||||
|
||||
if (wallCount <= 1 && dotCount >= 2 && Math.random() < 0.85) {
|
||||
maze[y][x] = DOT;
|
||||
} else if (wallCount === 0) {
|
||||
maze[y][x] = DOT;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Labyrinthe modifié avec chemins denses, grands espaces réduits');
|
||||
}
|
||||
|
||||
export function getBonusPositions() {
|
||||
return [
|
||||
{x: 1, y: 1, type: BONUS_CHERRY},
|
||||
{x: 28, y: 1, type: BONUS_CHERRY},
|
||||
{x: 1, y: 28, type: BONUS_CHERRY},
|
||||
{x: 28, y: 28, type: BONUS_CHERRY},
|
||||
{x: 14, y: 14, type: BONUS_LUDO},
|
||||
{x: 15, y: 14, type: BONUS_LUDO}
|
||||
];
|
||||
}
|
||||
|
||||
34
src/utils/leaderboard.js
Normal file
34
src/utils/leaderboard.js
Normal file
@ -0,0 +1,34 @@
|
||||
const STORAGE_KEY = 'pacmanScores';
|
||||
|
||||
export function saveScore(username, score) {
|
||||
if (!username || username.trim() === '') {
|
||||
username = 'Anonyme';
|
||||
}
|
||||
|
||||
const scoresJson = localStorage.getItem(STORAGE_KEY);
|
||||
let scores = scoresJson ? JSON.parse(scoresJson) : [];
|
||||
|
||||
scores.push({
|
||||
name: username,
|
||||
score: score,
|
||||
date: new Date().toISOString()
|
||||
});
|
||||
|
||||
scores.sort((a, b) => b.score - a.score);
|
||||
|
||||
const topScores = scores.slice(0, 10);
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(topScores));
|
||||
|
||||
return topScores;
|
||||
}
|
||||
|
||||
export function getLeaderboard() {
|
||||
const scoresJson = localStorage.getItem(STORAGE_KEY);
|
||||
if (!scoresJson) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const scores = JSON.parse(scoresJson);
|
||||
return scores.sort((a, b) => b.score - a.score);
|
||||
}
|
||||
|
||||
31
src/utils/maze.js
Normal file
31
src/utils/maze.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { ROWS, COLS, CELL_SIZE, WALL, DOT, EMPTY, TUNNEL } from './constants';
|
||||
|
||||
export function drawMaze(ctx, maze, bonuses) {
|
||||
ctx.fillStyle = '#000';
|
||||
ctx.fillRect(0, 0, COLS * CELL_SIZE, ROWS * CELL_SIZE);
|
||||
|
||||
for (let y = 0; y < ROWS; y++) {
|
||||
for (let x = 0; x < COLS; x++) {
|
||||
const cellX = x * CELL_SIZE;
|
||||
const cellY = y * CELL_SIZE;
|
||||
|
||||
if (maze[y][x] === WALL) {
|
||||
ctx.fillStyle = '#0000ff';
|
||||
ctx.fillRect(cellX, cellY, CELL_SIZE, CELL_SIZE);
|
||||
ctx.strokeStyle = '#000080';
|
||||
ctx.strokeRect(cellX, cellY, CELL_SIZE, CELL_SIZE);
|
||||
} else if (maze[y][x] === DOT) {
|
||||
ctx.fillStyle = '#fff';
|
||||
ctx.beginPath();
|
||||
ctx.arc(cellX + CELL_SIZE / 2, cellY + CELL_SIZE / 2, 2, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let bonus of bonuses) {
|
||||
bonus.update();
|
||||
bonus.draw(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
101
src/utils/mazeData.js
Normal file
101
src/utils/mazeData.js
Normal file
@ -0,0 +1,101 @@
|
||||
export const originalMaze1 = [
|
||||
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
|
||||
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,2,2,2,2,2,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,2,2,2,2,1],
|
||||
[1,1,1,1,1,1,2,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,2,1,1,1,1,1,1],
|
||||
[0,0,0,0,0,1,2,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,2,1,0,0,0,0,0],
|
||||
[0,0,0,0,0,1,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0],
|
||||
[0,0,0,0,0,1,2,1,1,0,1,1,1,3,3,3,3,1,1,1,0,1,1,2,1,0,0,0,0,0],
|
||||
[1,1,1,1,1,1,2,1,1,0,1,0,0,0,0,0,0,0,0,1,0,1,1,2,1,1,1,1,1,1],
|
||||
[0,0,0,0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,0,0,0],
|
||||
[1,1,1,1,1,1,2,1,1,0,1,0,0,0,0,0,0,0,0,1,0,1,1,2,1,1,1,1,1,1],
|
||||
[0,0,0,0,0,1,2,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,2,1,0,0,0,0,0],
|
||||
[0,0,0,0,0,1,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0],
|
||||
[0,0,0,0,0,1,2,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,2,1,0,0,0,0,0],
|
||||
[1,1,1,1,1,1,2,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,2,1,1,1,1,1,1],
|
||||
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,2,2,1,1,2,2,2,2,2,2,2,2,0,0,2,2,2,2,2,2,2,2,1,1,2,2,2,1],
|
||||
[1,1,1,2,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,2,1,1,1],
|
||||
[1,1,1,2,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,2,1,1,1],
|
||||
[1,2,2,2,2,2,2,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,2,2,2,2,1],
|
||||
[1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1],
|
||||
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
|
||||
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
|
||||
];
|
||||
|
||||
export const originalMaze2 = [
|
||||
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
|
||||
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,2,2,2,2,2,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,2,2,2,2,1],
|
||||
[1,1,1,1,1,1,2,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,2,1,1,1,1,1,1],
|
||||
[0,0,0,0,0,1,2,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,2,1,0,0,0,0,0],
|
||||
[0,0,0,0,0,1,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0],
|
||||
[0,0,0,0,0,1,2,1,1,0,1,1,1,3,3,3,3,1,1,1,0,1,1,2,1,0,0,0,0,0],
|
||||
[1,1,1,1,1,1,2,1,1,0,1,0,0,0,0,0,0,0,0,1,0,1,1,2,1,1,1,1,1,1],
|
||||
[0,0,0,0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,0,0,0],
|
||||
[1,1,1,1,1,1,2,1,1,0,1,0,0,0,0,0,0,0,0,1,0,1,1,2,1,1,1,1,1,1],
|
||||
[0,0,0,0,0,1,2,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,2,1,0,0,0,0,0],
|
||||
[0,0,0,0,0,1,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0],
|
||||
[0,0,0,0,0,1,2,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,2,1,0,0,0,0,0],
|
||||
[1,1,1,1,1,1,2,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,2,1,1,1,1,1,1],
|
||||
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,2,2,1,1,2,2,2,2,2,2,2,2,0,0,2,2,2,2,2,2,2,2,1,1,2,2,2,1],
|
||||
[1,1,1,2,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,2,1,1,1],
|
||||
[1,1,1,2,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,2,1,1,1],
|
||||
[1,2,2,2,2,2,2,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,2,2,2,2,1],
|
||||
[1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1],
|
||||
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
|
||||
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
|
||||
];
|
||||
|
||||
export const originalMaze3 = [
|
||||
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1],
|
||||
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,2,2,2,2,2,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,2,2,2,2,1],
|
||||
[1,1,1,1,1,1,2,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,2,1,1,1,1,1,1],
|
||||
[0,0,0,0,0,1,2,1,1,1,1,1,0,1,1,1,1,0,1,1,1,1,1,2,1,0,0,0,0,0],
|
||||
[0,0,0,0,0,1,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0],
|
||||
[0,0,0,0,0,1,2,1,1,0,1,1,1,3,3,3,3,1,1,1,0,1,1,2,1,0,0,0,0,0],
|
||||
[1,1,1,1,1,1,2,1,1,0,1,0,0,0,0,0,0,0,0,1,0,1,1,2,1,1,1,1,1,1],
|
||||
[0,0,0,0,0,0,2,0,0,0,1,0,0,0,0,0,0,0,0,1,0,0,0,2,0,0,0,0,0,0],
|
||||
[1,1,1,1,1,1,2,1,1,0,1,0,0,0,0,0,0,0,0,1,0,1,1,2,1,1,1,1,1,1],
|
||||
[0,0,0,0,0,1,2,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,2,1,0,0,0,0,0],
|
||||
[0,0,0,0,0,1,2,1,1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,2,1,0,0,0,0,0],
|
||||
[0,0,0,0,0,1,2,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,2,1,0,0,0,0,0],
|
||||
[1,1,1,1,1,1,2,1,1,0,1,1,1,1,1,1,1,1,1,1,0,1,1,2,1,1,1,1,1,1],
|
||||
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,1,1,1,1,2,1,1,1,1,1,1,2,1,1,2,1,1,1,1,1,1,2,1,1,1,1,2,1],
|
||||
[1,2,2,2,1,1,2,2,2,2,2,2,2,2,0,0,2,2,2,2,2,2,2,2,1,1,2,2,2,1],
|
||||
[1,1,1,2,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,2,1,1,1],
|
||||
[1,1,1,2,1,1,2,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,2,1,1,2,1,1,1],
|
||||
[1,2,2,2,2,2,2,1,1,2,2,2,2,1,1,1,1,2,2,2,2,1,1,2,2,2,2,2,2,1],
|
||||
[1,2,1,1,1,1,1,1,1,1,1,1,2,1,1,1,1,2,1,1,1,1,1,1,1,1,1,1,2,1],
|
||||
[1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,1],
|
||||
[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
|
||||
];
|
||||
|
||||
export const mazeVariants = [originalMaze1, originalMaze2, originalMaze3];
|
||||
|
||||
11
vite.config.js
Normal file
11
vite.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
server: {
|
||||
port: 3000,
|
||||
open: true
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user