PDA

Voir la version complète : Morpion et algorithme MinMax: une ia forcément invincible?


MrCool
01/08/2005, 19h56
Alors même que je n'ai pas fini l'article sur la création d'un morpion en SDL, je viens vous soumettre la future deuxième partie de cet article avec conception d'une IA à l'aide de MinMax.

Actuellement ce code fonctionne mais mon IA est très très bête. Je trouve cela bizarre de la part d'un algo qui explore (et pour le coûp c'est bien le cas!) *toutes* les coûps possibles.

Est-ce un comportement normal? J'aurais pensé qu'un tel algo serait "invincible".

NB: toute aide sera précieuse, mais ne cherchez pas à optimiser pour l'instant, je veux un algo qui reste très simple pour l'article. Il sera certainement refait plus tard avec AlphaBeta :)


#include <SDL/sdl.h>
#include <stdlib.h>

#define Maximum(a,b) ((a)>(b))?(a):(b)
#define Minimum(a,b) ((a)<(b))?(a):(b)

#define JOUEUR_IA CARRE
#define JOUEUR_HUMAIN ROND

enum e_case
{
VIDE=0,
CARRE=1,
ROND=-1
};

enum e_case TestGagnant(const enum e_case *plateau);
enum e_case MinMax(enum e_case *plateau, enum e_case joueur);
void IAJoue(enum e_case *plateau);

void DEBUT_PrintPlateau(enum e_case *plateau)
{
int i,j;
for(i=0;i<3;i++)
{
for(j=0;j<3;j++)
switch(plateau[i*3+j])
{
case VIDE:
printf("-");
break;
case ROND:
printf("O");
break;
case CARRE:
printf("X");
break;
}
printf("\n");
}
}


enum e_case TestGagnant(const enum e_case *plateau)
{
int i;
/* Teste les lignes */
for(i=0;i<3;i++)
if( (plateau[(i*3)+0]==plateau[(i*3)+1]) && (plateau[(i*3)+0]==plateau[(i*3)+2]) )
if(plateau[(i*3)+0]!=VIDE)
return plateau[(i*3)+0];

/* Teste les colonnes */
for(i=0;i<3;i++)
if( (plateau[i]==plateau[3+i]) && (plateau[i]==plateau[6+i]) )
if(plateau[i]!=VIDE)
return plateau[i];

/* Teste les diagonales */
if( (plateau[0]==plateau[4]) && (plateau[0]==plateau[8]) )
if(plateau[0]!=VIDE)
return plateau[0];

if( (plateau[2]==plateau[4]) && (plateau[2]==plateau[6]) )
if(plateau[2]!=VIDE)
return plateau[2];

return VIDE;
}


enum e_case MinMax(enum e_case *plateau, enum e_case joueur)
{
unsigned char i, j, estNul;
enum e_case copie[9];
enum e_case res, tmp;

DEBUT_PrintPlateau(plateau);

/* teste s'il y a un gagnant */
tmp=TestGagnant(plateau);
if(tmp!=VIDE)
return tmp;

/* la partie est soit nulle, soit non-finie */

/* initialisation des variables */
estNul=1;
res=VIDE;

if(joueur==JOUEUR_IA)
tmp=-2;
else
tmp=2;

/* Copie du tableau */
for(i=0;i<9;i++)
copie[i]=plateau[i];

for(i=0;i<9;i++)
if(copie[i]==VIDE)
{ /* il s'agit d'un coûp possible, évaluons-le */
copie[i]=joueur;

tmp=MinMax(copie, (joueur==CARRE)?ROND:CARRE);
if(joueur==JOUEUR_IA) /* l'ia joue */
res=Maximum(tmp,res); /* on maximise le score quand nous jouons; */
else
res=Minimum(tmp,res); /* on le minimise quand l'adverssaire joue. */

/* Un coûp est possible, cette situation n'est donc pas finale */
estNul=0;

/* Réinitialisation du tableau */
for(j=0;j<9;j++)
copie[j]=plateau[j];
}
if(estNul) /* la partie est nulle! */
res=0;

return res;
}


void IAJoue(enum e_case *plateau)
{
unsigned char i,j;
enum e_case copie[9];
enum e_case score[9];
int score_max, coup_max;

/* Initialisation */
for(i=0;i<9;i++)
{
score[i]=-2;
copie[i]=plateau[i];
}

for(i=0;i<9;i++)
{
if(plateau[i]==VIDE)
{
/* On tente un coûp. */
copie[i]=JOUEUR_IA;

/* Si le coûp est gagnant, inutile d'aller chercher plus loin! */
if(TestGagnant(copie)==JOUEUR_IA)
{
plateau[i]=JOUEUR_IA;
return;
}

/* Evaluation du score avec cette action. */
score[i]=MinMax(copie, JOUEUR_HUMAIN);

/* Réinitialisation du tableau */
for(j=0;j<9;j++)
copie[j]=plateau[j];
}
}

/* Détermination du meilleur coûp */
coup_max=0;
score_max=-2;
for(i=0;i<9;i++)
if(score[i]>score_max)
score_max=score[i], coup_max=i;

/* On joue! */
plateau[coup_max]=JOUEUR_IA;
}


int main(int argc, char **argv)
{
SDL_Surface *screen, *fond, *carre, *rond;
SDL_Event event;
SDL_Rect dest;
int isdone;
int cx, cy, i;
enum e_case joueur, gagnant;

enum e_case plateau[9]=
{VIDE,VIDE,VIDE,
VIDE,VIDE,VIDE,
VIDE,VIDE,VIDE};

SDL_Init(SDL_INIT_VIDEO);

screen = SDL_SetVideoMode(640,480,32,SDL_HWSURFACE|SDL_DOUBLEBUF);
SDL_WM_SetCaption("Morpion - Games Creators Network (http://www.games-creators.org)",NULL);

/* chargement des médias */
fond=SDL_LoadBMP("fond.bmp");
carre=SDL_LoadBMP("carre.bmp");
rond=SDL_LoadBMP("rond.bmp");

isdone=0;
joueur=JOUEUR_HUMAIN;
gagnant=VIDE;
while((!isdone)&&(gagnant==VIDE))
{
/* Si c'est à l'ia de joueur... elle joue! */
if(joueur==JOUEUR_IA)
{
IAJoue(plateau);
joueur=JOUEUR_HUMAIN;
gagnant=TestGagnant(plateau);
continue;
}

while(SDL_PollEvent(&event))
{
switch(event.type)
{
case SDL_MOUSEBUTTONUP:
if(joueur==JOUEUR_IA) break;
if(event.button.x<100) break;
if(event.button.x>295) break;
if(event.button.y<100) break;
if(event.button.y>295) break;

/* Détermination de la case */
cx = (event.button.x-100)/65;
cy = (event.button.y-100)/65;

if(plateau[cx+3*cy]!=VIDE) break;

plateau[cx+3*cy]=joueur;

if(joueur==CARRE)
joueur=ROND;
else
joueur=CARRE;

break;
case SDL_QUIT:
isdone=1;
break;
}
}

/* Test du gagnant */
gagnant=TestGagnant(plateau);

/* affichage fond */
SDL_BlitSurface(fond, NULL, screen, NULL);
/* affichage de la carte */
dest.w=dest.h=64;
for(i=0;i<9;i++)
switch(plateau[i])
{
case VIDE:
break;
case CARRE:
dest.x=100+(i%3)*65, dest.y=100+(i/3)*65;
SDL_BlitSurface(carre, NULL, screen, &dest);
break;
case ROND:
dest.x=100+(i%3)*65, dest.y=100+(i/3)*65;
SDL_BlitSurface(rond, NULL, screen, &dest);
break;
}

/* inversion des tampons */
SDL_Flip(screen);
}

if(gagnant!=VIDE)
{
dest.w=dest.h=64;
while(!isdone)
{
while(SDL_PollEvent(&event))
if(event.type==SDL_QUIT) isdone=1;

dest.x=300+rand()%(640-64-300);
dest.y=rand()%(480-64);
if(gagnant==ROND)
SDL_BlitSurface(rond, NULL, screen, &dest);
else
SDL_BlitSurface(carre, NULL, screen, &dest);

SDL_Flip(screen);
}
}

/* Libération des médias */
SDL_FreeSurface(fond);
SDL_FreeSurface(carre);
SDL_FreeSurface(rond);

SDL_Quit();
return 0;
}

Loulou
01/08/2005, 20h34
Est-ce normal que tu maximises ou minimises non pas un score, mais une couleur (VIDE, CARRE ou ROND) ?

D'ailleurs là ce que tu fais ce n'est pas tellement du minmax, c'est simplement une recherche en profondeur puisque tu vises directement le coup gagnant. Ca n'est pas forcément nécessaire pour un morpion, mais dans l'intérêt du tutoriel peut-être vaudrait-il mieux prendre un vrai score d'évaluation (pas seulement gagné / pas gagné), surtout si tu comptes l'évoluer en alpha-bêta ?

MrCool
01/08/2005, 22h27
En réalité maximiser/mimiser la couleur revient au même dans la mesure où mon type énuméré est fait pour.

-1: joueur humain gagne
0: partie nulle
1: ia gagne

Qu'aurais-tu renvoyé comme sortie d'un MinMax pour un morpion?

Loulou
01/08/2005, 22h44
En réalité maximiser/mimiser la couleur revient au même dans la mesure où mon type énuméré est fait pour
Ok, je me disais bien qu'il devait y avoir une feinte. Par contre c'est pas vraiment intuitif (je trouve), j'espère que t'as prévu de bien commenter ça.

Qu'aurais-tu renvoyé comme sortie d'un MinMax pour un morpion?
A vrai dire j'en n'ai aucune idée. Le morpion n'est pas vraiment fait pour, étant donné qu'il est assez simple pour chercher le coup gagnant directement. Donc tu peux oublier ma remarque en fait.


...à moins que tu sois motivé pour recommencer ton tuto avec un othello par exemple :D.


A part ça, bravo pour tous les tutos que tu écris en ce moment ;)

MrCool
01/08/2005, 22h52
J'ai regardé d'autres codes pour voir et en fait, le "niveau" de l'ia se règle en fixant la profondeur de la recherche. Ce sera assez facile d'ajouter ça plus tard.

Par contre, avec un nombre de niveaux infinis comme c'est le cas ici, cette ia devrait être imbattable... or ce n'est pas le cas.

Il y a donc un problème qq part et pour l'instant je ne vois pas où...

Loulou
01/08/2005, 23h13
Effectivement ça devrait gagner à tous les coups.

Est-ce que tu as essayé d'afficher (vite fait) quelques niveaux de ton arbre de recherche, histoire de voir si les valeurs calculées sont bonnes et si le minmax remonte les bons noeuds ?

MrCool
01/08/2005, 23h27
En effet c'est mal parti:

le tableau de score final au deuxième coup (premier coup pour l'ia) est le suivant:


| coup: 0 - score: 0 |
| coup: 1 - score: -1 |
| coup: 2 - score: -1 |
| coup: 3 - score: -1 |
| coup: 4 - score: -1 |
| coup: 5 - score: -1 |
| coup: 6 - score: -1 |
| coup: 7 - score: -1 |
| coup: 8 - score: -1 |

le plateau de jeu:

0 1 2
3 4 5
6 7 8


Il est impossible d'être sûr de perdre ou de faire un nul dès le premier coup... Je vais voir s'il y a d'autres résultat choquants.

MrCool
01/08/2005, 23h42
Bon j'ai trouvé ma première bêtise: j'appellais MinMax avec comme joueur actuel le joueur ia alors que c'est le joueur humain.

Les réactions de l'IA sont déjà bien meilleures mais il est loin d'être imbattable :)


edit: les scores durant le premier tableau sont toujours très bas. Mais en fait ça me paraît logique après coûp: il est impossible d'être sûr de ne pas perdre au premier coûp et comme MinMax prévoit la pire situation... ça paraît logique!

MrCool
02/08/2005, 00h02
J'ai réédité mon premier programme avec les modifications apportées.

J'ai modifié l'initialisation du tableau de score qui est désormais initialisé à -2.

Il reste encore un problème car dans certaines situations l'ia ne prend pas la bonne décision.

Je verrais ça demain ;)

MrCool
02/08/2005, 11h12
Encore un bug en moins... je modifiais mon indice de boucle for accidentellement.
Il ne reste plus qu'un cas où je peux battre mon algo.

Il s'agit de la configuration suivante.


XXO
-O-
--O

coup 0 => -2
coup 1 => -2
coup 2 => -2
coup 3 => -1
coup 4 => -2
coup 5 => -1
coup 6 => -1
coup 7 => -1
coup 8 => -2


Il choisit alors le coup 3 alors qu'il faudrait choisir le coup 6.

J'y suis presque ;)

HanLee
02/08/2005, 11h54
Heu j'veux juste dire que ya pas de circonflexe sur le 'u' de coup =P

Eva
03/08/2005, 20h22
XXO
-O-
--O


Il choisit alors le coup 3 alors qu'il faudrait choisir le coup 6.

J'y suis presque ;)
Ton IA a les X ou les O ?
Parce que s'il a les O, il a gagné là :
XXO
-OX
O-O
ou
XXO
-OO
X-O

Et pour des explications sur minmax/alphabeta et compagnie : www.swgbase.net/forums/

MrCool
03/08/2005, 20h33
Mon ia a les "X" :)

Je ne devrais pas retrouver dans de telles situations où je suis gagnant dans tous les cas.

J'ai déjà parcouru tes articles mais j'irais les relire en profondeur ^^

Eva
03/08/2005, 20h45
A mon avis, tu as fait une erreur de conception au niveau de la fonction d'évaluation.
Si tu veux une IA vraiment imbattable, il est très important d'intégrer le concept de nombre de tours dans ce que renvoie l'algorithme minmax.

C'est à dire, si ton IA peut gagner en 3, 4, 5 ou 8 tours, alors la note de la position gagnante en 3 tours doit être +infini, en 4 tours : +infini-1, 5 tours : + infini-2 et 8 tours : +infini-5 (avec +infini = un grand nombre, supérieur au maximum que peut renvoyer ta fonction d'évaluation).

MrCool
03/08/2005, 23h21
Oui justement c'est là que se situe mon interrogation.

Je suis parti sur l'idée que la note renvoyée était -1/0/1 et qu'on pouvait faire une ia imbattable uniquement via ce système.

Par exemple ce programme: http://www.cppfrance.com/codes/MORPION-IA-MINMAX-MINIMAX-/23851.aspx

C'est une ia de morpion avec MinMax et le même système de notation. Or en mode "imbattable", il semblerait qu'il le soit vraiment (ou alors c'est moi qui suis nul! :p)

Bref je suis assez perplexe...

Eva
04/08/2005, 00h32
En fait ça me semble assez logique (j'ai pas tellement regardé ton code, j'étais fort occupé avec l'interprêteur de lambda calcul aujourd'hui, donc petite saturation côté prog là :p, donc risque que je dise une bêtise).

A mon avis, ce qui se passe est que tu fais une recherche de tous les coups possibles à partir d'une situation jusqu'à ce que tu te retrouves dans une position gagnante ou perdante.

Si la position est gagnante, l'IA gagne la note, si elle est perdante, l'IA perd la note. Comme la note ne peut être que -1, 0 ou 1, la plupart du temps, l'IA va te sortir la première position gagnante trouvée (alors que ce n'est pas une solution gagnante en réalité).

Exemple :
012
345
678

XXO
-O-
--O
Dans cette situation, la première position gagnante pour l'IA est de remplir les cases 3 et 6 (dans cet ordre, puisque tu testes (probablement) 3 et 6 dans cet ordre.
C'est à dire l'IA va jouer en 3, puis faire une simulation du joueur jouant en 7 (une position où l'IA peut encore gagner) pour ensuite jouer en 6.

Bref, la note que renvoie le minmax ne devrait pas représenter une position gagnante ou perdante, mais plutôt une potentialité de victoire ou de défaite.

En clair, le but de l'IA n'est pas de chercher à aligner 3 croix, mais de chercher à atteindre une situation où elle peut à coup sûr aligner 3 croix au tour suivant. C'est à dire une situation où ses pièces sont placées de telle façon qu'il est possible d'aligner 3 croix, même si son adversaire lui bouche une possibilité.
Donc en clair une situation comme celle-ci :
OOX
-X-
--X

Ceci dit, je déconseille vraiment de faire une IA sur un jeu de morpion à 9 cases. En effet, pour ne pas perdre, il suffit de :
- placer son premier pion au centre (si on est le premier à jouer)
- placer son premier pion dans un coin (si on est le second à jouer et que le premier joueur a mit son premier pion au centre)

Si tu ne veux pas utiliser ce principe de "chercher à gagner dans au prochain tour" (ce qui revient à chercher à piéger son adversaire), tu peux arriver au même résultat (quasiment) en recherchant à gagner le plus vite possible (à la fois pour l'IA et le joueur). C'est à dire que les deux adversaires doivent réagir comme s'ils étaient des joueurs imbattables, et c'est là que le morpion à 9 cases montre qu'il n'est pas adapté pour un tutorial sur le minmax : si on sait jouer, il est impossible de perdre au morpion à 9 cases, ce qui veut dire qu'il est aussi impossible de gagner. Autrement dit, ton algo de recherche du meilleur coup sera incapable de dire quel sera le meilleur coup à partir d'une recherche systématique.

En effet, si le principe est de faire une recherche jusqu'à ce qu'il y ait un gagnant, cette recherche n'aboutira jamais ou donnera des résultats incohérents (comme le choix du 3 au lieu du 6 dans ton exemple), parce que l'IA et l'adversaire que l'IA doit simuler sont tous deux imbattables.

A mon avis, il vaut mieux ne pas faire de recherche systématique dans ce genre de jeux.

Eva
04/08/2005, 00h38
En clair, le but de l'IA n'est pas de chercher à aligner 3 croix, mais de chercher à atteindre une situation où elle peut à coup sûr aligner 3 croix au tour suivant.
Petite précision sur ce point, pendant que j'y pense. Quand je dis "chercher à atteindre", je veux dire que la fonction d'évaluation doit tendre vers ce phénomène (sans jamais, ou presque, l'atteindre).
C'est à dire qu'il ne l'atteint pas tant que quand l'événement est au delà de l'horizon.

(horizon = si tu fais une recherche minmax de profondeur P, alors l'horizon se trouve à P demi-coups de la situation actuelle)

MrCool
04/08/2005, 09h25
OK. Merci pour ton aide eva :)

TheTiger
25/08/2005, 20h54
l'ordinateur est théoriquement imbatable au pire on peut faire match null mais pour qu'il soit imbatable il faut qu'il ai testé toutes les combinaisons possibles ce qui est énorme dans certaint jeux...cela étant avec beaucoup de puissance et de mémoire on peut rendre le pc imbatable ;)
C'est une question de mhz de Mo et de minutes ;) A+

TheTiger
25/08/2005, 21h02
J'ai pas tout lu mais je me demande si vous connaissez la bonne vielle technique de l'IA alpha beta qui est une IA bête et méchante le principe est simple c'est programmé par une fonction de récurence:

On appel la fonction en précisant l'état du jeu et de qui est en traint de jouer
alors elle fait des supositions et jouer chaqu'une des possibilité en simulation et suivant si c'est à elle de jouer ou non et donc elle maximise le score pour elle est le minimise pour nous ;) en considérant que l'on est super intélligent et que l'on jour les meilleurs coup c'est à dire ce qui rapport le moin de point pour nous c'est pour ça que l'on l'appel mix max car alpha beta c'est une version évolué si vous voulez + de détailles suffi de me demander car je vais pas tout détailler sans être sur que ce soit util ;)