From 54ebd58e5aebc91dab6dc581c0fdd4a370423931 Mon Sep 17 00:00:00 2001 From: syoul Date: Fri, 28 Nov 2025 19:38:26 +0100 Subject: [PATCH] =?UTF-8?q?Refactoring=20vers=20React:=20migration=20compl?= =?UTF-8?q?=C3=A8te=20de=20l'application=20vanilla=20JS=20vers=20React=20a?= =?UTF-8?q?vec=20architecture=20modulaire?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 33 +++ index.html | 32 +- package.json | 20 ++ src/App.js | 16 + src/components/Footer/Footer.js | 12 + src/components/Game/Game.js | 226 ++++++++++++++ src/components/GameCanvas/GameCanvas.js | 81 +++++ src/components/GameInfo/GameInfo.js | 18 ++ src/components/Leaderboard/Leaderboard.js | 52 ++++ src/components/Lives/Lives.js | 16 + src/components/UserInput/UserInput.js | 20 ++ src/hooks/useGameState.js | 89 ++++++ src/hooks/useKeyboard.js | 15 + src/hooks/useLeaderboard.js | 25 ++ src/index.js | 12 + src/styles/App.css | 307 +++++++++++++++++++ src/utils/constants.js | 11 + src/utils/entities.js | 342 ++++++++++++++++++++++ src/utils/gameLogic.js | 196 +++++++++++++ src/utils/leaderboard.js | 34 +++ src/utils/maze.js | 31 ++ src/utils/mazeData.js | 101 +++++++ vite.config.js | 11 + 23 files changed, 1670 insertions(+), 30 deletions(-) create mode 100644 .gitignore create mode 100644 package.json create mode 100644 src/App.js create mode 100644 src/components/Footer/Footer.js create mode 100644 src/components/Game/Game.js create mode 100644 src/components/GameCanvas/GameCanvas.js create mode 100644 src/components/GameInfo/GameInfo.js create mode 100644 src/components/Leaderboard/Leaderboard.js create mode 100644 src/components/Lives/Lives.js create mode 100644 src/components/UserInput/UserInput.js create mode 100644 src/hooks/useGameState.js create mode 100644 src/hooks/useKeyboard.js create mode 100644 src/hooks/useLeaderboard.js create mode 100644 src/index.js create mode 100644 src/styles/App.css create mode 100644 src/utils/constants.js create mode 100644 src/utils/entities.js create mode 100644 src/utils/gameLogic.js create mode 100644 src/utils/leaderboard.js create mode 100644 src/utils/maze.js create mode 100644 src/utils/mazeData.js create mode 100644 vite.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..91c1445 --- /dev/null +++ b/.gitignore @@ -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/ + diff --git a/index.html b/index.html index 2b7eb58..125f4e1 100644 --- a/index.html +++ b/index.html @@ -4,37 +4,9 @@ Jeu Oulvic - -
-
-

OULVIC

-
- - -
-
-
Score: 0
-
Niveau: 1
-
Vies:
-
Prêt à jouer
-
- -
-

Utilisez les flèches directionnelles pour déplacer Oulvic

- -
-
-
-

Classement

-
-
-
- - +
+ - diff --git a/package.json b/package.json new file mode 100644 index 0000000..d68142f --- /dev/null +++ b/package.json @@ -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" + } +} + diff --git a/src/App.js b/src/App.js new file mode 100644 index 0000000..8e3f176 --- /dev/null +++ b/src/App.js @@ -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 ( +
+ +
+
+ ); +} + +export default App; + diff --git a/src/components/Footer/Footer.js b/src/components/Footer/Footer.js new file mode 100644 index 0000000..ac6cd1b --- /dev/null +++ b/src/components/Footer/Footer.js @@ -0,0 +1,12 @@ +import React from 'react'; + +function Footer() { + return ( + + ); +} + +export default Footer; + diff --git a/src/components/Game/Game.js b/src/components/Game/Game.js new file mode 100644 index 0000000..45a6267 --- /dev/null +++ b/src/components/Game/Game.js @@ -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 ( +
+
+

OULVIC

+ + + +
+

Utilisez les flèches directionnelles pour déplacer Oulvic

+ {!gameState.gameRunning && ( + + )} +
+
+ +
+ ); +} + +export default Game; + diff --git a/src/components/GameCanvas/GameCanvas.js b/src/components/GameCanvas/GameCanvas.js new file mode 100644 index 0000000..5afea83 --- /dev/null +++ b/src/components/GameCanvas/GameCanvas.js @@ -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 ; +} + +export default GameCanvas; + diff --git a/src/components/GameInfo/GameInfo.js b/src/components/GameInfo/GameInfo.js new file mode 100644 index 0000000..06ecb65 --- /dev/null +++ b/src/components/GameInfo/GameInfo.js @@ -0,0 +1,18 @@ +import React from 'react'; +import Lives from '../Lives/Lives'; + +function GameInfo({ score, level, lives, status }) { + return ( +
+
Score: {score}
+
Niveau: {level}
+ +
+ {status} +
+
+ ); +} + +export default GameInfo; + diff --git a/src/components/Leaderboard/Leaderboard.js b/src/components/Leaderboard/Leaderboard.js new file mode 100644 index 0000000..cab5c42 --- /dev/null +++ b/src/components/Leaderboard/Leaderboard.js @@ -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 ( +
+

Classement

+
Aucun score enregistré
+
+ ); + } + + return ( +
+

Classement

+
+ {scores.map((entry, index) => ( +
+
{index + 1}
+
{entry.name}
+
{entry.score}
+
+ ))} +
+
+ ); +} + +export default Leaderboard; + diff --git a/src/components/Lives/Lives.js b/src/components/Lives/Lives.js new file mode 100644 index 0000000..12d2f12 --- /dev/null +++ b/src/components/Lives/Lives.js @@ -0,0 +1,16 @@ +import React from 'react'; + +function Lives({ lives }) { + return ( +
+ Vies: + = 1 ? 1 : 0.3 }}>♥ + = 2 ? 1 : 0.3 }}>♥ + = 3 ? 1 : 0.3 }}>♥ + +
+ ); +} + +export default Lives; + diff --git a/src/components/UserInput/UserInput.js b/src/components/UserInput/UserInput.js new file mode 100644 index 0000000..7140d2a --- /dev/null +++ b/src/components/UserInput/UserInput.js @@ -0,0 +1,20 @@ +import React from 'react'; + +function UserInput({ username, onUsernameChange }) { + return ( +
+ + onUsernameChange(e.target.value)} + /> +
+ ); +} + +export default UserInput; + diff --git a/src/hooks/useGameState.js b/src/hooks/useGameState.js new file mode 100644 index 0000000..141b846 --- /dev/null +++ b/src/hooks/useGameState.js @@ -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 + }; +} + diff --git a/src/hooks/useKeyboard.js b/src/hooks/useKeyboard.js new file mode 100644 index 0000000..2ee1740 --- /dev/null +++ b/src/hooks/useKeyboard.js @@ -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]); +} + diff --git a/src/hooks/useLeaderboard.js b/src/hooks/useLeaderboard.js new file mode 100644 index 0000000..fe2f8b3 --- /dev/null +++ b/src/hooks/useLeaderboard.js @@ -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 }; +} + diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..45fc80b --- /dev/null +++ b/src/index.js @@ -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( + + + +); + diff --git a/src/styles/App.css b/src/styles/App.css new file mode 100644 index 0000000..0c2072b --- /dev/null +++ b/src/styles/App.css @@ -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; + } +} + diff --git a/src/utils/constants.js b/src/utils/constants.js new file mode 100644 index 0000000..ff005fa --- /dev/null +++ b/src/utils/constants.js @@ -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; + diff --git a/src/utils/entities.js b/src/utils/entities.js new file mode 100644 index 0000000..b0eab55 --- /dev/null +++ b/src/utils/entities.js @@ -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(); + } +} + diff --git a/src/utils/gameLogic.js b/src/utils/gameLogic.js new file mode 100644 index 0000000..a6e0973 --- /dev/null +++ b/src/utils/gameLogic.js @@ -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} + ]; +} + diff --git a/src/utils/leaderboard.js b/src/utils/leaderboard.js new file mode 100644 index 0000000..8045da0 --- /dev/null +++ b/src/utils/leaderboard.js @@ -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); +} + diff --git a/src/utils/maze.js b/src/utils/maze.js new file mode 100644 index 0000000..3545ec1 --- /dev/null +++ b/src/utils/maze.js @@ -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); + } +} + diff --git a/src/utils/mazeData.js b/src/utils/mazeData.js new file mode 100644 index 0000000..09f949b --- /dev/null +++ b/src/utils/mazeData.js @@ -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]; + diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..29c7b18 --- /dev/null +++ b/vite.config.js @@ -0,0 +1,11 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +export default defineConfig({ + plugins: [react()], + server: { + port: 3000, + open: true + } +}) +