diff --git a/TP1/CR_TP1.odt b/TP1/CR_TP1.odt new file mode 100644 index 0000000..b006a66 Binary files /dev/null and b/TP1/CR_TP1.odt differ diff --git a/TP1/aetoile.pl b/TP1/aetoile.pl new file mode 100644 index 0000000..a9fdc43 --- /dev/null +++ b/TP1/aetoile.pl @@ -0,0 +1,271 @@ +%******************************************************************************* +% AETOILE +%******************************************************************************* + +/* +Rappels sur l'algorithme + +- structures de donnees principales = 2 ensembles : P (etat pendants) et Q (etats clos) +- P est dedouble en 2 arbres binaires de recherche equilibres (AVL) : Pf et Pu + + Pf est l'ensemble des etats pendants (pending states), ordonnes selon + f croissante (h croissante en cas d'egalite de f). Il permet de trouver + rapidement le prochain etat a developper (celui qui a f(U) minimum). + + Pu est le meme ensemble mais ordonne lexicographiquement (selon la donnee de + l'etat). Il permet de retrouver facilement n'importe quel etat pendant + + On gere les 2 ensembles de fa�on synchronisee : chaque fois qu'on modifie + (ajout ou retrait d'un etat dans Pf) on fait la meme chose dans Pu. + + Q est l'ensemble des etats deja developpes. Comme Pu, il permet de retrouver + facilement un etat par la donnee de sa situation. + Q est modelise par un seul arbre binaire de recherche equilibre. + +Predicat principal de l'algorithme : + + aetoile(Pf,Pu,Q) + + - reussit si Pf est vide ou bien contient un etat minimum terminal + - sinon on prend un etat minimum U, on genere chaque successeur S et les valeurs g(S) et h(S) + et pour chacun + si S appartient a Q, on l'oublie + si S appartient a Ps (etat deja rencontre), on compare + g(S)+h(S) avec la valeur deja calculee pour f(S) + si g(S)+h(S) < f(S) on reclasse S dans Pf avec les nouvelles valeurs + g et f + sinon on ne touche pas a Pf + si S est entierement nouveau on l'insere dans Pf et dans Ps + - appelle recursivement etoile avec les nouvelles valeurs NewPF, NewPs, NewQs + +*/ + +%******************************************************************************* + +:- ['avl.pl']. % predicats pour gerer des arbres bin. de recherche +:- ['taquin.pl']. % predicats definissant le systeme a etudier + +%******************************************************************************* + +main() :- + % initialisations Pf, Pu et Q + + % lancement de Aetoile + initial_state(Init), + heuristique(Init,H), + empty(Pf), + empty(Pu), + empty(Qs), + insert([[H, H, 0], Init], Pf, Pf_new), + insert([Init, [H, H, 0], nil, nil], Pu, Pu_new), + aetoile(Pf_new,Pu_new,Qs). + + + %******** + % A FAIRE + %******** + + + +%******************************************************************************* + +% + +aetoile([],[], _):- + write('PAS de SOLUTION : L_ETAT FINAL N_EST PAS ATTEIGNABLE !'), + !. + +aetoile(Pf, Pu, Qs):- + + suppress_min([[F,H,G], U], Pf, Pf_tmp), + suppress([U,[F,H,G], Pere, Action], Pu, Pu_tmp), + Noeud = [U,[F,H,G], Pere, Action], + + % write('Noeud min : '), + % writeln(Noeud), + + + (final_state(U) -> + insert(Noeud, Qs, Qs_new), + affichage_solution(U, Qs_new) + ; + expand(Noeud, G, SList), + loop_successor(SList, Pf_tmp, Pu_tmp, Qs, Pf_new, Pu_new), + insert(Noeud, Qs, Qs_new), + + /*write('PF height: '), + height(Pf_tmp,PFSize), + writeln(PFSize), + write('PU :'), + put_flat(Pu_new), + write('\n'), + write('QS : '), + put_flat(Qs_new),*/ + + aetoile(Pf_new, Pu_new, Qs_new) + % Niter is Niter1+1, + % write('iteration n°'), + % writeln(Niter) + ). + + + + + +%******************************************************************************* + +affichage_solution(U, Qs):- + belongs([U, _Val, Pere, Action], Qs), + get_solution(Pere, Tmp), + write('\n'), + writeln('\nSolution : '), + print_l( [[U, Action] |Tmp] ). + + +get_solution(nil,[]):- !. + +get_solution([U,_Val,Pere, Action], [ [U, Action] | Rest]) :- + get_solution(Pere,Rest). + +print_l([]). + +print_l([[ [A,B,C], Action] |T]):- + print_l(T), + write('action : '), + writeln(Action), + writeln(A), + writeln(B), + writeln(C), + write('\n'). + + + + +expand([U, Val, P, A], GU, SList):- + Noeud = [U, Val, P, A], + findall([S,[F,H,G], Noeud , Action], + (rule(Action,C,U,S), heuristique(S, H), + G is GU+C, + F is H+G), + SList). + + + +loop_successor([],Pf,Pu,_,Pf,Pu). + +loop_successor([[S,[F,H,G], Pere, Action]| TailS_eval] , Pf , Pu, Qs, Pf_new, Pu_new) :- + + ( belongs([S, _, _, _], Qs) -> + + loop_successor(TailS_eval, Pf, Pu, Qs, Pf_new, Pu_new) + + ; + ( belongs( [S, [OldF, _, _], _, _ ], Pu) -> + + ( F < OldF -> + %writeln('dans F <'), + maj([S, [OldF, OldH, OldG], _, _], [S,[F,H,G], Pere, Action], Pu, Pu_tmp), + maj([[OldF, OldH, OldG], S], [[F,H,G], S], Pf, Pf_tmp) + ; + %writeln('dans F >'), + Pf_tmp = Pf, + Pu_tmp = Pu + ) + + ; + %writeln('hors belongs'), + insert([[F,H,G], S], Pf , Pf_tmp), + insert([S,[F,H,G], Pere, Action], Pu, Pu_tmp) + ), + + loop_successor(TailS_eval, Pf_tmp, Pu_tmp, Qs, Pf_new, Pu_new) + + ). + + + +maj(EltDel, EltAdd, AvlAvant, AvlApres):- + suppress(EltDel,AvlAvant, AvlTmp), + insert(EltAdd, AvlTmp, AvlApres). + + +%******* +% TESTS +%******* +get_pf(1, [[5,5,0], [[b, h, c], [a, f, d], [g,vide,e]] ]). +get_pf(2, [[7,6,1],[[b,h,c],[a,f,d],[vide,g,e]]] ). + + +get_current_pu(0, [[H,H,0], State]) :- + initial_state(State), + heuristique(State,H). + + + +get_pu(1, [[[b, h, c], [a, f, d], [g,vide,e]],[5,5,0], nil, nil]). +get_pu(2, [[[b,h,c],[a,f,d],[vide,g,e]],[7,6,1],[[[b,h,c],[a,f,d],[g,vide,e]],[5,5,0],nil, nil]]). + +get_slist_initial_state(SList) :- + SList = [ + [ [[b, h, c], [a, vide, d], [g, f, e]], [5,4,1], [ [[b, h, c], [a, f, d], [g, vide, e]], [5,5,0], nil, nil], up], + [ [[b, h, c], [a, f, d], [vide, g, e]], [7,6,1], [ [[b, h, c], [a, f, d], [g, vide, e]], [5,5,0], nil, nil], left], + [ [[b, h, c], [a, f, d], [g, e, vide]], [7,6,1], [ [[b, h, c], [a, f, d], [g, vide, e]], [5,5,0], nil, nil], right] + ]. + +get_pf(Nb,Pos, X) :- + getPu(Nb, [[F,_,_], U]), + getPf(Nb, Pere), + rule(Pos, 1, U, S), + heuristique(S,Hn), + Fn is Hn+F, + X = [S,[Fn,Hn,F],Pere]. + +get_pf_up(Nb, X) :- + getPf(Nb,up, X). + +get_pf_left(Nb, X) :- + getPf(Nb,left, X). + +get_pf_right(Nb, X) :- + getPf(Nb,right, X). + +get_pf_down(Nb, X):- + getPf(Nb, down, X). + + +maj_test(1, A):- + getPfUp(EltPfUp), + getPfLeft(EltPfLeft), + getPfRight(EltPfRight), + + +%******insert 3U from initial_state******* + + insert(EltPfUp, nil, Pf), + insert(EltPfLeft, Pf, Pf2), + insert(EltPfRight, Pf2, Pf3), + + +%******Replace LeftU with initial_state**** + + getPf(1, EltPf), + maj(EltPfLeft, EltPf, Pf3, A). + + + +insert_test(1, A) :- + getPfUp(2,EltPfUp), + getPfRight(2,EltPfRight), + + insert(EltPfUp, nil, Pf2), + insert(EltPfRight, Pf2, A), + belongs(EltPfRight, A). + +test_pred_expand() :- + get_pf(1,[[_F, _H, G], U]), + expand(U, G, SList), + get_slist_initial_state(SList). + + + + diff --git a/TP1/avl.pl b/TP1/avl.pl new file mode 100644 index 0000000..4e718cb --- /dev/null +++ b/TP1/avl.pl @@ -0,0 +1,368 @@ +%*************************** +% 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) + ). +*/ + diff --git a/TP1/taquin.pl b/TP1/taquin.pl new file mode 100644 index 0000000..ab204c1 --- /dev/null +++ b/TP1/taquin.pl @@ -0,0 +1,253 @@ +/* Fichier du probleme. + +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 + + +Les autres prédicats sont spécifiques au Taquin. +*/ + + +%:- lib(listut). % Laisser cette directive en commentaire si vous utilisez Swi-Prolog + + % Sinon décommentez la ligne si vous utilisez ECLiPSe Prolog : + % -> permet de disposer du predicat nth1(N, List, E) + % -> permet de disposer du predicat sumlist(List, S) + % (qui sont predefinis en Swi-Prolog) + + +%*************************** +%DESCRIPTION DU JEU DU TAKIN +%*************************** + + %******************** + % ETAT INITIAL DU JEU + %******************** + % format : initial_state(+State) ou State est une matrice (liste de listes) + + +initial_state([ [a, b, c], + [g,vide,d], + [h, f, e] ]). % h1=4, h2=5, f*=5 + + + +% AUTRES EXEMPLES POUR LES TESTS DE A* + +/* +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) +*/ + + + %****************** + % 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]]). + + + %******************** + % AFFICHAGE D'UN ETAT + %******************** + % format : write_state(?State) ou State est une liste de lignes a afficher + +write_state([]). +write_state([Line|Rest]) :- + writeln(Line), + write_state(Rest). + + +%********************************************** +% REGLES DE DEPLACEMENT (up, down, left, right) +%********************************************** + % format : rule(+Rule_Name, ?Rule_Cost, +Current_State, ?Next_State) + +rule(up, 1, S1, S2) :- + vertical_permutation(_X,vide,S1,S2). + +rule(down, 1, S1, S2) :- + vertical_permutation(vide,_X,S1,S2). + +rule(left, 1, S1, S2) :- + horizontal_permutation(_X,vide,S1,S2). + +rule(right,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. + + + + %******************* + % PARTIE A COMPLETER + %******************* + + %******************************************************************* + % Coordonnees X(colonne),Y(Ligne) d'une piece P dans une situation U + %******************************************************************* + % format : coordonnees(?Coord, +Matrice, ?Element) + % Définit la relation entre des coordonnees [Ligne, Colonne] et un element de la matrice + /* + Exemples + + ?- coordonnees(Coord, [[a,b,c],[d,e,f]], e). % quelles sont les coordonnees de e ? + Coord = [2,2] + yes + + ?- coordonnees([2,3], [[a,b,c],[d,e,f]], P). % qui a les coordonnees [2,3] ? + P=f + yes + */ + %******** + % A FAIRE + %******** + +coordonnees([L,C], Mat, Elt) :- + nth1(L, Mat, Ligne), + nth1(C, Ligne, Elt). + + +malplace(P, U, F) :- + coordonnees(Coord1, U, P), + coordonnees(Coord2, F, P), + Coord1 \= Coord2. + + %************* + % HEURISTIQUES + %************* + +heuristique(U,H) :- +% heuristique1(U, H). % au debut on utilise l'heuristique 1 + heuristique2(U, H). % ensuite utilisez plutot l'heuristique 2 + + + %**************** + %HEURISTIQUE no 1 + %**************** + % Nombre de pieces mal placees dans l'etat courant U + % par rapport a l'etat final F + + + % Suggestions : définir d'abord le prédicat coordonnees(Piece,Etat,Lig,Col) qui associe à une pièce présente dans Etat + % ses coordonnees (Lig= numero de ligne, Col= numero de Colonne) + + % Definir ensuite le predicat malplace(P,U,F) qui est vrai si les coordonnes de P dans U et dans F sont differentes. + % On peut également comparer les pieces qui se trouvent aux mêmes coordonnees dans U et dans F et voir s'il s'agit de la + % même piece. + + % Definir enfin l'heuristique qui détermine toutes les pièces mal placées (voir prédicat findall) + % et les compte (voir prédicat length) + %******** + % A FAIRE + %******** +% heuristique1(U, H) :- +% final_state(Final), eval_matrice(U, Final, H). + +% eval_matrice([],[],0). +% eval_matrice([LM1|RM1], [LM2|RM2], H):- +% eval_ligne(LM1, LM2, HL), +% eval_matrice(RM1, RM2, HR), +% H is HL+HR. + +% eval_ligne([],[],0). +% eval_ligne([EltL1|RL1], [EltL2|RL2], HL) :- +% (EltL1 \= EltL2, EltL1 \= vide -> +% H = 1 +% ; +% H = 0 +% ), +% eval_ligne(RL1,RL2, HE), +% HL is H+HE. + +heuristique1(U,H) :- + final_state(F), + findall(1, (malplace(X, U,F), X \= vide ), L), + length(L,H). + %**************** + %HEURISTIQUE no 2 + %**************** + + % Somme des distances de Manhattan à parcourir par chaque piece + % entre sa position courante et sa positon dans l'etat final + + %******** + % A FAIRE + %******** +heuristique2(U, H) :- + final_state(Final), + findall(Dist, (distance(U,Final,Elt,Dist), Elt \= vide), L), + sum_list(L, H). + +distance(M1, M2, Elt, Dist):- + coordonnees([X1, Y1], M1, Elt), + coordonnees([X2, Y2], M2, Elt), + Dist is abs(X2-X1) + abs(Y2-Y1). + diff --git a/TP1/taquin.txt b/TP1/taquin.txt new file mode 100644 index 0000000..e69de29 diff --git a/TP2/CR_TP2.odt b/TP2/CR_TP2.odt new file mode 100644 index 0000000..e2a2a01 Binary files /dev/null and b/TP2/CR_TP2.odt differ diff --git a/TP2/negamax.pl b/TP2/negamax.pl new file mode 100644 index 0000000..d2cb10e --- /dev/null +++ b/TP2/negamax.pl @@ -0,0 +1,202 @@ + /* + 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,Pmax,Pmax,[nil,H]) :- + heuristique(J, Etat, H), !. + +negamax(J, Etat, _, _, [nil, H]) :- + situation_terminale(J, Etat), + heuristique(J ,Etat, H), !. + +negamax(J, Etat, P, Pmax, [Coup, V2]) :- + P < Pmax, + not(situation_terminale(J,Etat)), + successeurs(J, Etat, Succ), + loop_negamax(J, P, Pmax, Succ, Couples), + meilleur(Couples, [Coup, V1]), + V2 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), %Récursion jusqu'à ce qu'il n'y ait plus de couple + adversaire(J,A), %Pour chaque couple, on récupère l'adversaire du joueur actuel, + Pnew is P+1, %On incrémente la profondeur courante, + negamax(A,Suiv,Pnew,Pmax, [_,Vsuiv]). %On relance négamax avec cette fois-ci le joueur adverse + % et la situation suivante (celle que l'on traite actuellement parmi les couples (Coup,Suiv)). + + /* + +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([X],X). +meilleur([[Coup, Val]|Rest], Bc) :- + meilleur(Rest, [BCoupTemp, BValTemp]), + + (Val < BValTemp -> + Bc = [Coup,Val] + ; + Bc = [BCoupTemp,BValTemp] + ). + + /****************** + PROGRAMME PRINCIPAL + *******************/ + +main(B,V, Pmax) :- + situation_initiale(Situation), + joueur_initial(J), + negamax(J, Situation, 0, Pmax, [B, V]). + +main(Pmax) :- + situation_initiale(Situation), + joueur_initial(J), + main_loop(J, Situation, Pmax). + + +main_loop(J, Situation, Pmax) :- + negamax(J, Situation, 0, Pmax, [B, V]), + writeln(B), writeln(V), + pretty_print(Situation), + successeur(J, Situation, B), + adversaire(J,A), + (B \= nil -> + main_loop(A, Situation, Pmax) + ). + + /* +A FAIRE : + Compl�ter puis tester le programme principal pour plusieurs valeurs de la profondeur maximale. + Pmax = 1, 2, 3, 4 ... + Commentez les r�sultats obtenus. + */ + + /**** + TESTS + *****/ + list_succ_init(LS) :- + LS = + [ [[1,1],[ [x,_,_],[_,_,_],[_,_,_] ]], + [[1,2],[ [_,x,_],[_,_,_],[_,_,_] ]], + [[1,3],[ [_,_,x],[_,_,_],[_,_,_] ]], + [[2,1],[ [_,_,_],[x,_,_],[_,_,_] ]], + [[2,2],[ [_,_,_],[_,x,_],[_,_,_] ]], + [[2,3],[ [_,_,_],[_,_,x],[_,_,_] ]], + [[3,1],[ [_,_,_],[_,_,_],[x,_,_] ]], + [[3,2],[ [_,_,_],[_,_,_],[_,x,_] ]], + [[3,3],[ [_,_,_],[_,_,_],[_,_,x] ]] ]. + + + + + test_pred_successeurs :- + situation_initiale(Situation), + joueur_initial(J), + successeurs(J,Situation, + LS), + list_succ_init(LS). + + diff --git a/TP2/tictactoe.pl b/TP2/tictactoe.pl new file mode 100644 index 0000000..4556398 --- /dev/null +++ b/TP2/tictactoe.pl @@ -0,0 +1,371 @@ + /********************************* + 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). + */ + +situation_initiale([ [_,_,_], + [_,_,_], + [_,_,_] ]). + + % Convention (arbitraire) : c''est x qui commence + +joueur_initial(x). + + + % 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), !. + +situation_terminale(J, Situation) :- + alignement(Alig,Situation), + (alignement_gagnant(Alig,J) ; alignement_perdant(Alig,J)), + !. + + + + /*************************** + 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) :- + nth1(_,M,L). + +colonne(C,M) :- + colonne(_,C,M). + +colonne(_,[],[]). +colonne(K, [Ele | Rest],[Ligne | M]) :- + nth1(K, Ligne, Ele), + colonne(K, Rest, M). + + /* 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(_,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(0, [], []). +seconde_diag(K, [E|D], [Ligne|M]) :- + seconde_diag(K1,D,M), + K is K1+1, + nth1(K,Ligne,E). + + + + /***************************** + 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) :- + ground(X), + X = J. +unifiable(X,_J) :- + var(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) :- + ground(Ali), + possible(Ali,J). + +alignement_perdant(Ali, J) :- + adversaire(J, Adv), + alignement_gagnant(Ali, Adv). + + /* **************************** + 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, Ligne), + nth1(C, Ligne, 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) :- + adversaire(J, Adv), + findall(1, ( alignement(Ali, Situation), possible(Ali, J) ), Lj), + findall(1, ( alignement(Ali, Situation), possible(Ali, Adv) ), Ladv), + length(Lj, LenJ), + length(Ladv, LenAdv), + H is LenJ - LenAdv. + + +pretty_print([]). + +pretty_print([A,B,C]):- + writeln(A), + writeln(B), + writeln(C), + write('\n\n'). + + /************************** + TEST ET SITUATIONS DE TEST + **************************/ +situation_gagnanteJ([ [x,o,_], + [x,x,x], + [o,_,o] ]). + +situation_gagnante2J([ [x,o,_], + [x,x,x], + [o,_,x] ]). + + +situation_perdanteJ([ [ o,_,x], + [o,x,x], + [o,x,o] ]). + +situation_perdante2J([ [ o,_,x], + [o,x,x], + [o,o,o] ]). + +situation_nulle([ [x,x,o], + [o,o,x], + [x,o,x] ]). + +situation_test1([[x,o,_], + [x,x,_], + [_,_,o]]). + +situation_test2([[x,_,_], + [_,o,_], + [_,_,x]]). + +situation_chiffre([[1,2,3], + [4,5,6], + [7,8,9]]). + +lignes_chiffre(L) :- + L= [[1,2,3],[4,5,6],[7,8,9]]. + +colonnes_chiffre(L) :- + L= [[1,4,7],[2,5,8],[3,6,9]]. + +diagonales_chiffre(L) :- + L= [[1,5,9],[3,5,7]]. + +alignements_chiffre(L) :- + L = [[1,2,3],[4,5,6],[7,8,9],[1,4,7],[2,5,8],[3,6,9],[1,5,9],[3,5,7]]. + +/*RUN ALL TESTS*/ +test_all_pred :- + test_pred_situation_terminale, + test_pred_ligne, + test_pred_colonne, + test_pred_diagonale, + test_pred_alignement, + test_pred_alignement_gagnant, + test_pred_alignement_perdant, + test_heuristique_situation_gagnante, + test_heuristique_situation_perdante, + test_heuristique_situation_nulle, + test_heuristique_avantage. + + +test_pred_situation_terminale :- + situation_nulle(Final), + joueur_initial(J), + situation_terminale(J,Final). + +test_pred_ligne:- + situation_chiffre(Situation), + findall(Ligne,(ligne(Ligne,Situation)), L), + length(L,3), + lignes_chiffre(L). + +test_pred_colonne:- + situation_chiffre(Situation), + findall(Colonne,(colonne(Colonne,Situation)), L), + length(L,3), + colonnes_chiffre(L). + +test_pred_diagonale:- + situation_chiffre(Situation), + findall(Diagonale,(diagonale(Diagonale,Situation)), L), + length(L,2), + diagonales_chiffre(L). + +test_pred_alignement :- + situation_chiffre(Situation), + findall(Ali, (alignement(Ali, Situation)), L), + length(L,8), + alignements_chiffre(L). + +test_pred_alignement_gagnant:- + situation_gagnanteJ(Situation), + situation_gagnante2J(Situation2), + joueur_initial(J), + findall(1, ( alignement(Ali, Situation), alignement_gagnant(Ali,J) ), L), + length(L,1), + + findall(1, (alignement(Ali,Situation2), alignement_gagnant(Ali,J)), L2), + length(L2,2). + +test_pred_alignement_perdant:- + situation_perdanteJ(Situation), + situation_perdante2J(Situation2), + joueur_initial(J), + findall(1, ( alignement(Ali, Situation), alignement_perdant(Ali,J) ), L), + length(L,1), + + findall(1, (alignement(Ali,Situation2), alignement_perdant(Ali,J)), L2), + length(L2,2). + +test_heuristique_situation_gagnante :- + situation_gagnanteJ(Situation), + joueur_initial(J), + heuristique(J, Situation, 10000). + +test_heuristique_situation_perdante :- + situation_perdanteJ(Situation), + joueur_initial(J), + heuristique(J, Situation, -10000). + +test_heuristique_situation_nulle :- + situation_nulle(Situation), + joueur_initial(J), + heuristique(J, Situation, 0). + +test_heuristique_avantage :- + situation_test1(Situation), + joueur_initial(J), + heuristique(J, Situation, 1). +