|
@@ -0,0 +1,428 @@
|
|
1
|
+/*********************************
|
|
2
|
+ DESCRIPTION DU JEU DU TIC-TAC-TOE
|
|
3
|
+ *********************************/
|
|
4
|
+
|
|
5
|
+ /*
|
|
6
|
+ Une situation est decrite par une matrice 3x3.
|
|
7
|
+ Chaque case est soit un emplacement libre (Variable LIBRE), soit contient le symbole d'un des 2 joueurs (o ou x)
|
|
8
|
+
|
|
9
|
+ Contrairement a la convention du tp precedent, pour modeliser une case libre
|
|
10
|
+ dans une matrice on n'utilise pas une constante speciale (ex : nil, 'vide', 'libre','inoccupee' ...);
|
|
11
|
+ On utilise plut�t un identificateur de variable, qui n'est pas unifiee (ex : X, A, ... ou _) .
|
|
12
|
+ La situation initiale est une "matrice" 3x3 (liste de 3 listes de 3 termes chacune)
|
|
13
|
+ o� chaque terme est une variable libre.
|
|
14
|
+ Chaque coup d'un des 2 joureurs consiste a donner une valeur (symbole x ou o) a une case libre de la grille
|
|
15
|
+ et non a deplacer des symboles deja presents sur la grille.
|
|
16
|
+
|
|
17
|
+ Pour placer un symbole dans une grille S1, il suffit d'unifier une des variables encore libres de la matrice S1,
|
|
18
|
+ soit en ecrivant directement Case=o ou Case=x, ou bien en accedant a cette case avec les predicats member, nth1, ...
|
|
19
|
+ La grille S1 a change d'etat, mais on n'a pas besoin de 2 arguments representant la grille avant et apres le coup,
|
|
20
|
+ un seul suffit.
|
|
21
|
+ 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
|
|
22
|
+ par un nouvel identificateur).
|
|
23
|
+ */
|
|
24
|
+%:- use_module(library(clpfd)).
|
|
25
|
+
|
|
26
|
+situation_initiale([ [_,_,_],
|
|
27
|
+ [_,_,_],
|
|
28
|
+ [_,_,_]]).
|
|
29
|
+
|
|
30
|
+ % Convention (arbitraire) : c'est x qui commence
|
|
31
|
+
|
|
32
|
+joueur_initial(x).
|
|
33
|
+
|
|
34
|
+cpu_time(Goal, Elapsed_Time) :-
|
|
35
|
+ statistics(process_cputime,Start),
|
|
36
|
+ call(Goal),
|
|
37
|
+ statistics(process_cputime,Finish),
|
|
38
|
+ Elapsed_Time is (Finish-Start)*1000.
|
|
39
|
+
|
|
40
|
+ % Definition de la relation adversaire/2
|
|
41
|
+
|
|
42
|
+adversaire(x,o).
|
|
43
|
+adversaire(o,x).
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+ /****************************************************
|
|
47
|
+ DEFINIR ICI a l'aide du predicat ground/1 comment
|
|
48
|
+ reconnaitre une situation terminale dans laquelle il
|
|
49
|
+ n'y a aucun emplacement libre : aucun joueur ne peut
|
|
50
|
+ continuer a jouer (quel qu'il soit).
|
|
51
|
+ ****************************************************/
|
|
52
|
+
|
|
53
|
+situation_terminale(_Joueur, Situation) :- ground(Situation).
|
|
54
|
+
|
|
55
|
+ /***************************
|
|
56
|
+ DEFINITIONS D'UN ALIGNEMENT
|
|
57
|
+ ***************************/
|
|
58
|
+
|
|
59
|
+alignement(L, Matrix) :- ligne( L,Matrix).
|
|
60
|
+alignement(C, Matrix) :- colonne( C,Matrix).
|
|
61
|
+alignement(D, Matrix) :- diagonale(D,Matrix).
|
|
62
|
+
|
|
63
|
+ /********************************************
|
|
64
|
+ DEFINIR ICI chaque type d'alignement maximal
|
|
65
|
+ existant dans une matrice carree NxN.
|
|
66
|
+ ********************************************/
|
|
67
|
+
|
|
68
|
+ligne(L, M) :- member(L,M).
|
|
69
|
+
|
|
70
|
+colonne_aux(_,[],[]).
|
|
71
|
+
|
|
72
|
+colonne_aux(N,[X|Cr],[L|Mr]):-
|
|
73
|
+ nth1(N,L,X),
|
|
74
|
+ colonne_aux(N,Cr,Mr).
|
|
75
|
+
|
|
76
|
+colonne(C,M) :-
|
|
77
|
+ colonne_aux(_,C,M).
|
|
78
|
+
|
|
79
|
+%colonne(C,M) :- transpose(M,T), member(C,T).
|
|
80
|
+
|
|
81
|
+ /* Definition de la relation liant une diagonale D a la matrice M dans laquelle elle se trouve.
|
|
82
|
+ il y en a 2 sortes de diagonales dans une matrice carree(https://fr.wikipedia.org/wiki/Diagonale) :
|
|
83
|
+ - la premiere diagonale (principale) : (A I)
|
|
84
|
+ - la seconde diagonale : (Z R)
|
|
85
|
+ A . . . . . . . Z
|
|
86
|
+ . \ . . . . . / .
|
|
87
|
+ . . \ . . . / . .
|
|
88
|
+ . . . \ . / . . .
|
|
89
|
+ . . . . X . . .
|
|
90
|
+ . . . / . \ . . .
|
|
91
|
+ . . / . . . \ . .
|
|
92
|
+ . / . . . . . \ .
|
|
93
|
+ R . . . . . . . I
|
|
94
|
+ */
|
|
95
|
+
|
|
96
|
+diagonale(D, M) :-
|
|
97
|
+ premiere_diag(1,D,M).
|
|
98
|
+
|
|
99
|
+% deuxieme definition A COMPLETER
|
|
100
|
+
|
|
101
|
+diagonale(D, M) :- seconde_diag(3,D,M).
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+premiere_diag(_,[],[]).
|
|
105
|
+premiere_diag(K,[E|D],[Ligne|M]) :-
|
|
106
|
+ nth1(K,Ligne,E),
|
|
107
|
+ K1 is K+1,
|
|
108
|
+ premiere_diag(K1,D,M).
|
|
109
|
+
|
|
110
|
+seconde_diag(_,[],[]).
|
|
111
|
+seconde_diag(K,[E|D],[Ligne|M]) :-
|
|
112
|
+ nth1(K,Ligne,E),
|
|
113
|
+ K1 is K-1,
|
|
114
|
+ seconde_diag(K1,D,M).
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+ /*****************************
|
|
119
|
+ DEFINITION D'UN ALIGNEMENT
|
|
120
|
+ POSSIBLE POUR UN JOUEUR DONNE
|
|
121
|
+ *****************************/
|
|
122
|
+
|
|
123
|
+possible([X|L], J) :- unifiable(X,J), possible(L,J).
|
|
124
|
+possible( [], _).
|
|
125
|
+
|
|
126
|
+ /* Attention
|
|
127
|
+ il faut juste verifier le caractere unifiable
|
|
128
|
+ de chaque emplacement de la liste, mais il ne
|
|
129
|
+ faut pas realiser l'unification.
|
|
130
|
+ */
|
|
131
|
+
|
|
132
|
+% A FAIRE
|
|
133
|
+unifiable(X,J) :- ( var(X) -> true ; J=X) .
|
|
134
|
+
|
|
135
|
+ /**********************************
|
|
136
|
+ DEFINITION D'UN ALIGNEMENT GAGNANT
|
|
137
|
+ OU PERDANT POUR UN JOUEUR DONNE J
|
|
138
|
+ **********************************/
|
|
139
|
+ /*
|
|
140
|
+ Un alignement gagnant pour J est un alignement
|
|
141
|
+possible pour J qui n'a aucun element encore libre.
|
|
142
|
+ */
|
|
143
|
+
|
|
144
|
+ /*
|
|
145
|
+ Remarque : le predicat ground(X) permet de verifier qu'un terme
|
|
146
|
+ prolog quelconque ne contient aucune partie variable (libre).
|
|
147
|
+ exemples :
|
|
148
|
+ ?- ground(Var).
|
|
149
|
+ no
|
|
150
|
+ ?- ground([1,2]).
|
|
151
|
+ yes
|
|
152
|
+ ?- ground(toto(nil)).
|
|
153
|
+ yes
|
|
154
|
+ ?- ground( [1, toto(nil), foo(a,B,c)] ).
|
|
155
|
+ no
|
|
156
|
+ */
|
|
157
|
+
|
|
158
|
+ /* Un alignement perdant pour J est un alignement gagnant pour son adversaire. */
|
|
159
|
+
|
|
160
|
+% A FAIRE
|
|
161
|
+
|
|
162
|
+alignement_gagnant(Ali, J) :- possible(Ali,J), ground(Ali).
|
|
163
|
+
|
|
164
|
+alignement_perdant(Ali, J) :- adversaire(J,A), alignement_gagnant(Ali, A).
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+ /* ****************************
|
|
168
|
+ DEFINITION D'UN ETAT SUCCESSEUR
|
|
169
|
+ ****************************** */
|
|
170
|
+
|
|
171
|
+ /*
|
|
172
|
+ Il faut definir quelle operation subit la matrice
|
|
173
|
+ M representant l'Etat courant
|
|
174
|
+ lorsqu'un joueur J joue en coordonnees [L,C]
|
|
175
|
+ */
|
|
176
|
+
|
|
177
|
+% A FAIRE
|
|
178
|
+successeur(J, Etat,[L,C]) :- nth1(L,Etat,Lig), nth1(C,Lig,X), var(X), X=J.
|
|
179
|
+
|
|
180
|
+ /**************************************
|
|
181
|
+ EVALUATION HEURISTIQUE D'UNE SITUATION
|
|
182
|
+ **************************************/
|
|
183
|
+
|
|
184
|
+ /*
|
|
185
|
+ 1/ l'heuristique est +infini si la situation J est gagnante pour J
|
|
186
|
+ 2/ l'heuristique est -infini si la situation J est perdante pour J
|
|
187
|
+ 3/ sinon, on fait la difference entre :
|
|
188
|
+ le nombre d'alignements possibles pour J
|
|
189
|
+ moins
|
|
190
|
+ le nombre d'alignements possibles pour l'adversaire de J
|
|
191
|
+*/
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+heuristique(J,Situation,H) :- % cas 1
|
|
195
|
+ H = 10000, % grand nombre approximant +infini
|
|
196
|
+ alignement(Alig,Situation),
|
|
197
|
+ alignement_gagnant(Alig,J), !.
|
|
198
|
+
|
|
199
|
+heuristique(J,Situation,H) :- % cas 2
|
|
200
|
+ H = -10000, % grand nombre approximant -infini
|
|
201
|
+ alignement(Alig,Situation),
|
|
202
|
+ alignement_perdant(Alig,J), !.
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+% on ne vient ici que si les cut precedents n'ont pas fonctionne,
|
|
206
|
+% c-a-d si Situation n'est ni perdante ni gagnante.
|
|
207
|
+
|
|
208
|
+% A FAIRE cas 3
|
|
209
|
+
|
|
210
|
+heuristique(J,Situation,H) :-
|
|
211
|
+ findall(X, (alignement(X,Situation),possible(X,J)),Lgagnant),
|
|
212
|
+ adversaire(J,A),
|
|
213
|
+ findall(Y, (alignement(Y,Situation),possible(Y,A)),Lperdant),
|
|
214
|
+ length(Lgagnant, LG),
|
|
215
|
+ length(Lperdant, LP),
|
|
216
|
+ H is LG-LP.
|
|
217
|
+
|
|
218
|
+test_heuristique :-
|
|
219
|
+ ( ( S1= [ [_,_,_], [_,_,_], [_,_,_] ],
|
|
220
|
+ heuristique(x,S1,0),
|
|
221
|
+ S2 = [ [x,o,_], [_,_,_], [_,_,_] ],
|
|
222
|
+ heuristique(x,S2,1),
|
|
223
|
+ S3 = [ [_,o,_], [_,x,_], [_,_,_] ],
|
|
224
|
+ heuristique(x,S3,2),
|
|
225
|
+ S4 = [ [o,_,_], [_,x,_], [_,_,_] ],
|
|
226
|
+ heuristique(x,S4,1),
|
|
227
|
+ S5 = [ [x,_,_], [_,o,_], [_,_,_] ],
|
|
228
|
+ heuristique(o,S5,1),
|
|
229
|
+ S6 = [ [x,_,_], [_,x,_], [_,_,x] ],
|
|
230
|
+ heuristique(o,S6,-10000),
|
|
231
|
+ S7 = [ [x,_,_], [_,x,_], [_,_,x] ],
|
|
232
|
+ heuristique(x,S7,10000)
|
|
233
|
+ ) ->
|
|
234
|
+ write('test heuristique successful')
|
|
235
|
+ ;
|
|
236
|
+ write('error heuristique test')
|
|
237
|
+ ).
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+/*******************************************
|
|
242
|
+ *
|
|
243
|
+ *
|
|
244
|
+ *
|
|
245
|
+ *
|
|
246
|
+ * NEGAMAX
|
|
247
|
+ *
|
|
248
|
+ *
|
|
249
|
+ *
|
|
250
|
+ * *****************************************/
|
|
251
|
+ /*
|
|
252
|
+ Ce programme met en oeuvre l'algorithme Minmax (avec convention
|
|
253
|
+ negamax) et l'illustre sur le jeu du TicTacToe (morpion 3x3)
|
|
254
|
+ */
|
|
255
|
+
|
|
256
|
+/*:- [tictactoe].*/
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+ /****************************************************
|
|
260
|
+ ALGORITHME MINMAX avec convention NEGAMAX : negamax/5
|
|
261
|
+ *****************************************************/
|
|
262
|
+
|
|
263
|
+ /*
|
|
264
|
+ negamax(+J, +Etat, +P, +Pmax, [?Coup, ?Val])
|
|
265
|
+
|
|
266
|
+ SPECIFICATIONS :
|
|
267
|
+
|
|
268
|
+ retourne pour un joueur J donne, devant jouer dans
|
|
269
|
+ une situation donnee Etat, de profondeur donnee P,
|
|
270
|
+ le meilleur couple [Coup, Valeur] apres une analyse
|
|
271
|
+ pouvant aller jusqu'a la profondeur Pmax.
|
|
272
|
+
|
|
273
|
+ Il y a 3 cas a decrire (donc 3 clauses pour negamax/5)
|
|
274
|
+
|
|
275
|
+ 1/ la profondeur maximale est atteinte : on ne peut pas
|
|
276
|
+ developper cet Etat ;
|
|
277
|
+ il n'y a donc pas de coup possible a jouer (Coup = rien)
|
|
278
|
+ et l'evaluation de Etat est faite par l'heuristique.
|
|
279
|
+
|
|
280
|
+ 2/ la profondeur maximale n'est pas atteinte mais J ne
|
|
281
|
+ peut pas jouer ; au TicTacToe un joueur ne peut pas jouer
|
|
282
|
+ quand le tableau est complet (totalement instancie) ;
|
|
283
|
+ il n'y a pas de coup a jouer (Coup = rien)
|
|
284
|
+ et l'evaluation de Etat est faite par l'heuristique.
|
|
285
|
+
|
|
286
|
+ 3/ la profondeur maxi n'est pas atteinte et J peut encore
|
|
287
|
+ jouer. Il faut evaluer le sous-arbre complet issu de Etat ;
|
|
288
|
+
|
|
289
|
+ - on determine d'abord la liste de tous les couples
|
|
290
|
+ [Coup_possible, Situation_suivante] via le predicat
|
|
291
|
+ successeurs/3 (deja fourni, voir plus bas).
|
|
292
|
+
|
|
293
|
+ - cette liste est passee a un predicat intermediaire :
|
|
294
|
+ loop_negamax/5, charge d'appliquer negamax sur chaque
|
|
295
|
+ Situation_suivante ; loop_negamax/5 retourne une liste de
|
|
296
|
+ couples [Coup_possible, Valeur]
|
|
297
|
+
|
|
298
|
+ - parmi cette liste, on garde le meilleur couple, c-a-d celui
|
|
299
|
+ qui a la plus petite valeur (cf. predicat meilleur/2);
|
|
300
|
+ soit [C1,V1] ce couple optimal. Le predicat meilleur/2
|
|
301
|
+ effectue cette selection.
|
|
302
|
+
|
|
303
|
+ - finalement le couple retourne par negamax est [Coup, V2]
|
|
304
|
+ avec : V2 is -V1 (cf. convention negamax vue en cours).
|
|
305
|
+
|
|
306
|
+A FAIRE : ECRIRE ici les clauses de negamax/5
|
|
307
|
+.....................................
|
|
308
|
+ */
|
|
309
|
+ negamax(J, Etat, P, P, [nil, Val]) :- heuristique(J,Etat,Val).
|
|
310
|
+
|
|
311
|
+ negamax(J,Etat, P, Pmax, [nil,Val]) :- P < Pmax ,
|
|
312
|
+ heuristique(J,Etat,Val),
|
|
313
|
+ Val = 10000.
|
|
314
|
+
|
|
315
|
+ negamax(J,Etat, P, Pmax, [nil,Val]) :- P < Pmax ,
|
|
316
|
+ heuristique(J,Etat,Val),
|
|
317
|
+ Val = -10000.
|
|
318
|
+
|
|
319
|
+ negamax(J,Etat, P, Pmax, [nil,Val]) :- P < Pmax ,
|
|
320
|
+ situation_terminale(J,Etat),
|
|
321
|
+ heuristique(J,Etat,Val).
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+ negamax(J,Etat, P, Pmax, [Coup,Val]) :- P < Pmax ,
|
|
325
|
+ successeurs(J,Etat,Successeurs),
|
|
326
|
+ loop_negamax(J,P,Pmax,Successeurs,Liste_Couples),
|
|
327
|
+ meilleur(Liste_Couples,[Coup,V1]),
|
|
328
|
+ Val is -V1.
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+ /*******************************************
|
|
334
|
+ DEVELOPPEMENT D'UNE SITUATION NON TERMINALE
|
|
335
|
+ successeurs/3
|
|
336
|
+ *******************************************/
|
|
337
|
+
|
|
338
|
+ /*
|
|
339
|
+ successeurs(+J,+Etat, ?Succ)
|
|
340
|
+
|
|
341
|
+ retourne la liste des couples [Coup, Etat_Suivant]
|
|
342
|
+ pour un joueur donne dans une situation donnee
|
|
343
|
+ */
|
|
344
|
+
|
|
345
|
+successeurs(J,Etat,Succ) :-
|
|
346
|
+ copy_term(Etat, Etat_Suiv),
|
|
347
|
+ findall([Coup,Etat_Suiv],
|
|
348
|
+ successeur(J,Etat_Suiv,Coup),
|
|
349
|
+ Succ).
|
|
350
|
+
|
|
351
|
+ /*************************************
|
|
352
|
+ Boucle permettant d'appliquer negamax
|
|
353
|
+ a chaque situation suivante :
|
|
354
|
+ *************************************/
|
|
355
|
+
|
|
356
|
+ /*
|
|
357
|
+ loop_negamax(+J,+P,+Pmax,+Successeurs,?Liste_Couples)
|
|
358
|
+ retourne la liste des couples [Coup, Valeur_Situation_Suivante]
|
|
359
|
+ a partir de la liste des couples [Coup, Situation_Suivante]
|
|
360
|
+ */
|
|
361
|
+
|
|
362
|
+loop_negamax(_,_, _ ,[], []).
|
|
363
|
+loop_negamax(J,P,Pmax,[[Coup,Suiv]|Succ],[[Coup,Vsuiv]|Reste_Couples]) :-
|
|
364
|
+ loop_negamax(J,P,Pmax,Succ,Reste_Couples),
|
|
365
|
+ adversaire(J,A),
|
|
366
|
+ Pnew is P+1,
|
|
367
|
+ negamax(A,Suiv,Pnew,Pmax, [_,Vsuiv]).
|
|
368
|
+
|
|
369
|
+ /*
|
|
370
|
+
|
|
371
|
+A FAIRE : commenter chaque litteral de la 2eme clause de loop_negamax/5,
|
|
372
|
+ en particulier la forme du terme [_,Vsuiv] dans le dernier
|
|
373
|
+ litteral ?
|
|
374
|
+ */
|
|
375
|
+
|
|
376
|
+ /*********************************
|
|
377
|
+ Selection du couple qui a la plus
|
|
378
|
+ petite valeur V
|
|
379
|
+ *********************************/
|
|
380
|
+
|
|
381
|
+ /*
|
|
382
|
+ meilleur(+Liste_de_Couples, ?Meilleur_Couple)
|
|
383
|
+
|
|
384
|
+ SPECIFICATIONS :
|
|
385
|
+ On suppose que chaque element de la liste est du type [C,V]
|
|
386
|
+ - le meilleur dans une liste a un seul element est cet element
|
|
387
|
+ - le meilleur dans une liste [X|L] avec L \= [], est obtenu en comparant
|
|
388
|
+ X et Y,le meilleur couple de L
|
|
389
|
+ Entre X et Y on garde celui qui a la petite valeur de V.
|
|
390
|
+
|
|
391
|
+A FAIRE : ECRIRE ici les clauses de meilleur/2
|
|
392
|
+ */
|
|
393
|
+ /*meilleur([Cm,Vm], [Cm,Vm]).
|
|
394
|
+ meilleur([[C,V]|R], [Cm,Vm]) :-
|
|
395
|
+ meilleur(R, [Cm,Vm]),
|
|
396
|
+ ( Vm > V -> ).*/
|
|
397
|
+ meilleur([X], X). %le meilleur dans une liste a un seul element est cet element
|
|
398
|
+ meilleur(Liste_de_Couples, MeilleurCouple ) :-
|
|
399
|
+ Liste_de_Couples = [ [C,V] | Fin ] ,
|
|
400
|
+ meilleur(Fin,[Cout,Val]),
|
|
401
|
+
|
|
402
|
+ ( Val > V -> MeilleurCouple = [C , V]
|
|
403
|
+ ;
|
|
404
|
+ MeilleurCouple = [Cout , Val]
|
|
405
|
+ ).
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+ test_meilleur :-
|
|
410
|
+ ( ( meilleur([[a,2],[b,3],[c,4] ],[a,2]),
|
|
411
|
+ meilleur([[a,2],[b,3],[c,1] ],[c,1]),
|
|
412
|
+ meilleur([[a,2],[b,2]],[b,2]),
|
|
413
|
+ meilleur([[a,2]],[a,2])
|
|
414
|
+ ) ->
|
|
415
|
+ write('test meilleur successful')
|
|
416
|
+ ;
|
|
417
|
+ write('error meilleur test')
|
|
418
|
+ ).
|
|
419
|
+
|
|
420
|
+ /******************
|
|
421
|
+ PROGRAMME PRINCIPAL
|
|
422
|
+ *******************/
|
|
423
|
+
|
|
424
|
+main(B,V, Pmax) :-
|
|
425
|
+ situation_initiale(Ini),
|
|
426
|
+ joueur_initial(J),
|
|
427
|
+ negamax(J,Ini,0,Pmax,[B,V]).
|
|
428
|
+
|