6) Finalisation du jeu
Objectifs de la séance
- Implémenter la détection de fin de partie et l'affichage du vainqueur.
- Afficher les personnages éliminés différemment sur le plateau.
- Stocker un historique des actions de la partie dans une nouvelle table SQLite.
- Introduire les sessions PHP pour conserver de petites informations entre les requêtes sans passer par la base de données.
- Finaliser la mise en forme de la page pour obtenir un jeu complet et jouable.
Notions théoriques
Ce qu'il reste à construire
À l'issue de la séance 5, le jeu permet de créer des personnages, de les déplacer et de les faire attaquer. Il lui manque trois choses pour être complet :
- La fin de partie n'est pas détectée : quand un personnage atteint 0 PV, le jeu continue.
- L'affichage général est fonctionnel mais brut. Quelques améliorations CSS et structurelles rendront le jeu réellement agréable à jouer.
Les sessions PHP
Une session PHP est un mécanisme qui permet de stocker de petites informations côté serveur, associées à un visiteur précis, entre plusieurs requêtes consécutives.
Il est possible de conserver l'historique des dernières actions entre les rechargements de page, sans avoir besoin de créer une table SQLite dédiée en utilisant les sessions PHP.
Contrairement à la base de données qui stocke les données durablement (elles survivent à un redémarrage du serveur), une session PHP est temporaire : elle disparaît quand le navigateur est fermé ou quand elle expire.
Pour utiliser les sessions, il faut appeler session_start() au tout début du script
PHP, avant tout affichage.
Comment fonctionne l'échange de cookies ?
Lorsqu'un visiteur accède à une page PHP qui appelle session_start(), PHP génère un identifiant de session unique (une chaîne aléatoire) et l'associe à un fichier temporaire sur le serveur où les données de session seront stockées.
PHP envoie ensuite un cookie HTTP au navigateur du visiteur, contenant cet identifiant de session. Le navigateur stocke ce cookie et le renvoie automatiquement à chaque requête suivante vers le même domaine.
Ainsi, à chaque nouvelle requête, PHP peut lire l'identifiant de session dans le cookie envoyé par le navigateur, retrouver le fichier de session correspondant sur le serveur, et charger les données associées à cette session dans le tableau $_SESSION.
PHP envoie alors un cookie au navigateur qui identifie la session.
<?php
session_start();
// Stocker une valeur
$_SESSION['message_precedent'] = "Mario a attaqué Lara.";
// Lire une valeur
if (isset($_SESSION['message_precedent'])) {
print $_SESSION['message_precedent'];
}
// Supprimer une valeur
unset($_SESSION['message_precedent']);
Stocker un historique dans SQLite
Pour un historique persistant (visible d'une session à l'autre), la base de données reste la solution appropriée.
Pour stocker les actions des joueurs, on va créer une table action qui enregistre chaque
événement du jeu : qui a fait quoi, quand, avec quel résultat.
CREATE TABLE IF NOT EXISTS action (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tour INTEGER NOT NULL,
description TEXT NOT NULL,
date_action TEXT NOT NULL
)
La colonne date_action contiendra la date et l'heure au format texte, obtenues
avec la fonction PHP date('Y-m-d H:i:s').
Gérer les tours de jeu
Un tour de jeu est simplement un compteur : chaque action du joueur incrémente ce compteur d'une unité.
On peut stocker ce compteur dans :
- la session PHP (léger, éphémère)
- ou dans la base de données (persistant entre fermetures du navigateur).
Pour ce projet, on utilisera la session : le numéro de tour est stocké dans
$_SESSION['tour'] et incrémenté à chaque action.
La balise <s> pour les personnages éliminés
En HTML, la balise <s> affiche du texte barré. C'est le moyen standard d'indiquer
qu'un contenu est annulé ou obsolète, sans le supprimer visuellement. On s'en servira
pour indiquer qu'un personnage est éliminé dans sa cellule du plateau.
<s>Lara</s> <!-- affiche "Lara" barré -->
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Finaliser le jeu de combat : détection de fin de partie, affichage amélioré et historique des actions.
Ce TP reprend le projet issu de la séance 5.
Le dossier contient :
Personnage.php,Guerrier.php,Mage.php,Database.php,Jeu.php,index.php,styles.css,plateau.php- et
jeu.db.
À la fin de cette séance, le projet contiendra les modifications et ajouts suivants :
| Fichier | Statut | Modifications |
|---|---|---|
Personnage.php | inchangé | — |
Guerrier.php | inchangé | — |
Mage.php | inchangé | — |
Database.php | modifié | tables action et partie, colonne joueur, méthodes de gestion du tour |
Jeu.php | modifié | verifierFinDePartie(), getJoueurCourant(), passerLeTour() |
index.php | modifié | identification du joueur en session, vérification du tour, <meta refresh>, formulaires filtrés |
styles.css | modifié | ajout de la classe .attente |
plateau.php | inchangé | — |
jeu.db | modifié | tables action et partie créées automatiquement |
Étape 1 - Détecter la fin de partie
Ajouter dans la classe Jeu une méthode verifierFinDePartie().
Elle parcourt $this->personnages et collecte les personnages dont les PV sont supérieurs
à 0. La fin de partie ne peut être déclarée que si au moins 2 personnages ont rejoint
l'arène : sans adversaire, il n'y a pas de combat possible et donc pas de vainqueur.
Si la partie comptabilise 2 personnages ou plus et que le nombre de vivants tombe à 1 ou 0, la partie est terminée :
- 1 vivant : ce personnage est le vainqueur.
- 0 vivant : égalité totale.
La méthode retourne un tableau des vivants si la partie est terminée, ou null si elle
est encore en cours (partie non commencée ou encore des combattants vivants).
Dans index.php, appeler cette méthode après chaque traitement d'action (juste avant
la construction des listes $vivants, $magesVivants, etc.). Si elle retourne un résultat
non nul, stocker le message de victoire dans $message et placer un indicateur
$partieTerminee = true pour masquer les formulaires d'action dans la partie HTML.
Avant de tester une condition de fin, il faut s'assurer que les prérequis
de cette condition sont réunis. Ici, la fin de partie n'a de sens qu'une fois qu'au
moins 2 personnages s'affrontent. Vérifier count($this->personnages) < 2 en
entrée de méthode est une clause de garde qui évite un faux positif dès la création
du premier personnage.
Étape 2 - Enrichir l'affichage du plateau pour les personnages éliminés
Sur le plateau actuel, un personnage éliminé (PV ≤ 0) s'affiche de la même manière qu'un personnage vivant. On veut le distinguer visuellement.
Modifier la double boucle for dans la grille HTML pour traiter différemment un personnage
éliminé :
- Sa cellule reçoit la classe CSS
"mort". - Son contenu affiche son nom barré (avec
<s>) et la mention "Éliminé". - Les règles CSS pour
.mort: fond gris, texte gris clair, opacité réduite.
Pour un personnage vivant, améliorer l'affichage en affichant clairement :
- Son nom.
- Ses PV courants et les PV maximum (par exemple "80 / 100 PV") : pour cela, stocker
la vie maximum est nécessaire. Plutôt que de modifier les classes maintenant, afficher
simplement les PV courants suivis d'une barre de progression HTML5
<progress>.
La balise <progress> est native en HTML5 :
<progress value="80" max="100"></progress>
Elle affiche une barre colorée proportionnelle au rapport valeur/maximum.
Le problème est qu'on ne stocke pas encore $vieMax. Pour contourner cela proprement,
utiliser les PV au moment de la création comme référence, qu'on peut lire depuis
la base avec un SELECT dédié. Pour simplifier cette séance, on peut prendre
une valeur fixe de 100 comme maximum (à améliorer en exercice complémentaire).
Différencier visuellement les états d'un objet (vivant/mort, disponible/bloqué) est une règle fondamentale d'ergonomie. Un joueur ne devrait jamais avoir à lire les PV pour savoir si un personnage est encore en jeu. La couleur et la mise en forme le disent immédiatement.
Étape 3 - Introduire les sessions pour le compteur de tour
Chaque action du joueur correspond à un tour. On veut afficher le numéro du tour courant et l'incrémenter à chaque action.
Ajouter session_start() au tout début de index.php (avant toute sortie HTML, idéalement
en toute première ligne).
Initialiser le compteur de tour s'il n'existe pas encore dans la session :
if (!isset($_SESSION['tour'])) {
$_SESSION['tour'] = 1;
}
Dans chaque bloc de traitement d'action (création, déplacement, attaque), incrémenter le compteur avant de construire le message :
$_SESSION['tour'] = $_SESSION['tour'] + 1;
Afficher le numéro du tour courant dans la page, par exemple juste sous le titre principal.
Vérifier que le compteur est remis à zéro lors de la réinitialisation de la partie :
dans le bloc if ($action == "reinitialiser"), ajouter $_SESSION['tour'] = 1;.
session_start() doit être la toute première instruction du fichier PHP,
avant toute sortie vers le navigateur (y compris les espaces ou sauts de ligne avant <?php).
Toute sortie avant l'envoi des en-têtes HTTP provoque l'erreur
"Cannot modify header information - headers already sent".
Étape 4 - Ajouter une table action dans la base et enregistrer l'historique
On veut conserver un journal des actions de la partie, accessible même après fermeture
du navigateur. Ce journal sera stocké dans une nouvelle table SQLite action.
Modifier la méthode creerTables() dans Database.php pour créer cette nouvelle table
en plus de celle existante :
CREATE TABLE IF NOT EXISTS action (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tour INTEGER NOT NULL,
description TEXT NOT NULL,
date_action TEXT NOT NULL
)
Ajouter dans Database.php 2 méthodes :
ajouterAction($tour, $description): insère une ligne dans la tableactionavec la date courante (date('Y-m-d H:i:s')).chargerHistorique($limite): retourne les$limitedernières actions, triées de la plus récente à la plus ancienne (ORDER BY id DESC LIMIT :limite).
Ajouter dans Jeu.php une méthode enregistrerAction($tour, $description) qui délègue
à $this->database->ajouterAction(...). Ce passage par Jeu évite d'appeler Database
directement depuis index.php.
Dans index.php, appeler $jeu->enregistrerAction(...) dans chaque bloc d'action,
avec un message descriptif, après avoir construit la variable $message.
Il est conseillé d'utiliser date('Y-m-d H:i:s') pour stocker les dates en base de données
dans un format standard (ISO 8601). Ce format est trié correctement par ordre alphabétique,
ce qui le rend compatible avec un tri SQL ORDER BY date_action. Éviter de stocker des
dates dans des formats locaux (comme "14/04/2026") qui ne se trient pas correctement.
Étape 5 - Afficher l'historique des actions
Ajouter dans la partie HTML de index.php une section qui affiche les dix dernières
actions de la partie, sous forme de liste ordonnée ou de tableau.
Charger l'historique depuis Jeu (qui délègue à Database) :
$historique = $jeu->getDatabase()->chargerHistorique(10);
Afficher chaque ligne avec le numéro de tour, l'heure et la description.
Cette section doit être masquée si l'historique est vide (par exemple au tout début d'une nouvelle partie).
htmlspecialchars() à l'affichage de données textuellesIl est conseillé d'appliquer htmlspecialchars() lors de l'affichage de données textuelles
lues depuis la base, même si elles ont déjà été filtrées à l'insertion. Cette "défense en
profondeur" garantit que même une donnée qui aurait échappé à la vérification initiale
ne pourra pas provoquer d'injection HTML lors de l'affichage.
Étape 6 - Afficher un récapitulatif des personnages sous le plateau
Ajouter sous le plateau de jeu une section listant tous les personnages avec leurs statistiques complètes, y compris ceux qui ont été éliminés.
Pour chaque personnage, afficher :
- son nom et son type ;
- ses PV courants ;
- pour un guerrier : son armure et sa frappe (dps) ;
- pour un mage : sa mana et sa puissance de sort (dps) ;
- son état : "Vivant" ou "Éliminé" selon ses PV.
Utiliser instanceof pour adapter l'affichage selon le type. Styler différemment
les lignes des personnages éliminés (par exemple en gris, avec une opacité réduite).
Étape 7 - Afficher la bannière de fin de partie
Quand $partieTerminee est true, afficher une bannière visuelle de fin de partie
en haut de la page, avec le nom du vainqueur et un texte d'invitation à recommencer.
Cette bannière doit être visuellement distincte du reste de la page : couleur de fond marquée, texte centré, taille de police plus grande.
Étape 8 - Vérification finale et tests complets
À ce stade, le jeu est complet. Cette étape consiste à le tester méthodiquement, scénario par scénario, pour s'assurer que tout fonctionne comme attendu.
Voici les scénarios à vérifier :
Scénario 1 - Partie de base
- Lancer le serveur et charger la page (
http://localhost:8000). - Si la base est vide, créer un guerrier et un mage avec des caractéristiques différentes.
- Vérifier que les 2 personnages s'affichent sur le plateau.
- Déplacer le guerrier vers le mage (plusieurs fois).
- Faire attaquer le guerrier sur le mage. Vérifier que les PV du mage diminuent.
- Faire attaquer le mage sur le guerrier (avec
attaquer()). Vérifier que les dégâts correspondent à la puissance du sort.
Scénario 2 - Fin de partie
- Répéter les attaques jusqu'à ce qu'un personnage soit éliminé.
- Vérifier que la bannière de fin s'affiche avec le bon vainqueur.
- Vérifier que les formulaires d'action ont disparu.
- Cliquer sur "Nouvelle partie" et confirmer. Vérifier que la page revient à l'état initial.
Scénario 3 - Limites du plateau
- Essayer de déplacer un personnage au-delà du bord du plateau.
- Vérifier que le personnage reste sur sa case (le déplacement est annulé sans message d'erreur).
Scénario 4 - Persistance
- Créer des personnages et effectuer quelques actions.
- Fermer l'onglet du navigateur.
- Rouvrir
http://localhost:8000. Vérifier que les personnages et l'historique sont toujours là. - Vérifier que le compteur de tour repart de 1 (il est dans la session, qui a été effacée).
Tester systématiquement les cas limites (bords du plateau, mana nulle, personnage déjà mort) est aussi important que de tester les cas classiques. Un jeu qui plante ou se comporte de manière incohérente dans des situations rares décourage les joueurs. Prévoir et gérer ces cas, même avec un simple "rien ne se passe", est une marque de qualité.
Solution complète
Solution complète du TP
Vous devez être connecté pour voir le contenu.
Exercices complémentaires
Exercice A - Stocker la vie maximale pour une barre de progression exacte
La barre <progress> utilise actuellement max="100" comme valeur fixe. Si un personnage
est créé avec 120 PV, la barre débordera (valeur supérieure au max).
Ajouter une colonne vie_max dans la table personnage de SQLite, initialisée au moment
de la création et jamais modifiée ensuite. Mettre à jour les méthodes sauvegarderPersonnage
et chargerPersonnages dans Database.php, ainsi que la classe Personnage (nouvelle
propriété $vieMax sans setter public).
Utiliser $p->getVieMax() comme valeur max de la balise <progress>.
Exercice B - Limiter le nombre de personnages par joueur
Pour l'instant, chaque joueur peut créer autant de personnages qu'il veut.
Ajouter une règle dans Jeu::creerPersonnage() : si le joueur possède déjà 2 personnages,
refuser la création et retourner un message d'erreur.
Modifier la méthode pour qu'elle retourne null en cas de succès, ou une chaîne d'erreur
si la limite est atteinte. Traiter ce retour dans index.php.
Exercice C - Surcharger __toString dans Mage pour l'affichage en barre de récapitulatif
La méthode __toString() héritée de Personnage affiche le nom et les points de vie.
Pour le mage, on voudrait aussi voir sa mana dans le récapitulatif texte.
Ajouter dans la classe Mage une méthode __toString() qui renvoie :
Le mage Mario a 80 points de vie et 100/100 de mana
Vérifier que print $mage dans index.php utilise bien la nouvelle méthode,
et que print $guerrier utilise toujours celle de Personnage.
Ce qu'il faut retenir
| Notion | Résumé |
|---|---|
session_start() | Démarre ou reprend une session PHP. Doit être appelé avant tout affichage. |
$_SESSION | Tableau superglobal associé à la session du visiteur. Persiste entre les requêtes tant que la session est active. |
unset($_SESSION['cle']) | Supprime une valeur de la session. |
date('Y-m-d H:i:s') | Retourne la date et l'heure courantes au format SQL standard. |
substr($s, $debut, $longueur) | Extrait une sous-chaîne à partir d'une position donnée. |
<progress value="" max=""> | Balise HTML5 pour afficher une barre de progression. |
<s>texte</s> | Affiche du texte barré en HTML. |
| Fin de partie | Vérifier après chaque action si le nombre de personnages vivants est ≤ 1. |
| Historique en base | Stocker les événements dans une table action avec horodatage pour les rendre persistants. |
| Session vs base | Sessions : légères, éphémères, liées au navigateur. Base de données : persistante, partagée entre tous les navigateurs. |
| Tour partagé en base | Stocker le numéro de tour dans SQLite (table partie) le rend commun à tous les joueurs. |
<meta http-equiv="refresh" content="3"> | Recharge la page automatiquement toutes les 3 secondes. Permet au joueur en attente de voir les actions de l'adversaire sans JavaScript. |
| Identification par session | Chaque joueur stocke son numéro (1 ou 2) dans $_SESSION['joueur']. La session est propre à chaque navigateur. |
Bilan des 6 séances
À l'issue de ces six séances, le projet "Jeu de combat PHP" est une application web complète, jouable à 2 en réseau local :
| Fichier | Rôle |
|---|---|
Personnage.php | Classe abstraite : modèle commun à tous les personnages |
Guerrier.php | Classe fille : guerrier avec armure |
Mage.php | Classe fille : mage avec mana et attaque par sort |
Database.php | Accès à SQLite : CRUD personnages, historique, tour partagé |
Jeu.php | Logique du jeu : actions, règles, validation, gestion du tour |
index.php | Interface : identification du joueur, formulaires POST, <meta refresh> |
styles.css | Feuille de styles CSS |
plateau.php | Affichage de la grille HTML |
jeu.db | Base de données SQLite (générée automatiquement) |
Les notions abordées au fil des séances :
| Séance | Notions clés |
|---|---|
| 1 | Variables, fonctions, conditions, require_once, print |
| 2 | Classes, private/public, accesseurs, héritage, abstract, __toString |
| 3 | Classes filles, valeurs par défaut, instanceof, switch, boucles for, grille HTML |
| 4 | SQLite, PDO, requêtes préparées, FETCH_ASSOC, persistance |
| 5 | Formulaires POST, $_POST, isset(), intval(), htmlspecialchars(), classe Jeu |
| 6 | Sessions $_SESSION, historique en base, fin de partie, tour partagé, <meta refresh>, date(), substr() |