- Durée: 6 périodes + travail à la maison
- Date de rendu: minuit le jour avant le prochain labo
On vous demande d'implémenter un démineur en mode texte. La surface de jeu fera 7 x 7 cellules mais elle doit être un paramètre configurable.
Dans chaque cellule vous indiquez le nombre de bombes que la cellule touche. Si c'est une bombe vous indiquez *
. Vous pouvez aussi indiquer 💣
en utilisant des émoji si votre terminal le supporte (Windows Terminal supporte ce type de caractères).
Voicl le plateau de jeu que l'utilisateur ne doit pas voir en temps normal, car il dévoile les bombes :
┌───┬───┬───┬───┬───┬───┬───┐
│ │ 1 │ 1 │ 1 │ │ │ │
├───┼───┼───┼───┼───┼───┼───┤
│ │ 1 │ * │ 1 │ │ │ │
├───┼───┼───┼───┼───┼───┼───┤
│ │ 2 │ 2 │ 2 │ │ │ │
├───┼───┼───┼───┼───┼───┼───┤
│ │ 1 │ * │ 1 │ │ │ │
├───┼───┼───┼───┼───┼───┼───┤
│ │ 1 │ 1 │ 1 │ │ │ │
├───┼───┼───┼───┼───┼───┼───┤
│ │ │ │ 1 │ 1 │ 1 │ │
├───┼───┼───┼───┼───┼───┼───┤
│ │ │ │ 1 │ * │ 1 │ │
└───┴───┴───┴───┴───┴───┴───┘
Un clic de souris permet de dévoiler une cellule.
Au démarrage du programme, une grille aléatoire est générée avec un certain nombre de bombes. L'utilisateur voit la grille avec aucune cellule exposée :
┌───┬───┬───┬───┬───┬───┬───┐
│ . │ . │ . │ . │ . │ . │ . │
├───┼───┼───┼───┼───┼───┼───┤
│ . │ . │ . │ . │ . │ . │ . │
├───┼───┼───┼───┼───┼───┼───┤
│ . │ . │ . │ . │ . │ . │ . │
├───┼───┼───┼───┼───┼───┼───┤
│ . │ . │ . │ . │ . │ . │ . │
├───┼───┼───┼───┼───┼───┼───┤
│ . │ . │ . │ . │ . │ . │ . │
├───┼───┼───┼───┼───┼───┼───┤
│ . │ . │ . │ . │ . │ . │ . │
├───┼───┼───┼───┼───┼───┼───┤
│ . │ . │ . │ . │ . │ . │ . │
└───┴───┴───┴───┴───┴───┴───┘
Cliquez sur une cellule (ligne 1, colonne 0) pourrait donner ceci :
┌───┬───┬───┬───┬───┬───┬───┐
│ │ 1 │ . │ . │ . │ . │ . │
├───┼───┼───┼───┼───┼───┼───┤
│ │ 1 │ . │ . │ . │ . │ . │
├───┼───┼───┼───┼───┼───┼───┤
│ │ 2 │ . │ . │ . │ . │ . │
├───┼───┼───┼───┼───┼───┼───┤
│ │ 1 │ . │ . │ . │ . │ . │
├───┼───┼───┼───┼───┼───┼───┤
│ │ 1 │ 1 │ 1 │ . │ . │ . │
├───┼───┼───┼───┼───┼───┼───┤
│ │ │ │ 1 │ . │ . │ . │
├───┼───┼───┼───┼───┼───┼───┤
│ │ │ │ 1 │ . │ . │ . │
└───┴───┴───┴───┴───┴───┴───┘
L'utilisateur gagne le jeu lorsque seul les bombes sont exposées :
┌───┬───┬───┬───┬───┬───┬───┐
│ │ 1 │ 1 │ 1 │ │ │ │
├───┼───┼───┼───┼───┼───┼───┤
│ │ 1 │ ? │ 1 │ │ │ │
├───┼───┼───┼───┼───┼───┼───┤
│ │ 2 │ 2 │ 2 │ │ │ │
├───┼───┼───┼───┼───┼───┼───┤
│ │ 1 │ ? │ 1 │ │ │ │
├───┼───┼───┼───┼───┼───┼───┤
│ │ 1 │ 1 │ 1 │ │ │ │
├───┼───┼───┼───┼───┼───┼───┤
│ │ │ │ 1 │ 1 │ 1 │ │
├───┼───┼───┼───┼───┼───┼───┤
│ │ │ │ 1 │ ? │ 1 │ │
└───┴───┴───┴───┴───┴───┴───┘
Lorsque l'utilisateur clique avec le bouton gauche de la souris, il démine une région. Lorsqu'il clique avec le bouton droit il marque la cellule comme à risque (?
).
ncurses est une bibliothèque écrite en C et permettant de créer des interfaces graphiques en ligne de commande. Deux exemples vous sont donnés dans le répertoire ncurses-examples. Pour compiler ces exemples vous devez avoir la bibliothèque installée sur votre distribution Linux :
apt-get install libncurses-dev
Ensuite vous pouvez utiliser :
g++ 1.cpp -lncurses
Ces deux exemples vous montre comment :
- Afficher un caractère à une certaine position
- Capturer la position de la souris
Une bonne approche est d'encapsuler toute la complexité de NCurses dans des classes et des méthodes :
displayBoard
Qui affiche le terrain ou met à jour le terrainloop()
Boucle principale qui détecte les clics sourisinitScreen()
Initialize l'écranfreeScreen()
Libère l'écran
Vous avez le choix dans le noms de ces méthodes et dans l'architecture de votre code pour cette partie.
Le déroulement suivant est proposé mais c'est à vous de vous organiser :
- Prise de connaissance des exemples en Ncurses
- Essai d'un petit programme pour afficher une matrice et obtenir la position d'un clic dans la matrice.
- Écriture de la classe de gestion de l'affichage
- Modélisation de l'architecture générale du programme
- Implémentation de la structure en C++
- Implémentation des algorithmes demandés
- Affichage minimaliste de la grille (sans les séparateurs)
- Gestion clics (capture de la cellule correspondant au clic gauche ou droit)
- Affichage avancé de la grille (avec les séparateurs)
Vous devez absolument découper votre programme en petites parties fonctionnelles sinon vous vous sentirez au fond du gouffre...
Une bonne approche serait de définir plusieurs classes. À vous de définir ce qui doit être public ou privé. Voici une proposition :
int ligne
Ligne ou se situe la celluleint column
Colonne ou se situe la cellulebool isBomb
Est-ce une bombe ou une cellule vide ?int number
Nombre de bombes en voisinage prochebool isExposed
Est-ce que la cellule est dévoilée ?bool isGuess
Est-ce que la cellule est un candidat de bombe?
?
Une cellule est réalisée avec un constructeur à deux paramètres : row
, column
. Chaque instance possède deux méthodes :
flip()
pour rendre la cellule visible (action clic gauche)toggleGuess()
pour marquer la cellule comme un candidat de bombe (action clic droit)
Le terrain de jeu Board
est une classe comportant les paramètres suivants :
int nRows
: Nombre de lignesint nCols
: Nombre de colonnesint nBombs
: Nombre de bombesCell[][] cells
: toutes les cellules (à vous d'utiliser la bonne structure de donnée pour sauver les cellules[][]
n'est pas du C++ valide)Cell[] bombs
: une liste de références sur toutes les bombesint numUnexposedRemaining
: nombre de cellules restantes à être dévoilées
Le constructeur prends en paramètre le nombre de lignes, le nombre de colonnes et le nombre de bombes à créer.
Une méthode privée initializeBoard()
instancie les cellules et initialise le terrain de jeu. Cete méthode est appelée par le constructeur de classe.
flipCell(Cell cell)
permet de changer l'état d'une cellule. expandBlank(Cell cell)
permet d'étendre la surface valide jusqu'aux frontières à proximité des bombes. getNumRemaining()
retourne le nombre de cellules restantes à retourner.
La classe Game
stoque les références sur le terrain et les cellules, ainsi que l'état du jeu.
Il y a plusieurs algorithmes à implémenter dans ce labo :
- Placer les bombes
- Numéroter les cellules
- Étendre une région
Pour placer les bombes, la meilleures solution est de créer une méthode shuffleBoard()
qui va mélanger le terrain de jeu en utilisant l'algorithme de Fisher-Yates. De cette façon il suffit de placer les bombes dans les premières cases du terrain et de mélanger le terrain.
Numéroter les cellules est de déterminer pour chaque cellule les bombes à proximité. Pour ce faire il faut détecter les proches voisins en utilisant une matrice de voisinage :
int deltas[][2] = {
{-1, -1}, {-1, 0}, {-1, 1},
{ 0, -1}, { 0, 0}, { 0, 1},
{ 1, 1}, { 1, 0}, { 1, 1}
};
Pour chaque bombe référencées dans la table des bombes, la matrice de voisinage est parcourue et les cellules environnantes sont incrémentées.
Étendre une région peut être résolu itérativement ou récursivement. C'est l'algorithme le plus difficile de ce labo. La solution récursive est généralement plus facile à implémenter.
Dans ce labo, il n'y a pas de raison d'utiliser d'allocation dynamique hormis pour les listes de cellules dont la taille n'est pas connue avant la création de l'objet. Dans votre première implémentation utilisez simplement un tableau statique à deux dimensions Cell cells[7][7]
. Par la suite lorsque cela fonctionnera, vous pourrez remplacer ce tableau par un vecteur multidimensionnel dynamique.
Dans ce labo, il n'y a pas d'héritage, les classes restent simples.
Commencer par développer votre programme dans un seul fichier, puis lorsque tout fonctionne découpez votre programme en fichiers séparés : game.cpp
, board.cpp
, main.cpp
...
Faites un flowchart sur papier pour bien saisir le déroulement de votre programme. Par exemple:
- Créer une instance de jeu
- Initialiser les grilles
- Initialiser l'écran
- Afficher la grille
- Entrer dans la boucle itérative du jeu
- Terminer le jeu
- Libérer l'écran
- Quitter le programme