texture zebre plus sourie menu

This commit is contained in:
2025-12-19 16:22:09 +01:00
parent 844c64c7b6
commit 1b474fcbca
4 changed files with 767 additions and 86 deletions

531
README.md
View File

@ -1,4 +1,4 @@
# 🎮 Oulvic - Jeu de type Pac-Man
# 🎮 OULVIC - Jeu de type Pac-Man
Un jeu de type Pac-Man développé en JavaScript vanilla par **Ludo** et **Syoul**.
@ -13,19 +13,25 @@ Un jeu de type Pac-Man développé en JavaScript vanilla par **Ludo** et **Syoul
5. [Comment jouer](#comment-jouer)
6. [Architecture du code](#architecture-du-code)
7. [Documentation technique](#documentation-technique)
8. [Systèmes avancés](#systèmes-avancés)
---
## 📝 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 :
**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
- Un personnage jouable personnalisable (couleurs et formes)
- Système de fantômes avec IA avancée et types variés
- 3 labyrinthes différents qui alternent
- Système de niveaux progressifs avec difficulté croissante
- Power-ups et bonus multiples
- Système de combo et mode Frenzy
- Zones spéciales (téléportation, bonus, danger)
- Classement des scores (localStorage)
- Interface responsive
- Interface responsive avec support mobile
- Menu principal animé
- Mode plein écran
---
@ -33,25 +39,87 @@ Un jeu de type Pac-Man développé en JavaScript vanilla par **Ludo** et **Syoul
### 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
- **Système de vies** : 3 vies représentées par des cœurs animés
- **Points** : Collecte de pastilles avec système de combo
- **Progression de niveau** : Passer au niveau suivant en mangeant 4 cerises
- **Mode pause** : Espace ou Échap pour mettre en pause
### 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 |
### Bonus de base
| Bonus | Points | Effet | Niveau |
|-------|--------|-------|--------|
| 🍒 Cerise | +100 | Rend les fantômes vulnérables. 4 cerises = niveau suivant | 1+ |
| ⭐ Étoile "L" | +200 | Bonus supplémentaire | 1+ |
### Power-ups (Niveau 2+)
| Power-up | Effet | Durée | Niveau |
|----------|-------|-------|--------|
| ⚡ Étoile de vitesse | Pacman devient 2x plus rapide | 10 secondes | 2+ |
| 🛡 Bouclier | Protège d'une collision avec un fantôme | 15 secondes | 2+ |
| 💣 Bombe | Repousse tous les fantômes proches | Instantané | 3+ |
| ✨ Multiplicateur | Double tous les points | 30 secondes | 3+ |
### Système de Combo
- Collectez les pastilles rapidement pour créer un combo
- Chaque combo augmente votre multiplicateur de score (jusqu'à x5)
- Le combo se réinitialise si vous attendez plus de 3 secondes entre deux collectes
- Points de base : 10 × combo × Frenzy × multiplicateur
### Mode Frenzy
- S'active automatiquement toutes les **30 secondes**
- Tous les fantômes deviennent vulnérables pendant **10 secondes**
- Score multiplié par **3** pendant la durée du mode
- Indicateur visuel avec animation
### 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
#### Types de fantômes
| Type | Description | Niveau |
|------|-------------|--------|
| 👤 Normal | Poursuit directement le joueur | 1+ |
| 🎯 Chasseur | Poursuit agressivement avec prédiction de mouvement | 2+ |
| 🛡 Patrouilleur | Bloque les passages stratégiques et patrouille | 3+ |
| ⚡ Rapide | 1.5x plus rapide que les autres fantômes | 4+ |
| 👻 Invisible | Devient invisible 50% du temps, difficile à voir | 5+ |
#### Nombre de fantômes par niveau
- **Niveau 1-2** : 4 fantômes
- **Niveau 3-4** : 5 fantômes
- **Niveau 5-9** : 6 fantômes
- **Niveau 10+** : 7 fantômes
#### Comportement
- **Poursuite** : Les fantômes poursuivent activement le joueur
- **Vulnérabilité** : Après avoir mangé une cerise, les fantômes deviennent vulnérables et fuient
- **Manger un fantôme vulnérable** : +200 points
- **Prédiction** : Les chasseurs prédisent la position future du joueur
### Zones spéciales (Niveau 4+)
| Zone | Couleur | Effet |
|------|---------|-------|
| Téléportation | Violet | Vous téléporte à un endroit aléatoire |
| Bonus | Doré | Double les points dans cette zone |
| Danger | Rouge | Ralentit Pacman temporairement |
### 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é
- Zones spéciales ajoutées selon le niveau
### Personnalisation
- **8 couleurs** : Jaune, Rouge, Bleu, Vert, Violet, Orange, Rose, Arc-en-ciel (animé)
- **2 formes** : Rond (classique) ou Triangle
- Sauvegarde des préférences dans localStorage
- Aperçu en temps réel dans le menu de personnalisation
### Interface
- **Menu principal** avec animation de fond
- **Classement** en temps réel pendant le jeu
- **Indicateurs visuels** pour les power-ups actifs
- **Compteur de pastilles** restantes
- **Timer de poursuite** quand les fantômes sont vulnérables
- **Mode plein écran** disponible
- **Contrôles tactiles** pour mobile
---
@ -59,9 +127,9 @@ Un jeu de type Pac-Man développé en JavaScript vanilla par **Ludo** et **Syoul
```
pacmanludo/
├── index.html # Page HTML principale
├── style.css # Feuille de styles
├── game.js # Logique du jeu
├── index.html # Page HTML principale avec menu et interface
├── style.css # Feuille de styles complète (1547 lignes)
├── game.js # Logique du jeu (2954 lignes)
└── README.md # Documentation
```
@ -73,32 +141,55 @@ pacmanludo/
2. **Ouvrir** `index.html` dans un navigateur web moderne
3. **Jouer !**
Aucune dépendance externe n'est requise.
Aucune dépendance externe n'est requise. Le jeu fonctionne entièrement côté client.
**Navigateurs supportés :**
- Chrome/Edge (recommandé)
- Firefox
- Safari
- Opéra
---
## 🎯 Comment jouer
### Contrôles
#### Clavier
| 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 |
| Espace / Échap | Mettre en pause / Reprendre |
#### Mobile
- Utilisez les boutons directionnels tactiles affichés en bas de l'écran
### 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
3. Collectez les power-ups pour obtenir des avantages
4. Évitez les fantômes (ou mangez-les quand ils sont vulnérables) !
5. 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)
- Les fantômes deviennent plus rapides (vitesse fixe x1.4)
- Le labyrinthe change (cycle de 3 labyrinthes)
- Nouveaux types de fantômes apparaissent
- De nouveaux power-ups deviennent disponibles
### Système de score
| Action | Points de base | Multiplicateurs |
|--------|----------------|-----------------|
| Pastille | 10 | × combo × Frenzy × multiplicateur |
| Fantôme vulnérable | 200 | × Frenzy × multiplicateur |
| Cerise | 100 | × Frenzy × multiplicateur |
| Étoile Ludo | 200 | × Frenzy × multiplicateur |
---
@ -109,34 +200,52 @@ Aucune dépendance externe n'est requise.
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)
├── main-menu (menu principal)
│ ├── menuBackgroundCanvas (animation de fond)
└── menu-container
├── Titre "OULVIC"
├── Meilleur score
── Boutons (Jouer, Personnaliser, Classement, Règles)
├── game-wrapper (interface de jeu)
── main-wrapper
│ │ ├── container (zone de jeu)
│ │ │ ├── game-header
│ │ │ ├── user-input-section
│ │ │ ├── game-info (score, niveau, vies, pastilles)
│ │ │ ├── power-ups-display
│ │ │ ├── frenzy-indicator
│ │ │ ├── pursuit-indicator
│ │ │ ├── gameCanvas (750x750)
│ │ │ ├── mobile-controls
│ │ │ └── game-overlay (Game Over)
│ │ └── game-leaderboard (classement en temps réel)
│ └── leaderboard-container (classement complet)
├── leaderboard-modal (modal classement)
├── rules-modal (modal règles)
└── customize-modal (modal personnalisation)
```
### `style.css`
| Section | Description |
|---------|-------------|
| Reset & Body | Styles de base, fond sombre avec effets |
| Reset & Body | Styles de base, fond sombre avec effets, curseur personnalisé |
| Animations | Animations CSS (neonFlicker, rainbow, heartbeat, etc.) |
| Menu Principal | Styles du menu avec animation de fond |
| 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 Info | Styles pour score, niveau, vies, combo |
| Canvas | Bordure dorée, ombres, effets de glow |
| Leaderboard | Tableau des scores stylisé avec médailles |
| Power-ups | Indicateurs visuels pour les power-ups actifs |
| Responsive | Media queries pour mobile et tablette |
| Modals | Styles pour les modales (règles, classement, personnalisation) |
### `game.js`
#### Constantes principales
```javascript
CELL_SIZE = 20 // Taille d'une cellule en pixels
CELL_SIZE = 25 // Taille d'une cellule en pixels
COLS = 30 // Nombre de colonnes
ROWS = 30 // Nombre de lignes
@ -147,6 +256,27 @@ EMPTY = 0 // Vide
TUNNEL = 3 // Tunnel (passage fantômes)
BONUS_CHERRY = 4 // Cerise bonus
BONUS_LUDO = 5 // Étoile bonus
BONUS_SPEED = 6 // Étoile de vitesse
BONUS_SHIELD = 7 // Bouclier
BONUS_BOMB = 8 // Bombe
BONUS_MULTIPLIER = 9 // Multiplicateur
// Zones spéciales
ZONE_TELEPORT = 10 // Zone de téléportation
ZONE_BONUS = 11 // Zone bonus
ZONE_DANGER = 12 // Zone danger
// Types de fantômes
GHOST_NORMAL = 'normal'
GHOST_HUNTER = 'hunter'
GHOST_PATROL = 'patrol'
GHOST_FAST = 'fast'
GHOST_INVISIBLE = 'invisible'
// Constantes de gameplay
COMBO_TIMEOUT = 3000 // 3 secondes pour maintenir le combo
FRENZY_INTERVAL = 30000 // 30 secondes entre chaque mode Frenzy
FRENZY_DURATION = 10000 // 10 secondes de durée du mode Frenzy
```
---
@ -157,62 +287,111 @@ BONUS_LUDO = 5 // Étoile bonus
Gère le personnage jouable.
#### Propriétés
| 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 |
| `speed` | number | Vitesse de déplacement (modifiable par power-ups) |
| `baseSpeed` | number | Vitesse de base (0.25) |
| `mouthAngle` | number | Animation de la bouche |
| `colorAnimation` | number | Animation des couleurs HSL |
| `mouthOpen` | boolean | État de la bouche (ouverte/fermée) |
| `colorAnimation` | number | Animation des couleurs HSL (pour arc-en-ciel) |
| `color` | string | Couleur du joueur (sauvegardée) |
| `shape` | string | Forme du joueur ('round' ou 'triangle') |
#### Méthodes
| 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 |
| `update()` | Met à jour la position, l'animation et collecte les objets |
| `canMove(direction)` | Vérifie si le mouvement est possible dans une direction |
| `collectDot()` | Collecte les pastilles, bonus et active les power-ups |
| `draw()` | Dessine Pac-Man sur le canvas avec la couleur et forme choisies |
---
### Classe `Ghost`
Gère les fantômes ennemis.
Gère les fantômes ennemis avec IA avancée.
#### Propriétés
| Propriété | Type | Description |
|-----------|------|-------------|
| `x, y` | number | Position en cellules |
| `pixelX, pixelY` | number | Position en pixels |
| `color` | string | Couleur du fantôme |
| `type` | string | Type de fantôme (normal, hunter, patrol, fast, invisible) |
| `direction` | number | Direction de déplacement |
| `speed` | number | Vitesse (augmente avec le niveau) |
| `speed` | number | Vitesse (ajustée selon le type et le niveau) |
| `baseSpeed` | number | Vitesse de base (0.15) |
| `moveCounter` | number | Compteur pour les changements de direction |
| `moveInterval` | number | Intervalle entre changements de direction |
| `isVulnerable` | boolean | État de vulnérabilité |
| `vulnerableTimer` | number | Durée restante de vulnérabilité |
| `isInvisible` | boolean | État d'invisibilité (pour GHOST_INVISIBLE) |
| `invisibleTimer` | number | Timer pour l'invisibilité |
| `patrolTarget` | object | Cible de patrouille (pour GHOST_PATROL) |
| `patrolIndex` | number | Index de patrouille |
#### Méthodes
| Méthode | Description |
|---------|-------------|
| `update()` | Met à jour la position et l'IA |
| `updateSpeed()` | Ajuste la vitesse selon le niveau |
| `update()` | Met à jour la position, l'IA et les états spéciaux |
| `updateSpeed()` | Ajuste la vitesse selon le type et 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) |
| `getDirectionToPacman(dirs)` | Calcule la meilleure direction vers Pac-Man avec prédiction |
| `getDirectionAwayFromPacman(dirs)` | Calcule la direction pour fuir Pac-Man (quand vulnérable) |
| `getPatrolDirection(dirs)` | Calcule la direction de patrouille (pour GHOST_PATROL) |
| `draw()` | Dessine le fantôme (corps + yeux, avec effets spéciaux) |
---
### Classe `Bonus`
Gère les objets bonus (cerises, étoiles).
Gère les objets bonus (cerises, étoiles, power-ups).
#### Propriétés
| Propriété | Type | Description |
|-----------|------|-------------|
| `x, y` | number | Position en cellules |
| `type` | number | Type de bonus (CHERRY ou LUDO) |
| `type` | number | Type de bonus (BONUS_CHERRY, BONUS_LUDO, etc.) |
| `animation` | number | Animation de pulsation |
#### Méthodes
| Méthode | Description |
|---------|-------------|
| `update()` | Met à jour l'animation |
| `draw()` | Dessine le bonus avec effet de scale |
| `draw()` | Dessine le bonus avec effet de scale et animation |
---
### Classe `MenuGhost`
Gère les fantômes animés dans le menu principal.
#### Propriétés
| Propriété | Type | Description |
|-----------|------|-------------|
| `x, y` | number | Position en pixels |
| `vx, vy` | number | Vitesse en pixels |
| `color` | string | Couleur du fantôme |
| `size` | number | Taille du fantôme |
#### Méthodes
| Méthode | Description |
|---------|-------------|
| `update()` | Met à jour la position |
| `draw(ctx)` | Dessine le fantôme sur le canvas du menu |
---
@ -223,10 +402,10 @@ Gère les objets bonus (cerises, étoiles).
| Fonction | Description |
|----------|-------------|
| `countDots()` | Compte le nombre de pastilles restantes |
| `drawMaze()` | Dessine le labyrinthe complet |
| `drawMaze()` | Dessine le labyrinthe complet avec zones spéciales |
| `fillEmptySpaces()` | Remplit les espaces vides avec des pastilles |
| `randomizeMaze()` | Ajoute des variations aléatoires au labyrinthe |
| `placeBonuses()` | Place les bonus aux positions définies |
| `placeBonuses()` | Place les bonus aux positions définies selon le niveau |
#### Gestion du jeu
@ -235,36 +414,96 @@ Gère les objets bonus (cerises, étoiles).
| `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 |
| `nextLevel()` | Passe au niveau suivant avec transition |
| `restartCurrentLevel()` | Recommence le niveau actuel (après perte de vie) |
| `createGhosts()` | Crée les fantômes selon le niveau et leurs types |
#### Interface utilisateur
| Fonction | Description |
|----------|-------------|
| `updateLivesDisplay()` | Met à jour l'affichage des vies |
| `updateLeaderboard()` | Rafraîchit le classement |
| `updateLeaderboard()` | Rafraîchit le classement principal |
| `updateGameLeaderboard()` | Rafraîchit le classement en jeu |
| `updateMenuLeaderboard()` | Rafraîchit le classement du menu |
| `saveScore()` | Sauvegarde le score en localStorage |
| `getScores()` | Récupère les scores depuis localStorage |
| `showGameOver()` | Affiche l'overlay Game Over |
| `hideGameOver()` | Cache l'overlay Game Over |
| `confirmUsername()` | Confirme le nom d'utilisateur |
| `resetUsernameInput()` | Réinitialise le champ nom d'utilisateur |
#### Personnalisation
| Fonction | Description |
|----------|-------------|
| `updateColorSelection()` | Met à jour la sélection de couleur |
| `updateShapeSelection()` | Met à jour la sélection de forme |
| `updatePlayerPreview()` | Met à jour l'aperçu du joueur |
| `startPreviewAnimation()` | Démarre l'animation de l'aperçu |
| `stopPreviewAnimation()` | Arrête l'animation de l'aperçu |
#### Menu et animations
| Fonction | Description |
|----------|-------------|
| `animateMenu()` | Anime le menu principal |
| `startMenuAnimation()` | Démarre l'animation du menu |
| `stopMenuAnimation()` | Arrête l'animation du menu |
| `resizeMenuCanvas()` | Redimensionne le canvas du menu |
| `updateBestScore()` | Met à jour l'affichage du meilleur score |
#### Plein écran
| Fonction | Description |
|----------|-------------|
| `toggleFullscreen()` | Active/désactive le mode plein écran |
| `updateFullscreenButton()` | Met à jour le bouton plein écran |
| `isFullscreen()` | Vérifie si on est en mode plein écran |
---
### 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
let score = 0; // Score actuel
let level = 1; // Niveau actuel
let lives = 3; // Vies restantes
let gameRunning = true; // État du jeu
let isPaused = false; // État de pause
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 combo
let comboCount = 0; // Nombre de pastilles dans le combo
let comboMultiplier = 1; // Multiplicateur actuel (max x5)
let lastDotTime = 0; // Temps de la dernière pastille collectée
// Power-ups actifs
let speedBoostActive = false; // État du boost de vitesse
let speedBoostTimer = 0; // Durée restante
let shieldActive = false; // État du bouclier
let shieldTimer = 0; // Durée restante
let scoreMultiplierActive = false; // État du multiplicateur
let scoreMultiplierTimer = 0; // Durée restante
let scoreMultiplierValue = 1; // Valeur du multiplicateur (x2)
// Mode Frenzy
let frenzyModeActive = false; // État du mode Frenzy
let frenzyTimer = 0; // Durée restante
let frenzyCooldown = 0; // Cooldown avant le prochain Frenzy
// Zones spéciales
let specialZones = []; // Zones spéciales actives
```
---
## 🔧 Systèmes avancés
### Système de stockage des scores
Les scores sont sauvegardés dans le `localStorage` du navigateur sous la clé `pacmanScores`.
@ -274,14 +513,14 @@ Les scores sont sauvegardés dans le `localStorage` du navigateur sous la clé `
{
username: "NomJoueur", // Nom saisi ou "Anonyme"
score: 1500, // Score final
level: 5, // Niveau atteint
date: "2025-12-01T..." // Date ISO
}
```
- Maximum **10 scores** conservés
- Triés par score décroissant
---
- Affichage avec médailles pour le top 3 (🥇 🥈 🥉)
### Labyrinthes
@ -293,8 +532,45 @@ mazeIndex = (level - 1) % 3
```
Les labyrinthes sont modifiés dynamiquement :
1. `fillEmptySpaces()` - Remplit les zones vides
2. `randomizeMaze()` - Ajoute de la variété
1. `fillEmptySpaces()` - Remplit les zones vides avec des pastilles
2. `randomizeMaze()` - Ajoute de la variété en modifiant certains murs
3. `placeBonuses()` - Place les bonus et zones spéciales selon le niveau
### Système de fruits
Les fruits changent selon le niveau :
- Niveau 1 : Cerise rouge
- Niveau 2 : Banane
- Niveau 3 : Orange
- Niveau 4 : Pomme
- Niveau 5 : Raisin
- Niveau 6 : Fraise
- Niveau 7+ : Ananas (rotation)
### IA des fantômes
#### Fantôme Normal
- Poursuit directement le joueur
- Changement de direction périodique
#### Chasseur (Niveau 2+)
- Prédit la position future du joueur (3-4 cases d'avance)
- Poursuite plus agressive
- Pas de restriction sur le retour en arrière
#### Patrouilleur (Niveau 3+)
- Si proche du joueur (< 12 cases) : poursuit
- Si loin : patrouille vers les zones centrales
- Bloque les passages stratégiques
#### Rapide (Niveau 4+)
- Vitesse 1.5x supérieure aux autres
- Même comportement de poursuite que Normal
#### Invisible (Niveau 5+)
- Devient invisible 50% du temps
- Timer alternant toutes les 5 secondes
- Difficile à voir et à éviter
---
@ -307,18 +583,34 @@ Les labyrinthes sont modifiés dynamiquement :
| Fond | `#0a0a0a` (noir) |
| Murs | `#0000ff` (bleu) |
| Pastilles | `#ffffff` (blanc) |
| Texte titre | `#ffd700` (or) |
| Texte titre | `#ffd700` (or) avec animation arc-en-ciel |
| Cerises | `#ff0000` (rouge) |
| Cœurs | `#ff0000` (rouge) |
| Cœurs | `#ff0000` (rouge) avec animation heartbeat |
| Zones téléportation | `rgba(255, 0, 255, 0.3)` (violet) |
| Zones bonus | `rgba(255, 215, 0, 0.3)` (doré) |
| Zones danger | `rgba(255, 0, 0, 0.3)` (rouge) |
### Fantômes
- Rouge : `#ff0000`
- Magenta : `#ff00ff`
- Cyan : `#00ffff`
- Orange : `#ffa500`
- Vert : `#00ff00`
- Jaune : `#ffff00`
### Pac-Man
- Couleur animée en HSL (arc-en-ciel)
- Couleur animée en HSL (arc-en-ciel) par défaut
- 8 couleurs disponibles : Jaune, Rouge, Bleu, Vert, Violet, Orange, Rose, Arc-en-ciel
- 2 formes : Rond (classique) ou Triangle
### Animations CSS
- `neonFlicker` : Effet de scintillement néon pour le titre
- `rainbow` : Animation arc-en-ciel pour le texte
- `heartbeat` : Animation de pulsation pour les cœurs
- `canvasGlow` : Effet de lueur pour le canvas
- `pulse` : Animation de pulsation pour les indicateurs
- `fadeIn` : Animation d'apparition
- `shake` : Animation de secousse pour Game Over
---
@ -329,30 +621,78 @@ Les labyrinthes sont modifiés dynamiquement :
Dans `game.js` :
```javascript
// Vitesse de base de Pac-Man (ligne 145)
// Vitesse de base de Pac-Man (ligne ~265)
this.baseSpeed = 0.25;
// Vitesse de base des fantômes (ligne ~575)
this.baseSpeed = 0.15;
// Vitesse de base des fantômes (ligne 277)
this.baseSpeed = 0.1;
// Accélération par niveau
// Accélération par niveau (ligne ~1504)
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;
// Pastille (ligne ~357)
let points = 10 * comboMultiplier * (frenzyModeActive ? 3 : 1) * scoreMultiplierValue;
// Cerise (ligne 224)
// Cerise (ligne ~372)
score += 100;
// Étoile Ludo (ligne 241)
score += 15;
// Étoile Ludo (ligne ~401)
let points = 200 * (frenzyModeActive ? 3 : 1) * scoreMultiplierValue;
// Fantôme vulnérable (ligne ~1298)
score += 200;
```
### Modifier les durées
```javascript
// Combo timeout (ligne ~230)
const COMBO_TIMEOUT = 3000; // 3 secondes
// Mode Frenzy (lignes ~245-246)
const FRENZY_INTERVAL = 30000; // 30 secondes
const FRENZY_DURATION = 10000; // 10 secondes
// Power-ups (lignes ~408, 415, 436)
speedBoostTimer = 600; // 10 secondes à 60 FPS
shieldTimer = 900; // 15 secondes
scoreMultiplierTimer = 1800; // 30 secondes
```
---
## 📱 Responsive Design
Le jeu est entièrement responsive avec :
- **Desktop** : Interface complète avec classement à droite
- **Tablette** : Layout adapté, classement en dessous
- **Mobile** : Contrôles tactiles, layout vertical, classement en dessous
Media queries :
- `@media (max-width: 1200px)` : Layout vertical
- `@media (max-width: 700px)` : Optimisations mobile
---
## 🐛 Dépannage
### Le jeu ne démarre pas
- Vérifiez que vous utilisez un navigateur moderne
- Ouvrez la console (F12) pour voir les erreurs
- Vérifiez que tous les fichiers sont présents
### Les scores ne se sauvegardent pas
- Vérifiez que le localStorage est activé dans votre navigateur
- Videz le cache si nécessaire
### Le mode plein écran ne fonctionne pas
- Certains navigateurs nécessitent une interaction utilisateur
- Essayez de cliquer sur le bouton après le chargement de la page
---
## 📄 Licence
@ -365,5 +705,26 @@ Projet créé par **Ludo** et **Syoul**.
- Inspiration : Pac-Man original (Namco, 1980)
- Développement : Ludo & Syoul
- Police : Press Start 2P (Google Fonts)
---
## 🔄 Changelog
### Version actuelle
- Système de combo
- Mode Frenzy automatique
- Power-ups multiples (vitesse, bouclier, bombe, multiplicateur)
- Types de fantômes variés avec IA avancée
- Zones spéciales (téléportation, bonus, danger)
- Personnalisation complète du joueur
- Menu principal animé
- Classement en temps réel
- Mode plein écran
- Support mobile avec contrôles tactiles
- Système de pause
- Fruits variés selon le niveau
---
**Amusez-vous bien avec OULVIC ! 🎮👻**

301
game.js
View File

@ -550,6 +550,124 @@ class Pacman {
// Restaurer la couleur originale
ctx.fillStyle = baseFillColor;
} else if (this.shape === 'zebre') {
// Dessiner un zèbre plus réaliste
const bodyWidth = size * 1.8;
const bodyHeight = size * 1.4;
ctx.save();
// Corps (ellipse allongée)
ctx.beginPath();
ctx.ellipse(0, size * 0.2, bodyWidth, bodyHeight, 0, 0, Math.PI * 2);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
ctx.stroke();
// Rayures noires horizontales sur le corps
ctx.fillStyle = '#000000';
const stripeCount = 8;
const stripeSpacing = (bodyHeight * 2) / stripeCount;
for (let i = 0; i < stripeCount; i++) {
if (i % 2 === 1) { // Alterner les rayures
const yPos = -bodyHeight + (i * stripeSpacing);
ctx.beginPath();
ctx.ellipse(0, yPos + size * 0.2, bodyWidth * 0.95, stripeSpacing * 0.5, 0, 0, Math.PI * 2);
ctx.fill();
}
}
// Tête (cercle légèrement plus petit au-dessus du corps)
const headSize = size * 0.9;
ctx.beginPath();
if (this.mouthOpen) {
ctx.arc(0, -bodyHeight - headSize * 0.2, headSize, 0.15, Math.PI * 2 - 0.15);
ctx.lineTo(0, -bodyHeight - headSize * 0.2);
ctx.closePath();
} else {
ctx.arc(0, -bodyHeight - headSize * 0.2, headSize, 0, Math.PI * 2);
}
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
ctx.stroke();
// Rayures sur la tête (verticales)
ctx.fillStyle = '#000000';
ctx.fillRect(-headSize * 0.4, -bodyHeight - headSize * 0.5, headSize * 0.15, headSize * 0.6);
ctx.fillRect(headSize * 0.25, -bodyHeight - headSize * 0.5, headSize * 0.15, headSize * 0.6);
// Oreilles pointues
ctx.beginPath();
// Oreille gauche
ctx.moveTo(-headSize * 0.6, -bodyHeight - headSize * 0.8);
ctx.lineTo(-headSize * 0.8, -bodyHeight - headSize * 1.4);
ctx.lineTo(-headSize * 0.4, -bodyHeight - headSize * 0.9);
ctx.closePath();
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.stroke();
// Rayure sur l'oreille gauche
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.moveTo(-headSize * 0.65, -bodyHeight - headSize * 0.95);
ctx.lineTo(-headSize * 0.75, -bodyHeight - headSize * 1.3);
ctx.lineTo(-headSize * 0.55, -bodyHeight - headSize * 1.0);
ctx.closePath();
ctx.fill();
// Oreille droite
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.moveTo(headSize * 0.6, -bodyHeight - headSize * 0.8);
ctx.lineTo(headSize * 0.8, -bodyHeight - headSize * 1.4);
ctx.lineTo(headSize * 0.4, -bodyHeight - headSize * 0.9);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Rayure sur l'oreille droite
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.moveTo(headSize * 0.65, -bodyHeight - headSize * 0.95);
ctx.lineTo(headSize * 0.75, -bodyHeight - headSize * 1.3);
ctx.lineTo(headSize * 0.55, -bodyHeight - headSize * 1.0);
ctx.closePath();
ctx.fill();
// Yeux
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(-headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.15, 0, Math.PI * 2);
ctx.arc(headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.15, 0, Math.PI * 2);
ctx.fill();
// Pupilles
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(-headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.08, 0, Math.PI * 2);
ctx.arc(headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.08, 0, Math.PI * 2);
ctx.fill();
// Reflets dans les yeux
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(-headSize * 0.32, -bodyHeight - headSize * 0.32, headSize * 0.03, 0, Math.PI * 2);
ctx.arc(headSize * 0.28, -bodyHeight - headSize * 0.32, headSize * 0.03, 0, Math.PI * 2);
ctx.fill();
// Queue (petite touffe)
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(bodyWidth * 0.6, bodyHeight * 0.6, size * 0.2, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
} else {
// Forme ronde (par défaut)
if (this.mouthOpen) {
@ -2339,9 +2457,25 @@ class MenuGhost {
this.size = 30 + Math.random() * 20;
this.color = ['#ff0000', '#ff00ff', '#00ffff', '#ffa500'][Math.floor(Math.random() * 4)];
this.animation = Math.random() * Math.PI * 2;
this.isDestroyed = false;
this.destroyAnimation = 0;
}
// Vérifier si le curseur touche le fantôme
isPointInside(mouseX, mouseY) {
const distance = Math.sqrt(
Math.pow(mouseX - this.x, 2) +
Math.pow(mouseY - this.y, 2)
);
return distance < this.size * 0.7;
}
update() {
if (this.isDestroyed) {
this.destroyAnimation += 0.1;
return;
}
this.x += this.speedX;
this.y += this.speedY;
this.animation += 0.05;
@ -2360,6 +2494,33 @@ class MenuGhost {
}
draw() {
if (this.isDestroyed) {
// Animation de destruction (particules qui s'éparpillent)
if (this.destroyAnimation > 2) {
return;
}
menuCtx.save();
menuCtx.globalAlpha = 1 - (this.destroyAnimation / 2);
// Dessiner des particules qui s'éparpillent
const particleCount = 8;
for (let i = 0; i < particleCount; i++) {
const angle = (Math.PI * 2 / particleCount) * i + this.destroyAnimation * 2;
const distance = this.destroyAnimation * 30;
const particleX = this.x + Math.cos(angle) * distance;
const particleY = this.y + Math.sin(angle) * distance;
menuCtx.fillStyle = this.color;
menuCtx.beginPath();
menuCtx.arc(particleX, particleY, 4, 0, Math.PI * 2);
menuCtx.fill();
}
menuCtx.restore();
return;
}
menuCtx.save();
menuCtx.translate(this.x, this.y);
@ -2440,6 +2601,13 @@ function animateMenu() {
ghost.draw();
});
// Remplacer les fantômes complètement détruits
for (let i = 0; i < menuGhosts.length; i++) {
if (menuGhosts[i].isDestroyed && menuGhosts[i].destroyAnimation > 2) {
menuGhosts[i] = new MenuGhost();
}
}
menuAnimationFrame = requestAnimationFrame(animateMenu);
}
@ -2488,6 +2656,25 @@ if (mainMenu) {
menuObserver.observe(mainMenu, { attributes: true, attributeFilter: ['style'] });
}
// Gestion de la destruction des fantômes avec le curseur
let mouseX = 0;
let mouseY = 0;
menuCanvas.addEventListener('mousemove', (e) => {
const rect = menuCanvas.getBoundingClientRect();
mouseX = e.clientX - rect.left;
mouseY = e.clientY - rect.top;
// Vérifier les collisions avec les fantômes
menuGhosts.forEach(ghost => {
if (!ghost.isDestroyed && ghost.isPointInside(mouseX, mouseY)) {
// Détruire le fantôme
ghost.isDestroyed = true;
ghost.destroyAnimation = 0;
}
});
});
// === GESTION DU MENU ===
// Afficher le jeu
playBtn.addEventListener('click', () => {
@ -2748,6 +2935,120 @@ function startPreviewAnimation() {
};
ctx.fillStyle = colorMap[selectedColor] || '#ffd700';
}
} else if (selectedShape === 'zebre') {
// Dessiner un zèbre plus réaliste
const bodyWidth = size * 1.8;
const bodyHeight = size * 1.4;
// Corps (ellipse allongée)
ctx.beginPath();
ctx.ellipse(0, size * 0.2, bodyWidth, bodyHeight, 0, 0, Math.PI * 2);
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
ctx.stroke();
// Rayures noires horizontales sur le corps
ctx.fillStyle = '#000000';
const stripeCount = 8;
const stripeSpacing = (bodyHeight * 2) / stripeCount;
for (let i = 0; i < stripeCount; i++) {
if (i % 2 === 1) { // Alterner les rayures
const yPos = -bodyHeight + (i * stripeSpacing);
ctx.beginPath();
ctx.ellipse(0, yPos + size * 0.2, bodyWidth * 0.95, stripeSpacing * 0.5, 0, 0, Math.PI * 2);
ctx.fill();
}
}
// Tête (cercle légèrement plus petit au-dessus du corps)
const headSize = size * 0.9;
ctx.beginPath();
if (mouthOpen) {
ctx.arc(0, -bodyHeight - headSize * 0.2, headSize, 0.15, Math.PI * 2 - 0.15);
ctx.lineTo(0, -bodyHeight - headSize * 0.2);
ctx.closePath();
} else {
ctx.arc(0, -bodyHeight - headSize * 0.2, headSize, 0, Math.PI * 2);
}
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.strokeStyle = '#000000';
ctx.lineWidth = 2;
ctx.stroke();
// Rayures sur la tête (verticales)
ctx.fillStyle = '#000000';
ctx.fillRect(-headSize * 0.4, -bodyHeight - headSize * 0.5, headSize * 0.15, headSize * 0.6);
ctx.fillRect(headSize * 0.25, -bodyHeight - headSize * 0.5, headSize * 0.15, headSize * 0.6);
// Oreilles pointues
ctx.beginPath();
// Oreille gauche
ctx.moveTo(-headSize * 0.6, -bodyHeight - headSize * 0.8);
ctx.lineTo(-headSize * 0.8, -bodyHeight - headSize * 1.4);
ctx.lineTo(-headSize * 0.4, -bodyHeight - headSize * 0.9);
ctx.closePath();
ctx.fillStyle = '#ffffff';
ctx.fill();
ctx.stroke();
// Rayure sur l'oreille gauche
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.moveTo(-headSize * 0.65, -bodyHeight - headSize * 0.95);
ctx.lineTo(-headSize * 0.75, -bodyHeight - headSize * 1.3);
ctx.lineTo(-headSize * 0.55, -bodyHeight - headSize * 1.0);
ctx.closePath();
ctx.fill();
// Oreille droite
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.moveTo(headSize * 0.6, -bodyHeight - headSize * 0.8);
ctx.lineTo(headSize * 0.8, -bodyHeight - headSize * 1.4);
ctx.lineTo(headSize * 0.4, -bodyHeight - headSize * 0.9);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Rayure sur l'oreille droite
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.moveTo(headSize * 0.65, -bodyHeight - headSize * 0.95);
ctx.lineTo(headSize * 0.75, -bodyHeight - headSize * 1.3);
ctx.lineTo(headSize * 0.55, -bodyHeight - headSize * 1.0);
ctx.closePath();
ctx.fill();
// Yeux
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(-headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.15, 0, Math.PI * 2);
ctx.arc(headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.15, 0, Math.PI * 2);
ctx.fill();
// Pupilles
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(-headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.08, 0, Math.PI * 2);
ctx.arc(headSize * 0.3, -bodyHeight - headSize * 0.3, headSize * 0.08, 0, Math.PI * 2);
ctx.fill();
// Reflets dans les yeux
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(-headSize * 0.32, -bodyHeight - headSize * 0.32, headSize * 0.03, 0, Math.PI * 2);
ctx.arc(headSize * 0.28, -bodyHeight - headSize * 0.32, headSize * 0.03, 0, Math.PI * 2);
ctx.fill();
// Queue (petite touffe)
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(bodyWidth * 0.6, bodyHeight * 0.6, size * 0.2, 0, Math.PI * 2);
ctx.fill();
} else {
// Dessiner un cercle
if (mouthOpen) {

View File

@ -287,6 +287,10 @@
<div class="shape-preview triangle-preview"></div>
<span>Triangle</span>
</div>
<div class="shape-option" data-shape="zebre" data-name="Zèbre">
<div class="shape-preview zebre-preview"></div>
<span>Zèbre</span>
</div>
</div>
</div>
<div class="customize-preview">

View File

@ -156,7 +156,7 @@ body::after {
height: 100%;
z-index: 1;
opacity: 0.3;
pointer-events: none;
pointer-events: auto;
}
.menu-container {
@ -1539,6 +1539,21 @@ footer {
filter: drop-shadow(0 0 5px rgba(255, 255, 255, 0.3));
}
.zebre-preview {
width: 50px;
height: 50px;
border-radius: 50%;
background: repeating-linear-gradient(
90deg,
#ffffff 0px,
#ffffff 8px,
#000000 8px,
#000000 16px
);
border: 3px solid #fff;
box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);
}
.shape-option span {
font-size: 0.7em;
color: #fff;