1. Introduction

Pendant la pandémie de la COVID-19, j'ai beaucoup joué avec des amis à des jeux en ligne, tels que le pictionary et le petit bac. Ces jeux d'apparence simple nous ont beaucoup fait rire. Nous y avons passé des soirées entières. Ces moments funs m'ont marqué, c'est ainsi que j'ai eu l'idée de faire un site WEB sur lequel on pourrait jouer. J'ai pris le puissance 4, car j'y joue souvent, en classe bizarrement...

1.1 But

Mon but, comme vous l'aurez compris, est de créer un site fonctionnel sur lequel on pourrait jouer au puissance 4. Pour ce faire, je vais devoir apprendre à utiliser du PHP, du CSS, de l'HTML et du SQL. Le site devra être capable de faire ces deux fonctions à la fois : gérer les utilisateurs et leur historique de match et créer un salon dans lequel deux joueurs s'affronteront dans un duel de puissance 4.

2. Matériel et méthodes

2.1 Matériel

  • Un serveur permettant d'exécuter du code PHP
  • Une base de données MySQL
  • Un ordinateur ayant une connexion internet
  • Un IDE comme Visual Studio Code avec l'extension "Remote - SSH" (pas obligatoire, mais cela aide grandement)

2.2 Méthode

Afin de créer un site de jeu comme un puissance 4, il nous faut élaborer un site dynamique. Pour cela, le langage de programmation PHP est parfaitement adapté. L'avantage de ce langage est qu'il est exécuté du côté du serveur. Cela permet, pour un même URL, que deux utilisateurs aient deux pages complétement différentes . Dans notre cas, la page de jeu est un exemple. Le joueur a trois statuts possibles: en attente qu'un deuxième joueur rejoigne la partie; en attente que l'adversaire joue; capable de jouer un coup. La page sera différente pour chaque cas. La base de données SQL servira à stocker les différentes informations telles que les pseudos des utilisateurs, leur mot de passe, les coups des joueurs, etc. La figure 1 montre la structure du site. Les utilisateurs seront obligés d'être identifiés pour pouvoir jouer, cela dans un but de simplification du projet.

Structure du site WEB, avr. 2022
Figure 1 - Structure du site


2.2.1 La base de données SQL

Une fois que la structure du site est faite, il faut réfléchir à comment la base de données sera organisée. Elle est le noyau du site. Pour satisfaire les demandes du site WEB, il nous faut créer deux tables: une qui stockera les données des utilisateurs et l'autre les données relatives aux parties.

La première table nommée IDs comporte quatre colonnes:user_id; user_name; user_password et user_creation_date. La case user_id de chaque utilisateur aura une valeur différente et unique (clé primaire), il suffit de cocher la case auto-incrémentation. Elle nous permettra d'authentifier le joueur en le stockant dans le cookie du côté client.

Table 1, données des utilisateurs, avr. 2022
Figure 2 - Table 1, données des utilisateurs


La deuxième table nommée gameHistory comporte neuf colonnes: game_id; room_key; user_id1; user_id2; user_moves1; user_moves2; winner; is_finished; date_game. La première case de chaque partie aura une valeur différente et unique (clé primaire), il suffit de cocher la case auto-incrémentation, la deuxième contient la clé d'entrée de la partie, les troisième et quatrième contiennent les identifiants uniques de chacun des joueurs, les cases cinq et six contiennent les coordonnées des jetons joués sous forme de liste json (c'est-à-dire comme cela: [[x1,y1],[x2,y2],etc]) de chaque joueur séparément, la colonne sept contient l'identifiant du joueur victorieux, s'il y a égalité aucun identifiant n'est noté. La case numéro huit contient, sous forme de booléen, le statut de la partie (si elle est finie ou non) et enfin, la dernière case contient la date de la création de la partie. Cette façon d'organiser la table nous permet de faire l'historique des parties en plus de l'utiliser en parallèle pendant une partie. La clé de la partie est générée aléatoirement à la création du salon et est supprimée lorsque la partie est terminée. Je me suis grandement inspiré du jeu Among Us avec son système de code pour rejoindre une partie.

Table 2, données des parties, avr. 2022
Figure 3 - Table 2, données des parties


2.2.2 Les notions importantes du projet

Les sessions

Les sessions en PHP permettent de stocker des informations du côté de l'utilisateur. Dans le cadre du site, elles servent à stocker les informations suivantes : l'identifiant du joueur; l'identifiant de la partie; l'ordre de passage; le réglage son et le statut du joueur. Cela permet qu'à chaque rafraichissement de la page de jeu, le serveur puisse déterminer dans quelle situation le joueur se trouve pour lui envoyer une page HTML personnalisée.
Avant toute chose, il faut mettre ceci en haut de la page PHP:

<?php session_start(); ?>

Pour créer une session, il suffit simplement de faire ceci :

<?php
$_SESSION["game_ID"]=$game_id;
?>

Et pour supprimer une session il suffit d'utiliser la fonction unset() :

<?php
unset($_SESSION["status"];
?>

Nous pouvons utiliser les sessions comme bon nous semble. Il ne faut pas oublier de faire un test de l'existence de la session avant d'attribuer la valeur de la session à une variable.

<?php
if (isset($_SESSION["user_ID"]) { 
$user_id = $_SESSION["user_ID"];
}
?>

Ce qui ne faut surtout pas oublier sur le site est de supprimer les sessions de données liées à une partie lorsqu'elle est finie ou que l'utilisateur la quitte, car sinon elles pourraient altérer le bon déroulement de la partie suivante. C'est pour cela que quand nous retournons à la page d'accueil pendant une partie, il faut à nouveau entrer la clé de la partie si nous voulons la continuer.

Les requêtes SQL

Les requêtes SQL sont primordiales dans ce projet. Elles permettent le bon fonctionnement du jeu. A la place de créer une connexion entre les utilisateurs, il suffit de télécharger les mêmes données depuis le serveur. Dans ce projet, j'ai opté pour la méthode en orienté objet. Il faut tout d'abord créer un objet.

<?php
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
  die("Connexion échouée");
}
?>

Après l'avoir créé, nous pouvons faire des requêtes SQL :

<?php
$request = "SELECT game_id FROM table_name WHERE room_key='$room_key'";
$raw_data = $conn->query($request);
?>

La variable raw_data est encore sous forme brute. Pour pouvoir l'utiliser nous allons utiliser la méthode fetch_assoc() sur raw_data :

<?php
$data = $raw_data->fetch_assoc();
$game_id = $data["game_id"]; 
?>

Il ne faut pas oublier de fermer la connexion à la fin du script PHP.

<?php
$conn->close();
?>

Le site PHPMyAdmin est très utile pour comprendre comment faire les requêtes SQL. On peut avoir grâce à un bouton la traduction de l'action que nous faisons dans l'interface graphique en langage SQL.

La gestion des jetons et l'ajout d'un nouveau jeton

Comme dit plutôt, les différents jetons sont stockés sous forme de liste de coordonnées au format json, car il est impossible d'envoyer une liste au format PHP dans une base de données SQL. Les utilisateurs récupèrent les listes de mouvements depuis la base de donnée via une requête SQL, et ensuite les décodent en utilisant la fonction json_decode($encoded_data_list). Une fois cette étape effectuée, nous avons deux variables sous forme de liste PHP utilisables. Le puissance 4 a des règles basiques: les jetons que nous mettons sont toujours à la hauteur la plus basse possible. Dès lors, il est facile d'ajouter une nouvelle paire de coordonnées à la liste. Il suffit de savoir la position sur l'axe x, grâce à des boutons renvoyant une valeur avec la méthode POST, de compter le nombre de jetons qu'il y a dans cette colonne et d'ajouter 1 pour la coordonnée y si la colonne n'est pas déjà remplie au maximum. La fonction en-dessous retourne en parallèle une valeur booléenne, qui correspond à savoir si la coordonnée saisie est valable.

<?php
function is_last_move_legal($last_move_x) $user_moves1, $user_moves2) {
        if($last_move_x){
          $all_moves = array_merge($user_moves1,$user_moves2);
          $iteration_y = 0;
          $GLOBALS["last_move_y"] = 0; 
          foreach($all_moves as $coord) {
            if($coord[0] == $last_move_x){
              $iteration_y++; 
            }
          }
          if ($iteration_y < 6){
            // < 6 car la hauteur max est 6 au puissance 4

            $GLOBALS["last_move_y"] = $iteration_y + 1;
            return True;
          }
        }
        return False;
      }

?>

Ensuite, il ne nous reste plus qu'à l'ajouter dans la liste du bon utilisateur, encoder au format json avec la fonction json_encode($list) et faire une requête SQL pour mettre à jour la liste dans la base de données.

Les différents statuts dans une partie

Quand une partie est créée, il y a trois statuts possibles pour les joueurs: le statut en attente d'un adversaire est le statut par défaut. Il change quand dans la base de données, la case user_id2 de gameHistory est complétée. Ensuite, le joueur un commence à jouer, le joueur deux attend. Sa page est rafraîchie toutes les trois secondes. Il compare la longueur des listes de mouvements avec la fonction count(), et dès que sa liste est plus petite, cela veut dire que le joueur un a joué. Le joueur un et deux changent de statut. Le joueur un vérifie si son dernier coup ne lui a pas donné la victoire et le joueur deux vérifie s'il n'a pas perdu. La méthode pour vérifier s'il n'y a pas victoire est simple. Il suffit de faire, dans une boucle foreach de chaque coordonnée de la liste des jetons d'un joueur, trois tests if où nous regardons si trois jetons alignés, de même couleur que le jeton en train de se faire tester, existent dans la liste de mouvements.

<?php
      function controlVictory($status, $user_moves, $all_moves){
        // contrôlé s'il y a victoire
        if ($status=="waiting_for_the_second_player_to_play"){
            $win_possible = true;
        } 
        else {
          $win_possible = false;
        }
          // controle si le coup est gagnant
            foreach ($user_moves as $coord) {
              // controle a gauche
              if (in_array([$coord[0]-1,$coord[1]], $user_moves, true)) {
                if (in_array([$coord[0]-2,$coord[1]], $user_moves, true)) {
                  if (in_array([$coord[0]-3,$coord[1]], $user_moves, true)) {
                    $status = "end";
                  }
                }
              }
              // controle gauche haut
              if (in_array([$coord[0]-1,$coord[1]+1], $user_moves, true)) {
                if (in_array([$coord[0]-2,$coord[1]+2], $user_moves, true)) {
                  if (in_array([$coord[0]-3,$coord[1]+3], $user_moves, true)) {
                    $status = "end";
                  }
                }
              }
              // controle haut
              if (in_array([$coord[0],$coord[1]+1], $user_moves, true)) {
                if (in_array([$coord[0],$coord[1]+2], $user_moves, true)) {
                  if (in_array([$coord[0],$coord[1]+3], $user_moves, true)) {
                    $status = "end";
                  }
                }
              }
              // controle droite haut
              if (in_array([$coord[0]+1,$coord[1]+1], $user_moves, true)) {
                if (in_array([$coord[0]+2,$coord[1]+2], $user_moves, true)) {
                  if (in_array([$coord[0]+3,$coord[1]+3], $user_moves, true)) {
                    $status = "end";
                  }
                }
              }
            }
          if ($status == "end" and $win_possible) {
            $_SESSION["win"] = true;
          }
          elseif (count($all_moves) >= 42 and $status != "end"){
            $_SESSION["draw"] = true;
            $status = "end";
          }
          return $status; 
      }
?>

Notez que nous pouvons vérifier que la moitié des sens, car nous itérons sur l'entièreté de la liste et qu'il y a toujours un jeton qui sera le premier de la combinaison. S'il y a 42 jetons et que personne n'a gagné, cela signifie qu'il y a une égalité.

L'affichage du puissance 4

L'affichage du jeu est plutôt simple. On imprime une table HTML en PHP dans laquelle chaque cellule a une des trois classes CSS possibles en plus de celle par défaut pour les dimensions: rouge; jaune ou vide. Après, il faut simplement changer la couleur du fond dans le CSS.

<?php
        print "<table class=puissance4>";
        for($y=6; $y>0; $y--){
          print "<tr>";
          for($x=1; $x<8; $x++){
            if (in_array([$x,$y], $user_moves1, true)) {
              $color = "red";
            }
            elseif (in_array([$x,$y], $user_moves2, true)){
              $color = "yellow";
            }
            else {
              $color = "empty";
            }
  ?>
            <td class="case <?php print $color;?>"></td> 
  <?php
          }
          print "</tr>";
        }
  ?>
      <td class="case number">1</td>
      <td class="case number">2</td>
      <td class="case number">3</td>
      <td class="case number">4</td>
      <td class="case number">5</td>
      <td class="case number">6</td>
      <td class="case number">7</td>
  <?php
        print "</table>";
?>

3. Résultats

3.1 Images

Page d'accueil, avr. 2022
Figure 4 - Page d'accueil


Page de connexion, avr. 2022
Figure 5 - Page du profile, non-connecté


Page du profile, avr. 2022
Figure 6 - Page du profile, connecté


Page de jeu, statut d'attente d'un adversaire, avr. 2022
Figure 7 - Page de jeu, statut d'attente d'un adversaire


Page de jeu, statut jouable, avr. 2022
Figure 8 - Page de jeu, statut jouable


Page de jeu, statut d'attente de l'adversaire, avr. 2022
Figure 9 - Page de jeu, statut d'attente de l'adversaire


3.2 Gestion de compte

Cette partie fonctionne correctement. Un utilisateur arrive sans problème à créer son compte, il ne faut juste pas mettre un mot de passe que vous utilisez vraiment, car il est écrit tel quel dans la base de données. La connexion marche à merveille. Le tableau de l'historique des matchs s'affiche correctement. Cette partie du projet n'est clairement pas essentielle.

3.3 Le jeu

La création de la partie fonctionne bien. Le jeu est jouable. La fonction de détection de fin de partie marche très bien. Le résultat s'inscrit bien dans la base de données. Nous pouvons enchaîner sans problèmes plusieurs parties, tant que nous ne rejoignons pas directement une partie depuis une autre, sans avoir quitté la précédente.

4. Discussion

4.1 Difficultés rencontrées

Lors de ce projet j'ai fait face à beaucoup de problèmes. La syntaxe de PHP est déstabilisante si l'on vient d'un langage de programmation comme Python, il faut mettre ; à chaque fin de lignes d'instructions et ne pas oublier les accolades. La fonction var_dump() est très utile pour faire du débogage. C'est la fonction que j'ai la plus utilisée durant le projet. Elle permet de localiser rapidement où est le soucis, car elle donne beaucoup d'informations sur la variable testée. Hormis ces problèmes-ci, j'ai aussi fait face à des difficultés avec les requêtes SQL où je me suis tiré les cheveux pour comprendre qu'il fallait mettre des guillemets simples à

WHERE room_key='$key'

car room_key dans la base de données est de type str. Mais j'ai eu surtout beaucoup de problèmes avec CSS. Je changeais des paramètres d'une classe HTML, rien ne changeait à l'affichage, même en faisant CTRL + SHIFT + R. Des formulaires qui ne se centraient pas, par exemple. Le tableau de l'historique des matchs est franchement peu esthétique.

4.2 Piste d'améliorations

4.2.1 Le CSS

Le site n'est honnêtement pas des plus beaux. La page ne s'affiche pas correctement sur nos téléphones. Le CSS pourrait être améliorer.

4.2.2 Le multijoueur

Le multijoueur est limité. Nous ne pouvons jouer qu'avec une personne à qui l'on partage le code. Une solution simple possible serait d'ajouter un tableau dans la page d'accueil qui afficherait tous les codes de parties publiques.

4.2.3 La sécurité

Les mots de passe pourraient être stockés de manière plus sécurisée. Actuellement, les mots de passe sont directement visibles dans la base de données. Faire un code César, pourrait déjà améliorer la sécurité des utilisateurs.

4.2.4 Les bogues

Quand nous rafraîchissons manuellement la page dans une partie juste après avoir joué, il est possible de rester bloqué dans le mode jouable. Même si nous ne pouvons pas jouer deux fois de suite parce que le programme vérifie la longueur des listes, cela reste embêtant, car nous ne pourrons pas voir la dernière action de l'adversaire. Une solution possible serait de détecter quand le rafraîchissement de la page est fait automatiquement ou manuellement et d'agir en conséquence.
Un autre bogue possible est si nous rejoignons une partie depuis une autre directement. Les données de l'ancienne partie ne seront pas supprimées et altéreront le bon fonctionnement de la nouvelle. Une solution possible est de faire une initialisation qui ne s'exécute qu'une fois et qui supprime toutes les données en session excepté l'identité du joueur.

4.2.5 Plus de personnalisations du compte

Un ajout inutile mais sympa serait de pouvoir choisir une image de profil qui s'afficherait durant une partie. Pouvoir changer les sons du jeu pourrait être amusant et enfin de pouvoir changer son pseudo permettrait de garder son compte même si nous n'aimons plus le pseudo actuel.

4.2.6 Ajouter un mode visuel aux anciens matchs

Les listes de mouvements sont toujours stockées dans la base de données. Il serait tout à fait possible d'ajouter ce mode sachant que le code pour l'affichage du puissance 4 est déjà fait.

4.3 Le concept

Le concept d'un site WEB sur lequel on joue n'est pas nouveau. Ces sites sont souvent gratuits, car il y a des publicités, ce qui permet de pouvoir jouer avec n'importe qui. Par conséquent, les jeux proposés sur ces sites sont souvent des jeux qui rassemblent beaucoup de personnes et très amusant à jouer en appel, cela pour rentabiliser un maximum. Je doute que mon site avec un affichage moyen, en proposant un jeu simple qu'on peut jouer avec un crayon et une feuille serait beaucoup utilisé.

4.4 Ce que j'ai appris avec ce projet

Ce projet m'a permis d'apprendre les bases sur le développement WEB. J'ai appris un nouveau langage de programmation, PHP, et à gérer une base de données SQL. J'ai aussi appris que je ne ferai jamais de CSS par plaisir. J'ai observé qu'une fois les notions de base de la programmation acquises, il est plus facile d'apprendre un nouveau langage, en tout cas de Python à PHP. J'ai appris à remettre en question mes méthodes et mes réflexions afin d'accomplir mes objectifs.

5. Conclusion

Ce projet m'a fait sortir de ma zone de confort en me poussant à chaque étape à réfléchir à un moyen de mettre en place les fonctionnalités. Les moments de doute sur les méthodes employées m'ont permis de remettre en cause mes idées, pour sortir avec des solutions plus abouties et plus optimales. Pensant par exemple initialement à utiliser du Javascript, pour finir par ne pas du tout en utiliser. Je suis fier de ce projet d'informatique qui a abouti avec succès, malgré les quelques bogues toujours présents et le design. Les différentes pages du site fonctionnent et plus important encore, le jeu est jouable. Beaucoup de choses peuvent être améliorer , autres que les bogues et le CSS, comme avoir un multijoueur plus abouti, ou même ajouter carrément d'autres jeux, par exemple le pictionary ou l'espion. J'ai appris les bases et les difficultés du développement WEB. J'ai constaté que PHP et Python se ressemblent en beaucoup de points. Ainsi je sors heureux et fier de ce projet printanier, qui m'aura opposé des challenges d'apprentissage, de réflexion et de design.

Références

  1. Creately, création de schéma UML, https://creately.com/
  2. Youtube, Tutoriel CSS Navbar : 3 façons de créer une barre de navigation avec Flexbox, https://www.youtube.com/watch?v=PwWHL3RyQgk&ab_channel=Skillthrive
  3. PHP, documentation PHP, https://www.php.net/manual/fr/
  4. Aptoide, Icon du puissance 4, https://cdn6.aptoide.com/imgs/3/0/7/3071fe1f1e94926bb2e06a36f5ff1682_icon.png
  5. Lasonotheque, son de la notification,https://lasonotheque.org/detail-1112-message-2.html
  6. Freesound, son de la victoire; de la défaite et du match nul, https://freesound.org/people/FunWithSound/sounds/456966/, https://freesound.org/people/Leszek_Szary/sounds/171673/, https://freesound.org/people/tonijsstrods/sounds/628690/