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
© Copyright 2024 ExpyDoc