Jeu classique, le démineur


INTRODUCTION

Le démineur qui est proposé ici est très simplifié. L'objectif est de proposer un exemple possible de fonctionnement sans la moindre fioriture, véritable ligne mélodique propre à des interprétations y compris lorsque celles-ci permettront de créer autre chose qu'un démineur... en effet que pourrait-on faire d'autre à partir d'un démineur ?

TELECHARGER

aaaTexte des explications, aaaExecutable (Windows), aaaSource (utilise la librairie Allegro)

 

EXPLICATIONS

1. Principe du jeu

Il s’agit de découvrir des mines dans un terrain miné sans déclancher d’explositon. En général le terrain est représenté par une grille dont la taille peut éventuellement être dynamique.
Chaque case est susceptible de cacher une mine. Lorsque le joueur clique sur une case, s’il y a une mine il a perdu. S’il n’y a pas de mine les cases voisines sont découvertes de façon récursive jusqu’à trouver les cases qui ont pour voisines des mines. Ces case affichent alors le nombre des mines qu’elles ont pour voisines dans les cases adjacentes. Bien entendu les mies restent cachées. Avec les indications données il s’agit ensuite de déduire progressivement quelles sont dans les cases restantes celles qui sont minées et celles qui ne le sont pas.

aaaaaaaaa

Sur l'image ci-dessus, le curseur indique la position du clic. En gris se trouve la zone découverte. Pour chaque case qui contient un nombre ce nombre indique le nombre de mines présentes dans les cases adjacentes.

Un clic droit permet d’isoler une case avec un drapeau. Si la case contient une mine celle-ci n’explosera pas et des points pourront éventuellement être comptabilisés. Si la case ne contient pas de mine elle ne donnera pas d’indication sur d’éventuelles mines dans ses cases voisines. Le drapeau peut être enlevé de la même façon qu’il a été mis, avec un clic droit, et sur une case avec drapeau.

L’objectif est de découvrir si possible toutes les mines dans le temps le plus court.

2. Mise en forme et initialisation

Le terrain est représenté par une matrice de TY lignes de TX colonnes définies par :
#define TY 20
#define TX 15

Une case sans rien aura la valeur 0 et une case avec mine aura la valeur MINE définie par le nombre maximum de cases voisines (huit) plus un :
#define MINE 9

Initialisation du terrain miné

La première fonction est celle de l’initialisation du terrain miné. Elle prend comme paramètres la matrice qui représente le terrain et le nombre de mines à disséminer dedans.

aaaaavoid init_mine(int t[TY][TX], int nb_mines)
aaaaa{
aaaaaint x,y;

(1)aaaaaamemset(t,0,sizeof(int)*TX*TY);

(2)aaaaaawhile(nb_mines>0){
aaa
aaaaaaaaaaax=rand()%TX;
aaaaaaaaaaay=rand()%TY;
aaaaaaaaaaaif ( t[y][x]!=MINE){
aaaaaaaaaaaaaat[y][x]=MINE;
aaaaaaaaaaaaaanb_mines--;
aaaaaaaaaaa}
aaaaaaaaa}

(3)aaaaafor (y=0; y<TY; y++)
aaaaaaaaaafor (x=0; x<TX; x++)
aaaaaaaaaaaaaif (t[y][x]==MINE)
aaaaaaaaaaaaaaaaincrement_voisins(t,x,y);
aaaaa}

(1) La matrice est premièrement entièrement initialisée avec des valeurs de 0.

(2) Ensuite des positions sont obtenues de façon aléatoire et si la position ne contient pas déjà une mine, c’est dire si la valeur à la position est différente de la valeur MINE alors la position prend la valeur MINE qui indique une mine.

(3) Il s’agit de comptabiliser pour chaque position le nombre des mines contenues dans les cellules voisines. Pour ce faire la matrice est parcourue entièrement et pour chaque position avec une mine les huit positions voisines sont incrémentées de 1, c’est le rôle de la fonction increment_voisins() :

void increment_voisins(int t[TY][TX],int x, int y)
{
(1)aaaaaif (x-1>=0){
(2)aaaaaaaif (t[y][x-1]!=MINE)
(3)aaaaaaaaat[y][x-1]++;
aaaaaaaaaif (y-1>=0)
aaaaaaaaaaaif (t[y-1][x-1]!=MINE)
aaaaaaaaaaaaat[y-1][x-1]++;
aaaaaaaaaif (y+1<TY)
aaaaaaaaaaaif (t[y+1][x-1]!=MINE)
aaaaaaaaaaaaat[y+1][x-1]++;
aaaaaaa}
aaaaaaaif (x+1<TX){
aaaaaaaaaif (t[y][x+1]!=MINE)
aaaaaaaaaaat[y][x+1]++;
aaaaaaaaaif (y-1>=0)
aaaaaaaaaaaif (t[y-1][x+1]!=MINE)
aaaaaaaaaaaaat[y-1][x+1]++;
aaaaaaaaaif (y+1<TY)
aaaaaaaaaaaif (t[y+1][x+1]!=MINE)
aaaaaaaaaaaaat[y+1][x+1]++;
aaaaaaa}
aaaaaaaif (y-1>=0)
aaaaaaaaaif (t[y-1][x]!=MINE)
aaaaaaaaaaat[y-1][x]++;
aaaaaaaif (y+1<TY)
aaaaaaaaaif (t[y+1][x]!=MINE)
aaaaaaaaaaat[y+1][x]++;
aaaaa}

(1) Il faut contrôler que la position pour x comme pour y est bien dans la matrice.
(2) Si oui voir s’il y a une mine à la position voisine.
(3) Si non incrémenter de un la position.
Et ces trois points pour les huit positions voisines de (x,y).

Affichage matrice

L’initialisation du jeu se clôt sur un affichage de la matrice. Dans le programme présenté, nous l’avons centré à l’écran à l’aide des valeurs DECX et DECY définies de la façon suivante :
#define DECX ((SCREEN_W-TX*TCAR)/2)
#define DECY ((SCREEN_H-TY*TCAR)/2)

La taille des carreaux est définie par :
#define TCAR 30

La fonction d’affichage dessine une grille centrée à l’écran avec TY*TX carreaux de TCAR taille, la couleur est verte :

aaaaavoid affiche_matrice(int t[TY][TX])
aaaaa{
aaaaaint x,y,i,j;
aaaaaaaafor (j=0;j<TY;j++)
aaaaaaaaaafor (i=0; i<TX;i++){
aaaaaaaaaaaax=DECX+i*TCAR;
aaaaaaaaaaaay=DECY+j*TCAR;
aaaaaaaaaaaarect(screen,x, y, x+TCAR,y+TCAR, makecol(0,255,0));
aaaaaaaa}
aaaaa}


Toutes les positions de la matrice sont passées en revue, chacune est traduite en coordonnées à l’ écran, c'est-à-dire ajout du décalage plus indice de position multiplié par taille du carreau. Un rectangle non plein de taille TCAR est ensuite affiché à partrir de ces coordonnées.

3. Processus du jeu

Clic gauche sur une position

Le joueur teste une position et clique gauche dessus, à partir de cette position plusieurs cas sont possibles :

1) Il n’y a rien à cette case, elle est à 0. Dans ce cas elle passe à -1 pour indiquer qu ’elle est découverte et une recherche récursive sur les cases voisines est lancée. Lorsqu’une case est à -1 rien ne se passe, il n’y a pas d’appel récursif.

2) Il y a une mine dans ce cas la partie est perdue, en général toutes les mines sont alors découvertes et affichées.

3) Si la case est voisine d’une ou plusieurs mines, le nombre de mines voisines de cette case est affiché dans cette case.

Deux fonctions réalisent ces opérations. Une fonction de recherche, récursive dans le cas 1 et une simple fonction d’affichage des mines dans le cas 2.

Fonction de recherche

(1)aaaaaint cherche(int t[TY][TX],int x,int y)
aaaaa{
aaaaaint xe,ye,res=0;

(2)aaaaaaif (x>=0 && x<TX && y>=0 && y<TY){
(3)aaaaaaaaxe = DECX+x*TCAR;
aaaaaaaaaaye = DECY+y*TCAR;
(4)aaaaaaaaswitch(t[y][x]){

(5)aaaaaaaaaacase 0 :
aaaaaaaaaaaaaat[y][x]=-1;
aaaaaaaaaaaaaarectfill(screen,xe+1, ye+1, xe+TCAR-1,ye+TCAR-1, makecol(0,255,0));
aaaaaaaaaaaaaacherche(t,x+1, y);
aaaaaaaaaaaaaacherche(t,x+1, y-1);
aaaaaaaaaaaaaacherche(t,x, y-1);
aaaaaaaaaaaaaacherche(t,x-1, y-1);
aaaaaaaaaaaaaacherche(t,x-1, y);
aaaaaaaaaaaaaacherche(t,x-1, y+1);
aaaaaaaaaaaaaacherche(t,x, y+1);
aaaaaaaaaaaaaacherche(t,x+1, y+1);
aaaaaaaaaaaaaabreak;

(6)aaaaaaaaaacase -1 :
aaaaaaaaaaaaaabreak;

(7)aaaaaaaaaacase MINE :
aaaaaaaaaaaaaares=1;
aaaaaaaaaaaaaaaffiche_mines(t);
aaaaaaaaaaaaaabreak;

(8)aaaaaaaaaadefault :
aaaaaaaaaaaaaaif (t[y][x]<MINE){
aaaaaaaaaaaaaaaatextprintf(screen,font, xe+2, ye+2,255,"%d",t[y][x]);
aaaaaaaaaaaaaaaat[y][x]=-1;
aaaaaaaaaaaaaa}
aaaaaaaaaaaaaabreak;
aaaaaaaaaa}
aaaaaaa}
(9)aaaaareturn res;
aaaaa}

(1) La fonction prend trois paramètres : la matrice de la zone de jeu et une position (x,y).
Elle renvoie 1 si le joueur est tombé sur une mine et 0 sinon. La valeur de retour transite par la variable "res" initialisée par défaut à 0. Elle sera mise à 1 en cas de clic sur une mine.

(2) Tout d’abord contrôle de la position (x,y) qui doit être dans la matrice.

(3) Calcule de la position correspondante à l'écran et récupération des valeurs dans les variables xe, ye.

(4) Selon la valeur dans la matrice à la position x,y, plusieurs cas sont possibles, d’où l’utilisation d’un switch.

(5) Dans ce premier cas il y a 0 dans la matrice, rien, alors la position est mise à -1 pour indiquer qu’elle est découverte, un rectangle plein vert est dessiné à l’écran. Ensuite les positions adjacentes sont explorées par appels récursifs de la fonction cherche(). Les cas suivants des lignes (6) et (7) vont jouer le rôle de testes d’arrêt.

(6) Si la position contient la valeur -1, tout est dit et rien ne se passe.

(7) Si la position contient la valeur MINE, la partie est perdue. La variable res est mise à 1. La fonction qui affiche toutes les mines à l’écran est appelée.

(8) Dans tous les autres cas si la position contient un nombre compris entre 1 et 8, ce qui indique un nombre de mines à proximité, ce nombre est affiché à l’écran dans la case correspondante une fois pour toute et la position de la matrice est mise à -1

Fonction d’affichage des mines

Pour afficher les mines il suffit de parcourir toute la matrice jeu et dessiner à l’écran par exemple un rectangle rouge plein à chaque position qui contient une mine. Dans le cas ou une mine est couverte par un drapeau (voir gestion des drapeaux ci-dessous) la couleur est bleu cyan pour indiquer une mine trouvée.

aaaaavoid affiche_mines(int t[TY][TX])
aaaaa{
aaaaaint x,y,xe,ye;
aaaaaaafor (y=0; y<TY; y++)
aaaaaaaaafor (x=0; x<TX; x++){
aaaaaaaaaaaxe = DECX+x*TCAR;
aaaaaaaaaaaye = DECY+y*TCAR;
aaaaaaaaaaaif (t[y][x]==MINE)
aaaaaaaaaaaaarectfill(screen,xe+1, ye+1, xe+TCAR-1,ye+TCAR-1, makecol(255,0,0));
aaaaaaaaaaaif (t[y][x]==MINE+DRAPEAU)
aaaaaaaaaaaaarectfill(screen,xe+1, ye+1, xe+TCAR-1,ye+TCAR-1, makecol(0,255,255));
aaaaaaaaa}
aaaaa}


Clic droit sur une position : gestion des drapeaux

Le joueur décide de placer ou d’enlever un drapeau à une position qu’il choisit avec un clic droit. Si la case ne contient pas de drapeau un drapeau est ajouté. Le drapeau est une valeur numérique supérieure à celle de MINE définit par :
#define DRAPEAU 10

Ainsi poser un drapeau revient à incrémenter de 10 la case concernée dans la matrice, à l’inverse retirer un drapeau, s’il y en a un revient à soustraire 10 pour la position concernée.
Fonction de gestion des drapeaux

(1)aaaaavoid drapeaux(int t[TY][TX],int x, int y)
aaaaa{
aaaaaint xe,ye;

(2)aaaaaif (x>=0 && x<TX && y>=0 && y<TY){
aaaaaaaaaaxe = DECX+x*TCAR;
aaaaaaaaaaye = DECY+y*TCAR;

(3)aaaaaaaif (t[y][x]>=0 && t[y][x]<=MINE){
aaaaaaaaaaaat[y][x]+=DRAPEAU;
aaaaaaaaaaaarectfill(screen,xe+1, ye+1, xe+TCAR-1,ye+TCAR-1, makecol(255,255,0));
aaaaaaaaa}
(4)aaaaaaaelse if (t[y][x]>MINE ){
aaaaaaaaaaaat[y][x]-=DRAPEAU;
aaaaaaaaaaaarectfill(screen,xe+1, ye+1, xe+TCAR-1,ye+TCAR-1, makecol(0,0,0));
aaaaaaaaa}
aaaaaaa}
aaaaa}

(1) La fonction drapeu prend en paramètres la matrice zone de jeu et une position (donnée par un clic droit.

(2) Vérification que la position passée est bien dans la matrice. Ensuite les coordonnées écran sont tout d’abord calculées

(3)(4) La valeur à la position est examinée. Si elle n’est pas déjà une position découverte (-1) ou s’il n’y a pas de drapeau ( valeur supérieure à MINE) la valeur DRAPEAU (10) est ajoutée.
S’il y a déjà un drapeau, c'est-à-dire une valeur plus grande que MINE il s’agit alors de soustraire la valeur DRAPEAU à cette position.

4. Boucle d’événements

Reste à voir la capture des évènements dans la boucle principale du jeu. c'est le code suivant :

(1)aaaaint stop =0 ;
aaaaaa(…)
aaaaaainit_mine(mat, TX*TY/10);
aaaaaaaffiche_matrice(mat);
aaaaaashow_mouse(screen);

(2)aaaawhile (!key[KEY_ESC]){

aaaaaaaaaclic =mouse_b;
(3)aaaaaaaif (clic && !stop){
aaaaaaaaaaaawhile(mouse_b){}
aaaaaaaaaaaax=(mouse_x-DECX)/TCAR;
aaaaaaaaaaaay=(mouse_y-DECY)/TCAR;

aaaaaaaaaaaascare_mouse();
(4)aaaaaaaaaif (clic&1)
aaaaaaaaaaaaaastop=cherche(mat,x,y);

(5)aaaaaaaaaif (clic&2)
aaaaaaaaaaaaaadrapeaux(mat,x,y);
aaaaaaaaaaaaunscare_mouse();
aaaaaaaaa}

aaaaaaaaa// réinitialisation, nouvelle partie
(6)aaaaaaif (key[KEY_ENTER]){
aaaaaaaaaaascare_mouse();
aaaaaaaaaaaclear(screen);
aaaaaaaaaaainit_mine(mat, TX*TY/10);
aaaaaaaaaaaaffiche_matrice(mat);
aaaaaaaaaaastop=0;
aaaaaaaaaaaunscare_mouse();
aaaaaaaa}
aaaa}

(1) Préliminaire à cette boucle, tout d’abord la présence d’une variable « stop » mise à 0 et qui va permettre de bloquer le jeu lorsque la partie sera terminée. Vient plus loin l’initialisation de la matrice jeu avec des mines. Le nombre de mines est ici un dixième par rapport au nombre total des positions dans la matrice. Pour finir la matrice est affichée à l’écran.

(2) Le teste d’arrêt de la boucle while est un appuie sur escape. Le premier point est la capture dans la variable clic de l’état de la souris donné par la variable mouse_b.

(3) Si il y a clic et si la partie n’est pas terminée, on attend le relâchement du clic, on passe d’une position donnée en pixels à l’écran à une position en coordonnées matrice (soustraire le décalage à gauche et diviser par la taille d’un carreau).

(4) S’il s’agit d’un clic gauche, la fonction cherche() est appelée à partir de la position matrice donnée et la variable stop est mise à jour avec la valeur de retour. Dans le cas où il y ait un affichage à l’écran il convient de cacher le curseur de la souris afin qu’il ne gène pas l’affichage sur la partie d’écran qu’il recouvre.

(5) S’il s’agit d’un clic droit c’est la fonction de gestion des drapeaux qui est appelée.

(6) Appuyer sur la touche enter permet de réinitialiser le jeu et commencer une nouvelle partie (initialisation avec mines de la matrice, affichage de la matrice-jeu à l’écran, mise à 0 de la variable « stop » de contrôle de la partie.