1 er commit

This commit is contained in:
2025-12-01 20:32:27 +01:00
commit 11250de8af
4 changed files with 1821 additions and 0 deletions

369
README.md Normal file
View File

@ -0,0 +1,369 @@
# 🎮 Oulvic - Jeu de type Pac-Man
Un jeu de type Pac-Man développé en JavaScript vanilla par **Ludo** et **Syoul**.
---
## 📖 Table des matières
1. [Description](#description)
2. [Fonctionnalités](#fonctionnalités)
3. [Structure du projet](#structure-du-projet)
4. [Installation](#installation)
5. [Comment jouer](#comment-jouer)
6. [Architecture du code](#architecture-du-code)
7. [Documentation technique](#documentation-technique)
---
## 📝 Description
**Oulvic** est un clone de Pac-Man développé entièrement en HTML5, CSS3 et JavaScript vanilla. Le jeu utilise l'élément `<canvas>` pour le rendu graphique et propose une expérience de jeu complète avec :
- Un personnage jouable aux couleurs changeantes
- 4 fantômes avec intelligence artificielle
- 3 labyrinthes différents
- Système de niveaux progressifs
- Classement des scores (localStorage)
- Interface responsive
---
## ✨ Fonctionnalités
### Gameplay
- **Déplacement fluide** : Mouvement pixel par pixel avec interpolation
- **Système de vies** : 3 vies représentées par des cœurs
- **Points** : Collecte de pastilles (+10 points) et bonus
- **Progression de niveau** : Passer au niveau suivant en mangeant 4 cerises
### Bonus
| Bonus | Points | Effet |
|-------|--------|-------|
| 🍒 Cerise | +100 | Active la poursuite des fantômes pendant un temps limité. 4 cerises = niveau suivant |
| ⭐ Étoile "L" | +15 | Bonus supplémentaire |
### Fantômes
- **4 fantômes** avec couleurs distinctes (rouge, magenta, cyan, orange)
- **IA adaptative** : Les fantômes poursuivent Pac-Man après qu'une cerise soit mangée
- **Vitesse croissante** : Les fantômes accélèrent à chaque niveau
### Labyrinthes
- 3 variantes de labyrinthes qui alternent à chaque niveau
- Génération dynamique avec remplissage des espaces vides
- Randomisation légère pour plus de variété
---
## 📁 Structure du projet
```
pacmanludo/
├── index.html # Page HTML principale
├── style.css # Feuille de styles
├── game.js # Logique du jeu
└── README.md # Documentation
```
---
## 🚀 Installation
1. **Cloner ou télécharger** le projet
2. **Ouvrir** `index.html` dans un navigateur web moderne
3. **Jouer !**
Aucune dépendance externe n'est requise.
---
## 🎯 Comment jouer
### Contrôles
| Touche | Action |
|--------|--------|
| ↑ Flèche Haut | Déplacer vers le haut |
| ↓ Flèche Bas | Déplacer vers le bas |
| ← Flèche Gauche | Déplacer vers la gauche |
| → Flèche Droite | Déplacer vers la droite |
### Objectif
1. Collectez toutes les pastilles blanches pour marquer des points
2. Mangez les cerises pour activer la poursuite et progresser
3. Évitez les fantômes !
4. Atteignez le score le plus élevé possible
### Progression
- **4 cerises mangées** = passage au niveau suivant
- À chaque niveau :
- Pac-Man accélère légèrement (+5% par niveau)
- Les fantômes deviennent plus rapides (+20% par niveau)
- Le labyrinthe change (cycle de 3 labyrinthes)
---
## 🏗️ Architecture du code
### `index.html`
Structure HTML de la page :
```html
├── main-wrapper
│ ├── container (zone de jeu)
│ │ ├── Titre "OULVIC"
│ │ ├── Champ nom d'utilisateur
│ │ ├── Barre d'informations (score, niveau, vies, statut)
│ │ ├── Canvas du jeu (600x600)
│ │ └── Instructions et bouton rejouer
│ └── leaderboard-container (classement)
└── footer (crédits)
```
### `style.css`
| Section | Description |
|---------|-------------|
| Reset & Body | Styles de base, fond sombre avec effets |
| Layout | Flexbox pour disposition responsive |
| Game Info | Styles pour score, niveau, vies |
| Canvas | Bordure dorée, ombres |
| Leaderboard | Tableau des scores stylisé |
| Responsive | Media queries pour mobile |
### `game.js`
#### Constantes principales
```javascript
CELL_SIZE = 20 // Taille d'une cellule en pixels
COLS = 30 // Nombre de colonnes
ROWS = 30 // Nombre de lignes
// Types de cellules
WALL = 1 // Mur (bleu)
DOT = 2 // Pastille (blanc)
EMPTY = 0 // Vide
TUNNEL = 3 // Tunnel (passage fantômes)
BONUS_CHERRY = 4 // Cerise bonus
BONUS_LUDO = 5 // Étoile bonus
```
---
## 📚 Documentation technique
### Classe `Pacman`
Gère le personnage jouable.
| Propriété | Type | Description |
|-----------|------|-------------|
| `x, y` | number | Position en cellules |
| `pixelX, pixelY` | number | Position en pixels (mouvement fluide) |
| `direction` | number | Direction actuelle (0=haut, 1=droite, 2=bas, 3=gauche) |
| `nextDirection` | number | Prochaine direction demandée |
| `speed` | number | Vitesse de déplacement |
| `mouthAngle` | number | Animation de la bouche |
| `colorAnimation` | number | Animation des couleurs HSL |
| Méthode | Description |
|---------|-------------|
| `update()` | Met à jour la position et l'animation |
| `canMove(direction)` | Vérifie si le mouvement est possible |
| `collectDot()` | Collecte les pastilles et bonus |
| `draw()` | Dessine Pac-Man sur le canvas |
---
### Classe `Ghost`
Gère les fantômes ennemis.
| Propriété | Type | Description |
|-----------|------|-------------|
| `x, y` | number | Position en cellules |
| `pixelX, pixelY` | number | Position en pixels |
| `color` | string | Couleur du fantôme |
| `direction` | number | Direction de déplacement |
| `speed` | number | Vitesse (augmente avec le niveau) |
| `moveInterval` | number | Intervalle entre changements de direction |
| Méthode | Description |
|---------|-------------|
| `update()` | Met à jour la position et l'IA |
| `updateSpeed()` | Ajuste la vitesse selon le niveau |
| `canMove(direction)` | Vérifie si le mouvement est possible |
| `getDirectionToPacman(dirs)` | Calcule la meilleure direction vers Pac-Man |
| `draw()` | Dessine le fantôme (corps + yeux) |
---
### Classe `Bonus`
Gère les objets bonus (cerises, étoiles).
| Propriété | Type | Description |
|-----------|------|-------------|
| `x, y` | number | Position en cellules |
| `type` | number | Type de bonus (CHERRY ou LUDO) |
| `animation` | number | Animation de pulsation |
| Méthode | Description |
|---------|-------------|
| `update()` | Met à jour l'animation |
| `draw()` | Dessine le bonus avec effet de scale |
---
### Fonctions principales
#### Gestion du labyrinthe
| Fonction | Description |
|----------|-------------|
| `countDots()` | Compte le nombre de pastilles restantes |
| `drawMaze()` | Dessine le labyrinthe complet |
| `fillEmptySpaces()` | Remplit les espaces vides avec des pastilles |
| `randomizeMaze()` | Ajoute des variations aléatoires au labyrinthe |
| `placeBonuses()` | Place les bonus aux positions définies |
#### Gestion du jeu
| Fonction | Description |
|----------|-------------|
| `initGame()` | Initialise une nouvelle partie |
| `gameLoop()` | Boucle principale du jeu (requestAnimationFrame) |
| `checkCollisions()` | Détecte les collisions Pac-Man/fantômes |
| `nextLevel()` | Passe au niveau suivant |
| `restartCurrentLevel()` | Recommence le niveau actuel (après perte de vie) |
#### Interface utilisateur
| Fonction | Description |
|----------|-------------|
| `updateLivesDisplay()` | Met à jour l'affichage des vies |
| `updateLeaderboard()` | Rafraîchit le classement |
| `saveScore()` | Sauvegarde le score en localStorage |
| `getScores()` | Récupère les scores depuis localStorage |
---
### Variables globales d'état
```javascript
let score = 0; // Score actuel
let level = 1; // Niveau actuel
let lives = 3; // Vies restantes
let gameRunning = true; // État du jeu
let totalDots = 0; // Pastilles restantes
let cherriesEaten = 0; // Cerises mangées (niveau actuel)
let isChangingLevel = false; // Flag de transition
let cherryEatenRecently = false; // Mode poursuite activé
let cherryEatenTimer = 0; // Durée du mode poursuite
```
---
### Système de stockage des scores
Les scores sont sauvegardés dans le `localStorage` du navigateur sous la clé `pacmanScores`.
**Format des données :**
```javascript
{
username: "NomJoueur", // Nom saisi ou "Anonyme"
score: 1500, // Score final
date: "2025-12-01T..." // Date ISO
}
```
- Maximum **10 scores** conservés
- Triés par score décroissant
---
### Labyrinthes
Le jeu dispose de 3 labyrinthes prédéfinis (`originalMaze1`, `originalMaze2`, `originalMaze3`) stockés comme tableaux 2D de 30x30.
**Rotation des labyrinthes :**
```javascript
mazeIndex = (level - 1) % 3
```
Les labyrinthes sont modifiés dynamiquement :
1. `fillEmptySpaces()` - Remplit les zones vides
2. `randomizeMaze()` - Ajoute de la variété
---
## 🎨 Design
### Palette de couleurs
| Élément | Couleur |
|---------|---------|
| Fond | `#0a0a0a` (noir) |
| Murs | `#0000ff` (bleu) |
| Pastilles | `#ffffff` (blanc) |
| Texte titre | `#ffd700` (or) |
| Cerises | `#ff0000` (rouge) |
| Cœurs | `#ff0000` (rouge) |
### Fantômes
- Rouge : `#ff0000`
- Magenta : `#ff00ff`
- Cyan : `#00ffff`
- Orange : `#ffa500`
### Pac-Man
- Couleur animée en HSL (arc-en-ciel)
---
## 🔧 Personnalisation
### Modifier la difficulté
Dans `game.js` :
```javascript
// Vitesse de base de Pac-Man (ligne 145)
this.baseSpeed = 0.15;
// Vitesse de base des fantômes (ligne 277)
this.baseSpeed = 0.1;
// Accélération par niveau
pacman.speed = pacman.baseSpeed * (1 + (level - 1) * 0.05);
ghost.speed = ghost.baseSpeed * (1 + (level - 1) * 0.2);
```
### Modifier les points
```javascript
// Pastille (ligne 213)
score += 10;
// Cerise (ligne 224)
score += 100;
// Étoile Ludo (ligne 241)
score += 15;
```
---
## 📄 Licence
Projet créé par **Ludo** et **Syoul**.
---
## 🙏 Crédits
- Inspiration : Pac-Man original (Namco, 1980)
- Développement : Ludo & Syoul

1110
game.js Normal file

File diff suppressed because it is too large Load Diff

40
index.html Normal file
View File

@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<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>
</body>
</html>

302
style.css Normal file
View File

@ -0,0 +1,302 @@
* {
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;
}
.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;
}
}