5) Interactions
Formulaires et actions de jeu

Objectifs de la séance
- Comprendre le cycle requête/réponse et comment un formulaire HTML déclenche du code PHP.
- Écrire des formulaires HTML utilisant la méthode POST.
- Lire et valider les données envoyées par un formulaire avec
$_POSTetisset(). - Convertir les données reçues avec
intval()ethtmlspecialchars(). - Créer la classe
Jeuqui centralise la logique du jeu et fait le lien entre l'interface et la base de données. - Utiliser la syntaxe alternative PHP (
if():/endif;) dans les templates HTML. - Sauvegarder l'état du jeu en base après chaque action du joueur.
Notions théoriques
Le cycle d'une interaction avec un formulaire
Jusqu'ici, index.php s'exécutait de la même manière à chaque requête : il chargeait
les personnages, affichait la page, et s'arrêtait.
Le joueur n'avait aucun moyen d'agir.
Avec un formulaire, le cycle devient le suivant :
- Le navigateur envoie une requête GET vers
index.php(premier chargement). - PHP génère la page HTML avec le formulaire et l'envoie au navigateur.
- L'utilisateur remplit le formulaire et clique sur le bouton.
- Le navigateur envoie une requête POST vers
index.php, en joignant les données du formulaire. - PHP reçoit ces données dans le tableau
$_POST, les traite (déplacement, attaque...), sauvegarde les changements en base, puis génère à nouveau la page HTML mise à jour.
À chaque clic sur un bouton, on recommence à partir de l'étape 4.
C'est ce cycle répété qui permet de jouer.
La méthode POST
Un formulaire HTML peut envoyer ses données de deux manières :
- GET : les données apparaissent dans l'URL (
?nom=Lara&vie=100). Utile pour des recherches ou des filtres, mais inadapté pour des actions qui modifient des données. - POST : les données sont envoyées dans le corps de la requête HTTP, invisibles dans l'URL. C'est la méthode à utiliser pour toute action qui modifie l'état du jeu.
<form method="POST" action="index.php">
<input type="text" name="nom" />
<input type="submit" value="Envoyer" />
</form>
En PHP, les données envoyées en POST sont accessibles via le tableau superglobal $_POST :
if (isset($_POST['nom'])) {
$nom = $_POST['nom'];
}
$_POST est un tableau associatif dont les clés correspondent aux attributs name
des champs du formulaire.
Vérifier et nettoyer les données reçues
Toute donnée reçue depuis l'extérieur (formulaire, URL, cookie...) doit être traitée avec méfiance. Deux règles minimales à respecter systématiquement :
1. Vérifier l'existence avec isset()
isset($variable) renvoie true si la variable existe et n'est pas null.
Sans cette vérification, accéder à $_POST['action'] quand aucun formulaire n'a été
soumis provoquerait un avertissement PHP (notice : undefined index).
if (isset($_POST['action'])) {
$action = $_POST['action'];
// traiter l'action
}
2. Convertir le type avec intval()
Toutes les valeurs de $_POST sont des chaînes de caractères, même quand elles
représentent des nombres. Si un champ <input type="number" name="vie"> renvoie "100",
PHP voit une chaîne, pas un entier. La fonction intval() convertit une chaîne en entier :
$vie = intval($_POST['vie']); // "100" devient 100
3. Neutraliser le HTML avec htmlspecialchars()
Si une valeur textuelle saisie par l'utilisateur est réaffichée telle quelle dans la page,
un utilisateur malveillant pourrait y injecter des balises HTML ou du JavaScript
(attaque XSS). htmlspecialchars() convertit les caractères spéciaux en entités HTML :
$nom = htmlspecialchars($_POST['nom']);
// "<script>" devient "<script>" et s'affiche comme du texte
La syntaxe alternative PHP dans les templates
Quand on mélange PHP et HTML (dans la partie affichage d'une page), les accolades {}
peuvent rendre le code difficile à lire. PHP propose une syntaxe alternative plus lisible :
| Syntaxe classique | Syntaxe alternative |
|---|---|
if (...) { | if (...): |
} | endif; |
foreach (... as ...) { | foreach (... as ...): |
} | endforeach; |
<?php if (count($personnages) > 0): ?>
<ul>
<?php foreach ($personnages as $p): ?>
<li><?php print $p->getNom(); ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
Cette syntaxe est préférable dans les templates (fichiers qui mélangent HTML et PHP)
car elle rend immédiatement visible à quoi correspond chaque endif ou endforeach,
sans avoir à chercher l'accolade ouvrante correspondante.
Le champ hidden pour identifier l'action
Quand une même page contient plusieurs formulaires (créer un personnage, déplacer, attaquer...),
il faut distinguer lequel a été soumis. La solution standard est d'ajouter un champ caché
(type="hidden") dans chaque formulaire, avec une valeur identifiant l'action :
<form method="POST" action="index.php">
<input type="hidden" name="action" value="deplacer" />
<!-- ... autres champs ... -->
</form>
Côté PHP, on lit $_POST['action'] pour savoir quelle action traiter.
Le rôle de la classe Jeu
À ce stade, index.php orchestre tout : il charge les personnages, traite les actions,
sauvegarde, et affiche. Plus le jeu gagne en fonctionnalités, plus ce fichier devient
ingérable.
La classe Jeu va centraliser toute la logique métier :
- charger et sauvegarder la partie via
Database; - valider et exécuter les actions (déplacer, attaquer, vérifier les limites du plateau) ;
- exposer les données nécessaires à l'affichage.
index.php ne fera plus que lire les données de $_POST, déléguer le traitement à Jeu,
puis afficher le résultat. Ce découpage est essentiel pour maintenir un code lisible
quand la complexité augmente.
Point de départ du TP
Ce TP reprend le projet issu de la séance 4.
Le dossier de notre projet contient actuellement les fichiers suivants :
Personnage.php,Guerrier.php,Mage.php,Database.php,index.php- et
jeu.db.
À la fin de cette séance, notre projet contiendra un fichier supplémentaire :
| Fichier | Statut | Description |
|---|---|---|
Personnage.php | inchangé | classe abstraite parente |
Guerrier.php | inchangé | classe fille guerrier |
Mage.php | inchangé | classe fille mage |
Database.php | inchangé | gestion SQLite |
Jeu.php | nouveau | logique centrale du jeu |
index.php | refondu | traitement des formulaires POST + affichage |
jeu.db | existant | fichier de base de données SQLite |
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Rendre le jeu interactif avec des formulaires POST
Étape 1 - Comprendre ce que PHP reçoit quand un formulaire est soumis
Avant de traiter les données d'un formulaire, il est utile de comprendre exactement ce que PHP reçoit.
Modifier index.php pour y ajouter un petit formulaire de test, et afficher le contenu
de $_POST à chaque soumission :
<form method="POST" action="index.php">
<label for="test_nom">Nom :</label>
<input type="text" name="test_nom" id="test_nom" />
<label for="test_vie">Vie :</label>
<input type="number" name="test_vie" id="test_vie" value="100" />
<input type="submit" value="Tester" />
</form>
Ajouter en haut du fichier PHP, avant tout autre traitement :
<?php
if (isset($_POST['test_nom'])) {
print "<pre>";
var_dump($_POST);
print "</pre>";
}
?>
Soumettre le formulaire avec différentes valeurs. Observer :
- que les clés du tableau
$_POSTcorrespondent aux attributsnamedes champs ; - que toutes les valeurs sont des chaînes de caractères (
string), même le champ numérique ; - ce qui se passe quand on soumet sans remplir un champ.
Ce formulaire de test peut être supprimé à la fin de cette étape.
Avant de développer le traitement d'un formulaire, Il est conseillé de toujours vérifier
ce que PHP reçoit avec var_dump($_POST). C'est le moyen le plus rapide d'identifier
des erreurs de nommage de champs ou des problèmes de type.
Ne jamais supposer la structure de
$_POSTsans l'avoir observée.
Étape 2 - Créer la structure de la classe Jeu
Créer un nouveau fichier Jeu.php.
La classe Jeu doit :
- inclure
Database.php,Guerrier.phpetMage.phpavecrequire_once; - avoir trois propriétés privées :
$personnages(tableau),$database(objetDatabase),$taillePlateau(entier) ; - dans le constructeur, instancier
Database, initialiser$personnagesà un tableau vide[], et$taillePlateauà5; - exposer trois méthodes publiques d'accès :
getPersonnages(),getTaillePlateau()etgetDatabase().
Ne pas encore écrire les méthodes de logique métier (chargerPartie, creerPersonnage,
etc.) : on les ajoutera aux étapes suivantes.
Tester dans index.php que la classe s'instancie sans erreur :
require_once "Jeu.php";
$jeu = new Jeu();
print "<p>Taille du plateau : " . $jeu->getTaillePlateau() . "</p>";
Il est conseillé de construire une classe par étapes successives plutôt que de tout écrire d'un coup. Tester après chaque ajout garantit que les erreurs sont détectées immédiatement, au moment où on les introduit, et non après avoir accumulé des dizaines de lignes.
Étape 3 - Ajouter chargerPartie à la classe Jeu
Ajouter dans Jeu une méthode publique chargerPartie().
Elle délègue simplement la lecture à $this->database->chargerPersonnages() et stocke
le résultat dans $this->personnages.
Modifier index.php pour utiliser cette méthode et afficher les personnages chargés :
$jeu = new Jeu();
$jeu->chargerPartie();
$personnages = $jeu->getPersonnages();
// afficher les personnages...
Si la base est vide (premier lancement), le tableau sera vide. Ce cas sera géré à l'étape suivante.
Étape 4 - Ajouter le premier formulaire : créer un personnage
On va maintenant ajouter le premier formulaire jouable. Il permettra à l'utilisateur de créer un personnage en choisissant son nom, son type (guerrier ou mage), ses points de vie et ses dégâts.
Dans la partie HTML de index.php, ajouter le formulaire suivant :
<h2>Créer un personnage</h2>
<form method="POST" action="index.php">
<input type="hidden" name="action" value="creer" />
<label for="nom">Nom :</label>
<input type="text" name="nom" id="nom" required />
<br/>
<label for="type">Type :</label>
<select name="type" id="type">
<option value="guerrier">Guerrier</option>
<option value="mage">Mage</option>
</select>
<br/>
<label for="vie">Points de vie :</label>
<input type="number" name="vie" id="vie" value="100" min="1" required />
<br/>
<label for="dps">Dégâts par tour :</label>
<input type="number" name="dps" id="dps" value="10" min="1" required />
<br/>
<input type="submit" value="Créer le personnage" />
</form>
En haut du fichier PHP (avant le HTML), ajouter le traitement de base : détecter si le formulaire a été soumis et afficher les valeurs reçues pour l'instant.
<?php
require_once "Jeu.php";
$jeu = new Jeu();
$jeu->chargerPartie();
$message = "";
if (isset($_POST['action']) && $_POST['action'] == "creer") {
$nom = $_POST['nom'];
$type = $_POST['type'];
$vie = intval($_POST['vie']);
$dps = intval($_POST['dps']);
$message = "Reçu : " . $nom . " (" . $type . "), " . $vie . " PV, " . $dps . " DPS";
}
?>
Afficher $message dans la page. Tester le formulaire et vérifier que les valeurs
s'affichent correctement.
Il est conseillé d'appliquer htmlspecialchars() sur toute chaîne saisie par l'utilisateur
avant de l'afficher dans la page. Cette précaution élimine les risques de failles XSS
(Cross-Site Scripting), qui permettraient à un utilisateur malveillant d'injecter
du code JavaScript dans la page affichée aux autres visiteurs.
Étape 5 - Ajouter creerPersonnage à la classe Jeu
Ajouter dans Jeu une méthode publique creerPersonnage($nom, $type, $vie, $dps).
Elle doit :
- Déterminer automatiquement la position de départ du personnage selon son rang :
le premier personnage créé part en
(0, 0)(coin supérieur gauche), le deuxième en(4, 4)(coin inférieur droit, à l'opposé). Cette logique est encapsulée dans la méthode —index.phpn'a pas à connaître les coordonnées de départ. - Créer l'objet approprié selon
$type: unGuerrier(avec armure5, stockée dans$mana) ou unMage(avec mana100). - Sauvegarder le personnage en base via
$this->database->sauvegarderPersonnage($p), récupérer l'identifiant retourné et l'attribuer au personnage avec$p->setId(...). - Ajouter le personnage dans
$this->personnages.
Modifier index.php pour appeler cette méthode lorsque le formulaire est soumis,
puis recharger la liste des personnages depuis la base.
Tester : créer deux personnages, vérifier que le premier apparaît en (0, 0) et le
second en (4, 4), puis recharger la page et vérifier qu'ils persistent.
Après une action qui modifie la base (création, mise à jour, suppression), Il est conseillé de toujours recharger les données depuis la base avant d'afficher la page. Cela garantit que ce qui est affiché reflète exactement ce qui est en base, sans décalage entre les objets en mémoire et les données persistées.
Étape 6 - Ajouter le formulaire de déplacement
Le plateau de jeu est maintenant alimenté par la base. On veut permettre au joueur de déplacer un personnage d'une case dans une des quatre directions.
Au lieu d'une liste déroulante pour choisir la direction, on utilisera quatre boutons directionnels disposés en croix, placés à droite du plateau de jeu. Cette disposition est à la fois plus intuitive et plus proche de ce qu'un joueur attend d'un jeu.
La mise en page côte à côte (plateau à gauche, contrôles à droite) s'obtient avec du CSS flexbox sur un conteneur englobant.
La solution la plus propre sans JavaScript consiste à placer le sélecteur de combattant
et les quatre boutons dans un seul et même formulaire. Chaque bouton est un
<button type="submit" name="direction" value="..."> : quand on clique dessus, HTML
soumet le formulaire en incluant la valeur de ce bouton dans $_POST['direction'], ainsi
que la valeur courante du <select name="personnage"> visible. Le sélecteur et les boutons
partagent ainsi le même formulaire, ce qui garantit que le combattant sélectionné est
bien celui qui est déplacé.
Ajouter la méthode deplacerPersonnage($index, $direction) dans la classe Jeu.
Elle doit :
- Vérifier que le personnage existe dans
$this->personnages[$index]avecisset(). - Sauvegarder les coordonnées actuelles avant le déplacement.
- Appeler
$p->deplacer($direction). - Vérifier que le personnage ne sort pas du plateau (coordonnées entre 0 et
$taillePlateau - 1). Si c'est le cas, annuler le déplacement en restaurant les coordonnées sauvegardées. - Mettre à jour le personnage en base avec
$this->database->mettreAJourPersonnage(...).
Quand une information peut être déduite du contexte côté serveur (ici, le joueur possède un seul personnage identifiable par sa session), ne pas la redemander à l'utilisateur. Supprimer un champ superflu simplifie l'interface et élimine une surface d'attaque : un joueur malveillant ne peut plus envoyer l'index d'un personnage qui ne lui appartient pas.
Étape 7 - Afficher le plateau avec les personnages chargés depuis la base
On va maintenant reconnecter le plateau de jeu (grille HTML) avec les personnages
chargés depuis la base via la classe Jeu.
Le code de la grille est identique à celui de la séance 3, mais $personnages est
maintenant fourni par $jeu->getPersonnages() et la taille du plateau par
$jeu->getTaillePlateau().
Intégrer la grille dans index.php (après le traitement des formulaires, dans la partie HTML).
Vérifier que :
- les personnages créés s'affichent à leur position ;
- après un déplacement, le personnage apparaît bien à sa nouvelle case ;
- le plateau est vide si aucun personnage n'est en base.
Étape 8 - Ajouter le formulaire d'attaque
Ajouter dans la classe Jeu une méthode attaquer($indexAttaquant, $indexCible).
Elle doit :
- Vérifier que les deux index existent dans
$this->personnagesavecisset(). - Vérifier que l'attaquant et la cible ne sont pas le même personnage (un personnage ne peut pas s'attaquer lui-même).
- Vérifier que la cible est encore en vie (points de vie supérieurs à 0).
- Appeler
$attaquant->attaquer($cible). - Mettre à jour la cible en base.
Ajouter dans la partie HTML de index.php le formulaire d'attaque, visible uniquement
s'il y a au moins deux personnages. Chaque liste déroulante (attaquant et cible)
affiche les personnages vivants uniquement.
Construire la liste des vivants dans index.php (côté affichage)
plutôt que dans Jeu. C'est un choix de présentation (n'afficher que les vivants
dans le formulaire), pas une règle du jeu. La distinction entre logique métier
(dans les classes) et logique de présentation (dans index.php) doit toujours
être respectée.
Étape 9 - Afficher un message de retour après chaque action
Pour l'instant, après une action, la page se recharge en silence. L'utilisateur ne sait pas si son action a fonctionné. Il faut afficher un message clair.
La variable $message est déjà initialisée à "" en haut du fichier. Elle est remplie
dans chaque bloc de traitement. Il reste à l'afficher dans la page HTML de manière visible.
Ajouter dans la partie HTML, juste après la balise <body>, un bloc conditionnel
qui affiche le message dans une <div> stylisée, uniquement si $message n'est pas vide.
Utiliser la syntaxe alternative if(): / endif;.
Affiner également les messages pour qu'ils soient plus informatifs :
- pour la création : indiquer le nom et le type du personnage créé ;
- pour le déplacement : indiquer le nom du personnage et sa nouvelle position ;
- pour l'attaque : indiquer le nom de l'attaquant, le nom de la cible et les PV restants.
Pour les deux derniers cas, il faut lire l'état du jeu après l'action
(appeler chargerPartie() avant de construire le message).
Il est conseillé de toujours informer l'utilisateur du résultat de ses actions. Une interface qui ne donne aucun retour force l'utilisateur à inspecter la page pour comprendre ce qui s'est passé. Un message simple et précis améliore considérablement l'expérience.
Étape 10 - Ajouter un bouton de réinitialisation de la partie
Pour pouvoir recommencer une partie, ajouter un formulaire simple avec un seul bouton :
<h2>Gestion de la partie</h2>
<form method="POST" action="index.php">
<input type="hidden" name="action" value="reinitialiser" />
<input type="submit" value="Nouvelle partie" />
</form>
Ajouter dans la classe Jeu une méthode reinitialiser() qui appelle
$this->database->supprimerTousLesPersonnages() et réinitialise $this->personnages
à un tableau vide.
Traiter cette action dans index.php.
Une précaution importante : ce bouton supprime toutes les données. Ajouter un attribut
onclick qui demande une confirmation à l'utilisateur avant d'envoyer le formulaire :
<input type="submit" value="Nouvelle partie"
onclick="return confirm('Supprimer tous les personnages et recommencer ?');" />
Attention, toute action destructrice (suppression, réinitialisation) doit demander confirmation avant d'être exécutée. Même dans un prototype de jeu, cette règle préserve du clics accidentels. Dans une application de production, on irait plus loin avec une double confirmation ou une page dédiée.
Solution complète
Solution complète du TP
Vous devez être connecté pour voir le contenu.
Exercices complémentaires
Exercice A - Afficher l'identifiant de chaque personnage dans le plateau
Modifier l'affichage des cellules de la grille pour y faire apparaître l'identifiant
SQLite de chaque personnage (son id). Cela sera utile pour déboguer les problèmes
de mise à jour.
Vérifier dans DB Browser for SQLite que les identifiants correspondent bien à ceux stockés en base.
Exercice B - Permettre la création d'un troisième personnage ou plus
La méthode creerPersonnage() place le 1er personnage en (0, 0) et le 2ème en (4, 4).
Si l'on crée un 3ème personnage (ou davantage), la règle actuelle le placerait aussi en
(4, 4), ce qui provoquerait une superposition.
Modifier creerPersonnage() pour que tout personnage au-delà du deuxième soit placé
sur la première case libre trouvée en parcourant le plateau de gauche à droite,
ligne par ligne.
Exercice C - Ajouter la validation côté serveur sur la création
Pour l'instant, la validation de la création repose uniquement sur les attributs HTML
(required, min). Un utilisateur peut contourner ces contrôles en modifiant le HTML
dans l'inspecteur du navigateur ou en envoyant une requête HTTP directement.
Ajouter une validation côté PHP avant d'appeler creerPersonnage() :
- Le nom ne doit pas être vide (après
trim()). - Les points de vie doivent être supérieurs à 0.
- Les dégâts doivent être supérieurs à 0.
- Le type doit être
"guerrier"ou"mage".
En cas d'erreur, mettre un message d'erreur dans $message et ne pas créer le personnage.
Ce qu'il faut retenir
| Notion | Résumé |
|---|---|
| Méthode POST | Les données de formulaire sont envoyées dans le corps de la requête, invisibles dans l'URL. |
$_POST | Tableau superglobal contenant les données envoyées par un formulaire POST. |
isset() | Vérifie qu'une variable existe et n'est pas null avant d'y accéder. |
intval() | Convertit une chaîne en entier. Toutes les valeurs de $_POST sont des chaînes. |
htmlspecialchars() | Neutralise les balises HTML dans les données utilisateur. Protège contre les failles XSS. |
trim() | Supprime les espaces superflus en début et fin de chaîne. |
Champ hidden | Champ invisible dans le formulaire, utilisé pour transmettre l'identifiant de l'action. |
| Syntaxe alternative | if(): / endif;, foreach(): / endforeach; - plus lisible dans les templates HTML. |
Classe Jeu | Centralise la logique métier. index.php délègue les actions à Jeu, qui délègue la persistance à Database. |
| Séparation des responsabilités | Logique métier dans les classes, présentation dans index.php. |
Aperçu de la prochaine séance
Le jeu est désormais interactif : on peut créer des personnages, les déplacer et les faire combattre. Il manque encore la détection de fin de partie et quelques améliorations d'expérience de jeu.
La séance 6 finalisera l'application : système de tours, victoire quand un seul personnage reste en vie, historique des actions de la partie, et amélioration de l'affichage global.