commit f79dad09fd4cabea85c42a1087acf4b76c869ec9 Author: Yohan Simard Date: Sun Mar 21 01:31:33 2021 +0100 Initial commit diff --git a/tp1/aetoile.pl b/tp1/aetoile.pl new file mode 100644 index 0000000..eed3cec --- /dev/null +++ b/tp1/aetoile.pl @@ -0,0 +1,107 @@ +:- include('avl.pl'). % predicats pour gerer des arbres bin. de recherche +:- include('taquin.pl'). % predicats definissant le systeme a etudier + +main :- + % Calcul de H pour la situation de départ + initial_state(U0), + heuristique(U0, H0), + + % initialisations Pf, Pu et Q + insert([ [H0, H0, 0], U0 ], nil, Pf), + insert([U0, [H0, H0, 0], nil, nil], nil, Pu), + empty(Q), + !, + + % lancement de Aetoile + aetoile(Pf, Pu, Q). + +%******************************************************************************* + +afficher_solution(_, nil). + +afficher_solution(Q, S) :- + belongs([S, [_,_,G], Pere, Action], Q), + !, + write_state(S), + print(Action), + print(' ('), + print(G), + print(')'), + nl, nl, + afficher_solution(Q, Pere). + +%******************************************************************************* + +% Cas arbres vides -> plus d'état à développer -> pas de solution +aetoile(Pf, _, _) :- + empty(Pf), + !, + print('Pas de solution : l\'état final n\'est pas atteignable !\n'). + +% Cas état final -> solution trouvée +aetoile(Pf, Pu, Q) :- + suppress_min([[F, H, G], Sf], Pf, _), + final_state(Sf), + !, + suppress([Sf, [F, H, G], Pere, A], Pu, _), + insert([Sf, [F, H, G], Pere, A], Q, Q1), + print('Solution trouvée :\n'), + afficher_solution(Q1, Sf). + +aetoile(Pf, Pu, Q) :- + suppress_min([[F, H, G], S], Pf, Pf1), + suppress([S, [F, H, G], Pere, A], Pu, Pu1), + expand(S, G, Successors), + loop_successors(Successors, S, Pf1, Pu1, Q, Pf2, Pu2), + insert([S, [F, H, G], Pere, A], Q, Q1), + aetoile(Pf2, Pu2, Q1). + +%******************************************************************************* + +expand(S, Gs, Successors) :- + findall([Successor, [F, H, G], Action], ( + rule(Action, Cost, S, Successor), + G is Gs + Cost, + heuristique(Successor, H), + F is G + H + ), Successors). + +%******************************************************************************* + +% Cas pas de successseurs +loop_successors([], _,Pf, Pu, _, Pf, Pu) :- !. + +% Cas successeur déjà traité : on ignore +loop_successors([[S,_,_] | Rest], Pere, Pf, Pu, Q, Pf_new, Pu_new) :- + belongs([S,_,_,_], Q), + %!, + loop_successors(Rest, Pere, Pf, Pu, Q, Pf_new, Pu_new), + !. + +% Cas successeur déjà connu : on met à jour le coût +loop_successors([[S, [F, H, G], Action] | Rest], Pere, Pf, Pu, Q, Pf_new, Pu_new) :- + belongs([S, [F_old,_,_], _, _], Pu), + %!, + update_trees(S, H, [F, G, Pere, Action], F_old, Pf, Pf1, Pu, Pu1), + loop_successors(Rest, Pere, Pf1, Pu1, Q, Pf_new, Pu_new), + !. + +% Cas successeur inconnu : on l'ajoute à P +loop_successors([[S, [F, H, G], Action] | Rest], Pere, Pf, Pu, Q, Pf_new, Pu_new) :- + insert([S, [F, H, G], Pere, Action], Pu, Pu1), + insert([[F, H, G], S], Pf, Pf1), + loop_successors(Rest, Pere, Pf1, Pu1, Q, Pf_new, Pu_new). + +%******************************************************************************* + +% Mise à jour des coûts et des parents seulement si plus faible +update_trees(S, H, [F, G, Pere, Action], F_old, Pf, Pf2, Pu, Pu2) :- + (F_old is min(F, F_old) -> + Pf = Pf2, + Pu = Pu2 + ; + suppress([S,_,_,_], Pu, Pu1), + insert([S, [F, H, G], Pere, Action], Pu1, Pu2), + suppress([[F_old,_,_], S], Pf, Pf1), + insert([[F, H, G], S], Pf1, Pf2) + ). diff --git a/tp1/avl.pl b/tp1/avl.pl new file mode 100644 index 0000000..f389207 --- /dev/null +++ b/tp1/avl.pl @@ -0,0 +1,367 @@ +%*************************** +% Gestion d'un AVL en Prolog +%*************************** + +%*************************** +% INSA TOULOUSE - P.ESQUIROL +% mars 2017 +%*************************** + +%************************* +% unit tests : OK +% integration aetoile : OK +%************************* + +% Les AVL sont des arbres BINAIRES DE RECHERCHE H-EQUILIBRES : +% La hauteur de l'avl A est définie par : +% -1, si A est vide (A=nil) +% 1 + max( hauteur(ss_arbre_gauche(A)), hauteur(ss_arbre_droitee(A)) ) sinon + +% Tout noeud de l'arbre est soit : +% - une feuille +% - un noeud interne tel que la différence de hauteur entre le sous-arbre droit +% et le sous-arbre gauche appartient à [-1,0,+1] + + +%*********************************************** +% PREDICATS EXPORTES ET COMPLEXITE ALGORITHMIQUE +%*********************************************** +% soit N = nombre de noeuds de l'arbre % UTILITE POUR A* +% % ---------------- +% empty(?Avl) O(1) %<<< initialisation de P et Q +% height(+Avl, ?Height) O(1) +% put_flat(+Avl) O(N) +% put_90(+Avl) O(N) +% belongs(+Elem, +Avl) O(log N) %<<< appartenance d'un noeud à Q +% subtree(+Elem, +Avl, Ss_Avl) O(log N) +% insert(+Elem, +Avant, ?Apres) O(log N) %<<< insertion d'un nouveau noeud dans P ou dans Q +% suppress(+Elem,+Avant,?Apres) O(log N) %<<< mise à jour <=> suppression puis insertion +% suppress_min(?Min,+Avant,?Apres) O(log N) %<<< supression du noeud minimal +% suppress_max(?Max,+Avant,?Apres) O(log N) + +%**************************** +% Prédicats internes (prives) +%**************************** + +% left_rotate(+Avant, ?Apres) O(1) +% right_rotate(+Avant, ?Apres) O(1) +% left_balance(+Avant, ?Apres) O(1) +% right_balance(+Avant, ?Apres) O(1) + + + + %------------------------------ + % Constructeur et test AVL vide + %------------------------------ + +empty(nil). + + %----------------- + % Hauteur d'un AVL + %----------------- + % par convention, un avl vide a une hauteur de -1 + % sinon la hauteur est enregistree au meme niveau que la racine de l'avl + % elle n'est pas calculee recursivement "from scratch" + % elle est mise à jour de façon incrémentale, apres chaque insertion ou suppression + % d'ou sa complexité en O(1) :-) + +height(nil, -1). +height(avl(_G,_R,_D, H), H). + + %------------------- + % Affichage d'un AVL + %------------------- + % dans l'ordre croissant (lexicographique) + +put_flat(nil). +put_flat(avl(G,R,D,_H)) :- + put_flat(G), + nl, write(R), + put_flat(D). + + %---------------------------- + % Affichage (couché) d'un AVL + %---------------------------- + +put_90(Avl) :- + nl, writeln('----------------------------------'), + put_90(Avl,""). + +put_90(nil,Str) :- + write(Str), write('.'). +put_90(avl(G,R,D,_H),Str) :- + append_strings(Str, " ", Str2), + put_90(D,Str2), + nl, write(Str), write(R),nl, + put_90(G,Str2). + + %----------------------------------------- + % Appartenance d'un element donne a un AVL + %----------------------------------------- + +belongs(Elem, avl(G,Racine,D,_Hauteur)) :- + (Elem = Racine -> + true + ; + (Elem @< Racine -> + belongs(Elem, G) + ; + belongs(Elem, D) %Racine @< Elem + ) + ). + + %------------------------------------------------------------ + % Recherche du sous-arbre qui a comme racine un element donne + %------------------------------------------------------------ + +subtree(Elem, avl(G,Racine,D,H), A) :- + (Elem = Racine -> + A = avl(G,Racine,D,H) + ; + (Elem @< Racine -> + subtree(Elem,G,A) + ; + subtree(Elem,D,A) %Racine @< Elem + ) + ). + + %---------------------- + % Rotations dans un avl + %---------------------- + % Les rotations ci-dessous décrivent uniquement les cas ou la rotation est possible. + % Dans les autres cas, ces relations échouent ; plus précisément : + % a/ si l'arbre est un avl vide, alors aucune rotation n'est possible ; + % b/ si l'arbre est un avl non vide mais si son ss-arbre gauche est un avl vide + % alors la rotation droite n'est pas possible ; + % c/ si l'arbre est un avl non vide mais si son ss-arbre droite est un avl vide + % alors la rotation gauche n'est pas possible. + +right_rotate(avl(G,R,D,_H), A_Apres) :- + height(D,HD), + G = avl(SG,RG,SD,_HG), + height(SD,HSD), + H_Inter is 1 + max(HSD, HD), + Inter = avl(SD,R,D,H_Inter), + height(SG,HSG), + H_Apres is 1 + max(HSG,H_Inter), + A_Apres = avl(SG,RG,Inter,H_Apres). + +left_rotate(avl(G,R,D,_), A_Apres) :- + height(G,HG), + D = avl(SG,RD,SD,_), + height(SG,HSG), + H_Inter is 1 + max(HSG, HG), + Inter = avl(G,R,SG,H_Inter), + height(SD,HSD), + H_Apres is 1 + max(H_Inter,HSD), + A_Apres = avl(Inter,RD,SD,H_Apres). + + %--------------------------------- + % Insertion equilibree dans un avl + %--------------------------------- + % On suppose que l'arbre avant insertion est equilibré (difference de hauteur + % entre les ss-arbres gauche et droite de 1 au maximum) + % L'insertion doit assurer qu'apres insertion l'arbre est toujours equilibre + % sinon les rotations necessaires sont effectuees. + + % On suppose que les noeuds contiennent des informations que l'on peut comparer + % a l'aide d'une relation d'ordre lexicographique (la cle c'est l'info elle-meme) + % En prolog, c'est la relation '@<' + % On peut comparer par exemple des integer, des string, des constantes, + % des listes d'entiers, des listes de constantes, etc ... bref, des termes clos + % T1 @< T2 est vrai si T1 est lexicographiquement inférieur a T2. + +insert(Elem, nil, avl(nil,Elem,nil,0)). +insert(Elem, AVL, NEW_AVL) :- + AVL = avl(Gauche,Racine,Droite,_Hauteur), + (Elem = Racine -> + % l'élément est déjà present, pas d'insertion possible + fail + ; + (Elem @< Racine -> + % insertion dans le ss-arbre gauche + insert(Elem, Gauche, New_Gauche), + height(New_Gauche, New_HG), + height(Droite, HD), + H_Int is 1+max(New_HG, HD), + AVL_INT = avl(New_Gauche, Racine, Droite, H_Int), + right_balance(AVL_INT, NEW_AVL) + ; + % Elem @> Racine + % insertion dans le ss-arbre droite + insert(Elem, Droite, New_Droite), + height(New_Droite, New_HD), + height(Gauche, HG), + H_Int is 1+max(New_HD, HG), + AVL_INT =avl(Gauche, Racine,New_Droite, H_Int), + left_balance(AVL_INT, NEW_AVL) + ) + ). + + %------------------------------------------------ + % Suppression d'un element quelconque dans un avl + %------------------------------------------------ + % On suppose que l'élément à supprimer appartient bien à l'AVL, + % sinon le predicat échoue (en particulier si l'AVL est vide). + +suppress(Elem, AVL, NEW_AVL) :- + AVL = avl(Gauche, Racine, Droite, _Hauteur), + (Elem = Racine -> + % cas de la suppression de la racine de l'avl + (Gauche = nil -> % cas simple d'une feuille ou d'un avl sans fils gauche + NEW_AVL = Droite + ; + (Droite = nil -> % cas simple d'un avl avec fils gauche mais sans fils droit + NEW_AVL = Gauche + ; + % cas d'un avl avec fils gauche ET fils droit + %Gauche \= nil + %Droite \= nil + suppress_max(Max, Gauche, New_Gauche), + AVL_INT = avl(New_Gauche,Max,Droite,_), + left_balance(AVL_INT, NEW_AVL) + ) + ) + ; + % cas des suppressions d'un element autre que la racine + (Elem @< Racine -> + % suppression dans le ss-arbre gauche + suppress(Elem, Gauche, New_Gauche), + AVL_INT = avl(New_Gauche, Racine, Droite,_), + left_balance(AVL_INT, NEW_AVL) + ; + %Racine @< Droite + % suppression dans le ss-arbre droite + suppress(Elem, Droite, New_Droite), + AVL_INT = avl(Gauche, Racine, New_Droite,_), + right_balance(AVL_INT, NEW_AVL) + ) + ). + + %------------------------------------------------------- + % Suppression du plus petit element dans un avl non vide + %------------------------------------------------------- + % Si l'avl est vide, le prédicat échoue + +suppress_min(Min, AVL, NEW_AVL) :- + AVL = avl(Gauche,Racine,Droite, _Hauteur), + (Gauche = nil -> + Min = Racine, + NEW_AVL = Droite + ; + % Gauche \= nil + suppress_min(Min, Gauche, New_Gauche), + AVL_INT = avl(New_Gauche, Racine, Droite,_), + left_balance(AVL_INT, NEW_AVL) + ). + + %------------------------------------------------------- + % Suppression du plus grand element dans un avl non vide + %------------------------------------------------------- + % Si l'avl est vide, le prédicat échoue + +suppress_max(Max, AVL, NEW_AVL) :- + AVL = avl(Gauche,Racine,Droite, _Hauteur), + (Droite = nil -> + Max = Racine, + NEW_AVL = Gauche + ; + % Droite \= nil + suppress_max(Max, Droite, New_Droite), + AVL_INT = avl(Gauche, Racine, New_Droite,_), + right_balance(AVL_INT, NEW_AVL) + ). + + %---------------------------------------- + % Re-equilibrages d'un avl vers la gauche + %---------------------------------------- + % - soit apres insertion d'un element dans le sous-arbre droite + % - soit apres suppression d'un élément dans le sous-arbre gauche + %---------------------------------------------------------------- + +left_balance(Avl, New_Avl) :- + Avl = avl(Gauche, Racine, Droite, _Hauteur), + height(Gauche, HG), + height(Droite, HD), + (HG is HD-2 -> + % le sous-arbre droite est trop haut + Droite = avl(G_Droite, _R_Droite, D_Droite, _HD), + height(G_Droite, HGD), + height(D_Droite, HDD), + (HDD > HGD -> + % une simple rotation gauche suffit + left_rotate(Avl, New_Avl) + ; + % il faut faire une rotation droite_gauche + right_rotate(Droite, New_Droite), + height(New_Droite, New_HD), + H_Int is 1+ max(HG, New_HD), + Avl_Int = avl(Gauche, Racine, New_Droite, H_Int), + left_rotate(Avl_Int, New_Avl) + ) + ; + % la suppression n'a pas desequilibre l'avl + New_Hauteur is 1+max(HG,HD), + New_Avl = avl(Gauche, Racine, Droite, New_Hauteur) + ). + + %---------------------------------------- + % Re-equilibrages d'un avl vers la droite + %---------------------------------------- + % - soit apres insertion d'un element dans le sous-arbre gauche + % - soit apres suppression d'un élément dans le sous-arbre droite + %---------------------------------------------------------------- + +right_balance(Avl, New_Avl) :- + Avl = avl(Gauche, Racine, Droite, _Hauteur), + height(Gauche, HG), + height(Droite, HD), + (HD is HG-2 -> + % le sous-arbre gauche est trop haut + Gauche = avl(G_Gauche, _R_Gauche, D_Gauche, _HG), + height(G_Gauche, HGG), + height(D_Gauche, HDG), + (HGG > HDG -> + % une simple rotation droite suffit + right_rotate(Avl, New_Avl) + ; + % il faut faire une rotation gauche_droite + left_rotate(Gauche, New_Gauche), + height(New_Gauche, New_HG), + H_Int is 1+ max(New_HG, HD), + Avl_Int = avl(New_Gauche, Racine, Droite, H_Int), + right_rotate(Avl_Int, New_Avl) + ) + ; + % la suppression n'a pas desequilibre l'avl + New_Hauteur is 1+max(HG,HD), + New_Avl = avl(Gauche, Racine, Droite, New_Hauteur) + ). + +%----------------------------------------- +% Arbres utilises pour les tests unitaires +%----------------------------------------- +avl_test(1, nil). +avl_test(2, avl(nil, 1, nil, 0)). +avl_test(3, avl(nil, 1, avl(nil,2,nil,0), 1)). +avl_test(4, avl(avl(nil,1,nil,0),2, nil, 1)). +avl_test(5, avl(avl(nil,1,nil,0), 2, avl(nil,3,nil,0),1) ). +avl_test(6, avl(avl(nil,5,nil,0), 6, avl(nil,7,nil,0),1) ). +avl_test(7, avl(G,4,D,2)) :- + avl_test(5,G), + avl_test(6,D). +avl_test(8, avl(G,5,D,2)) :- + D = avl(nil,6,nil,0), + avl_test(3,G). +avl_test(9, avl(G,3,D,2)) :- + G = avl(nil,1,nil,0), + avl_test(4,D). + +/* Test uniquement valable avec ECLiPSe + +avl_test(10, Final) :- + empty(Init), + (for(I,1,20), fromto(Init,In,Out,Final) do + insert(I,In,Out) + ). +*/ \ No newline at end of file diff --git a/tp1/taquin.pl b/tp1/taquin.pl new file mode 100644 index 0000000..d03e7ff --- /dev/null +++ b/tp1/taquin.pl @@ -0,0 +1,177 @@ +/* +Doit contenir au moins 4 predicats qui seront utilises par A* + etat_initial(I) % definit l'etat initial + etat_final(F) % definit l'etat final + rule(Rule_Name, Rule_Cost, Before_State, After_State) % règles applicables + heuristique(Current_State, Hval) % calcul de l'heuristique +*/ + +%******************** +% ETAT INITIAL DU JEU +%******************** +% format : initial_state(+State) ou State est une matrice (liste de listes) + +/* +initial_state([ [b, h, c], % C'EST L'EXEMPLE PRIS EN COURS + [a, f, d], % + [g,vide,e] ]). % h1=4, h2=5, f*=5*/ + +/* + +initial_state([ [ a, b, c], + [ g, h, d], + [vide,f, e] ]). % h2=2, f*=2 + +initial_state([ [b, c, d], + [a,vide,g], + [f, h, e] ]). % h2=10 f*=10 + +initial_state([ [f, g, a], + [h,vide,b], + [d, c, e] ]). % h2=16, f*=20 +initial_state([ [e, f, g], + [d,vide,h], + [c, b, a] ]). % h2=24, f*=30 +*/ +/* +initial_state([ [a, b, c], + [g,vide,d], + [h, f, e]]). % etat non connexe avec l'etat final (PAS DE SOLUTION) + +*/ +initial_state([[ d , l , n , g ], + [ vide , b , a , c ], + [ e , m , k , h ], + [ j , f , i , o]]). + +%****************** +% ETAT FINAL DU JEU +%****************** +% format : final_state(+State) ou State est une matrice (liste de listes) + +/* +final_state([ [a, b, c], + [h,vide,d], + [g, f, e]]). % etat non connexe avec l'etat final (PAS DE SOLUTION) +*/ +final_state([[ a , b , c , d ], + [ e , f , g , h ], + [ i , j , k , l ], + [ m , n , o , vide]]). + +%******************** +% AFFICHAGE D'UN ETAT +%******************** +% format : write_state(?State) ou State est une liste de lignes a afficher +write_state([]). +write_state([Line|Rest]) :- + print(Line), + nl, + write_state(Rest). + +%********************************************** +% REGLES DE DEPLACEMENT (up, down, left, right) +%********************************************** +% format : rule(+Rule_Name, ?Rule_Cost, +Current_State, ?Next_State) +rule(down , 1, S1, S2) :- + vertical_permutation(_X,vide,S1,S2). + +rule(up , 1, S1, S2) :- + vertical_permutation(vide,_X,S1,S2). + +rule(right, 1, S1, S2) :- + horizontal_permutation(_X,vide,S1,S2). + +rule(left , 1, S1, S2) :- + horizontal_permutation(vide,_X,S1,S2). + +%*********************** +% Deplacement horizontal +%*********************** +% format : horizontal_permutation(?Piece1,?Piece2,+Current_State, ?Next_State) +horizontal_permutation(X,Y,S1,S2) :- + append(Above,[Line1|Rest], S1), + exchange(X,Y,Line1,Line2), + append(Above,[Line2|Rest], S2). + +%*********************************************** +% Echange de 2 objets consecutifs dans une liste +%*********************************************** +exchange(X,Y,[X,Y|List], [Y,X|List]). +exchange(X,Y,[Z|List1], [Z|List2] ):- + exchange(X,Y,List1,List2). + +%********************* +% Deplacement vertical +%********************* +vertical_permutation(X,Y,S1,S2) :- + append(Above, [Line1,Line2|Below], S1), % decompose S1 + delete(N,X,Line1,Rest1), % enleve X en position N a Line1, donne Rest1 + delete(N,Y,Line2,Rest2), % enleve Y en position N a Line2, donne Rest2 + delete(N,Y,Line3,Rest1), % insere Y en position N dans Rest1 donne Line3 + delete(N,X,Line4,Rest2), % insere X en position N dans Rest2 donne Line4 + append(Above, [Line3,Line4|Below], S2). % recompose S2 + +%*********************************************************************** +% Retrait d'une occurrence X en position N dans une liste L (resultat R) +%*********************************************************************** +% use case 1 : delete(?N,?X,+L,?R) +% use case 2 : delete(?N,?X,?L,+R) +delete(1,X,[X|L], L). +delete(N,X,[Y|L], [Y|R]) :- + delete(N1,X,L,R), + N is N1 + 1. + +%******************************************************************* +% Coordonnees X(colonne),Y(Ligne) d'une piece P dans une situation U +%******************************************************************* +% Associe un état State et une pièce P à ses coordonnées X et Y +coordonnees(State, P, X, Y) :- + nth1(Y, State, Ligne), + nth1(X, Ligne, P). + +%************* +% HEURISTIQUES +%************* +heuristique(U,H) :- + heuristique2(U, H). + +%**************** +%HEURISTIQUE 1 +%**************** +% Nombre de pieces mal placees dans l'etat courant U +% par rapport a l'etat final F +heuristique1(U, H) :- + final_state(F), + findall([X, Y], meme_piece(U, F, X, Y), BienPlacees), + length(BienPlacees, NbBienPlacees), + length(U, N), + H is N*N - NbBienPlacees. % On considère que les taquins seront toujours carré + +% Détermine si la pièce aux coord X,Y en S1 est la même que celle en S2 +meme_piece(S1, S2, X, Y) :- + coordonnees(S1, P, X, Y), + coordonnees(S2, P, X, Y), + P \= vide, + !. + +% Cas particulier : on ne compte pas 'vide' +meme_piece(S1, _, X, Y) :- + coordonnees(S1, vide, X, Y). + +%**************** +%HEURISTIQUE no 2 +%**************** +% Somme des distances de Manhattan à parcourir par chaque piece +% entre sa position courante et sa positon dans l'etat final +heuristique2(U, H) :- + final_state(F), + findall(Dist, manhattan(U, F, _, Dist), ListDist), + sum_list(ListDist, H). + +manhattan(S1, S2, P, Dist) :- + coordonnees(S1, P, X1, Y1), + coordonnees(S2, P, X2, Y2), + P \= vide, + Dist is abs(X1 - X2) + abs(Y1 - Y2). + diff --git a/tp2/negamax.pl b/tp2/negamax.pl new file mode 100644 index 0000000..5a88fe9 --- /dev/null +++ b/tp2/negamax.pl @@ -0,0 +1,125 @@ +:- include('tictactoe.pl'). + +% Algorithme MinMax avec convention Negamax. Retourne pour un joueur J donné, devant jouer dans une situation donnée Etat, de profondeur donnée P, le meilleur couple [Coup, Valeur] apres une analyse pouvant aller jusqu'à la profondeur Pmax. + +% Cas 1 : Profondeur max atteinte -> éval par heuristique +negamax(J, Etat, P, Pmax, [rien, Val]) :- + P >= Pmax, + !, + heuristique(J, Etat, Val). + +% Cas 2 : Aucun coup possible -> éval par heuristique +negamax(J, Etat, _, _, [rien, Val]) :- + successeurs(J, Etat, Succ), + length(Succ, 0), + !, + heuristique(J, Etat, Val). + +% Cas 3 : Évaluation du sous-arbre et retour du meilleur couple [Coup, Valeur] +negamax(J, Etat, P, Pmax, [Coup, Val]) :- + successeurs(J, Etat, Succ), + !, + loop_negamax(J, P, Pmax, Succ, ListCouples), + meilleur(ListCouples, [Coup, V1]), + Val is -V1. + +%******************************************************* + +% retourne la liste des couples [Coup, Etat_Suivant] pour la situation Etat +successeurs(J, Etat, Succ) :- + copy_term(Etat, Etat_Suiv), + findall([Coup,Etat_Suiv], + successeur(J, Etat_Suiv, Coup), + Succ). + +testSuccesseur :- + situation_initiale(S), + successeurs(x, S, LSucc), + forall(member([Coup, Succ], LSucc), + (write(Coup), nl, + printState(Succ), nl) + ). + +%******************************************************* + +% retourne la liste des couples [Coup, Valeur_Situation_Suivante] à partir de la liste des [Coup, Situation_Suivante], en appliquant l'algorithme negamax sur chacun d'entre eux. +loop_negamax(_, _, _ ,[], []) :- !. + +loop_negamax(J, P, Pmax, [[Coup, Suiv] | Succ], [[Coup, Vsuiv] | Reste_Couples]) :- + % Récursion pour traiter le reste des successeurs + loop_negamax(J, P, Pmax, Succ, Reste_Couples), + % Récupération de l'adversaire de J + adversaire(J, A), + % Incrément de la profondeur avant l'appel de négamax + Pnew is P+1, + % Calcul de la valeur de negamax pour la situation Suiv. + % On n'a pas besoin de connaitre le coup de l'adversaire en profondeur P+1 qui donne cette valeur, seul le coup de J en P nous intéresse (et on le connait déjà). D'ou le _ dans les arguments passés à negamax. + negamax(A, Suiv, Pnew, Pmax, [_, Vsuiv]). + +%******************************************************* + +% Retourne le couple [Coup, Valeur] dont la valeur est la plus faible (car convention negamax) +meilleur([E], E) :- !. + +meilleur([[C,V] | RestCouples], [BestC, BestV]) :- + meilleur(RestCouples, [TempC, TempV]), + min([TempC,TempV], [C,V], [BestC, BestV]). + +min([C1,V1], [C2,V2], [C,V]) :- + (V1 < V2) -> + V = V1, + C = C1 + ; + V = V2, + C = C2. + +testMeilleur :- + meilleur([[[1,2], -5], [[3,2], 2], [[3,3], -6], [[1,1], 7]], [[3,3], -6]), + meilleur([[[1,2], 5]], [[1,2], 5]). + +%******************************************************* + +% Retourne le coup et la valeur retournés par Negamax depuis l'état initial +main(BestMove, Value, Pmax) :- + situation_initiale(S0), + joueur_initial(J), + negamax(J, S0, 1, Pmax, [BestMove, Value]). + +% Bug pour I = 9 +testMain :- + forall(between(1, 9, I), ( + main(B, V, I), + write('I = '), write(I), nl, + write('B = '), write(B), nl, + write('V = '), write(V), nl, nl + )). + +%******************************************************* + +% Applique Negamax et joue le coup suggéré successivement jusqu'à remplissage de la grille +testNegamax :- + situation_initiale(S0), + joueur_initial(J), + iter(J, S0). + +iter(_, S) :- + situation_terminale(S), + !, + write('Match nul !'), nl. + +iter(_, S) :- + alignement(Ali, S), + alignement_gagnant(Ali, J), + !, + write(Ali), nl, + write(S), nl, + write(J), write(' a gagné !'), nl. + +iter(J, S) :- + negamax(J, S, 1, 9, [BestMove, Val]), + successeur(J, S, BestMove), + write(BestMove), write(' -> '), write(Val), nl, + printState(S), nl, + adversaire(J, A), + iter(A, S). + diff --git a/tp2/tictactoe.pl b/tp2/tictactoe.pl new file mode 100644 index 0000000..c1819bf --- /dev/null +++ b/tp2/tictactoe.pl @@ -0,0 +1,191 @@ +/********************************* +DESCRIPTION DU JEU DU TIC-TAC-TOE +*********************************/ + +situation_initiale([ [_,_,_], + [_,_,_], + [_,_,_] ]) . + +% Convention (arbitraire) : c'est x qui commence +joueur_initial(x). + +% Definition de la relation adversaire/2 +adversaire(x,o). +adversaire(o,x). + +situation_terminale(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). + +ligne(L, M) :- + nth1(_, M, L). + +colonne(C, M) :- + colonne(C, M, _). + +colonne([E | Crest], [L | Mrest], Ncol) :- + nth1(Ncol, L, E), + colonne(Crest, Mrest, Ncol). + +colonne([], [], _). + +diagonale(D, M) :- + premiere_diag(1,D,M). + +diagonale(D, M) :- + length(M, N), + seconde_diag(N,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). + +% Alignement potentiel possible pour le joueur J +possible([X | L], J) :- unifiable(X, J), possible(L, J), !. +possible([], _). + +unifiable(X, _) :- + var(X), + !. +unifiable(_, J) :- + var(J), + !. +unifiable(X, J) :- + X == J. + +% Vérifie que Ali est un alignement gagnant pour J +alignement_gagnant(Ali, J) :- + ground(Ali), + maplist(=(J), Ali). + +% Un alignement perdant pour J est un alignement gagnant pour son adversaire +alignement_perdant(Ali, J) :- + adversaire(J, O), + alignement_gagnant(Ali, O). + + +/* **************************** +DEFINITION D'UN ETAT SUCCESSEUR +****************************** */ +successeur(J, Etat, [Nl, Nc]) :- + nth1(Nl, Etat, L), + nth1(Nc, L, Current), + \+ ground(Current), + Current = 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 +*/ + +%1 +heuristique(J, Situation, H) :- + alignement(Alig, Situation), + alignement_gagnant(Alig, J), + H = 10000, % grand nombre approximant +infini + !. + +% 2 +heuristique(J, Situation, H) :- + alignement(Alig, Situation), + alignement_perdant(Alig, J), + H = -10000, % grand nombre approximant -infini + !. + +% 3 +heuristique(J, Situation, H) :- % cas 3 + findall(Alig, alignement(Alig, Situation), L), + adversaire(J, O), + findall(AligPoss, (member(AligPoss, L), possible(AligPoss, J)), LpossJ), + findall(AligPoss, (member(AligPoss, L), possible(AligPoss, O)), LpossO), + length(LpossJ, NbJ), + length(LpossO, NbO), + H is NbJ - NbO. + +% Affiche la grille +printState([]). +printState([R | Rest]) :- + printRow(R), + nl, + printState(Rest). + +printRow([]). +printRow([X | Rest]) :- + (ground(X) -> + write(X) + ; + write('_') + ), + printRow(Rest). + + +% ----- Tests unitaires ----- + +testPossible :- + A = [_,_,_], + possible(A, x), + B = [x,_,x], + possible(B, x), + C = [_,o,x], + \+ possible(C, x). + +testAlignementGagnant :- + A = [o,x,o], + B = [o,_,o], + C = [o,o,o], + \+ alignement_gagnant(A, o), + \+ alignement_gagnant(B, o), + \+ alignement_gagnant(A, _). + alignement_gagnant(C, o), + alignement_gagnant(C, J), + J == o, + +testHeuristique :- + LS = [ + [[[ _,_,_ ], + [ _,_,_ ], + [ _,_,_ ]], 0], + + [[[ _,_,_ ], + [ _,o,_ ], + [ _,_,_ ]], 4], + + [[[ x,x,o ], + [ o,o,x ], + [ x,o,x ]], 0], + + [[[ x,_,_ ], + [ o,o,x ], + [ x,o,x ]], -1], + + [[[ x,x,x ], + [ o,o,x ], + [ x,o,o ]], -10000] + ], + forall(member([S, Ho], LS), ( + heuristique(o, S, Ho), + Hx is -Ho, + heuristique(x, S, Hx) + )).