No description
  • Vue 44.5%
  • PHP 29.4%
  • TypeScript 25.2%
  • CSS 0.5%
  • Shell 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-11-03 10:56:15 +01:00
docker Refactor database interactions and improve error handling 2026-11-02 10:53:56 +01:00
public/modeles feat: Ajout de modèles 3D de bateaux et mise à jour de la documentation des crédits 2026-10-22 10:46:03 +02:00
scripts Refactor database interactions and improve error handling 2026-11-02 10:53:56 +01:00
server feat: add decor editing functionality to the card editor 2026-11-03 10:49:55 +01:00
src feat: ajout de la fonction de copie profonde pour les données de carte et mise à jour des références dans l'éditeur 2026-11-03 10:56:15 +01:00
.dockerignore feat: Ajout de la configuration Docker avec Dockerfile, docker-compose et fichiers de configuration associés 2026-10-23 08:56:47 +02:00
.env.example Refactor database interactions and improve error handling 2026-11-02 10:53:56 +01:00
.gitignore feat: Ajout de la configuration Docker avec Dockerfile, docker-compose et fichiers de configuration associés 2026-10-23 08:56:47 +02:00
doc-tech.md feat: ajout de la vérification des règles du jeu et mise à jour des modes de jeu 2026-11-02 10:32:37 +01:00
docker-compose.yml Refactor database interactions and improve error handling 2026-11-02 10:53:56 +01:00
Dockerfile Refactor database interactions and improve error handling 2026-11-02 10:53:56 +01:00
index.html feat: amélioration de la réactivité et de l'accessibilité sur mobile 2026-10-24 09:38:57 +02:00
package.json feat: add replay functionality for completed games 2026-11-02 10:08:52 +01:00
pnpm-lock.yaml init : version de base 2026-10-22 08:41:02 +02:00
README.md Refactor database interactions and improve error handling 2026-11-02 10:53:56 +01:00
tsconfig.json init : version de base 2026-10-22 08:41:02 +02:00
vite.config.ts feat: Ajout de la gestion des certificats HTTPS pour le développement en réseau local 2026-10-22 15:43:57 +02:00

Bataille navale 3D

Squelette pédagogique : Vue 3 + BabylonLite (WebGPU) côté navigateur, serveur PHP + MariaDB pour arbitrer les parties.

Démarrage

pnpm install
pnpm serveur        # API PHP (port 8000) + WebSocket (port 8001), dans un premier terminal
pnpm dev            # http://localhost:5173 (dans un second terminal ; /api est proxifié vers le PHP)

Le serveur demande PHP 8.3+ avec l'extension pdo_mysql et un serveur MariaDB (10.6+). pnpm serveur prend le premier binaire PHP qui a l'extension ($PHP, php, puis php8.5, php8.4, php8.3) et affiche une erreur claire s'il n'en trouve aucun. Sous Ubuntu : sudo apt install mariadb-server php8.X-mysql.

Base de développement, à créer une fois (base navale, utilisateur et mot de passe navale) :

sudo mariadb < scripts/base-locale.sql

Les tables sont créées automatiquement au premier appel. Pour une autre base, définir les variables NAVALE_BD_HOTE, NAVALE_BD_PORT (ou NAVALE_BD_SOCKET), NAVALE_BD_NOM, NAVALE_BD_UTILISATEUR et NAVALE_BD_MOT_DE_PASSE (voir server/src/Base.php).

Ancienne base SQLite (server/data/navale.sqlite) : php server/migrer-sqlite.php [--vider] la copie dans MariaDB (il faut aussi pdo_sqlite ; la base SQLite n'est pas modifiée).

Il faut un navigateur avec WebGPU (Chrome / Edge 113+, ou Firefox / Safari récents). Sans WebGPU, les grilles affichent un message d'erreur au lieu de la scène 3D.

Autres commandes :

pnpm typecheck      # vérification TypeScript + Vue
pnpm check:picking  # vérifie, sans GPU, la conversion pointeur → case
pnpm check:regles   # compare les règles du client (rules.ts) et du serveur (Regles.php) sur des milliers de cas
pnpm build

La version de @babylonjs/lite est volontairement figée (1.34.0) : l'API évolue vite.

Jouer depuis le réseau local (HTTPS)

WebGPU n'est disponible que dans un contexte sécurisé : https://, ou http://localhost. Pour jouer depuis un autre appareil du réseau local, il faut donc du HTTPS :

pnpm certificat     # une fois (et à relancer si l'adresse IP change)
pnpm serveur        # API PHP + WebSocket, écoutent seulement sur 127.0.0.1
pnpm dev:reseau     # Vite en https sur toutes les interfaces : https://<ip-de-la-machine>:5173/

pnpm certificat crée dans certs/ (ignoré par git) une autorité de développement et un certificat signé par elle, valable pour localhost et toutes les adresses IPv4 de la machine. Vite passe automatiquement en HTTPS dès que ce certificat existe (y compris pnpm dev) ; /api et /ws (devenu wss://) restent relayés vers le PHP local.

Sur chaque appareil qui joue, installer une fois certs/autorite.crt comme autorité de confiance (Chrome/Edge : Paramètres › Confidentialité et sécurité › Sécurité › Gérer les certificats › Autorités ; Firefox : Paramètres › Vie privée › Certificats › Autorités ; Android/iOS : installer le fichier puis l'activer). Sans cela, le navigateur affiche un avertissement qu'il faut accepter manuellement. Ne jamais partager certs/autorite.key.

Architecture

src/
├─ game/        logique pure (aucune dépendance à Vue ni à Babylon)
│   ├─ types.ts     constantes, CellState, Ship, flotte
│   └─ rules.ts     placement valide ? résultat d'un tir ? partie finie ?
├─ engine/      tout ce qui touche à BabylonLite (aucune dépendance à Vue)
│   ├─ boardGeometry.ts   case ↔ position dans le monde 3D
│   ├─ cellPicking.ts     pointeur → case (rayon ∩ plan de la mer)
│   └─ boardScene.ts      createBoardScene(canvas, options) → petite API
├─ components/  Vue : enveloppes fines autour du moteur
│   ├─ BoardView3D.vue    une grille 3D (utilisée deux fois en jeu)
│   └─ ShipPlacement.vue  placement des bateaux, vue de dessus
├─ api/client.ts appels au serveur PHP
└─ App.vue      phases du jeu, synchronisation avec le serveur, animations des tirs

server/
├─ public/index.php     point d'entrée unique de l'API (routeur)
├─ websocket.php        serveur WebSocket : pousse l'état des parties en temps réel
├─ src/Regles.php       traduction de game/rules.ts (le serveur valide tout)
├─ src/Parties.php      création de partie, tirs, vue d'une partie pour un joueur
├─ src/JoueurFictif.php adversaire de développement
├─ src/Base.php         connexion MariaDB + schéma (parties, joueurs, tirs…)
├─ migrer-sqlite.php    import d'une ancienne base SQLite
└─ data/                ancienne base SQLite, le cas échéant (ignorée par git)

Canards en plastique

Chaque joueur peut poser de 0 à 2 canards en plastique (une case chacun, canard_1, canard_2). Un canard protège les 3×3 cases autour de lui des explosions : explosion de pigeon, salve et torpille sautent ces cases (elles restent à viser). Les tirs individuels ne sont pas affectés : un tir sur le canard le détruit (« coulé ») et supprime sa protection. Les canards ne comptent pas pour la victoire. Le joueur fictif en pose 2.

Événements aléatoires

Après chaque tir individuel (le vôtre ou celui de l'adversaire), il y a 5 % de chances (Parties::PROBABILITE_EVENEMENT) qu'un événement frappe les deux grilles :

Événement Effet sur chaque grille
⛈ Tempête 3 éclairs sur des cases au hasard
🌋 Séisme une zone 3×3 au hasard s'effondre
🌊 Raz-de-marée une colonne entière au hasard est balayée
🐙 Kraken une case du plus grand bateau encore à flot est frappée
🐋 Baleine elle traverse les deux grilles le long d'une même ligne au hasard et percute toutes les cases occupées (bateaux, pigeons, canards) encore intactes
🧊 Iceberg il percute une case (un bateau une fois sur deux, sinon de l'eau ; jamais un pigeon ni un canard). Le bateau heurté coule entièrement après 2 tours de son propriétaire (Parties::TOURS_AVANT_NAUFRAGE), c'est-à-dire quand celui-ci a fini de jouer deux fois ; un bandeau prévient le joueur. Les cases du naufrage sont des tirs evenement: 'naufrage'

Les événements tirés dépendent de la carte (Parties::EVENEMENTS_PAR_CARTE, avec des poids), et l'interface les habille selon elle (descriptionEvenement dans src/game/cartes.ts) :

Carte Événements (les plus fréquents en gras)
🏝 Plage tempête, raz-de-marée, kraken, baleine
🧊 Banquise blizzard (tempête), iceberg, baleine boréale
⚓ Port tempête, raz-de-marée, kraken
🌋 Volcan éruption (4 bombes de lave au hasard), séisme volcanique, tsunami (raz-de-marée)
☁️ Nuages orage (tempête), tornade (une ligne entière balayée)
🌊 Abysses kraken des abysses, cachalot (baleine), courant des abysses (raz-de-marée), séisme sous-marin

Sur le volcan, la grille n'est pas de l'eau mais une mer de lave (croûte sombre, lave orange, veines incandescentes, ondulation lente).

Chaque carte ne garde que les événements cohérents avec son décor : pas de séisme ni de vague dans les airs, pas d'iceberg hors des eaux polaires, pas d'orage au fond de l'océan.

Le mode dev déclenche n'importe quel événement sur n'importe quelle carte.

Chaque case frappée compte comme un tir de l'adversaire du propriétaire de la grille (champ evenement du tir) : pigeons touchés qui explosent, bateaux coulés et victoire compris. Le tour ne change pas. Les canards ne protègent pas (ce ne sont pas des explosions). Le mode dev permet de déclencher chaque événement (action evenement, { type }).

Rejouer après une touche

Un tir qui touche un bateau ou un pigeon sans le couler permet de rejouer. La main passe à l'adversaire dès qu'on tire à l'eau ou qu'on coule une cible. Les bonus comptent toujours pour un seul coup (ils passent la main quel que soit leur résultat). La règle vaut aussi pour le joueur fictif.

Pigeons de combat

En plus des 5 bateaux, chaque joueur peut poser de 0 à 3 pigeons de combat (une case chacun, identifiants pigeon_1 à pigeon_3). Ils se coulent en un tir et comptent pour la victoire. Le joueur fictif en pose toujours 3. Ils sont dessinés en primitives (en vol, ailes battantes).

Pigeon kamikaze : un pigeon touché s'envole vers la grille de celui qui l'a abattu, se pose sur une case que son propriétaire n'a pas encore visée, jamais au bord (la zone 3×3 est toujours entière dans la grille, et de préférence sans case déjà visée), puis explose. Ces 9 tirs comptent pour le propriétaire du pigeon. Si l'explosion touche un pigeon adverse, celui-ci repart à son tour : les réactions en chaîne sont possibles, et un pigeon peut faire gagner son camp pendant le tour adverse. Chaque tir enregistre le pigeon à l'origine (pigeon, centre) et un ordre global, pour que le client rejoue les animations dans l'ordre.

Progression : niveaux et succès

Chaque compte joueur gagne de l'expérience (XP) à chaque partie terminée avec un vainqueur, contre le joueur fictif comme en ligne :

Résultat XP
Victoire contre le joueur fictif 60 (facile), 100 (moyen), 160 (difficile)
Victoire en ligne 180
Défaite 25
Chaque case touchée +2
Chaque succès débloqué +50

Le niveau n demande 50 × n × (n − 1) XP au total (niveau 2 : 100, 3 : 300, 4 : 600…), avec un grade (Mousse, Matelot, Quartier-maître… jusqu'à Amiral au niveau 30), dans la limite du niveau 999 (Progression::NIVEAU_MAX, l'XP continue de s'accumuler au-delà). Il y a 17 succès : première victoire, série de victoires, victoire sans perte, précision ≥ 60 %, victoire en 45 tirs ou moins, toutes les cartes jouées, etc. (Progression::SUCCES).

Le niveau s'affiche dans l'en-tête (un clic ouvre « Ma progression » : statistiques et succès) et à côté du pseudo de l'adversaire. La fenêtre de fin de partie indique l'XP gagnée et les succès débloqués. Les parties comptabilisées sont gardées dans progression_parties, qui survit à la purge des parties. Les parties où le mode dev a servi (actions dev, parties de test) ne rapportent rien.

Dans l'espace développeur, l'onglet Joueurs affiche le niveau, l'XP et le nombre de succès de chaque compte ; le bouton Progression déplie le détail (statistiques, succès, ajustements, dernières parties comptabilisées). Les administrateurs peuvent y ajouter ou retirer de l'XP avec un motif (progression_ajustements), débloquer ou retirer un succès et remettre la progression à zéro, ou fixer directement le niveau (de 1 à 999 ; l'XP est ajustée au début du niveau choisi) ; chaque action est inscrite dans l'audit.

Classement : les joueurs sont classés par XP (puis victoires). Il se consulte depuis « Ma progression » (onglet Classement), où chaque joueur choisit de figurer ou non dans le classement public (oui par défaut). L'onglet Joueurs de l'espace développeur affiche le classement complet, comptes cachés compris (grisés).

Bonus

Chaque joueur dispose de trois bonus, trois exemplaires de chacun (Parties::STOCK_BONUS_INITIAL), sauf exceptions indiquées, utilisables à la place d'un tir :

Bonus Effet
Salve tir en croix : la case visée et ses 4 voisines
Torpille (2 exemplaires) tire sur toute la ligne ou toute la colonne visée (les cases déjà visées sont ignorées) ; le sens se change avec les boutons Ligne / Colonne, la touche R ou un clic droit
Sonar indique combien de cases occupées (non touchées) contient la zone 3×3 visée, sans tirer
Lancer de pain révèle les pigeons ennemis (encore en vol) dans la zone 3×3 visée, sans tirer ; ils restent visibles
Trou noir (1 seul exemplaire) zone de 3×2 (3 colonnes, la ligne visée et celle du dessous) : aspire les pigeons et canards ennemis (détruits, et les pigeons n'explosent pas) et touche les cases de bateaux ennemis ; les cases vides ne sont pas tirées
Super pigeon (1 seul exemplaire) ajoute un pigeon à votre flotte, sur la case libre la plus éloignée des tirs adverses (jamais une case déjà tentée) ; touché, il explose en 5×5 chez l'adversaire. Pas de cible à choisir

On clique sur le bonus (il s'« arme »), la zone concernée s'affiche au survol, puis on clique sur la grille. Le joueur fictif n'utilise pas de bonus.

Mode dev

Avec pnpm dev, un bouton DEV (en bas à droite, ou Ctrl+Maj+D) ouvre un panneau de débogage : identifiant de partie (copiable), version, tour, état du WebSocket, tirs et bonus restants, et les actions :

Action (/dev/...) Effet
flotte-adverse révèle (ou masque) toute la flotte adverse sur sa grille
tout-exploser tire sur toutes les cases adverses restantes : victoire (à son tour)
defaite l'adversaire tire sur toutes vos cases : défaite
recharger-bonus rend les bonus de jeu déjà utilisés
adversaire-maintenant le joueur fictif joue sans attendre son délai (même en pause)
pause-adversaire met en pause / relance le joueur fictif ({ enPause } pour forcer l'état)
reprendre-tour vous redonne la main
invincibilite { joueur: 'moi' | 'adversaire', actif } : les bateaux de ce joueur ne peuvent plus être touchés
definir-bonus { joueur, bonus, nombre } : exemplaires restants d'un bonus (0 à 99)
forcer-tir-adverse l'adversaire tire sur { colonne, ligne } de votre grille — dans le panneau, « Viser ma grille » puis clic

Côté client, le panneau affiche aussi la case survolée (coordonnées), un journal (états reçus, actions, erreurs, connexion WebSocket), permet de copier l'état en JSON, et règle la vitesse des animations (×0,25 à ×10, ou instantané). Le panneau a une section Triche : invincibilité de chaque joueur (case à cocher) et tableau des bonus restants des deux joueurs (−, +, ou saisie). Contre un joueur invincible, un tir sur ses bateaux compte comme raté (et un pigeon touché ne s'envole pas) ; les règles ignorent les tirs ratés pour savoir si un bateau est coulé.

Le serveur n'accepte ces actions qu'en développement (php -S, ou NAVALE_DEV=1 — que pnpm serveur définit pour l'API comme pour le serveur WebSocket), ou, en production, d'un développeur administrateur connecté. Deux actions s'ajoutent : difficulte ({ difficulte }, « expert » compris) et interrompre (fin sans vainqueur).

Espace développeur

Adresse /#dev (lien discret en bas de page). L'inscription est fermée : les comptes se gèrent sur le serveur.

php server/creer-developpeur.php leo                      # crée le compte (admin) ou change son mot de passe
php server/creer-developpeur.php alice --role observateur # rôle : admin (tout) ou observateur (lecture seule)
php server/creer-developpeur.php --lister
php server/creer-developpeur.php --supprimer alice
# Avec Docker :
docker compose exec app php /var/www/server/creer-developpeur.php leo
Onglet Contenu (✱ : administrateurs seulement)
Parties liste, observation en temps réel, relecture coup par coup, carte de probabilités du tireur adverse, export JSON ; ✱ purge, suppression, interventions rangées par thème (déroulement, joueur fictif, tirs et événements dont le tir forcé par clic sur une grille, et par joueur : invincibilité, stock de chaque bonus, recharge, tout exploser, victoire immédiate), prise de la place du joueur fictif, parties de test aux flottes imposées
Joueurs comptes joueurs (inscription, dernière activité, sessions, parties en ligne), catalogue des badges et historique des bannissements ; ✱ renommer, nouveau mot de passe, fermer les sessions, supprimer (les parties restent, sous le dernier pseudo), bannir du jeu en ligne, lever un bannissement, créer des badges et les attribuer ou retirer un par un
Statistiques parties par jour, résultats contre le joueur fictif par difficulté, durée moyenne, bonus et événements utilisés
Simulation ✱ parties entre deux niveaux du joueur fictif (règles simplifiées : tirs simples)
Serveur santé (signe de vie du WebSocket, base, erreurs récentes) ; ✱ interrupteurs (événements, parties à deux, chaque bonus) et purge automatique
Base diagnostic MariaDB : intégrité (CHECK TABLE), clés étrangères, schéma, cohérence, verrous récents, connexions, espace libre, volumes ; ✱ analyse, optimisation (OPTIMIZE TABLE), nettoyage
Audit qui a fait quelle action d'administration, et quand
Compte sessions ouvertes (révocables), changement de mot de passe

Un administrateur connecté a aussi le panneau DEV en jeu. Les statistiques portent sur les parties conservées (la purge les retire), sauf bonus et événements, journalisés à part.

Easter eggs

Attention, ça gâche la surprise
Déclencheur Effet
Code Konami (↑ ↑ ↓ ↓ ← → ← → B A) les bateaux dansent, les pigeons tourbillonnent
Taper « pigeon » « Rou-rou ! », les pigeons font la toupie
Cliquer 7 fois sur le titre (en moins de 2 s) « Bataille de pigeons » (et retour)
Viser les quatre coins de la grille adverse une remarque sur la tactique
Gagner sans aucun tir dans l'eau « Amiral légendaire »
Taper « baguette » pendant une partie +2 lancers de pain (une fois par partie)
Taper « boum » feu d'artifice sur les deux grilles
Taper « plouf » pluie de gerbes d'eau
Viser 5 cases consécutives en diagonale une remarque d'échecs
Au moins 2 pigeons kamikazes dans la même chaîne « Réaction en chaîne »
Valider une flotte alignée à gauche, bateaux empilés « Formation de parade »
Lancer le jeu entre minuit et 5 h les pigeons dorment
Lancer le jeu un 1er avril poisson d'avril

Détections dans src/game/easterEggs.ts, animations celebrer et feuxDArtifice dans boardScene.ts.

Partie à deux joueurs

Après le placement, on choisit Contre le joueur fictif, Créer une partie à deux, ou Rejoindre avec un code. Le créateur reçoit un code (l'identifiant de la partie, 16 caractères) et un lien ?partie=<code> à transmettre ; la partie reste à l'état attente jusqu'à ce que le second joueur la rejoigne avec sa flotte. Le créateur tire en premier. Chacun ne reçoit que sa vue de la partie (jeton différent) ; le WebSocket prévient le créateur dès l'arrivée de l'adversaire.

Comptes joueurs : jouer en ligne (créer une partie à deux ou en rejoindre une) demande un compte. L'inscription est ouverte (pseudo de 3 à 20 caractères, unique sans tenir compte de la casse ; mot de passe d'au moins 8 caractères), depuis l'écran « Contre qui jouer ? ». Un développeur connecté (espace #dev, tout rôle) joue en ligne sans compte joueur, sous le nom <identifiant> (dev). La partie contre le joueur fictif reste sans compte. Chaque joueur voit le pseudo de son adversaire ; on ne peut pas rejoindre sa propre partie. Routes : /api/comptes/... (en tête de server/public/index.php).

Cartes : sur l'écran « Contre qui jouer ? », on choisit le thème du plateau (mémorisé dans le navigateur) — 🏝 plage tropicale, 🧊 banquise (neige, igloo, pingouins, blocs de glace, neige qui tombe), ⚓ port de nuit (quais, phare au faisceau qui balaie le large, réverbères, caisses et tonneaux) 🌋 île volcanique (volcan fumant, lave, fumerolles, braises), ☁️ royaume des nuages (mer de nuages, îles flottantes, moulin, montgolfière, oiseaux) ou 🌊 abysses (coraux et algues lumineux, méduses, épave, coffre au trésor, poisson-lanterne, bulles). La carte est enregistrée avec la partie : en ligne, celui qui rejoint joue sur la carte du créateur. Purement visuelle : les règles ne changent pas. La flotte s'adapte : navires enneigés sur la banquise, dirigeables dans les nuages, sous-marins dans les abysses (src/engine/flottes.ts) ; et les événements aussi (voir plus haut). Thèmes décrits dans src/game/cartes.ts, décors dans src/engine/decors.ts.

Badges : distinctions honorifiques (libellé, couleur, emoji, description au survol) créées par les administrateurs et attribuées une par une aux comptes joueurs, depuis l'onglet Joueurs. Ils s'affichent à côté du pseudo (en-tête, écran de choix, grille adverse, observation) et ne donnent aucun droit.

Mode confidentiel : si l'adversaire peut voir l'écran, le bouton « 🔒 Mode confidentiel » (au-dessus de « Ma flotte ») masque ses bateaux ; seuls restent les tirs reçus et les bateaux déjà coulés, que l'adversaire connaît. « 👁 Maintenir pour voir » affiche la flotte tant qu'on le garde enfoncé (souris, doigt, Espace ou Entrée). Le choix est mémorisé dans le navigateur. Un site ne peut pas savoir si deux joueurs sont dans la même pièce : quand l'adversaire joue depuis la même adresse IP (même box, même école), le jeu propose d'activer le mode (champ memeReseau de la vue ; l'IP des joueurs n'est jamais envoyée aux clients).

Bannissements (onglet Joueurs de l'espace développeur) : un administrateur bannit un compte du jeu en ligne avec un motif (montré au joueur) et une durée facultative (1 h, 24 h, 7 j, 30 j ou définitif). Le navigateur ne donne pas accès à la carte réseau (adresse MAC) : pour freiner la création d'un nouveau compte, on bannit avec lui ses identifiants d'appareil (aléatoires, gardés dans le localStorage et envoyés dans l'en-tête X-Appareil) et, au choix, ses adresses IP (attention : une IP peut être partagée par tout un réseau). Toute IP ou tout appareil utilisé ensuite par le compte banni rejoint le bannissement. Inscription et jeu en ligne sont refusés depuis ces cibles ; la partie contre le joueur fictif reste possible, et les développeurs ne sont pas concernés. Un VPN, un autre réseau ou l'effacement des données du site contournent ces protections : elles freinent, sans être infaillibles.

Pour tester seul : deux navigateurs (ou une fenêtre privée), connectés à deux comptes différents — la session joueur est gardée dans localStorage, partagée entre les onglets d'un même navigateur.

Déconnexion : chaque consultation de l'état (WebSocket toutes les 100 ms, ou interrogation de secours) note la présence du joueur (joueurs.vu_le). Sans signe de vie depuis 5 s, un joueur est considéré déconnecté et son adversaire voit un compte à rebours ; au bout de 30 s, la partie est interrompue, sans vainqueur (parties.interrompue_par). Délais : Parties::DELAI_PRESENCE et Parties::DELAI_INTERRUPTION.

Sons

Tous les sons sont synthétisés dans le navigateur (Web Audio API, src/engine/sons.ts) : aucun fichier audio. Tir, explosion, coulé, plouf, sonar, pigeon (« rou-rou » et battements d'ailes), trou noir et couic de canard. Chaque événement est annoncé par une alarme de bord à deux tons, puis a son ambiance, calée sur son animation : pluie et vent (tempête, avec le tonnerre à chaque éclair), grondement et craquements de roche (séisme), vague qui monte puis s'écrase (raz-de-marée), rugissement puis claquement des tentacules (kraken), chant et souffle de l'évent (baleine), glace qui gémit puis choc (iceberg), coque qui gémit et bulles (naufrage), et boum grave suivi d'un long grondement (explosion générale du mode dev). Le bouton 🔊 de l'en-tête coupe le son (mémorisé dans le navigateur). Les navigateurs n'autorisent le son qu'après un premier clic ou une première touche.

API

Requête Corps Réponse
GET /api/config — { deuxJoueurs, bonusDesactives } (fonctionnalités désactivées par les développeurs)
POST /api/parties { bateaux, adversaire: 'fictif' | 'humain', difficulte } { partieId, jeton }
POST /api/parties/{id}/rejoindre { bateaux } { partieId, jeton } — le second joueur humain
GET /api/parties/{id} en-tête X-Jeton sa flotte, ses tirs, les tirs reçus, les bateaux adverses coulés
POST /api/parties/{id}/tirs { colonne, ligne } + X-Jeton { resultat: 'rate' | 'touche' | 'coule' }
POST /api/parties/{id}/bonus { bonus, colonne, ligne } + X-Jeton { bonus, tirs, detectees }
POST /api/parties/{id}/dev/{action} X-Jeton selon l'action — mode dev, voir ci-dessous

La seule exception est le mode dev, réservé en production aux développeurs administrateurs. Les routes /api/dev/... de l'espace développeur sont décrites en tête de server/public/index.php.

Le serveur ne renvoie jamais la flotte adverse : seuls les bateaux entièrement coulés sont révélés.

Temps réel (WebSocket)

Le client ouvre ws://<hôte>/ws (proxifié par Vite vers server/websocket.php, port 8001) et s'abonne : { "type": "abonner", "partieId", "jeton" }. Le serveur répond { "type": "etat", "etat" } tout de suite, puis à chaque nouvelle version de la partie. Les tirs passent toujours par l'API HTTP : le serveur WebSocket surveille la base toutes les 100 ms (et se reconnecte si MariaDB coupe la connexion). Il est écrit sans dépendance (poignée de main et trames RFC 6455 à la main).

C'est aussi lui qui fait jouer le joueur fictif, JoueurFictif::DELAI secondes après le dernier coup. Si le WebSocket est coupé, le client interroge l'API toutes les 1,5 s et se reconnecte tout seul (le joueur fictif joue alors lors de ces consultations).

Sens des échanges :

props (état du jeu) ──▶ composant Vue ──▶ méthodes de BoardScene ──▶ rendu 3D
                               ▲                    │
                               └─── événements ◀────┘  (clic, survol)

Règle d'or : le composant Vue ne touche jamais un objet 3D, et le moteur ne connaît pas Vue.

Repère 3D

Lite utilise un repère main gauche : +x vers la droite, +y vers le haut, +z s'éloigne de la caméra. La caméra est côté -z, donc la ligne 0 de la grille est en haut de l'écran. La caméra est fixe (pas de attachControl).

Particularités de BabylonLite à connaître

  • Tout ajouter avant registerScene() : ce qui est ajouté après n'apparaît pas. C'est pourquoi les marqueurs (bateau, touché, raté) sont créés une fois pour toutes puis montrés/cachés avec setSubtreeVisible.
  • Pas d'ActionManager ni d'observable de pointeur : on écoute les événements DOM du canvas. Les coordonnées doivent être relatives au canvas (clientX - rect.left).
  • Les objets (caméra, mesh, matériau) sont des données ; le comportement est dans des fonctions (addToScene(scene, mesh), setSubtreeVisible(mesh, false)…).

Reste à faire

Rien pour l'instant. (En production, l'image Docker fait passer /ws par Apache vers le serveur WebSocket.)

Crédits

Modèles de bateaux : Watercraft Kit de Kenney (www.kenney.nl), licence CC0 — copie de la licence dans public/modeles/LICENCE-Kenney.txt.