:: Enseignements :: Licence :: L3 :: 2012-2013 :: Programmation C ::
[LOGO]

Morpion


L'objectif de ce TD est de réaliser un jeu simple, basé sur le principe du jeu Morpion, permettant de jouer à 1 ou 2 joueurs.

Règles du jeu

Le jeu se joue à deux joueurs sur une grille de 8x8 cases, vide en début de partie. Tour à tour, chaque joueur place un pion sur une case vide du plateau. Le premier joueur à aligner 4 pions de sa couleur (verticalement, horizontalement ou en diagonal) a gagné. Si la grille est pleine avant qu'aucun joueur n'ait gagné, la partie est déclarée nulle.

Exercice 1 - Moteur du jeu

  1. Dans un fichier plateau.h, définir les structures de données et les prototypes des fonctions nécessaires au jeu, et l'implémentation de ces fonctions dans un fichier plateau.c.

    Les points suivants devront être respectés :
    • Le type Grille contient un tableau de cases à deux dimensions alloué dynamiquement, ainsi que des informations sur les dimensions de la grille et le nombre de cases vides ;
    • Les fonctions suivantes seront déclarées :
      • Grille * initGrille(int x, int y) : alloue une grille vide de taille x*y, renvoie NULL en cas d'échec.
      • void libererGrille(Grille * G) : libère l'espace mémoire occupé par G et les données associées.
      • int placerPion(Grille * G, int J, int x, int y) : essaye de placer un pion du joueur numéro J sur la case (x,y) de la grille G, qui doit être vide. La fonction renvoie 1 si tout s'est bien passé, 0 si la case est occupée ou n'existe pas.
      • int estPleineGrille(Grille * G) : renvoie 1 si la grille G n'a plus de cases libres, 0 sinon.
      • int alignePion(Grille * G, int x, int y, int n) : renvoie 1 si un alignement de n pions d'un même joueur existe comprenant le pion situé sur la case (x,y) de G, et 0 sinon.
      • void afficherGrille(Grille * G) : affiche la grille G à l'écran.
  2. Avant de passer à la suite, écrire un jeu de tests dans un programme test_plateau.c, afin de tester le bon fonctionnement des fonctions implémentées. Utiliser la macro assert pour arrêter l'exécution du programme lorsqu'un test a échoué.
  3. Ecrire le programme principal du jeu dans le fichier jeu.c. Il conviendra de définir au moins les deux fonctions suivantes :
    • int recupererOrdre(int *x, int *y) : recupère les coordonnées x et y de la prochaine case à jouer du joueur courant. Le prototype indiqué est minimal et peut éventuellement être modifié.
    • int main() : fonction principale, contient l'initialisation des données et la boucle principale du jeu.

Exercice 2 - Gestion d'options

On va rajouter des options au programme que l'on gèrera à l'aide de la fonction getopt_long. Une liste non exhaustive des options que l'on souhaite pouvoir utiliser est :
  • -y n (ou --hauteur): fixe la hauteur du plateau à n cases ;
  • -x n (ou --largeur): fixe la largeur du plateau à n cases ;
  • -a n (ou --alignement): fixe à n le nombre de pions à aligner pour gagner ;
  • -h (ou --help) : affiche une aide et quitte le programme.
On gardera par défaut les valeurs de 8x8 pour les dimensions du plateau et de 4 pour le nombre de pions à aligner.

Exercice 3 - Joueur et stratégies

Le but de cet exercice est de donner la possibilité de jouer contre l'ordinateur. Pour cela, dans un fichier strategies.h, on définit un type Joueur qui correspondra à un joueur, humain ou ordinateur. Cette structure comprendra un entier identifiant le joueur et un pointeur vers une fonction du type :
void placePion(Joueur * J, Grille * G, int * x, int * y);
qui étant donnés un joueur J et une grille G, retourne les coordonnées x et y du prochain pion à placer. Dans le cas d'un joueur humain, la fonction pointée se chargera de récupérer les coordonnées saisies au clavier par l'utilisateur. Dans le cas d'un joueur ordinateur, la fonction correspondra à une stratégie. Les stratégies suivantes devront être implémentées en premier lieu, dans un fichier strategies.c :
  • random : statégie inexistante, le joueur pose son pion sur une case libre au hasard ;
  • defense : stratégie défensive, le joueur pose son pion de façon à gêner au mieux le plus grand alignement de pions adverses ;
  • attack : stratégie offensive, le joueur pose son pion de façon à obtenir le plus grand alignement possible.
Pour chaque type de joueur, on écrira une fonction qui crée et initialise correctement une nouvelle structure Joueur. Par exemple :
Joueur * creerJoueurHumain(int id);
Joueur * creerJoueurRandom(int id);
etc.
On ajoutera les options -1 (resp. -2) pour permettre de fixer une stratégie pour le joueur 1 (resp. 2). Vous réflechirez ensuite à l'écriture de stratégies plus efficaces.

Exercice 4 - Gestion d'erreurs

Dans un nouveau fichier util.h, on définit le prototype d'une fonction de gestion d'erreurs :
void die(char *format, ...)
Cette fonction devra afficher l'heure et la chaîne formatée indiquée dans format, puis quitter le programme. On utilisera pour cela la fonction vfprintf.

Par exemple :
die("Le joueur %d a triché", i);
devra afficher :
16:42 * Erreur : le joueur 1 a triché
Réaliser l'implémentation de cette fonction dans un fichier util.c, et l'utiliser dans le cadre de la gestion d'erreurs durant le déroulement du jeu. On utilisera strftime pour afficher l'heure.

Exercice 5 - Structuration et compilation

On souhaite maintenant s'assurer d'une bonne structuration du code. Le petit jeu devrait se composer des fichiers suivants :
  • plateau.c et plateau.h, qui contiennent les types et les fonctions pour la gestion du plateau de jeu ;
  • utils.c et utils.h, qui contiennent les types et les fonctions pour la gestion d'erreurs dans le jeu ;
  • strategies.c et strategies.h qui contiennent les types et les fonctions pour faire jouer l'ordinateur ;
  • jeu.c qui contient la fonction principale main, la gestion des options et la boucle principale du jeu.

Une fois assurés de la bonne structuration du code, réaliser un fichier Makefile, respectant les contraintes suivantes :
  • Le fichier devra contenir la cible all construisant le programme, et la cible clean supprimant tous les fichiers objets (.o) et temporaires. La cible all sera la cible par défaut.
  • Une bibliothèque statique libpuissance4.a devra être générée à partir de plateau.c et stratégies.c (on utilisera pour cela la commande ar). Cette bibliothèque sera utilisée pour la compilation du jeu.
  • Une bibliothèque dynamique libpuissance4.so devra être générée à partir de plateau.c et stratégies.c.
  • Pour améliorer l'organisation et la lisibilité de l'ensemble du projet, les règles suivantes devront également être respectées :
    • à la racine de l'arborescence ne doit se trouver que le fichier Makefile ;
    • tous les fichiers sources (fichiers .c) devront se trouver dans un sous-répertoire src ;
    • tous les fichiers d'en-tête (.h) devront se trouver dans un sous-repertoire include ;
    • la bibliothèque libpuissance4.a devra se trouver dans un répertoire lib/ ;
    • le programme du jeu compilé devra se trouver dans le sous-répertoire bin.