Projet tutoré : programmation du jeu 2048

Projet tutoré : programmation du jeu 2048
L’objectif de ce projet tutoré est de coder en langage C le jeu 2048. Pour connaître les règles,
se reporter aux informations de la page Web :
http://fr.wikipedia.org/wiki/2048_(jeu_vidéo)
Le projet est à faire en binôme en dehors des heures de cours. Le code de ce programme
devra impérativement respecter les spécifications données dans ce document. Cela signifie que
la structure de données devra être la même que celle donnée ci-dessous et toutes les fonctions
demandées (avec les mêmes en-têtes) devront être implémentées. Il est cependant possible d’ajouter
d’autres structures de données et/ou fonctions. Ce travail sera évalué lors de 4 séances. À chaque
séance, chaque binôme aura un créneau de 15 minutes défini préalablement pendant lequel il sera
évalué.
La première partie explique le travail à réaliser et la deuxième décrit l’évaluation du projet en
indiquant notamment le travail à présenter à chaque séance d’évaluation.
1
Travail à réaliser
Le développement du programme se divise en 4 parties. Chaque partie est décrite précisément
dans ce qui suit.
1.1
Partie 1
Cette partie explique la structure de données à définir, correspondant à une partie de 2048.
Elle donne aussi les fonctions de base à implémenter, permettant de modifier cette structure de
données.
Le jeu 2048 se joue sur une grille 4 * 4, et le but est d’atteindre la valeur 2048. On souhaite
pouvoir créer une version dans laquelle le joueur peut choisir la taille de la grille, ainsi que la
valeur à atteindre pour gagner.
Question 1 : Définir le type structuré jeu contenant les champs suivants :
– un entier n correspondant à la taille de la grille (n * n),
– un entier valMax correspondant à la valeur à atteindre pour gagner,
– un entier nbCasesLibres correspondant au nombre de cases libres sur la grille,
– un pointeur d’entiers grille permettant de stocker l’adresse de la grille allouée dynamiquement.
Il est très important pour la suite de comprendre le champ grille. Ce pointeur contiendra
l’adresse d’un tableau à une dimension alloué dynamiquement. Ce tableau représentera la grille
du jeu. Les lignes seront codées les unes à la suite des autres. Les n premières cases du tableau
grille représenteront la première ligne de la grille, les n suivantes la deuxième ligne, etc. Chaque
case de ce tableau contiendra la valeur de la case (0 si la case est vide).
Ainsi, la grille de la figure 1 sera représentée par le tableau grille suivant :
0
0
2
4
0
0
4
8
0
1
2
16
32
0
2
2
16
Figure 1 – Exemple de grille
Question 2 : Définir la fonction initialiseJeu prenant en paramètre un pointeur sur une
variable de type jeu, un entier correspondant à la taille de la grille et un entier correspondant à
la valeur que l’on souhaite atteindre pour gagner. La fonction allouera le tableau correspondant
à la grille, l’initialisera avec des cases vides, et initialisera les autres champs avec les valeurs
appropriées.
1
2
3
4
5
6
7
8
9
10
/*!
* Alloue la grille de la variable jeu passée par adresse.
* Initialise les cases de la grille avec des cases vides (valeurs nulles)
* Initialise les champs n et valMax avec les valeurs passées en paramètre
*
* \param p : pointeur sur une partie de 2048
* \param n : taille de la grille
* \param valMax : valeur à atteindre pour gagner
*/
void initialiseJeu (jeu * p, int n, int valMax) {...}
Question 3 : Définir la fonction libereMemoire prenant en paramètre un pointeur sur une
variable de type jeu et libérant la mémoire allouée pour stocker la grille.
1
2
3
4
5
6
/*!
* Libère la mémoire allouée pour la grille du jeu passé par adresse.
*
* \param p : pointeur sur une partie de 2048
*/
void libereMemoire(jeu * p) {...}
Question 4 : Définir la fonction indiceValide prenant en paramètre un pointeur sur une variable
de type jeu et deux entiers i et j, et retournant 1 si la case (i,j) existe (i et j correspondent à
des indices valides de la grille), et 0 sinon.
Remarque : Les indices i et j sont valides s’ils correspondent à des entiers compris entre 0
(inclus) et n (exclus), où n est la taille de la grille.
1
2
3
4
5
/*!
* Fonction retournant 1 si la case (i,j) existe, 0 sinon.
*
*/
int indiceValide (jeu * p, int i, int j) {...}
Question 5 : Définir la fonction getVal prenant en paramètre un pointeur sur une variable de
type structuré jeu, un numéro de ligne et un numéro de colonne et retournant la valeur de la case
2
dans le damier. La fonction vérifiera d’abord que les numéros de ligne et colonne sont valides. Si
les indices ne sont pas valides, la fonction retournera -1.
1
2
3
4
5
6
7
8
9
1
2
3
1
2
3
/*!
* Fonction retournant la valeur de la case (ligne,colonne) de la partie p,
* ou -1 si la case n’existe pas.
*
* \param p : pointeur sur la partie en cours
* \param ligne : entier correspondant au numéro de ligne
* \param colonne : entier correspondant au numéro de colonne
*/
int getVal(jeu * p, int ligne, int colonne) { ... }
Exemple : Supposons que pEx est un pointeur sur une variable de type jeu dont le champ grille
correspond à la représentation mémoire de la figure 1. L’exécution du code :
printf ("Valeur sur la case (2,0) : %d\n", getVal(pEx,2,0));
printf ("Valeur sur la case (0,2) : %d\n", getVal(pEx,0,2));
printf ("Valeur sur la case (5,5) : %d\n", getVal(pEx,5,5));
affichera alors :
Valeur sur la case (2,0) : 0
Valeur sur la case (0,2) : 2
Valeur sur la case (5,5) : -1
Question 6 : Définir la fonction setVal permettant de modifier la valeur d’une case de la grille.
Si les indices de ligne et de colonne sont valides, alors la fonction modifiera la valeur de la case
(i,j) avec la valeur passée en paramètre.
1
2
3
4
5
6
7
8
9
/*!
* Fonction modifiant la valeur de la case (ligne,colonne) de la partie p, avec la valeur val
*
* \param p : pointeur sur la partie en cours
* \param ligne : entier orrespondant au numéro de ligne
* \param colonne : entier orrespondant au numéro de colonne
* \param val : entier à mettre dans la case (i,j) (si elle existe)
*/
void setVal(jeu * p, int ligne, int colonne, int val) { ... }
Question 7 : Définir la fonction affichage. Cette fonction devra afficher à l’écran la grille du
jeu passé en paramètre.
1
2
3
4
5
6
/*!
* Fonction affichant la grille à l’écran.
*
* \param p : pointeur sur la partie que l’on souhaite afficher
*/
void affichage (jeu * p) { ... }
Il existe plusieurs façons de réaliser l’affichage de la grille. On peut distinguer 3 manières :
– Affichage simple : on affiche les valeurs du champ grille sous forme d’un tableau à deux
dimensions. Un exemple est donné par la figure 2.
– Affichage moyen : Le contour des cases est tracé à l’aide d’un symbole (par exemple l’étoile).
Un exemple est donné par la figure 3.
3
Figure 2 – Affichage simple de la grille
Figure 3 – Affichage moyen de la grille
– Affichage difficile : on affiche les cases sur plusieurs lignes et colonnes en utilisant des couleurs. Un exemple est donné par la figure 4. Pour utiliser les couleurs du terminal, vous
pouvez utiliser les fichiers couleursTerminal.c et couleursTerminal.h qui se trouvent sur
la page http://lipn.univ-paris13.fr/~lacroix/enseignement.php. Les explications se
trouvent dans le deuxième fichier en commentaires. De plus, les valeurs sont centrées et une
case vide est représentée à l’aide d’un point.
Figure 4 – Affichage difficile de la grille
Vous pouvez choisir l’une des trois méthodes d’affichage, sachant que plus la méthode est
simple, moins elle rapporte de points. (Cependant, il vaut vraiment mieux faire la plus méthode
simple correctement que la méthode la plus difficile fausse !)
1.2
Partie 2
Les fonctions à implémenter dans cette partie sont seront utiles par la suite pour programmer
le jeu.
Question 8 : Définir la fonction caseVide prenant en paramètre un pointeur sur un jeu et deux
entiers i et j, et retournant 1 si la case est vide et 0 sinon. Si la case n’existe pas, la fonction
retourne 0.
1
2
3
4
/*
* Retourne 1 si la case est vide, 0 sinon
*/
int caseVide(jeu * p, int i, int j){...}
4
Question 9 : Définir la fonction ajouteValAlea prenant en paramètre un pointeur sur un jeu. Si
le jeu passé en paramètre contient au moins une case libre, alors la fonction ajoute aléatoirement
une case (de valeur 2 ou 4) dans la grille.
1
2
3
4
5
/*
* Ajoute une valeur (2 ou 4 choisi aléatoirement) sur une case vide
* (elle aussi choisie aléatoirement).
*/
void ajouteValAlea(jeu * p) {...}
Question 10 : Définir la fonction gagne prenant en paramètre un pointeur sur un jeu et,
retournant 1 si la partie est gagnée et 0 sinon. La partie est gagnée s’il y a une valeur dans la grille
supérieure ou égale à la valeur maximum (membre valMax).
1
2
3
4
/*
* Retourne 1 si la partie est gagnée, 0 sinon.
*/
int gagne(jeu * p) {...}
Question 11 : Définir la fonction perdu prenant en paramètre un pointeur sur un jeu. La fonction
retournera 1 si la partie est perdue, et 0 sinon. La partie est perdue s’il n’y a aucune case libre et
s’il n’y a pas deux valeurs égales consécutives (horizontalement ou verticalement).
1
2
3
4
/*
* Retourne 1 si la partie est perdue, 0 sinon.
*/
int perdu(jeu * p) {...}
Question 12 : Définir la fonction finPartie prenant en paramètre un pointeur sur un jeu, et
retournant 1 si la partie est finie (gagnée ou perdue), et 0 sinon.
1
2
3
4
/*
* Retourne 1 si la partie est terminée, 0 sinon.
*/
int finPartie (jeu * p) {...}
1.3
Partie 3
Les fonctions à réaliser dans cette partie permettent de jouer au jeu 2048.
Question 13 : Définir la fonction mouvementLigne prenant en paramètre un pointeur sur jeu,
un indice de ligne et une direction (1 pour la gauche et -1 pour la droite). La fonction effectuera
le déplacement des cases sur la ligne associée. De plus, elle retournera 1 si au moins une case a été
déplacée, et 0 sinon.
1
2
3
4
5
6
7
8
9
/*
* Effectue les mouvements (à gauche ou à droite) des cases d’une ligne.
* Renvoie 1 si l’on a deplacé au moins une case, 0 sinon.
*
* \param p : pointeur sur un jeu
* \param ligne : indice de ligne
* \param direction : 1 pour déplacement vers la gauche et -1 pour un déplacement vers la droite
*/
int mouvementLigne(jeu *p, int ligne, int direction) {...}
5
Question 14 : Définir la fonction mouvementLignes prenant en paramètre un pointeur sur jeu
et une direction (1 pour la gauche et -1 pour la droite). La fonction effectuera le déplacement des
cases sur toutes les lignes. De plus, elle retournera 1 si au moins une case a été déplacée, et 0
sinon.
1
2
3
4
5
6
7
8
/*!
* Effectue les mouvements (à gauche ou à droite) des cases sur toutes les lignes.
* Renvoie 1 si l’on a deplacé au moins une case, 0 sinon.
*
* \param p : pointeur sur un jeu
* \param direction : 1 pour déplacement vers la gauche et -1 pour un déplacement vers la droite
*/
int mouvementLignes(jeu * p, int direction) { ... }
Question 15 : Définir la fonction mouvementColonne prenant en paramètre un pointeur sur jeu,
un indice de colonne et une direction (-1 vers le bas et 1 vers le haut). La fonction effectuera le
déplacement des cases sur la colonne associée. De plus, elle retournera 1 si au moins une case a
été déplacée, et 0 sinon.
1
2
3
4
5
6
7
8
9
/*
* Effectue les mouvements (vers le haut ou vers le bas) des cases d’une colonne.
* Renvoie 1 si l’on a deplacé au moins une case, 0 sinon.
*
* \param p : pointeur sur un jeu
* \param colonne : indice de colonne
* \param direction : -1 pour déplacement vers la bas et 1 vers le haut
*/
int mouvementColonne(jeu *p, int colonne, int direction) {...}
Question 16 : Définir la fonction mouvementColonnes prenant en paramètre un pointeur sur jeu
et une direction (-1 vers le bas et 1 vers le haut). La fonction effectuera le déplacement des cases
sur toutes les colonnes. De plus, elle retournera 1 si au moins une case a été déplacée, et 0 sinon.
1
2
3
4
5
6
7
8
/*
* Effectue les mouvements (vers le haut ou vers le bas) des cases de toutes les colonnes.
* Renvoie 1 si l’on a deplacé au moins une case, 0 sinon.
*
* \param p : pointeur sur un jeu
* \param direction : -1 pour déplacement vers la bas et 1 vers le haut
*/
int mouvementColonnes(jeu *p, int direction) {...}
Question 17 : Définir la fonction mouvement prenant en paramètre un pointeur sur jeu et une
direction et effectuant le mouvement correspondant. La fonction renvoie 1 si au moins une case a
été déplacée, et 0 sinon.
1
2
3
4
5
6
7
8
9
10
/*
*
*
*
*
*
*
*
*
*
Effectue le mouvement sur les lignes ou sur les colonnes suivant la valeur de direction.
\param p : pointeur sur un jeu
\param direction : entier donnant la direction :
0 : vers le bas
1 : vers la droite
2 : vers le haut
3 : vers la gauche
Renvoie 1 si l’on a deplacé au moins une case, 0 sinon
6
*/
11
12
int mouvement(jeu * p, int direction);
Question 18 : Définir la fonction saisieD permettant à l’utilisateur de saisir la direction de
déplacement, ou d’arrêter le jeu. La fonction retournera -1 si l’utilisateur arrête la partie en cours,
et un entier correspondant à la direction souhaitée sinon (les valeurs de direction sont les mêmes
que dans la question précédente).
1
2
3
4
5
6
7
8
9
10
11
/*
* Fonction permettant la saisie d’une direction
* (saisie répétée pour les autres touches)
* Retourne :
* -1 si l’utilisateur arrête le jeu
* 0 si l’utilisateur souhaite déplacer vers le
* 1 si l’utilisateur souhaite déplacer vers le
* 2 si l’utilisateur souhaite déplacer vers le
* 3 si l’utilisateur souhaite déplacer vers le
*/
ou de l’arrêt du jeu
BAS
DROITE
HAUT
GAUCHE
int saisieD();
Il y a plusieurs façons de réaliser cette saisie. On peut distinguer deux manières (une facile et
une plus compliquée) :
– Cas simple : on demande à l’utilisateur d’utiliser les touches i, j, k et l pour les directions
et d’appuyer sur s pour arrêter le jeu. On répète la saisie pour toute autre touche saisie. À
chaque saisie, l’utilisateur appuie sur la touche entrée pour valider son choix.
Attention : Pour ne pas lire les sauts de lignes, il faut utiliser l’instruction
1
scanf(" %c",&car);
pour lire un caractère au clavier et le stocker dans la variable car de type char. Noter qu’il
y a un espace avant le symbole pourcentage.
– Cas difficile : On propose à l’utilisateur de saisir une direction avec les flèches directionnelles
et de sortir du jeu avec la touche echap. De plus, l’utilisateur n’appuie pas sur la touche entrée
pour valider son choix. Pour cela, vous devrez utiliser les fichiers saisieM.c et saisieM.h
qui se trouvent sur la page http://lipn.univ-paris13.fr/~lacroix/enseignement.php.
Les explications se trouvent dans le fichier d’en-tête.
Vous pouvez choisir l’une des deux méthodes, sachant que la méthode simple rapporte un peu
moins de points que la manière difficile. (Cependant, il vaut vraiment mieux faire la méthode
simple correctement que la manière difficile fausse !)
Question 19 : Définir la fonction jouer permettant de jouer au 2048. La fonction prendra en
paramètre un jeu en cours (qui a déjà été initialisé) et permettra à l’utilisateur de jouer jusqu’à
ce qu’il arrête le jeu (lors de sa saisie) ou jusqu’à ce que le jeu se termine.
1
2
3
4
5
6
/*
* Fonction permettant de jouer la partie en cours (on la suppose initialisée)
* Retourne 1 si la partie est terminée (l’utilisateur a gagné ou perdu), et 0 sinon
* (l’utilisateur a appuyé sur la touche Echap ou la touche s).
*/
int jouer(jeu * p);
1.4
Partie 4
Cette partie introduit les fonctions de menu, de sauvegarde et de chargement de partie.
Question 20 : Définir les fonctions de sauvegarde et chargement d’une partie.
7
1
2
3
4
5
6
7
1
2
3
4
5
6
7
/*!
* Fonction sauvegardant la partie en cours
* Retourne 0 en cas de problème, 1 sinon.
*
* \param p : pointeur sur la partie courante à sauvegarder
*/
int sauvegarde(jeu * p) { ... }
/*!
* Fonction chargeant la dernière partie sauvegardée.
* Retourne 0 en case de problème, 1 sinon.
*
* \param p : adresse du pointeur sur le jeu
*/
int chargement(jeu * p) { ... }
On supposera qu’une seule partie peut être sauvegardée à la fois. Le chargement et la sauvegarde
se feront à l’aide du fichier sauvegarde.txt. Vous choisirez vous-même le format de ce fichier.
Question 21 : Définir la fonction menu. La fonction doit afficher la liste des choix possibles. La
saisie est répétée jusqu’à ce que l’utilisateur choisisse une des options proposées.
1
2
3
4
5
6
7
8
9
/*
* Affiche le menu :
*
1 - Jouer
*
2 - sauvegarder
*
3 - charger
*
4 - terminer le programme
* Retourne la valeur saisie par l’utilisateur (saisie contrôlée)
*/
int menu() { ... }
Question 22 : Écrire le programme principal permettant de jouer à 2048. Le programme doit
permettre à l’utilisateur de sauvegarder ou charger une partie à tout instant. De plus, plusieurs
parties doivent pouvoir être jouées (dès qu’une partie est terminée, une nouvelle est automatiquement créée). Le programme ne s’arrête que lorsque l’utilisateur le décide lors du menu.
Question 23 : Séparer le code en plusieurs fichiers sources et créer un makefile permettant la
compilation du programme.
Améliorations : Il est possible bien sûr d’améliorer le code de ce programme en ajoutant d’autres
fonctionnalités telles que :
– plusieurs sauvegardes possibles (par exemple 5 sauvegardes différentes),
– calcul du score du joueur,
– sauvegarde des scores des différents joueurs,
– etc.
Vous pouvez si vous le souhaitez (et si tout le reste a été réalisé) ajouter ces fonctionnalités
(ou d’autres) dans votre programme. Ces ajouts pourront faire l’objet de quelques points bonus.
2
Évaluation du projet
Le projet tutoré sera évalué au cours de 4 séances, chaque séance correspondant à l’évaluation
d’une partie (autrement dit, le travail à présenter lors de la première séance correspond à celui
décrit dans la partie 1, etc). L’évaluation portera sur :
8
– l’implémentation des fonctions demandéees à chaque séance,
– la pertinence des jeux d’essai réalisés permettant de tester les différents cas de figure
pour chaque fonction demandée,
– les réponses aux questions posées lors de l’évaluation.
9