Introduction réseaux et web : TP

Le jeu 2048, deuxième séance

Ce document est soumis au Copyright © présent sur cette page.

Tous les TP se font en environnement Unix.

Vous devez redémarrer votre machine de la salle de TP sous Unix si elle est sous Windows.

Pré-requis

Pour faire ce TP, vous devez reprendre des choses faites lors du TP précédent :

Rappels
Aide / Mémo

Pour travailler depuis chez vous sur le serveur web que vous utilisez en TP, suivez les instructions sur cette page.

La plupart des questions posées dans les sujets de TP sont suivies d'une Aide / Mémo.
Avant de faire une question, il faut lire l'Aide / Mémo qui suit la question.

Objectifs du TP

L'objectif de ce TP est d'implémenter côté serveur l'algorithme du jeu 2048 en PHP pour faire fonctionner le jeu sur la page que vous avez conçue lors du TP précédent :

Pour cela, vous allez enrichir les fichiers jeu-2048.php, fonctions-2048.php et styles2048.css du TP précédent.

Affichage de la grille et du score après une action de jeu

Avant de programmer le jeu, il est nécessaire que la grille s'affiche après un clic sur l'un des boutons du jeu.

Affichage de la grille après une action de jeu

Pour l'instant, la page du jeu est jeu-2048/index.html. Lorsque vous cliquez sur un bouton, l'URL devient :
https://lifrw.univ-lyon1.fr/prenom.nom/jeu-2048/jeu-2048.php?action-joueur=xxxxxx est remplacé par la valeur du bouton cliqué.

Question

Pour commencer, il faut que chaque action de jeu affiche la grille comme le fait actuellement jeu-2048/index.html. Pour cela, il faut transférer dans jeu-2048/jeu-2048.php le code HTML de jeu-2048/index.html.

Aide

Comme vous l'avez vu au TP précédent, rappelez-vous qu'une page de type .php peut contenir du code HTML comme le ferait une page de type .html. Le code HTML est alors renvoyé au navigateur web. En revanche, si vous souhaitez que le serveur exécute du code PHP avant de renvoyer le résultat au navigateur, il faut l'inclure entre les balises <?php instructions; ?>.

Modification du score et variables globales

Le score d'un joueur vaut initialement 0 et augmente à chaque fois que des cases sont fusionnées. Vous aurez donc besoin d'une variable qui peut augmenter à chaque action du joueur.

Question

Créez une variable globale $score initialisée à 0.

Aide

Une variable globale doit être déclarée au début du fichier php qui utilise la variable. Dans votre cas, vous déclarerez les variables globales dans le fichier jeu-2048.php avant d'inclure le fichier fontions-2048.php car les fonctions vont également utiliser les variables globales.

Pour tester l'incrémentation de cette variable, vous allez créer une nouvelle fonction qui augmente le score de 1 à chaque fois que l'utilisateur clique sur le bouton Nouvelle partie.

Question

Dans votre fichier fontions-2048.php, créez une fonction affiche_score() qui incrémente le score de 1 si le bouton Nouvelle partie est cliqué puis qui affiche le score dans la page HTML qui sera renvoyée au navigateur web. Testez cette fonction en l'appelant dans le fichier jeu-2048.php. Quel score s'affiche si vous cliquez une fois sur Nouvelle partie ?

Question

Quel score s'affiche si vous cliquez une nouvelle fois sur Nouvelle partie ? L'affichage du score est-il correct ? Comment expliquez-vous cette valeur ?

Aide

Pour utiliser une variable globale à l'intérieur d'une fonction, il faut la redéclarer au début de la fonction précédée du mot-clé global.

Pour tester si l'utilisateur a cliqué sur le bouton Nouvelle partie, utilisez la condition suivante :
if ($_GET['action-joueur'] == "Nouvelle partie") { instruction1; } else { instruction2; }
Plus tard dans le sujet, vous pourrez procéder de manière similaire pour vérifier si l'utilisateur a cliqué sur Haut, Bas, Droite ou Gauche.

Pour afficher le contenu de la variable $score côté serveur avant ou après l'exécution de la fonction affiche_score() ou pour afficher des messages de debug, utilisez les logs comme vous l'avez fait au TP précédent.

Vous avez dû constater que la variable $score ne dépasse jamais la valeur 1 même si vous cliquez plusieurs fois de suite sur le bouton Nouvelle partie. En affichant la valeur de $score dans les logs avant de l'incrémenter, vous avez vu que le score repasse à 0 à chaque nouveau clic. En effet, après avoir incrémenté le score sur le serveur, sa valeur n'est pas conservée. Pour conserver la valeur du score entre deux actions successives, il est nécessaire de la stocker quelque part car le serveur ne conserve pas l'état des variables entre deux requêtes.
Pour conserver la valeur du score, vous utiliserez un fichier texte. Avant chaque incrémentation, vous allez lire la valeur du score dans ce fichier texte, la modifier dans votre programme, puis écrire la nouvelle valeur dans le fichier pour la retrouver à l'action suivante.

Question

Créez un fichier texte score.txt avec un 0 dedans. Pour cela, placez-vous dans le dossier jeu-2048 et tapez la commande echo 0 > score.txt. Vérifiez que le contenu est correct avec la commande cat.

Question

Ajoutez une fonction score_vers_fichier() qui copie la valeur de $score dans score.txt.

Ajoutez une fonction fichier_vers_score() qui copie dans $score la valeur lue dans score.txt.

Question

Modifiez votre fonction affiche_score() pour lire le nombre stocké dans score.txt avant de l'incrémenter, puis pour écrire la nouvelle valeur dans le même fichier. Vous utiliserez les deux fonctions précédentes.
Vérifiez que le score s'incrémente bien plusieurs fois lorsque vous cliquez plusieurs fois sur le bouton Nouvelle partie.

Aide

Pour lire le contenu d'un fichier, utilisez la fonction file_get_contents($fichier);. Cette fonction retourne une chaîne de caractères remplie avec le contenu de tout le fichier $fichier.

Pour écrire le contenu de $variable dans le fichier $fichier, vous pouvez utiliser la fonction file_put_contents($fichier, $variable);. Si le fichier contenait déjà quelque chose, l'ancien contenu est remplacé par $variable. Pour conserver l'ancien contenu et ajouter $variable à la fin du fichier, il faut faire : file_put_contents($fichier, $variable, FILE_APPEND);

Contrairement au langage C, les variables n'ont pas de type prédéfini en PHP. Il est possible d'ajouter 1 à une chaîne de caractères contenant un nombre.

Nouvelle partie, partie perdue et tirages aléatoires d'un nouveau nombre
Nouvelle partie, représentation et stockage de la grille côté serveur

Votre grille ne contient pour l'instant que des valeurs statiques : chaque case contient la valeur 0. Pourtant, les valeurs contenues dans la grille vont évoluer à chaque action de jeu. Comme pour le score, il va falloir conserver les valeurs de la grille dans un fichier texte côté serveur : grille.txt. Pour manipuler la grille dans votre programme, vous utiliserez une variable globale $grille qui sera une matrice, c'est à dire un tableau 4*4 à deux dimensions. Vous pourrez donc accéder à la valeur de la case i,j en utilisant $grille[$i][$j]$i et $j sont les coordonnées de la case dans la grille, $i et $j variant entre 0 et 3.

Une case vide dans la grille sera représentée par la valeur 0 à la fois dans la variable $grille et le fichier grille.txt. En revanche, lors de l'affichage de la grille dans le navigateur du joueur, vous n'afficherez rien quand la valeur de la case est 0.

Pour stocker la grille dans grille.txt, utilisez le format suivant :

Par exemple, une grille vide est stockée sous la forme suivante :
		0 0 0 0
		0 0 0 0
		0 0 0 0
		0 0 0 0
		

Question

Déclarez dans jeu-2048.php une variable globale $grille.

Question

Ajoutez dans fonctions-2048.php une fonction nouvelle_partie() qui initialise le score à 0 et la grille comme un tableau 2D rempli avec la valeur 0 dans toutes les cases.

Question

Ajoutez une fonction matrice_vers_fichier() qui copie dans grille.txt les valeurs stockées dans $grille. Vous utiliserez deux boucles for imbriquées.

Ajoutez une fonction fichier_vers_matrice() qui copie dans $grille les valeurs stockées dans grille.txt.

Dans grille.txt, faut-il mettre un espace après le dernier nombre de chaque ligne ? Que va faire la fonction fichier_vers_matrice() si tel est le cas ? Modifiez la fonction matrice_vers_fichier() en conséquence si nécessaire.

Question

Ajoutez une fonction affiche_case($i,$j) qui affiche avec echo le contenu de la case $i,$j. Si le contenu est égal à 0, vous n'afficherez rien.

Question

Testez les quatre fonctions que vous venez d'écrire :
Dans jeu-2048.php. si l'utilisateur clique sur Nouvelle partie, il faut exécuter la fonction nouvelle_partie() sinon il faut exécuter fichier_vers_matrice().
Appelez la fonction matrice_vers_fichier() à la fin de jeu-2048.php.
Pour tester la fonction affiche_case($i,$j), modifiez le code HTML qui affiche la grille dans jeu-2048.php. Vous utiliserez deux boucles for imbriquées dans lesquelles vous appelerez affiche_case($i,$j).

Aide

Rappel : pour utiliser une variable globale à l'intérieur d'une fonction, il faut la redéclarer au début de la fonction avec global.

Rappel : en informatique, la numérotation commence généralement à 0. La première case de la grille est $grille[0][0], la dernière est $grille[3][3].

Rappel : pour débugger, pensez à utiliser les logs comme vous l'avez vu au TP précédent.

La fonction array_fill($index,$num,$val) permet de remplir un tableau avec $num éléments où chaque élément vaut $val et l'indice du premier élément est $index. Par exemple, array_fill(0,4,array_fill(0,4,0)); permet de définir un tableau de quatre éléments, chaque élément étant lui-même un tableau de 4 éléments valant tous 0.

La strucutre d'une boucle for est : for ($i=0; $i < 10; $i++) { instructions; }

Aide

Pour la fonction fichier_vers_matrice(), il faut utiliser la fonction explode() qui permet de récupérer dans un tableau les différents morceaux d'une chaine de caractères en précisant le séparateur des morceaux. Dans l'exemple qui suit, le séparateur est le caractère espace :

$chaine = "1 2 3 4 5 6";
$valeurs = explode(' ', $chaine);
echo $valeurs[0]; // affiche "1"
echo $valeurs[2]; // affiche "3"
			

Vous pouvez vous inspirer du code ci-dessous pour écrire la fonction fichier_vers_matrice() :

// $chaine va contenir tout ce qu'il y a dans le fichier 'grille.txt'
$chaine = file_get_contents('grille.txt');
// on remplace dans $chaine tous les sauts de ligne par des espaces
$chaine = str_replace("\n", " ", $chaine);
// $valeurs est un tableau 1D qui va contenir tous les nombres de la grille
$valeurs = explode(' ', $chaine);
$n = 0;
for ($i = 0; $i < 4 ; $i++)
{
	for ($j = 0; $j < 4; $j++) 
	{
		$grille[$i][$j] = (int) ($valeurs[$n]);
		$n++;
	}
}
			

Tirages aléatoires des nombres et des coordonnées dans la grille, partie perdue

Au début de la partie, après un clic sur Nouvelle partie, il faut remettre le score à 0 (déjà fait), initialiser la grille (déjà fait), et faire apparaître deux fois le nombre 2 dans la grille. Pour placer ces deux nombres 2, il faut tirer aléatoirement leur position dans la grille.

Question

Dans fonctions-2048.php, ajoutez une fonction tirage_position_vide() qui retourne un tableau de deux éléments i et j correspondant aux coordonnées de la case de la grille qui va accueillir un nouveau nombre. Les valeurs i et j doivent être tirées aléatoirement entre 0 et 3 jusqu'à obtenir une position correspondant à une case vide. Vous supposerez que cette fonction n'est appelée que s'il reste au moins une case vide.

Question

Dans la fonction nouvelle_partie(), mettez deux fois le nombre 2 dans $grille. Vous appelerez tirage_position_vide() pour placer chaque nombre. Testez que tout fonctionne correctement en cliquant plusieurs fois sur le bouton Nouvelle partie.

Aide

Pour tirer un nombre aléatoirement, on utilise la fonction rand(borne_inf, borne_sup). Pour faire le tirage jusqu'à obtenir une case vide, vous utiliserez une boucle while (condition) { instructions; }

Par ailleurs, après chaque action du joueur, il faut tirer aléatoirement un nombre dont la valeur est soit 2, soit 4, ce nombre devant être placé aléatoirement dans une case vide de la grille, à condition que la grille ne soit pas pleine.

Question

Ajoutez une fonction grille_pleine() qui retourne true si la grille est pleine, false sinon. La grille est pleine si elle ne contient plus de zéro.

Ajoutez une fonction tirage_2ou4() qui retourne aléatoirement la valeur 2 ou 4.

Ajoutez une fonction place_nouveau_nb() qui fait appel à tirage_2ou4() et tirage_position_vide() pour placer un nouveau nombre 2 ou 4 dans une case vide de $grille, le nombre et la case étant tirés aléatoirement.

Question

Si un bouton de direction est cliqué, appelez la fonction place_nouveau_nb() à condition que la grille ne soit pas pleine. Si la grille est pleine, affichez un message au joueur pour lui dire que la partie est perdue. Testez que tout fonctionne correctement en appuyant sur les boutons de direction plusieurs fois jusqu'à remplir la grille et perdre la partie.

Aide

Pour tirer aléatoirement la valeur 2 ou 4, vous tirerez aléatoirement un nombre entre 1 et 2 et vous le multiplierez par 2.

A ce stade, votre programme permet de démarrer une nouvelle partie, de stocker l'état de la grille sur le serveur, d'ajouter dans la grille un nouveau nombre lors de chaque action de jeu et de déclarer la partie perdue lorsque la grille est pleine.

Algorithme du jeu : décalage et fusion des cases de la grille
Il reste maintenant à traiter les actions du joueur lorsqu'il clique sur un bouton de direction. Il y a principalement deux opérations à réaliser : le décalage des cellules dans la direction indiquée par le joueur et la fusion de deux cases voisines si elles contiennent la même valeur. La gestion du score sera traitée plus loin dans le sujet.
Décalage de toutes les cellules dans la direction souhaitée par le joueur

Lorsque le joueur clique sur un bouton de direction, il faut commencer par décaler ou tasser tous les nombres de la grille dans le sens indiqué par le joueur. Par exemple, si le joueur clique sur Gauche, sur chaque ligne, il faut décaler tous les nombres vers la gauche en ne laissant aucune case vide entre deux nombres. Rappelez-vous que les cases vides sont représentées par des zéros. Voici un exemple de décalage vers la gauche :

	Avant		Après
	0 2 0 4		2 4 0 0
	4 4 0 2		4 4 2 0
	0 0 4 4		4 4 0 0
	0 2 2 0		2 2 0 0
		

Question

Dans fonctions-2048.php, ajoutez une fonction decale_ligne_gauche($l) qui réalise le décalage vers la gauche de la ligne $l.

Question

Pour faire un décalage de toutes les lignes vers la gauche, il faut appeler quatre fois la fonction précédente. Testez que le décalage vers la gauche fontionne bien en utilisant une boucle for.

Aide

Rappel : en informatique, la numérotation commence généralement à 0 donc $l varie entre 0 et 3.

Pour faire le décalage, vous pouvez utiliser un tableau temporaire $ligne qui va stocker la nouvelle ligne après décalage :

$ligne = array_fill(0,4,0);
$i = 0;
for ($j = 0; $j < 4; $j++)
{
	if ($grille[$l][$j] != 0)
	{
		$ligne[$i] = $grille[$l][$j];
		$i++;
	}
}
$grille[$l] = $ligne;
	

Vous venez de traiter le décalage vers la gauche. Le décalage vers la droite est similaire sauf qu'il faut traiter la ligne dans l'autre sens. Le décalage vers le haut ou le bas est similaire également sauf qu'il faut procéder par colonne au lieu de le faire par ligne.

Question

En vous inspirant du décalage à gauche, ajoutez les fonctions decale_ligne_droite($l), decale_col_haut($c) et decale_col_bas($c).

Question

Testez que les décalages fontionnent bien dans tous les sens. Pour appliquer le décalage aux quatre lignes ou colonnes de la grille, vous utiliserez une boucle for dans laquelle vous insérerez un switch pour appeler la bonne fonction selon le bouton de direction choisi par le joueur.

Aide

Rappel : pour débugger, pensez à utiliser les logs comme vous l'avez vu au TP précédent.

Pour faire le décalage en colonne, vous ne pourrez pas faire $grille[$c] = $colonne; comme cela a été fait pour les lignes car $grille[$c] n'est pas une colonne. A la place, il faut utiliser une boucle for pour recopier les valeurs de $colonne dans $grille.

Voici la structure d'un switch :

switch ($variable) 
{
	case 'gauche':
	// instructions exécutées si $variable == 'gauche';
	break;
	case 'droite':
	// instructions exécutées si $variable == 'droite';
	break;
	// on peut mettre autant de case que l'on veut
}			
			

Sommation/fusion des cases de la grille

Après avoir fait le décalage dans la direction choisie par le joueur, il faut faire la sommation des cases adjacentes qui ont une valeur identique. Nous appelerons fusion cette opération. Il ne peut pas y avoir de fusions multiples : une case qui est le résultat d'une fusion ne peut pas fusionner de nouveau lors de cette action de jeu. Si deux cases fusionnent, une case contient la somme des deux cases fusionnées, l'autre case est laissée vide.

Comme pour le décalage, la fusion sera effectuée sur la ligne $l ou la colonne $c. Comme pour le décalage, vous allez faire une fonction de fusion différente pour chacune des quatre directions.

Voici deux exemples de fusion vers la gauche qui regroupent la plupart des cas possibles :

		Exemple 1			Exemple 2
	Avant		Après		Avant		Après
	2 4 0 0		2 4 0 0		4 8 4 4		4 8 8 0
	4 4 2 0		8 0 2 0		2 2 4 4		4 0 8 0
	4 2 2 4		4 4 0 4		2 2 2 0		4 0 2 0
	4 2 2 2		4 4 0 2		2 2 2 2		4 0 4 0
		

Après la fusion, il faut refaire un décalage pour éliminer les zéros qui sont apparus lors de la fusion.

Question

Ajoutez une fonction fusion_ligne_gauche($l) qui réalise la fusion vers la gauche des cases adjacentes de valeur égale sur la ligne $l.

Question

Pour faire la fusion sur toutes les lignes vers la gauche, il faut appeler quatre fois la fonction précédente. Testez que la fusion vers la gauche fontionne bien en utilisant une boucle for.

Aide

Pour faire la fusion vers la gauche, vous pouvez utiliser le code ci-dessous :

if ($grille[$l][0] == $grille[$l][1])
{
	$grille[$l][0] = 2 * $grille[$l][0];
	$grille[$l][1] = 0;
	if ($grille[$l][2] == $grille[$l][3])
	{
		$grille[$l][2] = 2 * $grille[$l][2];
		$grille[$l][3] = 0;		
	}		
}
else if ($grille[$l][1] == $grille[$l][2])
{
	$grille[$l][1] = 2 * $grille[$l][1];
	$grille[$l][2] = 0;
}	
else if ($grille[$l][2] == $grille[$l][3])
{
	$grille[$l][2] = 2 * $grille[$l][2];
	$grille[$l][3] = 0;
}	
	

Vous venez de traiter la fusion vers la gauche. La fusion vers la droite est similaire sauf qu'il faut traiter la ligne dans l'autre sens. La fusion vers le haut ou le bas est similaire également sauf qu'il faut procéder par colonne au lieu de le faire par ligne.

Question

En vous inspirant de la fusion à gauche, ajoutez les fonctions fusion_ligne_droite($l), fusion_col_haut($c) et fusion_col_bas($c).

Question

Testez que les fusions fontionnent bien dans tous les sens. Pour appliquer la fusion aux quatre lignes ou colonnes de la grille, vous appelerez les fonctions de fusion dans le switch utilisé pour le décalage. N'oubliez pas qu'il faut refaire un décalage après la fusion pour éliminer les zéros qui sont apparus du fait de la fusion.

A ce stade, le jeu fonctionne. Bravo ! Il ne reste plus qu'à gérer le calcul du score et à donner une couleur différente aux cases de la grille selon la valeur du nombre contenu dans la case.

Calcul du score et coloration des cases de la grille
Evolution du score lors de chaque nouvelle fusion

Concernant le score, vous disposez d'une variable globale $score, d'un fichier score.txt pour conserver le score entre deux actions du joueur et des fonctions score_vers_fichier() et fichier_vers_score(). La fonction affiche_score() est utilisée pour afficher le score dans la page du jeu.

Dans le jeu 2048, le score initial vaut 0. Ensuite, lors de chaque fusion, on ajoute au score la somme des cases fusionnées. Par exemple, si le score vaut 0 et que l'on fusionne deux cases valant 2, le score passe à 4.

Question

Modifiez les fonctions de fusion pour prendre en compte l'augmentation du score lors de chaque nouvelle fusion. Testez que le score s'affiche correctement à chaque nouvelle action de jeu.

Coloration des cases en fonction de la valeur des nombres

Pour l'instant, toutes les cases ont la même couleur. Or, dans le jeu 2048, les cases sont de plus en plus foncées en fonction de leur valeur. Il faut donc appliquer un style qui dépend de la valeur contenue dans la case. Pour cela, il faut d'une part définir un style différent pour chaque valeur possible dans la feuille de styles styles2048.css et, d'autre part, appliquer ce style dans le code HTML généré par jeu-2048.php.

Question

Ajoutez dans styles2048.css les styles permettant de définir une couleur de fond différente pour chaque valeur possible : 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048 et la cellule vide.

Question

Dans fonctions-2048.php, modifiez la fonction affiche_case($i,$j) pour qu'elle génére le code HTML permettant d'afficher la case de coordonnées [$i],[$j]. Vous utiliserez un switch pour faire un affichage différent selon la valeur contenue dans la case.

Question

Testez que l'affichage est correct.

Aide

Si votre grille est un tableau HTML, voici ci-dessous un exemple de code pour la valeur 32. Si vous avez choisi d'utiliser des blocs plutôt qu'un tableau HTML, il vous suffit de remplacer td par div.

switch ($grille[$i][$j]) 
{ 
	case 32:
		echo "<td class='c32'> valeur </td>";
		break;
}
			

Voici des exemples de couleur que vous pouvez utiliser :

Valeur 0 : #f5f5f5
Valeur 2 : #eeed4a
Valeur 4 : #ede0c8
Valeur 8 : #f2b179
Valeur 16 : #f59563
Valeur 32 : #f67c5f
Valeur 64 : #f65e3b
Valeur 128 : #edcf72
Valeur 256 : #edcc61
Valeur 512 : #edc850
Valeur 1024 : #edc53f
Valeur 2048 : #edc22e
			

Bravo, vous avez terminé le jeu 2048 ! Tant que la partie n'est pas perdue, le jeu continue même si le nombre 2048 a été atteint. Il s'agit alors d'atteindre le plus haut score possible.

Question

Vérifiez que la fin de partie se passe comme prévu : la partie est perdue si la grille est pleine et qu'aucune fusion n'est possible. Si ce n'est pas le cas, modifiez votre code.

Plutôt que de faire 4 fonctions de décalage (une dans chaque direction) et 4 fonctions de fusion (idem), il aurait été préférable d'écrire une fonction decalage($tab) et une fonction fusion($tab)$tab est un tableau 1D qui contient la ligne ou la colonne à décaler/fusionner. Avant l'appel de ces fonctions, $tab est rempli dans le bon ordre en tenant compte de la direction choisie par le joueur. Ces fonctions doivent retourner un nouveau tableau qui correspond au tableau décalé/fusionné.

Question

Ecrivez les fonctions decalage($tab) et fusion($tab) puis modifiez jeu-2048.php pour remplir $tab selon la direction choisie, appeler ces fonctions et modifier la grille en conséquence grâce au tableau retourné par chaque fonction.

A faire avant la prochaine séance

Entre deux séances de TP, vous avez au moins 15 jours pour :

Aide / Mémo

Pour travailler depuis chez vous sur le serveur web que vous utilisez en TP, suivez les instructions sur cette page.


Ce document est soumis au Copyright © présent sur cette page.