367 lines
12 KiB
C
367 lines
12 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.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
#include "libDefine.h"
|
|
#include "libIA.h"
|
|
#include "libPlay.h"
|
|
#include "libDisplay.h" // Pour les messages de debug
|
|
|
|
extern int taillePlateau;
|
|
extern arguments args;
|
|
|
|
//! Place les meilleures coordonnées dans x et y
|
|
int meilleurXY(CASE *plateau[], char couleur, int * x, int * y)
|
|
{
|
|
NOEUD noeud; // On crée le noeud-père
|
|
noeud.couleur = !couleur; // C'est l'adversaire qui vient de jouer
|
|
|
|
// On récupère ici la valeur du plateau.
|
|
// On ne s'en sert actuellement pas mais bon...
|
|
int valeur = minmax(plateau, &noeud, 6, -INFINI, INFINI);
|
|
if (args.verbose)
|
|
erreur("Veleur supposée du coup: %d\n", valeur);
|
|
|
|
// On récupère les coordonnées du meilleur coup directement fils
|
|
*x = noeud.meilleurX;
|
|
*y = noeud.meilleurY;
|
|
|
|
if (args.verbose)
|
|
erreur("Valeur de la case: %d\n", valeurCase(*x, *y));
|
|
|
|
return 0;
|
|
}
|
|
|
|
//! Calcule l'arbre des possibilités et renvoie la valeur du noeud en cours
|
|
/**
|
|
* Implémentation de l'algorithme Minimax
|
|
* S'appelle récursivement pour calculer la valeur d'un noeud à partir des
|
|
* noeuds fils.
|
|
* Si un noeud est terminal, sa valeur est sa valeur heuristique (pour le
|
|
* moment, c'est tout simplement le différentiel de score. Il n'y a pas de
|
|
* fonction heuristique). Sinon, elle est égale à la plus basse valeur des
|
|
* fils. (Grâce à la simplification Negamax, pas besoin d'alterner entre plus
|
|
* bas et plus haut selon qu'on est sur un noeud ennemi ou allié).
|
|
* A venir: heuristique; élagage
|
|
*/
|
|
int minmax(CASE *plateau[], NOEUD * noeud, int profondeur, int alpha, int beta)
|
|
{
|
|
if (profondeur <= 0) // Si on est un noeud terminal on renvoie la valeur du plateau
|
|
{
|
|
int score = valeurPlateau(!noeud->couleur, plateau);
|
|
return score;
|
|
}
|
|
|
|
int index;
|
|
int i, j;
|
|
int * listeValides = malloc(taillePlateau * taillePlateau * sizeof(int));
|
|
|
|
// On calcule le nombre de fils et on alloue la liste des fils en
|
|
// onséquence.
|
|
noeud->nbDeFils = nbCoupsValides(!(noeud->couleur), plateau, listeValides);
|
|
//if (noeud->nbDeFils > 2)
|
|
// noeud->nbDeFils = 2;
|
|
if (noeud->nbDeFils)
|
|
noeud->listeFils = calloc(noeud->nbDeFils, sizeof(NOEUD *)); // calloc met la mémoire à 0
|
|
else
|
|
noeud->listeFils = malloc(1*sizeof(NOEUD)); // Si le joueur ne peut pas jouer, il passe: 1 noeud
|
|
|
|
for(i=0; i<(noeud->nbDeFils); i++)
|
|
{
|
|
// On alloue le noeud fils
|
|
(noeud->listeFils)[i] = malloc(sizeof(NOEUD));
|
|
NOEUD * fils = (noeud->listeFils)[i];
|
|
fils->listeFils = NULL;
|
|
fils->nbDeFils = 0;
|
|
|
|
// On crée une copie de travail du plateau
|
|
// (Très consommateur de mémoire)
|
|
fils->plateau = copiePlateau(plateau);
|
|
|
|
// On calcule ses coordonnées
|
|
fils->x = listeValides[i]/taillePlateau;
|
|
fils->y = listeValides[i]%taillePlateau;
|
|
fils->couleur = !(noeud->couleur);
|
|
|
|
// On simule le coup sur la copie
|
|
jouerCoup(fils->x, fils->y, fils->couleur, fils->plateau);
|
|
|
|
// Convention Negamax
|
|
fils->valeur = -minmax(fils->plateau, fils, profondeur-1, -beta, -alpha);
|
|
|
|
// Libérez la mémoire! Libérez la mémoire!
|
|
for(j=0; j<(taillePlateau*taillePlateau); j++)
|
|
free(fils->plateau[j]);
|
|
free(fils->plateau);
|
|
|
|
// Coupure alpha/beta:
|
|
if (fils->valeur > alpha)
|
|
{
|
|
noeud->meilleurX = fils->x;
|
|
noeud->meilleurY = fils->y;
|
|
alpha = fils->valeur;
|
|
index = i;
|
|
if (alpha >= beta)
|
|
{
|
|
//On élague !
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (noeud->nbDeFils == 0) // Je ne peux pas jouer
|
|
{
|
|
noeud->nbDeFils = 1; // J'ai donc une seule branche: passer
|
|
|
|
(noeud->listeFils)[0] = malloc(sizeof(NOEUD));
|
|
NOEUD * fils = (noeud->listeFils)[0];
|
|
fils->listeFils = NULL;
|
|
fils->nbDeFils = 0;
|
|
fils->plateau = copiePlateau(plateau);
|
|
|
|
fils->couleur = !(noeud->couleur);
|
|
fils->x = -1; // Inutile pour le moment
|
|
fils->y = -1;
|
|
|
|
// Je saute donc l'étape "jouer le coup"
|
|
// Et je calule la valeur des fils
|
|
fils->valeur = -minmax(fils->plateau, fils, profondeur-1, -beta, -alpha);
|
|
alpha = fils->valeur;
|
|
for(j=0; j<(taillePlateau*taillePlateau); j++)
|
|
free(fils->plateau[j]);
|
|
free(fils->plateau);
|
|
}
|
|
|
|
|
|
// Le Front de Libération de la Mémoire intervient
|
|
|
|
free(listeValides);
|
|
|
|
for(i=0; i<(noeud->nbDeFils); i++)
|
|
free((noeud->listeFils)[i]);
|
|
free(noeud->listeFils);
|
|
|
|
return alpha;
|
|
}
|
|
|
|
int valeurPlateau(char couleur, CASE *plateau[])
|
|
{
|
|
int i;
|
|
|
|
int scorePondere[2]={0, 0}, scoreMobilite[2]={0, 0},
|
|
scoreLignes[2]={0, 0}, scoreBrut[2]={0, 0};
|
|
|
|
float totalLignes=0, totalMobilite=0,
|
|
totalBrut=0, totalPondere=0;
|
|
|
|
int fPondere , fMobilite,
|
|
fLignes, fBrut; // les facteur qui viennent ponderer les scores
|
|
/*
|
|
* La mobilite prend plus d'importance au fur et a mesure qu'on avance dans
|
|
* le jeu, idem pour le score brut mais l'importance vient plus
|
|
* progressivement
|
|
*
|
|
* Le score pondéré prend de l'importance en milieu de jeu et en perd au
|
|
* fur et à mesure
|
|
*
|
|
* Le score des lignes prend de l'importance en milieu de jeu et en gagne
|
|
* petit a petit, mais jamais trop
|
|
*
|
|
* Le score brut prend de l'importance tardivement mais devient presque la
|
|
* seule vers la fin
|
|
*/
|
|
|
|
|
|
/* * * * *\
|
|
BRUT
|
|
\* * * * */
|
|
|
|
score(plateau, scoreBrut);
|
|
if (scoreBrut[BLANC] + scoreBrut[NOIR] > 0) // Au moins un joueur a des lignes
|
|
{
|
|
// Le total est la proportion des points qu'a le joueur
|
|
totalBrut = proportion((float) scoreBrut[(int) couleur], (float) scoreBrut[(int) !couleur]);
|
|
fBrut = 1;
|
|
}
|
|
else // Personne n'a de ligne
|
|
{
|
|
fBrut = 0;
|
|
}
|
|
//erreur("Total Brut: %f\n", totalBrut);
|
|
|
|
|
|
/* * * * *\
|
|
PONDERE
|
|
\* * * * */
|
|
|
|
for (i=0; i<taillePlateau*taillePlateau; i++)
|
|
{
|
|
int x = i%taillePlateau, y=i/taillePlateau;
|
|
char contenu = adresseParXY(x, y, plateau)->couleur;
|
|
if (contenu == BLANC || contenu == NOIR)
|
|
scorePondere[(int) contenu] += valeurCase(x, y);
|
|
//erreur("Valeur de la case %d,%d: %d\n", x, y, valeurCase(x, y));
|
|
}
|
|
if (scoreBrut[BLANC] + scoreBrut[NOIR] > 0) // Au moins un joueur a des lignes
|
|
{
|
|
// Le total est la proportion des points qu'a le joueur
|
|
totalPondere = proportion((float) scorePondere[(int) couleur], (float) scorePondere[(int) !couleur]);
|
|
fPondere = 5;
|
|
}
|
|
else // Personne n'a de ligne
|
|
{
|
|
fPondere = 0;
|
|
}
|
|
//erreur("Total pondéré:%f\n", totalPondere);
|
|
|
|
|
|
/* * * * *\
|
|
LIGNES
|
|
\* * * * */
|
|
|
|
for(i=0; i<taillePlateau; i++)
|
|
{
|
|
int lin = valeurLigne(i, couleur, plateau);
|
|
int col = valeurColonne(i, couleur, plateau);
|
|
|
|
if (lin>0)
|
|
scoreLignes[(int) couleur] += lin;
|
|
else
|
|
scoreLignes[(int) !couleur] -= lin;
|
|
|
|
if (col>0)
|
|
scoreLignes[(int) couleur] += col;
|
|
else
|
|
scoreLignes[(int) !couleur] -= col;
|
|
}
|
|
if (scoreLignes[BLANC] + scoreLignes[NOIR] > 0) // Au moins un joueur a des lignes
|
|
{
|
|
// Le total est la proportion des points qu'a le joueur
|
|
totalLignes = proportion((float) scoreLignes[(int) couleur], (float) scoreLignes[(int) !couleur]);
|
|
fLignes = 1.5;
|
|
//erreur("Total Lignes:%f\n", totalLignes);
|
|
}
|
|
else // Personne n'a de ligne
|
|
{
|
|
fLignes = 0;
|
|
}
|
|
|
|
/* * * * *\
|
|
MOBILITE
|
|
\* * * * */
|
|
int * temp = malloc(taillePlateau*taillePlateau*sizeof(int));
|
|
scoreMobilite[BLANC] = nbCoupsValides(BLANC, plateau, temp);
|
|
scoreMobilite[NOIR] = nbCoupsValides(NOIR, plateau, temp);
|
|
free(temp);
|
|
if (scoreMobilite[BLANC] + scoreMobilite[NOIR] > 0)
|
|
{
|
|
totalMobilite = proportion((float) scoreMobilite[(int) couleur], (float) scoreMobilite[(int) !couleur]);
|
|
fMobilite = 2;
|
|
}
|
|
else
|
|
{
|
|
fMobilite = 0;
|
|
}
|
|
|
|
int retour = 100 * ((totalPondere*fPondere) +
|
|
(totalBrut*fBrut) +
|
|
(totalLignes*fLignes) +
|
|
(totalMobilite*fMobilite)) /
|
|
(fPondere+fBrut+fLignes+fMobilite);
|
|
|
|
//erreur("Valeur du plateau suivant:%d", retour);
|
|
//affichePlateau(plateau);
|
|
|
|
return retour;
|
|
}
|
|
|
|
int valeurColonne(int colonne, char couleur, CASE *plateau[])
|
|
{
|
|
int pions[4] = {0, 0, 0, 0}; // Seuls les deux premieres cases seront lues.
|
|
// En avoir 4 permet seulement d'avoir moins
|
|
// de tests
|
|
int i;
|
|
|
|
for (i=0; i<taillePlateau; i++)
|
|
(pions[(int) adresseParXY(i, colonne, plateau)->couleur])++;
|
|
|
|
if (pions[(int) couleur] + pions[JOKER] >= taillePlateau-1) // La colonne est au joueur: Il a toute la colonne (sauf éventuellement un point)
|
|
return max(1, abs( (taillePlateau/2) - colonne)); // sans le max, renverrait 0 pour la colonne du milieu...
|
|
|
|
// On renvoie un négatif si c'est l'ennemi
|
|
if (pions[!couleur] + pions[JOKER] >= taillePlateau-1)
|
|
return - max(1, abs( (taillePlateau/2) - colonne));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int valeurLigne(int ligne, char couleur, CASE *plateau[])
|
|
{
|
|
int pions[4] = {0, 0, 0, 0}; // Seuls les deux premieres cases seront lues.
|
|
// En avoir 4 permet seulement d'avoir moins
|
|
// de tests
|
|
int i;
|
|
|
|
for (i=0; i<taillePlateau; i++)
|
|
(pions[(int) adresseParXY(ligne, i, plateau)->couleur])++;
|
|
|
|
if (pions[(int) couleur] + pions[JOKER] >= taillePlateau-1) // La ligne est au joueur: Il a toute la ligne (sauf éventuellement un point)
|
|
return max(1, abs( (taillePlateau/2) - ligne)); // sans le max, renverrait 0 pour la ligne du milieu...
|
|
|
|
// On renvoie un négatif si c'est l'ennemi
|
|
if (pions[!couleur] + pions[JOKER] >= taillePlateau-1)
|
|
return - max(1, abs( (taillePlateau/2) - ligne));
|
|
|
|
return 0;
|
|
}
|
|
|
|
int valeurCase(int x, int y)
|
|
{
|
|
x = min(x, taillePlateau-x-1);
|
|
y = min(y, taillePlateau-y-1);
|
|
if (x > 1 && y > 1) // On est au milieu du plateau
|
|
return 40*(x+y)*taillePlateau;
|
|
|
|
if (x == 1 || y == 1) // On est sur l'avant dernière ligne
|
|
{
|
|
if (x==y) // On est en diagonale du coin
|
|
return 0; // La plus basse valeur
|
|
if (x==0 || y==0) // On est sur un bord, à coté d'un coin
|
|
return 50*taillePlateau;
|
|
// Sinon:
|
|
return 150*taillePlateau;
|
|
}
|
|
|
|
if (x == 0 || y == 0) // On est sur un bord, mais pas a coté du coin
|
|
{
|
|
if (x==y) // On est sur le coin !!!
|
|
return 3500*taillePlateau;
|
|
// Sinon, on est juste sur un bord:
|
|
return 600*taillePlateau;
|
|
}
|
|
|
|
if (args.verbose)
|
|
erreur("PAS GLOP!!!!\n");
|
|
return 0;
|
|
|
|
}
|
|
|