tete du loic

 Loïc YON [KIUX]

  • Enseignant-chercheur
  • Référent Formation Continue
  • Responsable des contrats pros ingénieur
  • Référent entrepreneuriat
  • Responsable de la filière F2 ingénieur
  • Secouriste Sauveteur du Travail
mail
loic.yon@isima.fr
phone
(+33 / 0) 4 73 40 50 42
location_on
ISIMA
  • twitter
  • linkedin
  • viadeo

[C] Moteur texte du jeu Mastermind

 Cette page commence à dater. Son contenu n'est peut-être plus à jour. Contactez-moi si c'est le cas!

Date de première publication : 2017/11/06

Principe du jeu

Nous allons vérifier que vos neurones n'ont pas trop souffert pendant le WEI, alors vous allez coder le moteur d’un jeu en mode "texte" qui s’intitule Mastermind

exemple de boite de jeu

Le principe est très simple : Il faut découvrir une combinaison de couleurs. Pour chaque proposition, on sait uniquement quels sont les pions bien placés et les pions mal placés.

Le jeu initial demande des combinaisons de couleurs à choisir parmi 6. une case ne peut être vide. Le nombre maximum de propositions est de 10.

Pour cette version texte, les couleurs sont remplacées par un chiffre entre 1 et 6. On réserve la valeur 0 à une case vide.

Nous aurons besoin de plusieurs variables :

Bases du jeu et développement orienté tests

De quelles fonctions va-t-on avoir besoin ?

Ces fonctions sont au cœur du jeu, il faut donc s'assurer que leur fonctionnement est correct. On va donc tout d'abord écrire les tests avant d'écrire les fonctions.

Ces fonctions seront déclarées dans le fichier d'entête master.h dont vous n'oublierez pas les gardiens. La définition des fonctions se trouvera dans le fichier d'implémentation master.c

Les tests correspondants à ces fonctions se trouvent dans un fichier tests.c

Le dépôt gitlab se trouve ici :

git clone https://gitlab.com/kiux/C6M.git

Il y a quelques surprises !!! Voici un extrait du fichier de tests qui est loin d'être exhaustif :

TEST(initialisation_solutions) {
   int i, j;
   int solution[NB_COLONNES];
   
   for (i = 0; i < 100; ++i) {
      initialiser_solution(solution);
      for (j = 0; j < NB_COLONNES; ++j) {
         CHECK(solution[j] > 0);
         CHECK(solution[j] <= NB_COULEURS);
      }
   }
}

Il y a deux manières d'avoir le nombre de pions bien placés ou mal placés :


TEST(combinaison1a) {
   int solution = {1, 1, 1, 1};
   int proposition[NB_COLONNES] = { 2, 2, 2, 2};
   combinaison c;
 
   c = compiler_proposition(proposition, solution);
   REQUIRE( 0 == c.bienp );
   REQUIRE( 0 == c.malp );

   proposition[1] = 1;
   c = compiler_proposition(proposition, solution);
   REQUIRE( 1 == c.bienp );
   REQUIRE( 0 == c.malp );   

   proposition[2] = 1;
   c = compiler_proposition(proposition, solution);
   REQUIRE( 2 == c.bienp );
   REQUIRE( 0 == c.malp );

   proposition[3] = 1;
   c = compiler_proposition(proposition, solution);
   REQUIRE( 3 == c.bienp );
   REQUIRE( 0 == c.malp );

   proposition[0] = 1;
   c = compiler_proposition(proposition, solution);
   REQUIRE( 4 == c.bienp );
   REQUIRE( 0 == c.malp );
}

TEST(combinaison1b) {
   int solution = {1, 1, 1, 1};
   int proposition[NB_COLONNES] = { 2, 2, 2, 2};
   int bienp, malp;

   compiler_proposition(proposition, solution, &bienp, &malp);
   REQUIRE( 0 == bienp );
   REQUIRE( 0 == malp );

   proposition[1] = 1;
   compiler_proposition(proposition, solution, &bienp, &malp);
   REQUIRE( 1 == bienp );
   REQUIRE( 0 == malp );   

   proposition[2] = 1;
   compiler_proposition(proposition, solution, &bienp, &malp);
   REQUIRE( 2 == bienp );
   REQUIRE( 0 == malp );

   proposition[3] = 1;
   compiler_proposition(proposition, solution, &bienp, &malp);
   REQUIRE( 3 == bienp );
   REQUIRE( 0 == malp );

   proposition[0] = 1;
   compiler_proposition(proposition, solution, &bienp, &malp);
   REQUIRE( 4 == bienp );
   REQUIRE( 0 == malp );
}

Moteur du jeu, interface texte et intégration

Principe

Ce n'est pas tout de tester des fonctions mais encore faut-il les utiliser dans un VRAI programme ! Vous allez créer un fichier master_texte.c.

Ce fichier va contenir une fonction main() et tout ce qui va bien pour avoir un programme qui fonctionne en version texte.

  1. Initialisation des différents variables
  2. Tant que l'utilisateur ne veut pas arrêter et n'a pas trouvé (dans la limite du nombre de coups autorisés) :
    • Demander de faire une proposition
    • Vérifier la validité de cette proposition
    • Comparer cette proposition à la solution
    • Mettre à jour et afficher le plateau
  3. Afficher un petit bilan (succès, condoléances :-))

Voici un extrait d'exécution :


saisir un jeu de couleur commme 1 1 2 3, valider par [entree]
? 1 1 2 2
=============b=m==
| 1 1 1 1 || 2 0 |
| 1 1 2 2 || 1 1 |
| 0 0 0 0 || 0 0 |
| 0 0 0 0 || 0 0 |
| 0 0 0 0 || 0 0 |
| 0 0 0 0 || 0 0 |
| 0 0 0 0 || 0 0 |
| 0 0 0 0 || 0 0 |
| 0 0 0 0 || 0 0 |
| 0 0 0 0 || 0 0 |
==================

La première proposition du joueur a été la combinaison 1 1 1 1 soit 4 fois la même couleur ! Le résultat est qu'il y a deux pions de couleur 1 dans la solution qui sont évidemment bien placés.

Avec la deuxième proposition, on sait désormais qu'un pion 1 est bien placé, un autre 1 est mal placé, et qu'il n'y a pas de pion de la couleur 2.

Imaginons que la solution soit 1 3 3 1 et que le joueur propose 4 3 4 4 alors il faut répondre qu'il y a une couleur de bien placée et c'est tout (pas qu'une couleur est à la fois bien placée et mal placée)

Makefile

Il peut être judicieux de faire un makefile avec différentes règles pour compiler les tests et le moteur de jeu.

Utilisation du hasard

Vous avez vu en semaines bloquées que le concept de hasard n'existe pas vraiment en informatique. La fonction rand() renvoie une suite de nombres pseudo-aléatoires et la suite est déterminée si on connait le premier terme fixé par la fonction srand()

Pour les jeux, on aime bien paramétrer la fonction srand() par un nombre que l'on ne connait pas à l'avance (time(0) ou get_pid() par exemple) mais ce n'est pas une excellente idée pour le débogage.

Vous devez mettre en place le mécanisme suivant :

int main(int argc, char** argv)  {}
int main(int argc, char*[] argv) {}

Ainsi, si une erreur se produit, vous pourrez reproduire le comportement du programme car vous connaîtrez la valeur du germe utilisée.

Ce TP se poursuivra un peu plus tard pour en faire une version graphique et en couleurs avec la SDL.

Compléments

Couleurs dans un terminal

Il est plus facile pour le joueur d'afficher des couleurs en lieu et place des nombres représentant la couleur. C'est tout à fait possible d'afficher de la couleur dans un terminal UNIX en utilisant ce que l'on appelle des séquences d'échappement en octal

Pour modifier la couleur de fond, il faut utiliser la séquence \033[nmn est un nombre qui va de 40 (fond noir) au 47.

La séquence \033[0m permet de restituer les valeurs par défaut au shell.

Plus d'explications ici.

Aller plus loin...

Vous pouvez si vous en avez envie prévoir de paramétrer et de fixer à l'exécution les valeurs de jeu : nombre de couleurs, taille d'une solution, nombre maximal de propositions, ...

Vous pouvez aussi mémoriser des statistiques par joueur : nombre de coups moyens pour trouver par exemple...