/* Fichier puiss.c Créé par Michaël Todorovic et Patrice Vallet le 3/04/2007 Déclaration des fonctions requises dans main.c : Initialise une grille et l'affiche pour vérifier qu'elle est correctement initialisée. Remplit cette grille aléatoirement de 1 et 2 et l'affiche pour vérifier son contenu. */ #include "puiss.h" #include #include #include #include /* - donnée : grille du jeu. Dans la mesure où un tableau est un pointeur, il n'y a pas besoin de spécifier que c'est un pointeur. La grille est utilisée comme un pointeur pour écrire dans la grille extérieure à la fonction. - résultat : grille du jeu remplie de 0 - traitement : fait une boucle pour remplir le tableau de 0 */ void initGrille (Grille myGrid) { int i, j; /* variables de boucle */ /* boucle de remplissage */ for (i = 0; i < LIGNE; i++) { for (j = 0; j < COLONNE; j++) { assert (i < LIGNE); assert (j < COLONNE); myGrid[i][j] = 0; } } } /* - donnée : grille du jeu. C'est une référence mais on ne l'utilise pas comme telle - résultat : aucun - traitement : parcours le tableau et affiche chaque élément de façon ordonée */ void afficheGrille (Grille myGrid) { int i, j; /* variables de boucle */ /* boucle d'affichage */ for (i = 0; i < LIGNE; i++) { for (j = 0; j < COLONNE; j++) { assert (i < LIGNE); assert (j < COLONNE); printf ("%d ", myGrid[i][j]); /* on affiche une ligne */ } /* on a fini la ligne donc on retourne à la ligne */ printf ("\n"); } } /* - donnée : grille du jeu. Dans la mesure où un tableau est un pointeur, il n'y a pas besoin de spécifier que c'est un pointeur. La grille est utilisée comme un pointeur pour écrire dans la grille extérieure à la fonction. - résultat : grille du jeu remplie de 1 et de 2 - traitement : fait une boucle pour remplir le tableau de 1 et 2 choisis aléatoirement */ void remplitAleaGrille (Grille myGrid) { int i, j; /* variables de boucle */ /* initialisation du générateur de nombres aléatoires */ srand (time (NULL)); /* remplissage du tableau avec des 1 ou 2 choisis aléatoirement */ for (i = 0; i < LIGNE; i++) { for (j = 0; j < COLONNE; j++) { assert (i < LIGNE); assert (j < COLONNE); myGrid[i][j] = (int) rand () % 2 + 1; /* on utilise un cast (int) pour faire les choses proprement */ } } } /* - donnée : aucune - résultat : 1 ou 2 symbolisant le numéro du joueur qui commence la partie - traitement : initialise le générateur de nombres aléatoires et sort un 1 ou un 2 */ int choixAleaJoueur (void) { return rand () % 2 + 1; /* rand renvoie un nombre en 0 et RAND_MAX. on fait une division entière par 2 pour récupérer */ /* le reste (0 ou 1) et lui ajouter 1 pour donner 1 ou 2 */ } /* - données : la grille de type Grille : c'est un pointeur mais on ne l'utilise qu'en lecture. row et col sont des entiers donnant respectivement la ligne et la colonne dans laquelle le pion a été placé. joueurN donne le numéro du joueur courant - résultat : 0 ou 1 pour dire si le joueur courant a aligné ALIGN pions ou pas - traitement : le principe de cette vérification n'est pas la plus simple qui soit mais c'est à priori la plus efficace. Soit (row,col) la position du dernier pion joué. On va vérifier tout d'abord à droite du pion avec col+i (i=1). Si on trouve un pion du joueur courant, on ajoute add (add=1) à i. On répète cette action jusqu'à trouver autre chose que le pion du joueur (pion de l'adversaire ou vide). Si on trouve autre chose, on arrête de chercher de ce coté. On incrémente rienTrouve qui est une variable servant à dire combien de fois on n'a rien trouvé : si elle est égale à 2, alors l'alignement n'est pas gagant. On cherche à gauche : i = -1 et add = -1 jusqu'à ne rien trouver d'intéressant. Une fois le test fini, on regarde si la variable total (contenant le nombre de pions alignés) est égale à ALIGN ou pas. Si oui alors l'alignement est gagnant donc on renvoie 1 sinon on renvoie 0. */ int verifLigne (Grille myGrid, int row, int col, int joueurN) { /* nombre de tests effectués : cette variable nous sert à ne pas faire trop de tests : si on dépasse ALIGN+2 tests alors on arrête car le joueur aura perdu : il aura dépassé le nombre de pions alignés */ int nbTests = 0; /* nombre total de pions alignés : on l'initialise à 1 car le pion joué compte dans l'alignement */ int total = 1; /* variable de déplacement autour du pion */ int i = 1; /* variable qui sert à incrémenter ou décrémenter i selon le sens de parcours */ int add = 1; /* variable qui nous sert à savoir si on a été jusqu'aux extrémités de l'alignement. Dès que rienTrouve est égal à 2, alors on a parcouru tout l'alignement potentiellement gagnant */ int rienTrouve = 0; /*********************************/ /* test des lignes */ /*********************************/ while (nbTests < (ALIGN + 2) && rienTrouve != 2) { assert (row < LIGNE); assert (col + 1 < COLONNE); /* on fait une petite vérification pour éviter de tester en dehors du tableau et donc éviter d'avoir une erreur */ if (myGrid[row][col + i] == joueurN && col + i < COLONNE && col + i >= 0) { /* si la case courante est égale à joueurN, cela veut dire qu'il y a un de ses pions */ i += add; /* on vérifiera la prochaine case au prochain passage */ total++; /* on a trouvé un nouveau pion donc on incrémente le nombre de pions alignés */ } else /* on n'a rien trouvé d'intéressant */ { rienTrouve++; /* on incrémente cette variable pour dire qu'on a rien trouvé */ i = add = -1; /* on change de sens de progression */ } nbTests++; /* on vient de faire un test complet */ } /*********************************/ /* fin de test des lignes */ /*********************************/ return (total == ALIGN); /* le nombre de pions alignés est égal à la puissance du jeu */ /* on retourne 1 pour dire que le joueurN a aligné une ligne */ /* on retourne 0 pour dire qu'il n'en a pas aligné */ } /* - données : la grille de type Grille : c'est un pointeur mais on ne l'utilise qu'en lecture. row et col sont des entiers donnant respectivement la ligne et la colonne dans laquelle le pion a été placé. joueurN donne le numéro du joueur courant - résultat : 0 ou 1 pour dire si le joueur courant a aligné ALIGN pions ou pas - traitement : le principe de cette vérification n'est pas la plus simple qui soit mais c'est à priori la plus efficace. Soit (row,col) la position du dernier pion joué. On va vérifier tout d'abord en dessous avec row+i (i=1). Si on trouve un pion du joueur courant, on ajoute add (add=1) à i. On répète cette action jusqu'à trouver autre chose que le pion du joueur (pion de l'adversaire ou vide). Si on trouve autre chose, on arrête de chercher de ce coté. On incrémente rienTrouve qui est une variable servant à dire combien de fois on n'a rien trouvé : si elle est égale à 2, alors l'alignement n'est pas gagant. On cherche au dessus : i = -1 et add = -1 jusqu'à ne rien trouver d'intéressant. Une fois le test fini, on regarde si la variable total (contenant le nombre de pions alignés) est égale à ALIGN ou pas. Si oui alors l'alignement est gagnant donc on renvoie 1 sinon on renvoie 0. */ int verifColonne (Grille myGrid, int row, int col, int joueurN) { /* nombre de tests effectués : cette variable nous sert à ne pas faire trop de tests : si on dépasse ALIGN+2 tests alors on arrête car le joueur aura perdu : il aura dépassé le nombre de pions alignés */ int nbTests = 0; /* nombre total de pions alignés : on l'initialise à 1 car le pion joué compte dans l'alignement */ int total = 1; /* variable de déplacement autour du pion */ int i = 1; /* variable qui sert à incrémenter ou décrémenter i selon le sens de parcours */ int add = 1; /* variable qui nous sert à savoir si on a été jusqu'aux extrémités de l'alignement. Dès que rienTrouve est égal à 2, alors on a parcouru tout l'alignement potentiellement gagnant */ int rienTrouve = 0; /*********************************/ /* test des colonnes */ /*********************************/ while (nbTests < (ALIGN + 2) && rienTrouve != 2) { assert (row + 1 < LIGNE); assert (col < COLONNE); /* on fait une petite vérification pour éviter de tester en dehors du tableau et donc éviter d'avoir une erreur */ if (myGrid[row + i][col] == joueurN && row + i >= 0 && row + i < LIGNE) /* si la case courante est égale à joueurN, cela veut dire qu'il y a un de ses pions */ { i += add; /* on vérifiera la prochaine case au prochain passage */ total++; /* on a trouvé un nouveau pion donc on incrémente le nombre de pions alignés */ } else /* on n'a rien trouvé d'intéressant */ { rienTrouve++; /* on incrémente cette variable pour dire qu'on a rien trouvé */ i = add = -1; /* on change de sens de progression */ } nbTests++; /* on vient de faire un test complet */ } /*********************************/ /* fin de test des colonnes */ /*********************************/ return (total == ALIGN); /* le nombre de pions alignés est égal à la puissance du jeu */ /* on retourne 1 pour dire que le joueurN a aligné une colonne */ /* on retourne 0 pour dire qu'il n'en a pas aligné */ } /* - données : la grille de type Grille : c'est un pointeur mais on ne l'utilise qu'en lecture. row et col sont des entiers donnant respectivement la ligne et la colonne dans laquelle le pion a été placé. joueurN donne le numéro du joueur courant - résultat : 0 ou 1 pour dire si le joueur courant a aligné ALIGN pions ou pas - traitement : le principe de cette vérification n'est pas la plus simple qui soit mais c'est à priori la plus efficace. Soit (row,col) la position du dernier pion joué. On va vérifier tout d'abord en haut à droite avec row-i et col+i (i=1). Si on trouve un pion du joueur courant, on ajoute add (add=1) à i. On répète cette action jusqu'à trouver autre chose que le pion du joueur (pion de l'adversaire ou vide). Si on trouve autre chose, on arrête de chercher de ce coté. On incrémente rienTrouve qui est une variable servant à dire combien de fois on n'a rien trouvé : si elle est égale à 2, alors l'alignement n'est pas gagant. On cherche en bas à gauche : i = -1 et add = -1 jusqu'à ne rien trouver d'intéressant. Une fois le test fini, on regarde si la variable total (contenant le nombre de pions alignés) est égale à ALIGN ou pas. Si oui alors l'alignement est gagnant donc on renvoie 1 sinon on renvoie 0. */ int verifDiagonale1 (Grille myGrid, int row, int col, int joueurN) { /* nombre de tests effectués : cette variable nous sert à ne pas faire trop de tests : si on dépasse ALIGN+2 tests alors on arrête car le joueur aura perdu : il aura dépassé le nombre de pions alignés */ int nbTests = 0; /* nombre total de pions alignés : on l'initialise à 1 car le pion joué compte dans l'alignement */ int total = 1; /* variable de déplacement autour du pion */ int i = 1; /* variable qui sert à incrémenter ou décrémenter i selon le sens de parcours */ int add = 1; /* variable qui nous sert à savoir si on a été jusqu'aux extrémités de l'alignement. Dès que rienTrouve est égal à 2, alors on a parcouru tout l'alignement potentiellement gagnant */ int rienTrouve = 0; /*********************************/ /* test de la 1è diagonale */ /*********************************/ while (nbTests < (ALIGN + 2) && rienTrouve != 2) { assert (row - 1 >= 0); assert (col + 1 < COLONNE); /* on fait une petite vérification pour éviter de tester en dehors du tableau et donc éviter d'avoir une erreur */ if (myGrid[row - i][col + i] == joueurN && row - i >= 0 && row - i < LIGNE && col + i >= 0 && col + i <= COLONNE - 1) /* si la case courante est égale à joueurN, cela veut dire qu'il y a un de ses pions */ { i += add; /* on vérifiera la prochaine case au prochain passage */ total++; /* on a trouvé un nouveau pion donc on incrémente le nombre de pions alignés */ } else /* on n'a rien trouvé d'intéressant */ { rienTrouve++; /* on incrémente cette variable pour dire qu'on a rien trouvé */ i = add = -1; /* on change de sens de progression */ } /* } */ nbTests++; /* on vient de faire un test complet */ } /*********************************/ /*fin de test de la 1è diagonale */ /*********************************/ return (total == ALIGN); /* le nombre de pions alignés est égal à la puissance du jeu */ /* on retourne 1 pour dire que le joueurN a aligné une diagonale */ /* on retourne 0 pour dire qu'il n'en a pas aligné */ } /* - données : la grille de type Grille : c'est un pointeur mais on ne l'utilise qu'en lecture. row et col sont des entiers donnant respectivement la ligne et la colonne dans laquelle le pion a été placé. joueurN donne le numéro du joueur courant - résultat : 0 ou 1 pour dire si le joueur courant a aligné ALIGN pions ou pas - traitement : le principe de cette vérification n'est pas la plus simple qui soit mais c'est à priori la plus efficace. Soit (row,col) la position du dernier pion joué. On va vérifier tout d'abord en bas à droite avec row+i et col+i (i=1). Si on trouve un pion du joueur courant, on ajoute add (add=1) à i. On répète cette action jusqu'à trouver autre chose que le pion du joueur (pion de l'adversaire ou vide). Si on trouve autre chose, on arrête de chercher de ce coté. On incrémente rienTrouve qui est une variable servant à dire combien de fois on n'a rien trouvé : si elle est égale à 2, alors l'alignement n'est pas gagant. On cherche en haut à gauche : i = -1 et add = -1 jusqu'à ne rien trouver d'intéressant. Une fois le test fini, on regarde si la variable total (contenant le nombre de pions alignés) est égale à ALIGN ou pas. Si oui alors l'alignement est gagnant donc on renvoie 1 sinon on renvoie 0. */ int verifDiagonale2 (Grille myGrid, int row, int col, int joueurN) { /* nombre de tests effectués : cette variable nous sert à ne pas faire trop de tests : si on dépasse ALIGN+2 tests alors on arrête car le joueur aura perdu : il aura dépassé le nombre de pions alignés */ int nbTests = 0; /* nombre total de pions alignés : on l'initialise à 1 car le pion joué compte dans l'alignement */ int total = 1; /* variable de déplacement autour du pion */ int i = 1; /* variable qui sert à incrémenter ou décrémenter i selon le sens de parcours */ int add = 1; /* variable qui nous sert à savoir si on a été jusqu'aux extrémités de l'alignement. Dès que rienTrouve est égal à 2, alors on a parcouru tout l'alignement potentiellement gagnant */ int rienTrouve = 0; /*********************************/ /* test de la 2è diagonale */ /*********************************/ while (nbTests < (ALIGN + 2) && rienTrouve != 2) { assert (row + 1 < LIGNE); assert (col + 1 < COLONNE); /* on fait une petite vérification pour éviter de tester en dehors du tableau et donc éviter d'avoir une erreur */ if (myGrid[row + i][col + i] == joueurN && row + i >= 0 && row + i < LIGNE && col + i >= 0 && col + i < COLONNE) /* si la case courante est égale à joueurN, cela veut dire qu'il y a un de ses pions */ { i += add; /* on vérifiera la prochaine case au prochain passage */ total++; /* on a trouvé un nouveau pion donc on incrémente le nombre de pions alignés */ } else /* on n'a rien trouvé d'intéressant */ { rienTrouve++; /* on incrémente cette variable pour dire qu'on a rien trouvé */ i = add = -1; /* on change de sens de progression */ } nbTests++; /* on vient de faire un test complet */ } /*********************************/ /*fin de test de la 2è diagonale */ /*********************************/ return (total == ALIGN); /* le nombre de pions alignés est égal à la puissance du jeu */ /* on retourne 1 pour dire que le joueurN a aligné une diagonale */ /* on retourne 0 pour dire qu'il n'en a pas aligné */ } /* - donnée : la grille de type Grille : c'est un pointeur mais on ne l'utilise qu'en lecture. - résultat : 1 si la grille est pleine, 0 sinon - traitement : parcours la première ligne (tout en haut) de la grille pour voir s'il toutes les cases sont occupées ou non et renvoie une valeur en conséquence */ int grillePleine (Grille myGrid) { int i; /* variable de boucle */ for (i = 0; i < COLONNE; i++) /* parcours de la première ligne de la grille */ if (!myGrid[0][i]) /* si on trouve une seule case vide */ return 0; /* on renvoie 0 pour dire que la grille n'est pas pleine */ /* si on arrive ici, c'est que la grille est pleine donc on renvoie 1 */ return 1; } /* - données : la grille de type Grille : c'est un pointeur mais on ne l'utilise qu'en lecture. row et col sont des entiers donnant respectivement la ligne et la colonne dans laquelle le pion a été placé. joueurN donne le numéro du joueur courant - résultat : 0 si le jeu continue, 1 si le joueur 1 a gagné, 2 si le joueur 2 a gagné, 3 pour dire que la partie est finie mais que personne n'a gagné - traitement : appelle différentes fonctions de tests (grille pleine, ligne, colonne, diagonales) et retourne une valeur en fonction du résultat des fonctions appelées */ int verifPionJoue (Grille myGrid, int row, int col, int joueurN) { int sortie; /* ce qui sera renvoyé à la fin de la fonction */ if (!grillePleine (myGrid)) /* la grille n'est pas pleine */ { if (!verifLigne (myGrid, row, col, joueurN)) /* on vérifie la ligne */ { /* ça n'a rien donné donc on tente la colonne */ if (!verifColonne (myGrid, row, col, joueurN)) /* on vérifie la colonne */ { /* ça n'a rien donné donc on tente la première diagonale */ if (!verifDiagonale1 (myGrid, row, col, joueurN)) /* on vérifie la première diagonale */ { /* ça n'a rien donné donc on tente la deuxième diagonale */ if (!verifDiagonale2 (myGrid, row, col, joueurN)) /* on vérifie la deuxième diagonale */ sortie = 0; /* ça n'a rien donné donc la partie continue */ else sortie = joueurN; /* la diagonale 2 est un alignement gagnant, on renverra le numéro du joueur courant */ } else sortie = joueurN; /* la diagonale 1 est un alignement gagnant, on renverra le numéro du joueur courant */ } else sortie = joueurN; /* la colonne est un alignement gagnant, on renverra le numéro du joueur courant */ } else sortie = joueurN; /* la ligne est un alignement gagnant, on renverra le numéro du joueur courant */ } else sortie = 3; /* la grille est pleine donc personne n'a gagné : fin du jeu */ return sortie; /* renvoi du statut du jeu */ } int ia (int profondeur, int joueur, Grille grilleDuJeu, int peutJouerDansLigne[COLONNE], int colonneJouee) { int sortie; int r, i, l, g; profondeur--; joueur = (joueur == 1 ? 2 : 1); int ret = verifPionJoue (grilleDuJeu, peutJouerDansLigne[colonneJouee], colonneJouee, joueur); if (!ret) { sortie = 0; if (profondeur != 0) { l = peutJouerDansLigne[colonneJouee]; grilleDuJeu[colonneJouee][l] = joueur; afficheGrille (grilleDuJeu); peutJouerDansLigne[colonneJouee]--; r = 0; joueur = (joueur == 1 ? 2 : 1); for (i = 0; i < COLONNE; i++) { if (peutJouerDansLigne[i] == -1) r++; else { g = ia (profondeur, joueur, grilleDuJeu, peutJouerDansLigne, i); if (g == joueur) sortie = joueur; else if (g) r++; } } joueur = (joueur == 1 ? 2 : 1); if (r == COLONNE) sortie = joueur; peutJouerDansLigne[colonneJouee]++; grilleDuJeu[colonneJouee][l] = 0; } } else sortie = joueur; return sortie; } void recupColonne (int colonne[COLONNE], Grille grilleDuJeu) { int i, j; int somme; for (i = 0; i < COLONNE; i++) { somme = 0; for (j = 0; j < LIGNE; j++) if (grilleDuJeu[j][i]) somme++; colonne[i] = LIGNE - somme - 1; } } int iaJoue (int joueurN, Grille myGrid, int peutJouerDansLigne[COLONNE]) { Grille myGrid2; int i, j; int retour[COLONNE]; int colonneAJouer = -1; for (i = 0; i < LIGNE; i++) for (j = 0; j < COLONNE; j++) myGrid2[i][j] = myGrid[i][j]; for (j = 0; j < COLONNE; j++) { if (peutJouerDansLigne[j] == -1) retour[j] = -1; else retour[j] = ia (2, joueurN, myGrid2, peutJouerDansLigne, j); } for (j = 0; j < COLONNE; j++) if (retour[j] == 0) /* c'est personne qui gagne */ colonneAJouer = j; for (j = 0; j < COLONNE; j++) if (retour[j] == 2) /* c'est le joueur adverse qui gagne */ colonneAJouer = j; for (j = 0; j < COLONNE; j++) if (retour[j] == 1) /* c'est l'ia qui gagne */ colonneAJouer = j; for (j = 0; j < COLONNE; j++) printf ("Retour[%d] = %d\n", j, retour[j]); return colonneAJouer; } /* - données : la grille de type Grille : c'est un pointeur et on l'utilise en lecture/écriture. joueurN est le numéro du joueur courant - résultat : 0 si le jeu continue, 1 si le joueur 1 a gagné, 2 si le joueur 2 a gagné, 3 pour dire que la partie est finie mais que personne n'a gagné - traitement : on va demander au joueur de sélectionner la colonne dans laquelle il veut jouer puis on va regarder si on peut placer son pion dans cette colonne. Si oui, alors on vérifie si son pion forme un alignement gagnant avec d'autres de ses pions. Si on ne peut pas placer son pion dans la colonne (parce que pleine), alors on lui redemande de jouer. */ int joueurNJoue (Grille myGrid, int joueurN) { int i = LIGNE - 1; /* on commence du bas de la grille (ligne 9) pour remonter */ int tourFait = 0; /* le tour est-il fini ? non par défaut */ int col; /* numéro de la colonne demandée par l'utilisateur */ int row = -1; /* ligne dans laquelle le pion a été placé */ int peutJouerDansLigne[COLONNE]; recupColonne (peutJouerDansLigne, myGrid); col = -1; /* on initilise colonne à -1 pour être sûr que la condition de la boucle de demande de colonne s'exécute correctement */ do /* on demande au joueur de jouer au moins une fois */ { if (joueurN == 2) { while (col < 0 || col >= COLONNE) { printf ("Joueur %d, où voulez vous jouer le pion ? ", joueurN); scanf ("%d", &col); } } else { col = iaJoue (joueurN, myGrid, peutJouerDansLigne); printf ("l'ia a décidé de jouer en colonne %d\n", col); /* ia */ } /* si la colonne demandée est dans de la grille, on essaie de placer le pion, sinon on ne fait rien */ if (col >= 0 && col <= COLONNE - 1) /* vérification de la colonne */ if (!myGrid[0][col]) /* ça ne sert à rien de vérifier la colonne si elle est pleine */ while (i >= 0) /* tant que la ligne n'est pas la première (celle tout en haut) */ { if (!myGrid[i][col]) /* si la case est libre, alors on place le pion */ { myGrid[i][col] = joueurN; /* placement */ row = i; /* stockage de la ligne */ i = -1; /* sortie du while */ tourFait = 1; /* sortie du do while */ } else i--; /* si la case n'est pas libre, on change de ligne */ } } while (!tourFait); /* on boucle tant que le joueur n'a pas placé son pion */ /* on vérifie si le pion placé fait gagner le joueur ou pas et on retourne le statut du jeu */ return verifPionJoue (myGrid, row, col, joueurN); }