From 7e31c799b2947d837b3492a5dda426207f1cca1c Mon Sep 17 00:00:00 2001 From: Nabzzz Date: Sat, 20 Mar 2021 18:51:15 +0100 Subject: [PATCH] TP2 version fonctionnelle --- TP2.pl | 428 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 428 insertions(+) create mode 100644 TP2.pl diff --git a/TP2.pl b/TP2.pl new file mode 100644 index 0000000..8f3b74a --- /dev/null +++ b/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�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� 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]). +