OloNO/lib/libPlay.c

687 lines
20 KiB
C

/* This file is part of Olono
Copyright (C) 2008 Martin Potier (<mpotier@isep.fr>) and
David Wagner (<dwagner@isep.fr>)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; version 3
of the License.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
/** \file
* libPlay.c
* Librairie indépendante du mode d'affichage.
* Elle gère toutes les règles du jeu et s'occupe d'effectuer les mouvements
* comme les retournements de pions.
*/
#include <stdio.h>
#include <stdlib.h>
#include "libPlay.h"
#include "libDefine.h"
#include "libDisplay.h"
#include "libIA.h"
static int elaguer(NOEUD * noeud);
static NOEUD tronc; // Le plateau d'origine
static NOEUD * actuel; // Pointe en permanence sur le plateau en cours
extern int taillePlateau;
//! Renvoie 0 si le coup n'est pas valide, le nombre de pièces qu'il retourne sinon.
/**
* Cette fonction renvoie le nombre de pièces à retourner et met les
* coordonnées des pièces à retourner dans le pointeur listeCoups.
*
* La liste increments contient les vecteurs selon lesquels parcourir le
* tableau: on suit toutes les droites et diagonales (première boucle for).
* Au début de chaque vecteur, on vérifie qu'on reste dans la plateau, que
* la case immédiatement n'est ni vide ni de la couleur du joueur. Dans le
* cas contraire, on passe immédiatement au vecteur suivant.
* Une fois ces prérequis vérifiés, on parcourt la suite du vecteur à la
* recherche d'une pierre amie (le while). On met les coordonnées des pions
* qui se trouvent sur le chemin. Si on trouve effectivement une pierre amie,
* le compteur total de pion à retourner est incrémenté.
* coupsTemp permet, si on ne rencontre pas d'amie, de ne pas fausser le compte
* de coups
*
* A tout moment, si on rencontre un joker, on le saute, c'est à dire qu'on
* fait un continue.
*/
char lister(int x,int y,char couleur,CASE *plateau[], int listeCoups[])
{
CASE caseDepart;
char increments[8][2] =
{
{-1, -1},
{-1, 0},
{-1, 1},
{ 0, 1},
{ 1, 1},
{ 1, 0},
{ 1, -1},
{ 0, -1}
}; // Liste des vecteurs
int inc=0; // Parcourt les vecteurs
int coups=0, coupsTemp; // Compteur de nombre de pions retournés
char rencontre = 0;
caseDepart = *adresseParXY(x, y, plateau);
if (caseDepart.couleur != VIDE)
return 0;
// On parcourt les vecteurs
for (inc=0; inc<8; inc++)
{
char incX=increments[inc][0], incY=increments[inc][1];
char couleurCase;
CASE * caseTemp;
caseTemp = caseSuivante(caseDepart, incX, incY, plateau);
// Si on est au bord du plateau ou s'il y a eu une erreur
if (caseTemp == NULL)
continue;
// Si on tombe ensuite sur une case de la meme couleur ou vide, on saute
if ( (caseTemp->couleur == couleur) || (caseTemp->couleur == VIDE) )
continue;
// Dans les autres cas, on continue d'incrementer
coupsTemp=1; // On retourne potentiellement 1 pion
// On l'ajoute à la liste des coups
listeCoups[coups] = (caseTemp->x)*taillePlateau + caseTemp->y;
while (1) // Pas besoin de vérification puisqu'on sort avec un break
{
caseTemp = caseSuivante(*caseTemp, incX, incY, plateau);
if (caseTemp == NULL)
break; // Il y a pas de case suivante. On est arrivé au bord sans trouver d'ami
couleurCase = caseTemp->couleur;
// Si on tombe sur le joker, on passe cette case:
if (couleurCase == JOKER)
{
continue;
}
// Case Vide, Stop:
if (couleurCase == VIDE)
{
break;
}
// Si on tombe sur un ami:
if (couleurCase == couleur)
{
rencontre = 1;
break; // On a trouve qqn et on a rentre les coups. On passe au vecteur suivant
}
// On est tombé sur un ennemi, on l'ajoute
listeCoups[coups + (coupsTemp++)] = (caseTemp->x)*taillePlateau + caseTemp->y;
}
if (rencontre)
coups += coupsTemp;
coupsTemp = 0;
rencontre = 0;
}
return coups;
}
//! Renvoie 1 si le coup est valide, 0 pour invalide
char valide(int x, int y, char couleur, CASE *plateau[])
{
CASE caseDepart;
char increments[8][2] =
{
{-1, -1},
{-1, 0},
{-1, 1},
{ 0, 1},
{ 1, 1},
{ 1, 0},
{ 1, -1},
{ 0, -1}
}; // Liste des vecteurs
int inc=0; // Parcourt les vecteurs
char rencontre = 0;
caseDepart = *adresseParXY(x, y, plateau);
if (caseDepart.couleur != VIDE)
return 0;
// On parcourt les vecteurs
for (inc=0; inc<8; inc++)
{
char incX=increments[inc][0], incY=increments[inc][1];
char couleurCase;
CASE * caseTemp;
caseTemp = caseSuivante(caseDepart, incX, incY, plateau);
// Si on est au bord du plateau ou s'il y a eu une erreur
if (caseTemp == NULL)
continue;
// Si on tombe ensuite sur une case de la meme couleur ou vide, on saute
if ( (caseTemp->couleur == couleur) || (caseTemp->couleur == VIDE) )
continue;
// Dans les autres cas, on continue d'incrementer
do
{
caseTemp = caseSuivante(*caseTemp, incX, incY, plateau);
if (caseTemp == NULL)
break; // Il y a pas de case suivante. On est arrivé au bord sans trouver d'ami
couleurCase = caseTemp->couleur;
// Si on tombe sur le joker, on passe cette case:
if (couleurCase == JOKER)
continue;
// Case Vide, Stop:
else if (couleurCase == VIDE)
break;
// On a trouvé un pote !
else if (couleurCase == couleur)
rencontre = 1;
} while(!rencontre);
if (rencontre)
return 1; // Au moins 1 vecteur
}
return 0; // Rien trouvé: pas valide
}
//! Renvoie le nombre de coups valides
char nbCoupsValides(char couleur, CASE *plateau[], int listeValides[])
{
char i, j;
int coups=0;
for(i=0; i<taillePlateau; i++)
{
for(j=0; j<taillePlateau; j++)
{
if (valide(i, j, couleur, plateau))
{
listeValides[coups] = i*taillePlateau + j;
coups++;
}
}
}
return coups; // Rien trouvé: pas valide
}
//! Retourne un Pion
/**
* Échange, entre NOIR et BLANC, la couleur d'une case.
* Si la case est vide ou un joker, ne fait rien.
*/
void retourne(int x, int y, CASE *plateau[])
{
CASE * caseARetourner = adresseParXY(x, y, plateau);
char couleur = caseARetourner->couleur;
// Le reste est tout con
if (couleur == BLANC)
couleur = NOIR;
// Important tout de même de else if, sinon ça ne marche pas.
else if (couleur == NOIR)
couleur = BLANC;
// Si c JOKER VIDE ou quoi que ce soit d'autre on se casse, c'est pas notre boulot.
else
return;
caseARetourner->couleur = couleur;
}
//! Joue le tour actuel si possible.
/**
* Cette fonction, appelée par le main, joue le tour d'un joueur passé
* en argument. S'il y a lieu, il retourne les pions et place le pion du
* joueur.
* Elle renvoie ensuite le nombre de pièces retournées.
* Donc si le joueur ne peut pas jouer, la fonction ne fait rien et renvoie 0.
*/
int peutJouer(char couleur, CASE *plateau[])
{
int i, j;
// On cherche s'il existe au moins 1 coup possible
for (i=0; i<taillePlateau; i++)
{
for (j=0; j<taillePlateau; j++)
{
if(valide(i, j, couleur, plateau))
return 1;
}
}
return 0;
}
//! Place un pion et retourne les pions adéquats
int jouerCoup(int x, int y, char couleur, CASE *plateau[])
{
int nbCoups;
int * listeCoups = malloc(taillePlateau * taillePlateau * sizeof(int));
nbCoups = lister(x, y, couleur, plateau, listeCoups);
if (!nbCoups)
{
free(listeCoups);
return 0;
}
int i;
for(i=0; i<nbCoups; i++) // On retourne les pions
retourne(listeCoups[i]/taillePlateau, listeCoups[i]%taillePlateau, plateau);
adresseParXY(x, y, plateau)->couleur = couleur; // On pose le pion
free(listeCoups);
return nbCoups;
}
//! Teste la fin de partie.
/**
* La partie se finit si le plateau est rempli ou si un joueur n'as plus de
* pièces sur le plateau (Là, il l'a mauvaise). Dans ce cas cette fonction renvoi 1.
* Tant que la partie n'est pas finie, cette fonction renvoi 0
*/
int testFinPartie(CASE *plateau[])
{
int i, j;
int unicolore=1, plein=1;
char couleur[2] = {0, 0};
for (i=0; i<taillePlateau; i++)
{
for (j=0; j<taillePlateau; j++)
{
CASE pion = *adresseParXY(i, j, plateau);
if (pion.couleur == JOKER)
continue;
if (plein && (pion.couleur == VIDE))
plein =0;
if (unicolore && (pion.couleur != VIDE))
{
couleur[0] |= (pion.couleur == BLANC);
// couleur[0] passe ou reste à 1 si on est sur une case blanche
couleur[1] |= (pion.couleur == NOIR);
if (couleur[0] && couleur[1])
unicolore = 0;
}
// Si on n'est ni unicolore ni plein, la partie est pas finie
if (!(unicolore || plein))
return 0;
}
}
return 1;
}
//! Renvoi le score de fin de partie.
/**
* Cette fonction compte le nombre de pions blancs et noirs afin de
* déterminer le score de fin de partie (donc pour que ce soit sensé :
* l'utiliser en fin de partie ...)
* Le score est rangé dans un tableau passé en argument,
* de la forme : score[blanc,noir].
*/
void score(CASE *plateau[], int * points)
{
// Déclaration des variables de boucle et de stockage :
int i=0,j=0;
// On lit
for (i=0; i<taillePlateau; i++)
{
for (j=0; j<taillePlateau; j++)
{
if (adresseParXY(i, j, plateau)->couleur == BLANC)
{
points[0]++;
}
else if (adresseParXY(i, j, plateau)->couleur == NOIR)
{
points[1]++;
}
}
}
}
//! Renvoit un simple pointeur comme tableau multidim
/**
* Crée et envoie une liste de pointeurs qui représente le tableau.
* Le pointeur renvoyé représente les colonne et chaque pointeur dans cette
* liste est une ligne. Le tableau renvoyé est initialisé à VIDE.
*/
CASE ** createPlateau()
{
// On crée une liste de pointeurs sur les cases
CASE ** plateau = malloc(taillePlateau * taillePlateau * sizeof(CASE *));
//erreur("Création d'un plateau à l'adresse %p\n", plateau);
// On crée maintenant chaque case, et on met son pointeur dans la liste
int i=0;
for (i=0;i<(taillePlateau*taillePlateau);i++)
{
plateau[i] = malloc(sizeof(CASE));
plateau[i]->x = i / taillePlateau;
plateau[i]->y = i % taillePlateau;
plateau[i]->couleur = VIDE;
}
// Cette fonction renvoit un pointeur sur le plateau
// pour modif et affichage ultérieur.
return plateau;
}
//! Copie un plateau passé en argument. utilise taillePlateau
CASE ** copiePlateau(CASE *plateau[])
{
CASE ** copie = malloc(taillePlateau * taillePlateau * sizeof(CASE *));
//erreur("Copie du plateau %p en %p\n", plateau, copie);
int i;
for(i=0; i<(taillePlateau*taillePlateau); i++)
{
copie[i] = malloc(sizeof(CASE));
copie[i]->x = i / taillePlateau;
copie[i]->y = i % taillePlateau;
copie[i]->couleur = adresseParXY(copie[i]->x, copie[i]->y, plateau)->couleur;
}
return copie;
}
//! Rempli le centre d'un tableau passé en argument
/**
* Initialise la partie en ajoutant au tableau vide le carré central donné
* en consigne. Il contient un Joker en son centre.
*/
void initPlateau(CASE *plateau[])
{
// Le motif donné par la consigne :
char motif[9];
if (taillePlateau % 2 == 1) // Plateau Impair
{
// La position de début du motif est à :
int debut = (taillePlateau/2)-1;
motif[0] = BLANC; motif[1] = NOIR; motif[2] = BLANC;
motif[3] = BLANC; motif[4] = JOKER; motif[5] = NOIR;
motif[6] = NOIR; motif[7] = BLANC; motif[8] = NOIR;
int i=0,j=0;
for (i=0;i<3;i++)
{
for (j=0;j<3;j++)
{
adresseParXY(debut+i, debut+j, plateau)->couleur = motif[j*3+i];
}
}
}
else
{
// La position de début du motif est à :
int debut = (taillePlateau/2)-1;
motif[0] = BLANC; motif[1] = NOIR;
motif[2] = NOIR; motif[3] = BLANC;
int i=0,j=0;
for (i=0;i<2;i++)
{
for (j=0;j<2;j++)
{
adresseParXY(debut+i, debut+j, plateau)->couleur = motif[j*2+i];
}
}
}
}
//! Trouve un coup et l'envoit
/**
* Fait appel à la libIA pour trouver le meilleur coup dans la position donnée
* Envoit ensuite le coup via la libDisplay (ecrire())
*/
int jouerSonTour(char couleur, CASE *plateau[])
{
int x, y;
int retour;
//erreur("On afiche le plateau que reçoit l'IA:\n");
affichePlateau(plateau);
if (!peutJouer(couleur, plateau))
{
retour = meilleurXY(plateau, couleur, &x, &y);
pasDeCoup();
//erreur("L'IA ne peut pas jouer\n");
return 1;
}
else
{
retour = meilleurXY(plateau, couleur, &x, &y); // Trouve le meilleur coup
if (retour)
return -1; // Erreur !
envoyerXY(x, y);
retour = jouerCoup(x, y, couleur, plateau);
if (retour == 0)
return -1; // Erreur comise
}
return 0; // Tout s'est bien déroulé
}
//! Lit le coup adverse et le joue
/**
* Via la libDisplay, on lit le coup adverse (lireCoup())
* Puis on le joue. S'il est illégal, on retourne un erreur.
*/
int jouerTourAdverse(char couleur, CASE *plateau[])
{
int retour;
char x;
int y;
retour = lireCoup(&x, &y);
if (retour == 0) // L'adversaire a joué
{
retour = jouerCoup(x, y, !couleur, plateau);
if (retour == 0) // Aucun pion retourné
return -1; // L'adversaire a joué un coup illégal
}
else if (retour == 1)
return 1; // L'adversaire n'avait pas de coup}
return 0; // L'adversaire a joué correctement
}
//! Joue le tour actuel si possible.
/**
* Cette fonction, appelée par le main, joue le tour d'un joueur passé
* en argument. S'il y a lieu, il retourne les pions et place le pion du
* joueur.
* Elle renvoie ensuite le nombre de pièces retournées.
* Donc si le joueur ne peut pas jouer, la fonction ne fait rien et renvoie 0.
*/
int jouerTourSimple(char couleur, CASE *plateau[])
{
int nbCoups=0;
int coords[2] = {0, 0};
// On cherche s'il existe au moins 1 coup possible
if(!peutJouer(couleur, plateau))
return 0; // Le joueur ne peut pas jouer
// On demande le coup du joueur
do
{
demanderXY(coords);
nbCoups = jouerCoup(coords[0], coords[1], couleur, plateau);
}
while (nbCoups == 0);
return nbCoups;
}
//! Initialise l'historique avec le plateau/tronc
/**
* Après cette fonction, le tronc contient le plateau de départ
* (On peut partir avec un plateau de partie déjà en cours)
* Et actuel pointe sur le tronc
*/
NOEUD * initHistorique(CASE *plateau[], char couleur)
{
tronc.nbDeFils = 0;
tronc.listeFils = NULL;
tronc.plateau = copiePlateau(plateau);
tronc.couleur = !couleur;
tronc.pere = NULL;
actuel = &tronc;
return &tronc;
}
//! ajoute un fils au noeud actuel
/**
* L'historique supporte les variantes: un noeud peut avoir plusieurs fils
* Cette fonction retourne un pointeur vers le noeud crée
*/
NOEUD * majHistorique(CASE *plateau[], char couleur)
{
NOEUD * fils = malloc(sizeof(NOEUD));
fils->nbDeFils = 0;
fils->listeFils = NULL;
fils->plateau = copiePlateau(plateau);
fils->couleur = couleur;
fils->pere = actuel;
// TODO : Si le fils existe déjà, il faut pas le recrée
ajouteFils(actuel, fils);
actuel = fils;
return actuel; // On informe le programme de l'emplacement du nouveau noeud
}
//! Libère les noeuds de l'historique
void endHistorique()
{
int j;
elaguer(&tronc); // On passe du désherbant hyperpuissant
for (j=0; j<(taillePlateau*taillePlateau); j++)
free(tronc.plateau[j]);
free(tronc.plateau);
}
//! renvoie la couleur du joueur dont c'est le tour
/**
* Fais pointer actuel sur son propre père.
* Recopie ensuite le plateau du noeud actuel dans le plateau passé en
* argument.
* Renvoie BLANC si on est au tronc; l'inverse de la couleur du plateau sinon.
*/
char reculerHistorique(CASE * plateau[])
{
if (actuel->pere != NULL)
{
actuel = actuel->pere;
int j;
for (j=0; j<taillePlateau*taillePlateau; j++)
*(plateau[j]) = *(actuel->plateau[j]);
}
return !(actuel->couleur);
}
//! renvoie la couleur du joueur dont c'est le tour
/**
* Fais pointer actuel sur le noeud passé en argument
* Recopie ensuite le plateau du noeud actuel dans le plateau passé en
* argument.
* Renvoie BLANC si on est au tronc; l'inverse de la couleur du plateau sinon.
*/
char sauterHistorique(CASE * plateau[], NOEUD * destination)
{
if (destination == NULL)
return -1;
int j;
actuel = destination;
for (j=0; j<taillePlateau*taillePlateau; j++)
*(plateau[j]) = *(actuel->plateau[j]);
return !(actuel->couleur);
}
//! renvoie la couleur du joueur dont c'est le tour
/**
* Fais pointer actuel sur son premier fils
* Recopie ensuite le plateau du noeud actuel dans le plateau passé en
* argument.
* Renvoie BLANC si on est au tronc; l'inverse de la couleur du plateau sinon.
*/
char avancerHistorique(CASE * plateau[])
{
if (actuel->nbDeFils <= 0 || actuel->listeFils == NULL)
return (actuel->couleur == VIDE) ? BLANC : !(actuel->couleur);
actuel = actuel->listeFils[actuel->nbDeFils - 1];
int j;
for (j=0; j<taillePlateau*taillePlateau; j++)
*(plateau[j]) = *(actuel->plateau[j]);
return !(actuel->couleur);
}
//! Ajoute un fils à la liste d'un noeud
/**
* Utilise realloc pour agrandir la liste du pere(premier argument) et
* ajoute un pointeur vers le fils(2ème argument) à la liste.
*/
void ajouteFils(NOEUD * pere, NOEUD * fils)
{
(pere->nbDeFils)++;
// On étend la liste
pere->listeFils = realloc(pere->listeFils, pere->nbDeFils * sizeof(NOEUD *));
pere->listeFils[pere->nbDeFils - 1] = fils;
}
//! libère tout les fils d'un noeud
static int elaguer(NOEUD * noeud)
{
int i, j;
for (i=0; i<noeud->nbDeFils; i++)
{
NOEUD * fils = noeud->listeFils[i];
if (fils->listeFils != NULL) // Ce fils a des fils, donc appel récursif de cette fonction
elaguer(fils);
// On libère le plateau:
for(j=0; j<(taillePlateau*taillePlateau); j++)
free((fils->plateau)[j]);
free(fils->plateau);
// On peut maintenant libérer ce fils
free(noeud->listeFils[i]);
}
// On libère la liste des fils:
free(noeud->listeFils);
return 0;
}