From 1b474fcbcade9b939e099d09a1a080fa2d974653 Mon Sep 17 00:00:00 2001 From: thedevilbox Date: Fri, 19 Dec 2025 16:22:09 +0100 Subject: [PATCH] texture zebre plus sourie menu --- README.md | 531 ++++++++++++++++++++++++++++++++++++++++++++--------- game.js | 301 ++++++++++++++++++++++++++++++ index.html | 4 + style.css | 17 +- 4 files changed, 767 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index 5019530..34f4928 100644 --- a/README.md +++ b/README.md @@ -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 `` 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 `` 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 ! 🎮👻** diff --git a/game.js b/game.js index 0dbba0e..40c0a16 100644 --- a/game.js +++ b/game.js @@ -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) { diff --git a/index.html b/index.html index 0b82c59..75e19d8 100644 --- a/index.html +++ b/index.html @@ -287,6 +287,10 @@
Triangle +
+
+ Zèbre +
diff --git a/style.css b/style.css index 86f72ee..b1868e1 100644 --- a/style.css +++ b/style.css @@ -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;