TP2 version fonctionnelle

这个提交包含在:
Nabzzz 2021-03-20 18:51:15 +01:00
父节点 68f191c7da
当前提交 7e31c799b2

428
TP2.pl 普通文件
查看文件

@ -0,0 +1,428 @@
/*********************************
DESCRIPTION DU JEU DU TIC-TAC-TOE
*********************************/
/*
Une situation est decrite par une matrice 3x3.
Chaque case est soit un emplacement libre (Variable LIBRE), soit contient le symbole d'un des 2 joueurs (o ou x)
Contrairement a la convention du tp precedent, pour modeliser une case libre
dans une matrice on n'utilise pas une constante speciale (ex : nil, 'vide', 'libre','inoccupee' ...);
On utilise plut<EFBFBD>t un identificateur de variable, qui n'est pas unifiee (ex : X, A, ... ou _) .
La situation initiale est une "matrice" 3x3 (liste de 3 listes de 3 termes chacune)
o<EFBFBD> chaque terme est une variable libre.
Chaque coup d'un des 2 joureurs consiste a donner une valeur (symbole x ou o) a une case libre de la grille
et non a deplacer des symboles deja presents sur la grille.
Pour placer un symbole dans une grille S1, il suffit d'unifier une des variables encore libres de la matrice S1,
soit en ecrivant directement Case=o ou Case=x, ou bien en accedant a cette case avec les predicats member, nth1, ...
La grille S1 a change d'etat, mais on n'a pas besoin de 2 arguments representant la grille avant et apres le coup,
un seul suffit.
Ainsi si on joue un coup en S, S perd une variable libre, mais peut continuer a s'appeler S (on n'a pas besoin de la designer
par un nouvel identificateur).
*/
%:- use_module(library(clpfd)).
situation_initiale([ [_,_,_],
[_,_,_],
[_,_,_]]).
% Convention (arbitraire) : c'est x qui commence
joueur_initial(x).
cpu_time(Goal, Elapsed_Time) :-
statistics(process_cputime,Start),
call(Goal),
statistics(process_cputime,Finish),
Elapsed_Time is (Finish-Start)*1000.
% Definition de la relation adversaire/2
adversaire(x,o).
adversaire(o,x).
/****************************************************
DEFINIR ICI a l'aide du predicat ground/1 comment
reconnaitre une situation terminale dans laquelle il
n'y a aucun emplacement libre : aucun joueur ne peut
continuer a jouer (quel qu'il soit).
****************************************************/
situation_terminale(_Joueur, Situation) :- ground(Situation).
/***************************
DEFINITIONS D'UN ALIGNEMENT
***************************/
alignement(L, Matrix) :- ligne( L,Matrix).
alignement(C, Matrix) :- colonne( C,Matrix).
alignement(D, Matrix) :- diagonale(D,Matrix).
/********************************************
DEFINIR ICI chaque type d'alignement maximal
existant dans une matrice carree NxN.
********************************************/
ligne(L, M) :- member(L,M).
colonne_aux(_,[],[]).
colonne_aux(N,[X|Cr],[L|Mr]):-
nth1(N,L,X),
colonne_aux(N,Cr,Mr).
colonne(C,M) :-
colonne_aux(_,C,M).
%colonne(C,M) :- transpose(M,T), member(C,T).
/* Definition de la relation liant une diagonale D a la matrice M dans laquelle elle se trouve.
il y en a 2 sortes de diagonales dans une matrice carree(https://fr.wikipedia.org/wiki/Diagonale) :
- la premiere diagonale (principale) : (A I)
- la seconde diagonale : (Z R)
A . . . . . . . Z
. \ . . . . . / .
. . \ . . . / . .
. . . \ . / . . .
. . . . X . . .
. . . / . \ . . .
. . / . . . \ . .
. / . . . . . \ .
R . . . . . . . I
*/
diagonale(D, M) :-
premiere_diag(1,D,M).
% deuxieme definition A COMPLETER
diagonale(D, M) :- seconde_diag(3,D,M).
premiere_diag(_,[],[]).
premiere_diag(K,[E|D],[Ligne|M]) :-
nth1(K,Ligne,E),
K1 is K+1,
premiere_diag(K1,D,M).
seconde_diag(_,[],[]).
seconde_diag(K,[E|D],[Ligne|M]) :-
nth1(K,Ligne,E),
K1 is K-1,
seconde_diag(K1,D,M).
/*****************************
DEFINITION D'UN ALIGNEMENT
POSSIBLE POUR UN JOUEUR DONNE
*****************************/
possible([X|L], J) :- unifiable(X,J), possible(L,J).
possible( [], _).
/* Attention
il faut juste verifier le caractere unifiable
de chaque emplacement de la liste, mais il ne
faut pas realiser l'unification.
*/
% A FAIRE
unifiable(X,J) :- ( var(X) -> true ; J=X) .
/**********************************
DEFINITION D'UN ALIGNEMENT GAGNANT
OU PERDANT POUR UN JOUEUR DONNE J
**********************************/
/*
Un alignement gagnant pour J est un alignement
possible pour J qui n'a aucun element encore libre.
*/
/*
Remarque : le predicat ground(X) permet de verifier qu'un terme
prolog quelconque ne contient aucune partie variable (libre).
exemples :
?- ground(Var).
no
?- ground([1,2]).
yes
?- ground(toto(nil)).
yes
?- ground( [1, toto(nil), foo(a,B,c)] ).
no
*/
/* Un alignement perdant pour J est un alignement gagnant pour son adversaire. */
% A FAIRE
alignement_gagnant(Ali, J) :- possible(Ali,J), ground(Ali).
alignement_perdant(Ali, J) :- adversaire(J,A), alignement_gagnant(Ali, A).
/* ****************************
DEFINITION D'UN ETAT SUCCESSEUR
****************************** */
/*
Il faut definir quelle operation subit la matrice
M representant l'Etat courant
lorsqu'un joueur J joue en coordonnees [L,C]
*/
% A FAIRE
successeur(J, Etat,[L,C]) :- nth1(L,Etat,Lig), nth1(C,Lig,X), var(X), X=J.
/**************************************
EVALUATION HEURISTIQUE D'UNE SITUATION
**************************************/
/*
1/ l'heuristique est +infini si la situation J est gagnante pour J
2/ l'heuristique est -infini si la situation J est perdante pour J
3/ sinon, on fait la difference entre :
le nombre d'alignements possibles pour J
moins
le nombre d'alignements possibles pour l'adversaire de J
*/
heuristique(J,Situation,H) :- % cas 1
H = 10000, % grand nombre approximant +infini
alignement(Alig,Situation),
alignement_gagnant(Alig,J), !.
heuristique(J,Situation,H) :- % cas 2
H = -10000, % grand nombre approximant -infini
alignement(Alig,Situation),
alignement_perdant(Alig,J), !.
% on ne vient ici que si les cut precedents n'ont pas fonctionne,
% c-a-d si Situation n'est ni perdante ni gagnante.
% A FAIRE cas 3
heuristique(J,Situation,H) :-
findall(X, (alignement(X,Situation),possible(X,J)),Lgagnant),
adversaire(J,A),
findall(Y, (alignement(Y,Situation),possible(Y,A)),Lperdant),
length(Lgagnant, LG),
length(Lperdant, LP),
H is LG-LP.
test_heuristique :-
( ( S1= [ [_,_,_], [_,_,_], [_,_,_] ],
heuristique(x,S1,0),
S2 = [ [x,o,_], [_,_,_], [_,_,_] ],
heuristique(x,S2,1),
S3 = [ [_,o,_], [_,x,_], [_,_,_] ],
heuristique(x,S3,2),
S4 = [ [o,_,_], [_,x,_], [_,_,_] ],
heuristique(x,S4,1),
S5 = [ [x,_,_], [_,o,_], [_,_,_] ],
heuristique(o,S5,1),
S6 = [ [x,_,_], [_,x,_], [_,_,x] ],
heuristique(o,S6,-10000),
S7 = [ [x,_,_], [_,x,_], [_,_,x] ],
heuristique(x,S7,10000)
) ->
write('test heuristique successful')
;
write('error heuristique test')
).
/*******************************************
*
*
*
*
* NEGAMAX
*
*
*
* *****************************************/
/*
Ce programme met en oeuvre l'algorithme Minmax (avec convention
negamax) et l'illustre sur le jeu du TicTacToe (morpion 3x3)
*/
/*:- [tictactoe].*/
/****************************************************
ALGORITHME MINMAX avec convention NEGAMAX : negamax/5
*****************************************************/
/*
negamax(+J, +Etat, +P, +Pmax, [?Coup, ?Val])
SPECIFICATIONS :
retourne pour un joueur J donne, devant jouer dans
une situation donnee Etat, de profondeur donnee P,
le meilleur couple [Coup, Valeur] apres une analyse
pouvant aller jusqu'a la profondeur Pmax.
Il y a 3 cas a decrire (donc 3 clauses pour negamax/5)
1/ la profondeur maximale est atteinte : on ne peut pas
developper cet Etat ;
il n'y a donc pas de coup possible a jouer (Coup = rien)
et l'evaluation de Etat est faite par l'heuristique.
2/ la profondeur maximale n'est pas atteinte mais J ne
peut pas jouer ; au TicTacToe un joueur ne peut pas jouer
quand le tableau est complet (totalement instancie) ;
il n'y a pas de coup a jouer (Coup = rien)
et l'evaluation de Etat est faite par l'heuristique.
3/ la profondeur maxi n'est pas atteinte et J peut encore
jouer. Il faut evaluer le sous-arbre complet issu de Etat ;
- on determine d'abord la liste de tous les couples
[Coup_possible, Situation_suivante] via le predicat
successeurs/3 (deja fourni, voir plus bas).
- cette liste est passee a un predicat intermediaire :
loop_negamax/5, charge d'appliquer negamax sur chaque
Situation_suivante ; loop_negamax/5 retourne une liste de
couples [Coup_possible, Valeur]
- parmi cette liste, on garde le meilleur couple, c-a-d celui
qui a la plus petite valeur (cf. predicat meilleur/2);
soit [C1,V1] ce couple optimal. Le predicat meilleur/2
effectue cette selection.
- finalement le couple retourne par negamax est [Coup, V2]
avec : V2 is -V1 (cf. convention negamax vue en cours).
A FAIRE : ECRIRE ici les clauses de negamax/5
.....................................
*/
negamax(J, Etat, P, P, [nil, Val]) :- heuristique(J,Etat,Val).
negamax(J,Etat, P, Pmax, [nil,Val]) :- P < Pmax ,
heuristique(J,Etat,Val),
Val = 10000.
negamax(J,Etat, P, Pmax, [nil,Val]) :- P < Pmax ,
heuristique(J,Etat,Val),
Val = -10000.
negamax(J,Etat, P, Pmax, [nil,Val]) :- P < Pmax ,
situation_terminale(J,Etat),
heuristique(J,Etat,Val).
negamax(J,Etat, P, Pmax, [Coup,Val]) :- P < Pmax ,
successeurs(J,Etat,Successeurs),
loop_negamax(J,P,Pmax,Successeurs,Liste_Couples),
meilleur(Liste_Couples,[Coup,V1]),
Val is -V1.
/*******************************************
DEVELOPPEMENT D'UNE SITUATION NON TERMINALE
successeurs/3
*******************************************/
/*
successeurs(+J,+Etat, ?Succ)
retourne la liste des couples [Coup, Etat_Suivant]
pour un joueur donne dans une situation donnee
*/
successeurs(J,Etat,Succ) :-
copy_term(Etat, Etat_Suiv),
findall([Coup,Etat_Suiv],
successeur(J,Etat_Suiv,Coup),
Succ).
/*************************************
Boucle permettant d'appliquer negamax
a chaque situation suivante :
*************************************/
/*
loop_negamax(+J,+P,+Pmax,+Successeurs,?Liste_Couples)
retourne la liste des couples [Coup, Valeur_Situation_Suivante]
a partir de la liste des couples [Coup, Situation_Suivante]
*/
loop_negamax(_,_, _ ,[], []).
loop_negamax(J,P,Pmax,[[Coup,Suiv]|Succ],[[Coup,Vsuiv]|Reste_Couples]) :-
loop_negamax(J,P,Pmax,Succ,Reste_Couples),
adversaire(J,A),
Pnew is P+1,
negamax(A,Suiv,Pnew,Pmax, [_,Vsuiv]).
/*
A FAIRE : commenter chaque litteral de la 2eme clause de loop_negamax/5,
en particulier la forme du terme [_,Vsuiv] dans le dernier
litteral ?
*/
/*********************************
Selection du couple qui a la plus
petite valeur V
*********************************/
/*
meilleur(+Liste_de_Couples, ?Meilleur_Couple)
SPECIFICATIONS :
On suppose que chaque element de la liste est du type [C,V]
- le meilleur dans une liste a un seul element est cet element
- le meilleur dans une liste [X|L] avec L \= [], est obtenu en comparant
X et Y,le meilleur couple de L
Entre X et Y on garde celui qui a la petite valeur de V.
A FAIRE : ECRIRE ici les clauses de meilleur/2
*/
/*meilleur([Cm,Vm], [Cm,Vm]).
meilleur([[C,V]|R], [Cm,Vm]) :-
meilleur(R, [Cm,Vm]),
( Vm > V -> ).*/
meilleur([X], X). %le meilleur dans une liste a un seul element est cet element
meilleur(Liste_de_Couples, MeilleurCouple ) :-
Liste_de_Couples = [ [C,V] | Fin ] ,
meilleur(Fin,[Cout,Val]),
( Val > V -> MeilleurCouple = [C , V]
;
MeilleurCouple = [Cout , Val]
).
test_meilleur :-
( ( meilleur([[a,2],[b,3],[c,4] ],[a,2]),
meilleur([[a,2],[b,3],[c,1] ],[c,1]),
meilleur([[a,2],[b,2]],[b,2]),
meilleur([[a,2]],[a,2])
) ->
write('test meilleur successful')
;
write('error meilleur test')
).
/******************
PROGRAMME PRINCIPAL
*******************/
main(B,V, Pmax) :-
situation_initiale(Ini),
joueur_initial(J),
negamax(J,Ini,0,Pmax,[B,V]).