Systemes a base de connaissances:Modelisation du Kalaha en Clips

De Wiki de Romain RUDIGER
Aller à : navigation, rechercher

« Systèmes à bases de connaissance »

Réalisation d’un programme jouant au Kalah avec Intelligence Artificielle

But du projet

Le but de ce projet est de réaliser un programme capable de jouer au jeu du Kalah. Dans un premier temps les règles du jeu seront étudiées puis il faudra déterminer la stratégie qui sera implémentée. Ensuite le choix du langage de programmation sera fait. Et pour finir le programme sera implémenté dans ce langage puis testé. Ce projet a pour but de nous montrer qu’il est possible de réaliser une intelligence artificielle pour jouer à un jeu qui n’est pas si simple que ça, et cela grâce aux différents cours de ce premier semestre.

Étude

L’étude des règles puis des stratégies possibles sera faite dans cette partie.

Les règles du jeu

Différentes règles simples existent, il s’agit de bien les comprendre pour bien les implémenter. Voici certaines règles importantes :

• Conquête de la cavité adverse : c’est lorsque l’on dépose son dernier cailloux dans une de ses cavité et que celle-ci est vide. Alors on prend son dernier cailloux ainsi que les cailloux d’en face pour les mettre dans son Kalah.

Celle règle peut parfois rapporter beaucoup de points, il faudra en tenir compte dans les stratégies.

• Possibilité de rejouer : c’est lorsque l’on dépose son dernier cailloux dans son grenier. Pour la stratégie il faudra déterminer s’il est mieux de rejouer ou de par exemple conquérir une cavité adverse ?

Les stratégies

Le Kalah est un jeu asynchrone opposant deux joueurs qui joueront donc chacun à son tour. À chaque instant du jeu, on dispose de toute les informations (ce n’est pas le cas du poker par exemple). Le joueur gagnant est celui qui à le maximum de cailloux dans son grenier. Il s’agira donc de maximiser le nombre de cailloux dans le grenier du joueur. Cette opération peut demander beaucoup de réflexion, ceci est du à la règle numéro une de la partie précédente (conquête de la cavité adverse). Il faudra donc prévoir les coups suivants possibles pour faire le meilleur !

Conception

Le choix du langage puis des détails de conception du programme que nous nous sommes posés avant la réalisation.

Le langage de réalisation

Un logiciel de programmation déclaratif doit être utilisé. Nous disposons donc de Prolog et de Clips qui sont tous les deux des langages de programmation par règles. Connaissant très mal les deux, sachant que ce qui est réalisable sur l’un, l’est sur l’autre, nous avons choisi de réaliser l’application en Clips. Caractéristiques principales de Clips :

  • Trois logiques de programmation sont possibles en Clips :
    • Programmation procédurale
    • Programmation à objet
    • Programmation par règles
  • Rapidité d’exécution et portabilité puisque écrit en langage C
  • L’intégration du moteur dans une application écrite dans un autre langage semble être aisée.
  • L’interpréteur de commande est interactif.

Le site officiel de Clips est : http://www.ghg.net/clips/CLIPS.html A partir du 31 mars 2008 ce sera : http://groups.google.com/group/CLIPSESG

La conception

La programmation par règles impose une réflexion bien aboutie des différentes règles et faits que nous utiliserons. Trois grandes parties :

  • le Kalah et ses règles du jeu.
  • La partie intelligence artificielle
  • Le moteur permettant de faire fonctionner ensemble les deux parties précédentes.


Le Kalah et ses règles

Cette partie se doit de modéliser le plateau de jeu, les règles ainsi que la fin d’une partie.

Le plateau de jeu

Le plateau du jeu sera modélisé par une liste de 14 éléments, en voici un schéma :

Polytech S3 SBC Kalah plateau.JPG

On distingue la séparation du plateau en deux zones, chacune d’elle appartient soit au joueur 0 (parfois appelé joueur du nord ou du haut), soit au joueur 1 (du sud ou du bas). Le plateau de jeu sera renseigné dans le fait (plateau $?plateauVal), la variable $?plateauVal est du type liste qui sera indexée de 1 à 14.

L’affichage du plateau

Une règle permet l’affichage du plateau directement sur l’interpréteur de commande, cette affichage sera donc très sommaire mais bien représentatif du plateau, exemple :

Polytech S3 SBC Kalah plateau aff.JPG

La règle affichant le plateau s’exécutera lors de la présence du fait (phase afficherPlateau), le plateau s’affichera et ce fait sera supprimé. Il existe cependant une exception : si l’IA est en cours d’exécution le plateau ne doit pas s’afficher, c’est la valeur du fait (affichageInfos ?affichageInfos) qui nous donnera cette information (si 0 on affiche, si 1 on n’affiche rien).

Les règles

Deux règles du jeu, la répartition des cailloux de la cavité jouée vers la droite dans toutes les cavités sauf dans le grenier de l’adversaire ; la conquête d’une cavité.

Répartition des cailloux

La répartition des cailloux (règle jouer) sera lancée sur la présence du fait (jouer ?numJoueur ?numJoue) avec le numéro du joueur qui a lancé cette répartition ainsi que la cavité jouée. La cavité jouée sera de 1 à 6 pour le joueur 1 et de 8 à 13 pour le joueur 0. Cette règle se chargera de vider la cavité jouée et de lancer une seconde règle (règle repartitionCailloux) qui répartira les cailloux jusqu’à épuisement. Voici le fait inséré pour la seconde règle : (repartir ?joueur ?numJoue ?nbCailloux). On retrouve le numéro du joueur qui nous permet de sauter le grenier adverse, le numéro de la cavité sur laquelle on doit déposer un caillou ainsi que le nombre de cailloux restant. Cette règle se charge de modifier le plateau en ajoutant un caillou dans la bonne cavité puis de se relancer pour la cavité suivante avec un caillou de moins à répartir. Cette règle sera donc en quelque sorte récursive. Une règle de fin (règle repartitionCaillouxFin) lorsqu’il n’y a plus de cailloux de savoir si le dernier caillou a été mis dans le grenier du joueur ou non et donc de savoir quel est le joueur suivant.

Conquête de la cavité adverse

Une règle permettant de conquérir la cavité adverse est nécessaire. Il faudra donc ajouter une détection de conquête dans la règle repartitionCailloux qui permettra de déclencher cette règle avec le fait : (conquerir ?numJoue ?numCaviteConquise). On retrouve le numéro de la cavité du joueur qui a généré cette conquête ainsi que le numéro de la cavité adverse. Il faudra alors modifier le plateau pour ajouter le nombre de cailloux total ainsi que mettre à zéro les deux cavités.

Fin d’une partie

Cette règle relativement simple permettra de détecter la fin du jeu et ainsi de calculer le plateau final et de déclarer le gagnant ! Deux règles : finDuJeu et gagnantDuJeu, la première détecte la fin du jeu (plus aucun caillou dans les 6 cavités d’un des deux joueurs) et calcule le plateau final ; la seconde règle détermine le gagnant du jeu et réinitialise l’environnement pour le début d’un seconde partie.


La partie intelligence artificielle

Nous avons décidé d’implémenter l’algorithme Min-Max pour déterminer la bonne solution. Cette décision se porte bien puisque le Kalah est un jeu avec 2 joueurs et à information complète. Une amélioration de l’algorithme sera possible en implémentant une fonction de coupe pour éviter de parcourir des solutions moins bonnes que celles déjà calculées

Un nœud

Un nœud de l’arbre représentera l’état du jeu (le plateau, le joueur suivant), le numéro de la cavité jouée ainsi que bien sûr un identifiant unique, l’identifiant de son père, une liste des identifiants de ses fils ainsi que la profondeur actuelle dans l’arbre qui servira pour l’algorithme.

Polytech S3 SBC Kalah noeud.JPG


L’arbre

La génération de l’arbre sera faite en largeur d’abord puisque dans le cas ou il est possible de rejouer, tous les fils seront recalculés avec cette information. Pour mieux comprendre, voici un schéma du fonctionnement des règles :

Polytech S3 SBC Kalah arbre.JPG

Une règle par « bulle » sera nécessaire plus une règle de fin de jeu lors du calcul du nouveau plateau qui sera différente de la règle finDuJeu qui elle sera conçue pour le jeux lui-même et non pas la génération de l’arbre pour déterminer le meilleur coup.

Min-Max

L’algorithme Min-Max fera un parcours par niveau de l’arbre précédemment obtenu. Voici le schéma représentatif du fonctionnement des règles :

Polytech S3 SBC Kalah min max.JPG
Conclusion

La partie Intelligence Artificielle ne sera pas facile à implémenter. Il a fallu repenser notre façon de modéliser puisqu’il a fallu réfléchir aux problèmes et aux règles permettant de résoudre ces problèmes et plus en terme de « fonctions » comme en Java ou en C.


Le moteur du jeu

C’est le moteur qui permettra aux autres éléments de fonctionner ! Dans un premier temps, il permettra de connaître le type de jeu que l’on souhaite faire, c'est-à-dire :

  • Type 0 : ordinateur contre ordinateur ;
  • Type 1 : ordinateur contre humain ;
  • Type 2 : humain contre humain.

Dans un second temps, il permettra de gérer les tours de rôle entre les deux joueurs. Il faudra donc selon le type de jeu soit si c’est un humain demander la cavité qu’il souhaite jouer soit si c’est un ordinateur lancé le calcul du coup et jouer le coup renvoyé. C’est dans cette partie que la vérification que le joueur joue une cavité qui lui appartient est faite.

Réalisation

Lors de la réalisation du jeu sous clips, beaucoup de problèmes sont arrivés notamment le déclenchement impromptu de règles. Il a donc fallu ajouter plusieurs faits ainsi que des priorités d’exécution entre les règles. Le débogage n’est pas du tout évident sous l’environnement de développement Clips qui ne permet que l’utilisation de points d’arrêt. On peut tout de même observer les faits en cours ainsi que les règles suivantes qui s’exécuteront dans l’ordre.

Manuel d’utilisation

  • Exécuter CLIPSWin.exe.
  • Cliquez sur File > Load >
  • sélectionner le fichier Kalah_Clips_RUDIGER_COLAS.clp
  • Ouvrez la fenêtre des faits ainsi que l’agenda avec le menu « Window ».
  • Ensuite dans l’interpréteur de commande appuyer sur ctrl+e puis sur ctrl+r.

Vous obtiendrez alors :

A vous de jouer !

Exemple d’exécution du jeu

Ordinateur contre ordinateur

 Choix du type de jeu : 
  0 - Ordinateur contre ordinateur;
  1 - Ordinateur contre humain;
  2 - Humain contre humain.0
C'est l'ordinateur du haut qui commence (joueur 0).
Choix de l'intelligence de l'ordinateur : de faible (1 très rapide) à élevée (4 très long) ? 2
   6   6   6   6   6   6
0                         0
   6   6   6   6   6   6
C'est à l'ordinateur 0 de jouer.
L'ia 0 a décidé de jouer le kalaha : 8.
Kalaha joué : 8. Nombre de caillou(x) a répartir : 6.
   7   7   7   7   7   0
1                         0
   6   6   6   6   6   6
C'est à l'ordinateur 0 de jouer.
L'ia 0 a décidé de jouer le kalaha : 13.
Kalaha joué : 13. Nombre de caillou(x) a répartir : 7.
   0   7   7   7   7   0
2                         0
   7   7   7   7   7   7
C'est à l'ordinateur 1 de jouer.
L'ia 1 a décidé de jouer le kalaha : 6.
Kalaha joué : 6. Nombre de caillou(x) a répartir : 7.
   1   8   8   8   8   1
2                         1
   7   7   7   7   7   0
C'est à l'ordinateur 0 de jouer.
…
L'ia 0 a décidé de jouer le kalaha : 11.
Kalaha joué : 11. Nombre de caillou(x) a répartir : 1.
   0   0   0   0   0   0
35                         27
   0   0   10   0   0   0
   0   0   0   0   0   0
35                         37
   0   0   0   0   0   0
Le joueur 1 a gagné! (37 cailloux dans son grenier)

Ordinateur contre humain

Choix du type de jeu : 
  0 - Ordinateur contre ordinateur;
  1 - Ordinateur contre humain;
  2 - Humain contre humain.1
Voulez vous commencer ? (0 pour oui et 1 pour non) 
0
Vous commencez, vos kalahas sont ceux du bas.
Choix de l'intelligence de l'ordinateur : de faible (1 très rapide) à élevée (4 très long) ? 1
   6   6   6   6   6   6
0                         0
   6   6   6   6   6   6
C'est à vous, choix de la cavité (1-6) : 1
Kalaha joué : 1. Nombre de caillou(x) a répartir : 6.
   6   6   6   6   6   6
0                         1
   0   7   7   7   7   7
C'est à vous, choix de la cavité (1-6) : 2
Kalaha joué : 2. Nombre de caillou(x) a répartir : 7.
   6   6   6   6   7   7
0                         2
   0   0   8   8   8   8
C'est à l'ordinateur de jouer.
L'ia 0 a décidé de jouer le kalaha : 13.
Kalaha joué : 13. Nombre de caillou(x) a répartir : 6.
   0   6   6   6   7   7
1                         2
   1   1   9   9   9   8
C'est à l'ordinateur de jouer.
L'ia 0 a décidé de jouer le kalaha : 9.
Kalaha joué : 9. Nombre de caillou(x) a répartir : 1.
   0   0   0   0   0   0
26                         45
   0   0   0   0   1   0
   0   0   0   0   0   0
26                         46
   0   0   0   0   0   0
Le joueur 1 a gagné! (46 cailloux dans son grenier)

Conclusion

Ce projet nous a permis de démystifier le fonctionnement d’une intelligence artificielle dans un jeu. Mais également d’utiliser les algorithmes vue en cours d’intelligence artificielle ainsi que d’apprendre une autre façon de programmer. Concernant les résultats du programme lui-même, la réalisation a pris énormément de temps, ceci est du en partie au manque de savoir faire du langage déclaratif mais également au peu de fonctionnalités de débogage. Malheureusement ce n’est pas le joueur qui commence qui gagne donc soit l’affirmation est fausse (pour nous c’est le deuxième qui gagne à chaque coup) soit notre Intelligence Artificielle n’est pas si intelligente que ça. Par ailleurs à cause d’un manque de temps, l’algorithme Min-Max n’a pas pu être amélioré par une coupe Alpha-Béta qui aurait réduit considérablement le temps de calcul de l’IA.

Code source

;********************************************************************
;***** Jeux du Kalah par :   >Yoann Colas  
;*****        > Romain Rudiger
;***** le : 01/2008
;*****  pour : Polytech'Nantes
;*****  Version HUMAIN <-> MACHINE             
;********************************************************************

; le numéro de 1 à 14
; le type 0=circulaire 1=ovales (grenier 6 et 13)
; le nombre de cailloux par cavité est de 6


;Les faits de départ
(deffacts kalaha
  (phase init)
  
  ;(phase ia)
  ;(joueurIA 0)
  ;(profondeurMax 3)
  
  (kalahaJoueIA 0)
  (typeJeu -1) ; permettera de connaitre le type de jeu, si 0 : ia vs ia, si 1 ia vs humain, si 2 humain vs humain
  (affichageInfos 0) ; au début l'affichage des infos est activé
  (plateau 6 6 6 6 6 6 0 6 6 6 6 6 6 0)
  ;(plateau 0 10 10 0 0 0 27 0 0 0 1 0 0 24)
)

;********************************************************************
;***** Règles effectuant les changements lorsqu'un joueur joue une cavité *****
;********************************************************************
;>Détermine si choix du joueur est correcte puis si oui démarre les changements
;>Répartition des cailloux
;>Conquete de la cavité adverse si le dernier caillou est posé dans un kalaha du joueur qui joue actuellement

;**Regle permettant de démarrer le choix d'un joueur
;le fait jouer est composé de : 
;  @param le numéro de joueur (joueur haut 0 et bas 1)
;  @param la cavité jouée
(defrule jouer 
  ?jeu <-(jouer ?numJoueur ?numJoue)
  ?plateau <- (plateau $?plateauVal)
  (affichageInfos ?affichageInfos) ;pour savoir si l'on affiche ou non le choix du joueur 0 = oui 1 = non
  =>
  (if (neq (nth$ ?numJoue $?plateauVal) 0) ;il faut un nombre de cailloux != de 0.  
    then
      (if (eq ?affichageInfos 0) ;on affiche
        then
          (printout t "Kalaha joué : " ?numJoue ". Nombre de caillou(x) a répartir : " (nth$ ?numJoue $?plateauVal) "." t)
      )
      (assert (repartir ?numJoueur (+ 1 ?numJoue) (nth$ ?numJoue $?plateauVal)))

      (bind ?plateauValNew (replace$ ?plateauVal ?numJoue ?numJoue 0))
      (assert (plateau ?plateauValNew))
      (retract ?plateau)
    else ;il n'y a pas de caillou dans la cavité choisie !
      (assert (phase (sym-cat bad ?numJoueur)))
  )
  (retract ?jeu)
)

;**Répartition des cailloux dans chaque cavités de façon récursive.
;le fait répartir est constitué de : 
;  @param joueur le joueur actuel
;  @param numJoue la cavité actuelle
;  @param nbCailloux le nombre de cailloux restant
(defrule repartitionCailloux
  ?repartir_old <- (repartir ?joueur ?numJoue ?nbCailloux)
  (test(!= ?nbCailloux 0)) ;il reste des cailloux à répartir
  ?plateau <- (plateau $?plateauVal)
  =>
  ;calcul de la cavite suivante
  (if (eq ?numJoue 14)
    then (bind ?numJoueSuiv 1)
    else (bind ?numJoueSuiv (+ 1 ?numJoue))
  )
  ;detection du grenier adverse (on ne met rien)
  (if (eq ?joueur 0) 
    then ;joueur du haut
    (if (eq ?numJoue 7) ;grenier adverse donc on saute
      then 
        (assert (repartir 0 ?numJoueSuiv ?nbCailloux))
      else ;une cavité à remplir
        ;incrémentation du nombre de cailloux dans la cavité
        (bind ?plateauValNew (replace$ ?plateauVal ?numJoue ?numJoue (+ 1 (nth$ ?numJoue $?plateauVal))  )) ;création du nouveau plateau
        (assert (plateau ?plateauValNew)) ;ajout dans les faits du nouveau plateau
        (retract ?plateau) ;suppression de l'ancien plateau
        ;relance de la répartition avec un caillou de moins pour la cavité suivante
        (assert (repartir 0 ?numJoueSuiv (- ?nbCailloux 1)))
    )
    else;joueur du bas
    (if (eq ?numJoue 14) ;grenier adverse donc on saute
      then 
        (assert (repartir 1 ?numJoueSuiv ?nbCailloux))
      else ;une cavité à remplir
        ;incrémentation du nombre de cailloux dans la cavité
        (bind ?plateauValNew (replace$ ?plateauVal ?numJoue ?numJoue (+ 1 (nth$ ?numJoue $?plateauVal)))) ;création du nouveau plateau
        (assert (plateau ?plateauValNew)) ;ajout dans les faits du nouveau plateau
        (retract ?plateau) ;suppression de l'ancien plateau
        ;relance de la répartition avec un caillou de moins pour la cavité suivante
        (assert (repartir 1 ?numJoueSuiv (- ?nbCailloux 1)))
    )
  )
  ;il s'agit maintenant de détecter et lancer le traitement du cas ou :
  ;-c'était le dernier caillou à répartir (1)
  ;-il n'y avait pas de caillou dans la cavité (0)
  ;-et que ce n'est pas un grenier (!= 7 ^ 14)
  (if (and (eq 1 ?nbCailloux) (eq 0 (nth$ ?numJoue $?plateauVal)) 
           (!= 7 ?numJoue) (!= 14 ?numJoue))
    then 
      (if (< ?numJoue 7) ;si c'est un kalaha du bas
        then
          (if (eq 1 ?joueur) ;conquete seulement si c'est le joueur du bas qui a joué
            then
              (bind ?numCaviteConquise (abs (- ?numJoue 14))) ;kalaha d'en face (de 8 à 13)
              (assert (conquerir ?numJoue ?numCaviteConquise))
          )
        else
          (if (eq 0 ?joueur) ;conquete seulement si c'est le joueur du haut qui a joué
            then
              (bind ?numCaviteConquise (- 14 ?numJoue)) ;kalaha d'en face (de 1 à 6)
              (assert (conquerir ?numJoue ?numCaviteConquise))
          )
      )
  )
  (retract ?repartir_old)
)

;**règle détectant la fin de la récursivité de la répartition lorsqu'il n'y a plus de caillou à répartir.
(defrule repartitionCaillouxFin
  ?repartir_old <- (repartir ?joueur ?numJoue 0)
  (not (conquerir ? ?)) ;il ne faut pas de conquete de la case adverse en cours
  =>
  (retract ?repartir_old)
  ;Choisir le joueur suivant qui va jouer
  ;si le joueur a mit son dernier caillou dans son grenier, alors il rejoue sinon c'est l'autre joueur
  (if (or (eq ?numJoue 1) (eq ?numJoue 8)) ;si ça c'est fini sur un grenier
    then ;c'est le meme joueur qui rejoue
      (if (eq ?joueur 0)
        then ;c'est le joueur 0 qui rejoue
          (assert (phase ok0))
        else ;c'est le joueur 1 qui joue
          (assert (phase ok1))
      )
    else ;c'est le joueur adverse qui joue
      (if (eq ?joueur 0)
        then ;c'est le joueur 1 qui joue
          (assert (phase ok1))
        else ;c'est le joueur 0 qui joue
          (assert (phase ok0))
      )
  )
  ;affichage du plateau mit à jour dans tous les cas : 
  (assert (phase afficherPlateau))
)

;**Règle permettant de conquérir la cavité adverse
;ajoute les cailloux de chaque cavité dans le grenier du joueur a l'origine de la conquête
;le fait conquerir est composé de :
;  @param numCaviteConquerante Numéro de la cavité coté joueur à l'origine
;  @param numCaviteConquise Numéro de la cavité d'en face
(defrule conquerirCaviteAdverse
  ?conquete <- (conquerir ?numCaviteConquerante ?numCaviteConquise)
  ;on récupère le plateau
  ?plateau <- (plateau $?plateauVal)
  =>
  ;calcul du nombre de cailloux a ajouter dans l'un des greniers
  (bind ?nbCaillouxConquis (+ (nth$ ?numCaviteConquerante $?plateauVal) (nth$ ?numCaviteConquise $?plateauVal)))
  ;mise a zéro des deux cavités
  (bind ?plateauValNew (replace$ ?plateauVal ?numCaviteConquerante ?numCaviteConquerante 0)) ;création du nouveau plateau : cavité conquérante = 0
  (bind ?plateauValNew (replace$ ?plateauValNew ?numCaviteConquise ?numCaviteConquise 0)) ;modification du nouveau plateau : cavité conquise = 0
  ;ajoute dans le grenier concerné
  (if (> ?numCaviteConquerante 7) ;si joueur du haut (0)
    then
      (bind ?plateauValNew (replace$ ?plateauValNew 14 14 (+ ?nbCaillouxConquis (nth$ 14 $?plateauVal))))
    else  
      (bind ?plateauValNew (replace$ ?plateauValNew 7 7 (+ ?nbCaillouxConquis (nth$ 7 $?plateauVal))))
  )
  ;fin de la conquete
  (assert (plateau ?plateauValNew)) ;ajout dans les faits du nouveau plateau
  (retract ?plateau) ;suppression de l'ancien plateau
  (retract ?conquete)
)

;*****************************
;***** Affichage du plateau *****
;*****************************

;**affichage du plateau de jeux en utilisant le fait (phase afficherPlateau)
(defrule afficherPlateau (declare (salience 2))
  (affichageInfos ?affichageInfos) ;pour savoir si l'on affiche ou non le plateau 0 = oui 1 = non
  ?phase <- (phase afficherPlateau)
  ?plateau <- (plateau $?plateauVal)
  =>
  (if (eq ?affichageInfos 0) ;on affiche
    then
      (printout t "   " (nth$ 13 $?plateauVal) "   " (nth$ 12 $?plateauVal) "   " (nth$ 11 $?plateauVal) "   " (nth$ 10 $?plateauVal) "   " (nth$ 9 $?plateauVal) "   " (nth$ 8 $?plateauVal) crlf)
      (printout t (nth$ 14 $?plateauVal) "                         " (nth$ 7 $?plateauVal) crlf)
      (printout t "   " (nth$ 1 $?plateauVal) "   " (nth$ 2 $?plateauVal) "   " (nth$ 3 $?plateauVal) "   " (nth$ 4 $?plateauVal) "   " (nth$ 5 $?plateauVal) "   " (nth$ 6 $?plateauVal) crlf)
  )
  (retract ?phase)
)

;***********************************
;***** Règle détectant la fin du jeu *****
;***********************************

;Cette règle détecte que toutes les cavités du joueur 0, ou bien du joueur 1, sont à 0.
;Ensuite, tous les cailloux restants chez l'adversaire sont mis au grenier.
;Enfin, elle compare le nombre de cailloux dans chaque grenier et affiche le vainqueur.
;Une priorité est donnée pour passer avant le moteurKalaha

;**fin du jeu si l'un des deux joueurs n'a plus de caillou dans tout ses kalahas
(defrule finDuJeu
  (declare (salience 2))
  ?plateau <- (plateau $?plateauVal)
  (or (phase ok1) (phase ok0))
  ?phase <- (phase ?phaseVal)
  ;vérification qu'il n'y ai pas de répartition en cours
  (not (repartir ?joueur ?numJoue ?nbCailloux))
  ;pas d'ia en cours
  (not (iaRun ?))
  =>
  (if (or (eq ok1 ?phaseVal) (eq ok0 ?phaseVal)) ;la phase doit être juste à la fin d'une action d'un joueur
    then
      ;si les cavités du joueur 1 sont vides
      (if (eq (+ (nth$ 1 $?plateauVal) (nth$ 2 $?plateauVal) (nth$ 3 $?plateauVal) (nth$ 4 $?plateauVal) (nth$ 5 $?plateauVal) (nth$ 6 $?plateauVal)) 0)
        then ;le joueur 0 récupère ses cailloux
          (bind ?plateauValNew (replace$ ?plateauVal 14 14 (+ (nth$ 8 $?plateauVal) (nth$ 9 $?plateauVal) (nth$ 10 $?plateauVal) (nth$ 11 $?plateauVal) (nth$ 12 $?plateauVal) (nth$ 13 $?plateauVal) (nth$ 14 $?plateauVal))))
          (bind ?plateauValNew (replace$ ?plateauValNew 8 13 0 0 0 0 0 0)) ;mise a zéro de ses cavités
          (assert (plateau ?plateauValNew)) ;enregistrement du nouveau plateau
          (assert (finDuJeu)) ;détermination du gagnant
          (assert (phase afficherPlateau)) ;affichage du plateau final
          (retract ?plateau ?phase) ;retrait des anciens faits
      )
      ;si les cavités du joueur 0 sont vides
      (if (eq (+ (nth$ 8 $?plateauVal) (nth$ 9 $?plateauVal) (nth$ 10 $?plateauVal) (nth$ 11 $?plateauVal) (nth$ 12 $?plateauVal) (nth$ 13 $?plateauVal)) 0)
        then ;le joueur 1 récupère ses cailloux
          (bind ?plateauValNew (replace$ ?plateauVal 7 7 (+ (nth$ 1 $?plateauVal) (nth$ 2 $?plateauVal) (nth$ 3 $?plateauVal) (nth$ 4 $?plateauVal) (nth$ 5 $?plateauVal) (nth$ 6 $?plateauVal) (nth$ 7 $?plateauVal))))
          (bind ?plateauValNew (replace$ ?plateauValNew 1 6 0 0 0 0 0 0)) ;mise a zéro de ses cavités
          (assert (plateau ?plateauValNew)) ;enregistrement du nouveau plateau
          (assert (finDuJeu)) ;détermination du gagnant
          (assert (phase afficherPlateau)) ;affichage du plateau final
          (retract ?plateau ?phase) ;retrait des anciens faits
      )
  )
)

;** Détermination du gagnant
;Détection du fait fin de jeu
;récupération du plateau
;determination du max des 2 greniers
(defrule gagnantDuJeu
  (finDuJeu)
  ?plateau <- (plateau $?plateauVal)
  =>
  (if (< (nth$ 14 $?plateauVal) (nth$ 7 $?plateauVal)) ;si le grenier du joueur 1 (7) a plus de cailloux que celui du joueur 0 (14)
    then ;affichage joueur 1 a gagné
      (printout t "Le joueur 1 a gagné! (" (nth$ 7 $?plateauVal) " cailloux dans son grenier)" t)
    else ;affichage joueur 0 a gagné
      (printout t "Le joueur 0 a gagné! (" (nth$ 14 $?plateauVal) " cailloux dans son grenier)" t)
  )
  (reset)
)

;***********************************************************************************************
;***** Moteur du jeu permettant de gérer l'alternance entre les joueurs et la saisie des choix des  joueurs *****
;***********************************************************************************************

(defrule moteurkalah
  (declare (salience 1))
  ?phase <- (phase ?valPhase)
  ?typeJeu <- (typeJeu ?typeJeuVal)
  ?kalahaJoueIA <- (kalahaJoueIA ?kalahaJoueIAVal) ;kalaha joué par l'ordi
  (not (finDuJeu)) ;il ne faut pas que l'on soit en fin de partie
  (not (ia)) ;pas d'ia en cours
  =>
  (if (eq init ?valPhase) ;la partie n'a pas commencé... choix de la force de l'ia puis si ia contre ia ou ia contre humain ou humain contre humain
    then
      ;choix du type de jeu
      (printout t "Bienvenu dans le jeu du kalah, la partie se fait avec 6 cailloux." crlf "Choix du type de jeu : " crlf "  0 - Ordinateur contre ordinateur;" crlf "  1 - Ordinateur contre humain;" crlf "  2 - Humain contre humain.")
      (bind ?choixTypeJeu (read)) ;saisie du type de jeu par le joueur
      (while (or (< ?choixTypeJeu 0) (> ?choixTypeJeu 2)) ;vérification du choix de l'utilisateur
        (printout t "Mauvais choix, choisir entre 0 , 1 et 2 : " )
        (bind ?choixJoueur (read))
      )
      (assert (typeJeu ?choixTypeJeu))
      (retract ?typeJeu)
      ;choix du joueur qui commence
      (if (!= ?choixTypeJeu 0) ;si type = 0 : les joueurs du haut et du bas sont l'ordinateur donc pas de choix.
        then
          (if (= ?choixTypeJeu 1) ;si type =  : choix si l'humain commence ou l'ordinateur
            then
              (printout t "Voulez vous commencer ? (0 pour oui et 1 pour non) " t)
              (bind ?choixJoueur (read)) ;saisie du numéro de joueur
              (while (and (neq 0 ?choixJoueur) (neq 1 ?choixJoueur)) ;vérification du choix de l'utilisateur
                (printout t "Mauvais choix, choisir entre 0 et 1 : " )
                (bind ?choixJoueur (read))
              )
              (if (eq ?choixJoueur 0) ;l'humain veut commencer
                then
                  (printout t "Vous commencez, vos kalahas sont ceux du bas." t)
                  (assert (phase ok1))
                else ;l'ia commence
                  (printout t "C'est l'ordinateur qui commence, vos kalahas sont ceux du bas." t)
                  (assert (phase ok0))
              )              
            else ;humain contre humain
              (printout t "C'est le joueur 0 qui possede les kalahas du haut qui commence." t)
              (assert (phase ok0))
          )
        else ;c'est l'ordinateur du haut qui commence, ce choix n'a pas d'importance
          (printout t "C'est l'ordinateur du haut qui commence (joueur 0)." t)
          (assert (phase ok0))
      )
      (if (or (eq ?choixTypeJeu 0) (eq ?choixTypeJeu 1)) ;s'il y a un ordinateur qui joue
        then
          (printout t "Choix de l'intelligence de l'ordinateur : de faible (1 très rapide) à élevée (4 très long) ? " )
          (bind ?choixprofondeurMax (+ 1 (read))) ;saisie de la profondeur de recherche de l'ia
          (while (or (< ?choixprofondeurMax 2) (> ?choixprofondeurMax 5)) ;vérification du choix de l'utilisateur
            (printout t "Mauvais choix, choisir entre 1 et 5 : " )
            (bind ?choixprofondeurMax (+ 1(read)))
          )
          (assert (profondeurMax ?choixprofondeurMax))
      )
      (assert (kalahaJoueIA 0))
      (assert (phase afficherPlateau)) ;affichage du plateau initial
      (retract ?phase);suppression de l'ancienne phase de départ
    else ;la partie a déjà commencé, valeurs possibles : 
    ;- bad0et bad1 si le hoix est éronné
    ;- ok0 et ok1 si c'est un humain qui a joué (1 et 0)
    ;- FinIA0et FinIA1 si c'est lia qui a joué (0 et 1)
      (if (eq bad0 ?valPhase)
        then
          (printout t "! Le choix précédent est eronné" t )
          ;(bind ?valPhase ok0) ;le joueur 0 doit recommencer
          (retract ?phase)
          (assert (phase ok0)) ;le joueur 0 doit recommencer
        else
          (if (eq bad1 ?valPhase)
            then
              (printout t "! Le choix précédent est eronné" t )
              ;(bind ?valPhase ok1) ;le joueur 1 doit recommencer
              (retract ?phase)
              (assert (phase ok1)) ;le joueur 1 doit recommencer
            else
              (if (eq 2 ?typeJeuVal) ;humain vs humain
                then
                  (if (eq ok0 ?valPhase) ;c'est au joueur 0 de jouer
                    then
                      (printout t "Joueur 0 : choix de la cavité (1-6) : ")
                      (bind ?choixCavite (- 14 (read))) ;saisie du numéro de joueur
                      (while (or (< ?choixCavite 8) (> ?choixCavite 13)) ;vérification du choix de l'utilisateur
                        (printout t "Mauvais choix, choisir entre 1 et 6 : " )
                        (bind ?choixCavite (- 14 (read)))
                      )
                      (assert (jouer 0 ?choixCavite))
                      (retract ?phase)
                    else 
                      (if (eq ok1 ?valPhase)  ;c'est au joueur 1 de jouer
                        then
                          (printout t "Joueur 1 : choix de la cavité (1-6) : ")
                          (bind ?choixCavite (read)) ;saisie du numéro de joueur
                          (while (or (< ?choixCavite 1) (> ?choixCavite 6)) ;vérification du choix de l'utilisateur
                            (printout t "Mauvais choix, choisir entre 1 et 6 : " )
                            (bind ?choixCavite (read))
                          )
                          (assert (jouer 1 ?choixCavite))
                          (retract ?phase)
                      )
                  )
                else ;typejeu 0 ou 1
                  (if (eq 1 ?typeJeuVal) ;humain vs ordi
                    then
                      (if (eq FinIA0 ?valPhase) ;l'ia a fini de calculer 
                        then
                          (assert (jouer 0 ?kalahaJoueIAVal))
                          (retract ?phase)
                        else
                          (if (eq ok0 ?valPhase) ;c'est a l'ia de jouer
                            then
                              (printout t "C'est à l'ordinateur de jouer." t)
                              (retract ?phase)
                              (assert (joueurIA 0))
                            else
                              (if (eq ok1 ?valPhase) ;c'est a l'humain de jouer
                                then
                                  (printout t "C'est à vous, choix de la cavité (1-6) : ")
                                  (bind ?choixCavite (read)) ;saisie du numéro de joueur
                                  (while (or (< ?choixCavite 1) (> ?choixCavite 6)) ;vérification du choix de l'utilisateur
                                    (printout t "Mauvais choix, choisir entre 1 et 6 : " )
                                    (bind ?choixCavite (read))
                                  )
                                  (assert (jouer 1 ?choixCavite))
                                  (retract ?phase)
                              )
                          )
                      )
                    else ;ordi vs ordi
                      (if (eq FinIA0 ?valPhase) ;c'est au joueur 0 de jouer
                        then
                          (assert (jouer 0 ?kalahaJoueIAVal))
                          (retract ?phase)
                        else 
                          (if (eq FinIA1 ?valPhase)  ;c'est au joueur 1 de jouer
                            then
                              (assert (jouer 1 ?kalahaJoueIAVal))
                              (retract ?phase)
                            else
                              (if (eq ok0 ?valPhase)
                                then
                                  (printout t "C'est à l'ordinateur 0 de jouer." t)
                                  (retract ?phase)
                                  (assert (joueurIA 0))
                                else
                                  (if (eq ok1 ?valPhase)
                                    then
                                      (printout t "C'est à l'ordinateur 1 de jouer." t)
                                      (retract ?phase)
                                      (assert (joueurIA 1))
                                  )
                              )
                          )
                      )
                  )
              )
          )
      )
  )
)

;************************************
;**************     IA     **************
;************************************

;**Structure d'un noeud
(deftemplate noeud
  (slot id (type INTEGER)) ;id unique
  (multislot plateau (type INTEGER)) ;état du plateau pour ce noeud
  (slot idPere (type INTEGER)) ;id du père
  (multislot idfils (type INTEGER)) ;liste des ids des fils (au maximum 6 fils)
  (slot joueur (type INTEGER)) ;joueur qui va jouer
  (slot kalahaJoue (type INTEGER)) ;kalaha joué pour obtenir ce noeud
  (slot profondeur (type INTEGER)) ;profondeur a laquelle se trouve le noeud
)

;**Fait pour connaitre les noeuds pour lesquelles il faut calculer ses fils
(deftemplate noeudAFaire
  (slot idPere (type INTEGER)) ;id du père pour connaître l'état du plateau
  (multislot idFilsAFaire (type INTEGER)) ;liste des ids des fils (au maximum 6 fils) restant a créer
)

;**Fait pour gérer le calcul du nouveau plateau d'un noeud a créer
(deftemplate calculerPlateauNoeud
  (slot id (type INTEGER)) ;id unique du noeud a créer
  (slot idPere (type INTEGER)) ;id du père pour renseigner le champ idPere du futur noeud
  (multislot plateau (type INTEGER)) ;état du plateau pour ce futur noeud avant la répartition
  (slot joueKalaha (type INTEGER)) ;numéro du kalaha joué qu'il faut répartir sur le plateau
  (slot joueur (type INTEGER)) ;joueur a joué
  (slot profondeur (type INTEGER)) ;profondeur a laquelle se trouve le noeud
)

;**Faits dedépart de l'ia
(deffacts IA
  ;(id 0)
  ;(profondeurMax 5) ;profondeur de recherche maximale - 1
  (plateauIA 0 0 0 0 0 0 0 0 0 0 0 0 0) ;le plateau IA est une copie du plateau de jeu lorsque c'est au tour de l'ia de jouer, seul plateauIA est modifié par l'IA lors de la recherche de la meilleure solution.
  (idFilsAFaire) ;Fait pour connaitre les noeuds pour lesquelles il faut calculer ses fils,
)

;************************************************
;**************     Génération arbre     **************
;************************************************

;**Départ de la génération de l'arbre principal des solutions possibles
; Se déclenche sur la présence du fait (JoueurIA ?j) avec j le numéro du joueur pour lequel joue l'ia
(defrule debutGenerationArbre
  (profondeurMax ?p)
  ?joueurIA <- (joueurIA ?joueurIAVal) ;numéro 0 ou 1 du joueur pour qui l'on cherche la meilleure solution
  ?plateau <- (plateau $?plateauVal) ;plateau d'origine
  ?plateauIA <- (plateauIA $?plateauIAVal)  ;ancien plateau de l'ia lors du calcul précédent
  ?affichageInfos <- (affichageInfos ?) ;ancien fait a supprimer
  ?idFilsAFaire <- (idFilsAFaire)
  =>
  (assert (plateauIA ?plateauVal)) ;actualisation du plateau de l'IA par les valeurs du plateau d'origine
  (retract ?plateauIA) ;suppression de l'ancien plateau d'IA
  (assert (noeud (id 0) (plateau $?plateauVal) (idPere 0) (idfils ) (joueur ?joueurIAVal) (kalahaJoue -1) (profondeur 0))) ;noeud racine de l'arbre principal
  (assert (ia)) ;permet de désactiver le moteur kalaha
  (assert (phase iaGenerationArbre)) ;fait mettant a jour l'état du programme
  (assert (affichageInfos 1)) ;pour pas que les infos ne s'affichent pendant l'ia
  (retract ?joueurIA ?affichageInfos ?idFilsAFaire)
  (assert (idFilsAFaire 0)) ;on commence par la racine !
  (assert (id 0)) ;initialise les ids a 0
  (assert (profondeurMaxAtteinte 0)) ;profondeur maximum atteinte par l'arbre à 0 pour l'initialisation
)

;**Génération d'un arbre : lancement création des noeuds suivants
(defrule GenerationNoeuds
  (declare (salience 9))
  (phase iaGenerationArbre)
  ?idFilsAFaire <- (idFilsAFaire $?idFilsAFaireListe) ;on récupere la liste des noeudAfaire pour calculer les noeuds fils de l'un d'entre eux.
  (not (SupprimerNoeuds $?)) ;un ou plusieurs noeuds doivent être supprimés
  (not (GenerationNoeud ?)) ;les fils d'un noeud ne doivent pas être entrain d'être générés
  (not (iaRun ?)) ;le calcul d'un nouveau plateau est en cours
  =>
  (if (neq  (implode$ ?idFilsAFaireListe) "") ;s'il reste des noeuds à faire
    then ;alors on lance le traitement d'un noeud
      (bind ?idNoeudAFaireVal (first$ ?idFilsAFaireListe))
      (bind ?idNoeudRestantAFaire (rest$ ?idFilsAFaireListe))
      ;ajout du fait lancant le traitement du premier noeud de la liste
      (assert (GenerationNoeud ?idNoeudAFaireVal))
      ;Le noeudAFaire ne l'est plu, on le retire du fait idFilsAFaire
      (assert (idFilsAFaire ?idNoeudRestantAFaire))
      (retract ?idFilsAFaire)
  )
)

;**Création des fils d'un noeud
(defrule GenerationDUnNoeud
  (declare (salience 10))
  (phase iaGenerationArbre)
  ?GenerationNoeud <- (GenerationNoeud ?GenerationNoeudVal)
  (noeud (id ?GenerationNoeudVal) (plateau $?plateau) (idPere ?idPere) (idfils $?idfils) (joueur ?joueur) (kalahaJoue ?kalahaJoueVal) (profondeur ?profondeurNoeudAFaire)) ;noeud dont il faut créer les fils
  ?idOld <- (id ?idVal) ;valeur de l'id du dernier noeud créé
  (profondeurMax ?profondeurMaxVal) ;profondeur de recherche maximale
  ?profondeurMaxAtteinte <- (profondeurMaxAtteinte ?profondeurMaxAtteinteVal) ;profondeur maximale atteinte dans l'arbre
  (not (SupprimerNoeuds $?)) ;un ou plusieurs noeuds doivent être supprimés avant cette règle
  (not (iaRun ?)) ;le calcul d'un nouveau plateau est en cours
  =>
  (bind ?idValOld ?idVal)
  (bind ?profondeurNoeudAFaire (+ 1 ?profondeurNoeudAFaire)) ;incrémentation de la profondeur pour les nouveaux noeuds
  (if (< ?profondeurNoeudAFaire ?profondeurMaxVal)
    then
    (loop-for-count (?i 1 6) do ;création des fils pour chacun des 6 kalahas s'il y a au moins un cailloux dedans
      (bind ?idVal (+ ?idVal 1))
      ;récupération de la valeur du kalaha concerné
      (if (eq 1 ?joueur) ;si c'est le joueur 1
        then
          (bind ?valKalahaSelectionne (nth$ ?i $?plateau)) ;numéro de kalaha sera ?i > valeur de la cavité joué
          (bind ?joueKalaha ?i) ;numéro du kalaha joué
        else
          (bind ?valKalahaSelectionne (nth$ (+ ?i 7) $?plateau)) ;numéro de kalaha sera ?i + 7 (haut du plateau) > valeur de la cavité joué
          (bind ?joueKalaha (+ ?i 7)) ;numéro du kalaha joué
      )
      ;création d'un nouveau noeud s'il y a au moins un cailloux dans le kalaha séléectionné
      (if (> ?valKalahaSelectionne 0)
        then
          (assert (calculerPlateauNoeud (id ?idVal) (idPere ?GenerationNoeudVal) (plateau ?plateau) (joueKalaha ?joueKalaha) (joueur ?joueur) (profondeur ?profondeurNoeudAFaire)))
      )
    )
    (if (< ?profondeurMaxAtteinteVal ?profondeurNoeudAFaire)
      then
        (assert (profondeurMaxAtteinte ?profondeurNoeudAFaire))
        (retract ?profondeurMaxAtteinte)
    )
  )
  (if (neq ?idValOld ?idVal)
    then
      ;MAJ id
      (assert (id ?idVal))
      (retract ?idOld)
  )
  ;suppression du fait lançant la génération des fils du noeud qui est maintenant faite
  (retract ?GenerationNoeud)
)

;**Calculer le Plateau d'un nouveau Noeud
;**Créer ensuite ce noeud avec le plateau calculé
;**Ajouter ce noeud si l'on a pas atteind la profondeur max dans le fait idFilsAFaire
(defrule CalculerPlateauNoeud
  (declare (salience 11))
  (phase iaGenerationArbre)
  ?plateauAncien <- (plateau $?plateauVal) ;plateau d'origine
  ?calculerPlateauNoeud <- (calculerPlateauNoeud (id ?idValNoeud) (idPere ?idPere) (plateau $?plateauEntree) (joueKalaha ?joueKalaha) (joueur ?joueur) (profondeur ?profondeurNoeudAFaire))
  (not (jouer ? ?)) ;le calcul d'un nouveau plateau est en cours
  (not (repartir ? ? ?)) ;le calcul d'un nouveau plateau est en cours
  (not (iaRun ?)) ;le calcul d'un nouveau plateau est en cours
  (not (SupprimerNoeuds $?)) ;un ou plusieurs noeuds doivent être supprimés
  =>
  ;lancement du calcul du nouveau plateau joué pas ?joueur le kalaha ?joueKalaha
  (retract ?plateauAncien) ;suppression de l'ancien
  (assert (plateau $?plateauEntree)) ;mise à jour du nouveau plateau
  (assert (jouer ?joueur ?joueKalaha)) ;lancement de la répartition    
  (assert (iaRun ?idValNoeud))
)

(defrule enregistrementResultatCalculerPlateauNoeud
  (declare (salience 12))
  ?phaseiaGen <- (phase iaGenerationArbre)
  (or (phase ok1) (or (phase ok0) (phase fin)))
  ?phase <- (phase ?phaseVal)
  (test (neq ?phase ?phaseiaGen))
  ?calculerPlateauNoeud <- (calculerPlateauNoeud (id ?idVal) (idPere ?idPere) (plateau $?plateauEntree) (joueKalaha ?joueKalaha) (joueur ?joueur) (profondeur ?profondeurNoeudAFaire))
  ?pere <- (noeud (id ?idPere) (plateau $?plateauPere) (idPere ?idPerePere) (idfils $?idfilsPere) (joueur ?joueurPere) (kalahaJoue ?kalahaJoueValPere) (profondeur ?profondeurNoeudAFairePere)) ;pere du noeud a créer
  ?plateauResultat <- (plateau $?plateauResultatVal) ;plateau final
  ?ia <- (iaRun ?idVal)
  ?idFilsAFaire <- (idFilsAFaire $?idFilsAFaireListe) ;on récupere la liste des noeudAfaire pour y ajouter le nouveau noeud
  (profondeurMax ?profondeurMaxVal) ;profondeur de recherche maximale
  (not (jouer ? ?))
  (not (phase afficherPlateau))
  (not (repartir ? ? ?))
  (not (SupprimerNoeuds $?)) ;un ou plusieurs noeuds doivent être supprimés
  =>
  ;Numero du joueur suivant
  (bind ?joueurSuivant -1)
  (if (eq ok1 ?phaseVal)
    then
      (bind ?joueurSuivant 1)
    else
      (if (eq ok0 ?phaseVal)
        then
          (bind ?joueurSuivant 0)
        else
          (if (eq fin ?phaseVal)
            then
              (bind ?joueurSuivant -2)
          )
      )
  )
  (if (> ?joueurSuivant -1)
    then
      ;Si le joueur ne peut pas rejouer
      (if (neq ?joueur ?joueurSuivant)
        then          
          (assert (noeud (id ?idVal) (plateau $?plateauResultatVal) (idPere ?idPere) (idfils) (joueur ?joueurSuivant) (kalahaJoue ?joueKalaha) (profondeur ?profondeurNoeudAFaire)))
          (if (neq ?profondeurNoeudAFaire ?profondeurMaxVal) ;si l'on a pas atteind une profondeur de 5, on continue de descendre.
            then ;alors ajout du nouveau noeud dans la liste des noeuds à traiter
              (assert (idFilsAFaire (insert$ ?idFilsAFaireListe 99999999 ?idVal))) ;Ajout dans la liste du nouveau noeud pour générer ses fils
              (retract  ?idFilsAFaire) ;on retire l'ancien fait
          )
          ;ajout du nouveau noeud dans la liste des fils de son pere
          (modify ?pere (idfils (insert$ ?idfilsPere 1 ?idVal)))
          ;déblocage de la génération des noeuds (iaRun) et suppression du fait temporaire servant a générer le noeud
          (retract ?ia ?calculerPlateauNoeud ?phase)
        else
          ;Si le joueur doit rejouer
          (if (eq ?joueur ?joueurSuivant)
            then
              ;on crée un fait pour supprimer les fils
              (assert (SupprimerNoeuds ?idfilsPere))
              
              ;on modifie le père : MAJ de son plateau avec le nouveau, Suppression des fils, le calaha joué ne change pas car seule le premier coup est nécessaire
              (if (eq 0 ?idPere)
                then
                  (if (eq ?kalahaJoueValPere -1) ;premier rejoue de la racine, enregistrement du choix fait
                    then
                      (modify ?pere (plateau ?plateauResultatVal) (idfils) (kalahaJoue ?joueKalaha))
                    else ;pas de modif du kalahajoue
                      (modify ?pere (plateau ?plateauResultatVal) (idfils))
                  )
                else ;ce n'est pas la racine, on enregistre seulement le plateau
                  (modify ?pere (plateau ?plateauResultatVal) (idfils))
              )
              
              (assert (idFilsAFaire (insert$ ?idFilsAFaireListe 99999999 ?idPere))) ;Ajout dans la liste du père pour recalculer ses nouveaux fils
              (retract  ?idFilsAFaire) ;on retire l'ancien fait
              
              ;déblocage de la génération des noeuds (iaRun) et suppression du fait temporaire servant a générer le noeud
              (retract ?ia ?calculerPlateauNoeud ?phase)
          )
      )
  )
  (if (eq -2 ?joueurSuivant) ;cas de la fin du jeu, il n'y a donc pas de fils a calculer pour ce nouveau noeud.
    then
      (assert (noeud (id ?idVal) (plateau $?plateauResultatVal) (idPere ?idPere) (idfils) (joueur 0) (kalahaJoue ?joueKalaha) (profondeur ?profondeurNoeudAFaire)))
      ;ajout du nouveau noeud dans la liste des fils de son pere
      (modify ?pere (idfils (insert$ ?idfilsPere 1 ?idVal)))
      (retract ?ia ?calculerPlateauNoeud ?phase)
  )
)

;**fin du jeu pour un noeud de l'ia
(defrule finDuJeuIA
  (declare (salience 15))
  ?plateau <- (plateau $?plateauVal)
  (or (phase ok1) (phase ok0))
  ?phase <- (phase ?phaseVal)
  (iaRun ?)
  ;vérification qu'il n'y ai pas de répartition en cours
  (not (repartir ?joueur ?numJoue ?nbCailloux))
  (not (finDuJeu))
  =>
  (if (or (eq ok1 ?phaseVal) (eq ok0 ?phaseVal)) ;la phase doit être juste à la fin d'une action d'un joueur
    then
      ;si les cavités du joueur 1 sont vides
      (if (eq (+ (nth$ 1 $?plateauVal) (nth$ 2 $?plateauVal) (nth$ 3 $?plateauVal) (nth$ 4 $?plateauVal) (nth$ 5 $?plateauVal) (nth$ 6 $?plateauVal)) 0)
        then ;le joueur 0 récupère ses cailloux
          (bind ?plateauValNew (replace$ ?plateauVal 14 14 (+ (nth$ 8 $?plateauVal) (nth$ 9 $?plateauVal) (nth$ 10 $?plateauVal) (nth$ 11 $?plateauVal) (nth$ 12 $?plateauVal) (nth$ 13 $?plateauVal) (nth$ 14 $?plateauVal))))
          (bind ?plateauValNew (replace$ ?plateauValNew 8 13 0 0 0 0 0 0)) ;mise a zéro de ses cavités
          (assert (plateau ?plateauValNew)) ;enregistrement du nouveau plateau
          (assert (phase fin)) ;non relance de cette regle
          (retract ?plateau ?phase) ;retrait des anciens faits
      )
      ;si les cavités du joueur 0 sont vides
      (if (eq (+ (nth$ 8 $?plateauVal) (nth$ 9 $?plateauVal) (nth$ 10 $?plateauVal) (nth$ 11 $?plateauVal) (nth$ 12 $?plateauVal) (nth$ 13 $?plateauVal)) 0)
        then ;le joueur 1 récupère ses cailloux
          (bind ?plateauValNew (replace$ ?plateauVal 7 7 (+ (nth$ 1 $?plateauVal) (nth$ 2 $?plateauVal) (nth$ 3 $?plateauVal) (nth$ 4 $?plateauVal) (nth$ 5 $?plateauVal) (nth$ 6 $?plateauVal) (nth$ 7 $?plateauVal))))
          (bind ?plateauValNew (replace$ ?plateauValNew 1 6 0 0 0 0 0 0)) ;mise a zéro de ses cavités
          (assert (plateau ?plateauValNew)) ;enregistrement du nouveau plateau
          (assert (phase fin)) ;non relance de cette regle
          (retract ?plateau ?phase) ;retrait des anciens faits
      )
  )
)

;**Suppression des NoeudsInutiles
(defrule SupprimerNoeudsInutiles
  (declare (salience 13))
  ?SupprimerNoeuds <- (SupprimerNoeuds $?SupprimerNoeudsListe) ;les noeuds a supprimer
  ?idFilsAFaire <- (idFilsAFaire $?idFilsAFaireListe) ;on récupere la liste des noeudAfaire pour y ajouter le nouveau noeud
  =>
  (if (neq  (implode$ ?SupprimerNoeudsListe) "") ;s'il reste des noeuds à supprimer
    then ;alors on lance la suppression du noeud
      (bind ?SupprimerNoeudVal (first$ ?SupprimerNoeudsListe))
      (bind ?SupprimerNoeudsRestantsListe (rest$ ?SupprimerNoeudsListe))
      (if (member$ ?SupprimerNoeudVal $?idFilsAFaireListe)
        then
          ;suppression de l'id du noeud de la liste idFilsAFaire s'il existe
          (bind ?idFilsAFaireListe (delete$ ?idFilsAFaireListe (member$ ?SupprimerNoeudVal ?idFilsAFaireListe) (member$ ?SupprimerNoeudVal ?idFilsAFaireListe)))
          (assert (idFilsAFaire $?idFilsAFaireListe))
          (retract ?idFilsAFaire)
      )
      ;ajout du fait lancant le traitement de la suppression du premier noeud de la liste
      (assert (SuppressionNoeud ?SupprimerNoeudVal))
      ;Le noeudAFaire ne l'est plu, on le retire du fait idFilsAFaire
      (assert (SupprimerNoeuds ?SupprimerNoeudsRestantsListe))
      (retract ?SupprimerNoeuds)
    else
      ;s'il n'y a plus rien a supprimer
      (retract ?SupprimerNoeuds)
  )
)

(defrule SupprimerNoeud
  (declare (salience 14))
  ?SuppressionNoeud <- (SuppressionNoeud ?SuppressionNoeudVal)
  ?noeud <- (noeud (id ?SuppressionNoeudVal) (plateau $?) (idPere ?) (idfils $?) (joueur ?) (kalahaJoue ?) (profondeur ?)) ;noeud a supprimer
  =>
  (retract ?noeud ?SuppressionNoeud)
)

(defrule SupprimerCalculerPlateauNoeud
  (declare (salience 15))
  (SupprimerNoeuds $?)
  ?calculerPlateauNoeud <- (calculerPlateauNoeud (id ?) (idPere ?) (plateau $?) (joueKalaha ?) (joueur ?) (profondeur ?))
  =>
  (retract ?calculerPlateauNoeud)
)

;*****************
;****minmax
;*****************
;** Détection de la fin de calcul de l'arbre, lancement minmax
(defrule DemarrerMinMax
  (declare (salience 8))
  ?idFilsAFaire <- (idFilsAFaire) ;plus de fils a faire pour l'arbre
  ?profondeurMax <- (profondeurMaxAtteinte ?p) ;récupère la profondeur max de l'arbre
  (not (calculerPlateauNoeud (id ?) (idPere ?) (plateau $?) (joueKalaha ?) (joueur ?) (profondeur ?)))
  ?phase <- (phase iaGenerationArbre)
  ?id <- (id ?)
  =>
  ;calcul de la profondeur de démarrage du minmax
  (bind ?niveauDemarrage (- ?p 1)) ;profondeur max de l'arbre -1 pour être sur les pere des feuilles
  (assert (MinMax))
  (assert (ProfondeurActuelleMinMax ?niveauDemarrage))
  (retract ?phase ?id) ;anciens fait
)

;** calcul du min ou du max de chaque noeud
(defrule MinMaxExploreNoeud
  (declare (salience 15))
  (MinMax)
  ?ProfondeurActuelleMinMax <- (ProfondeurActuelleMinMax ?ProfondeurActuelleMinMaxVal) ;récupere la profondeur MinMax
  ?noeud <- (noeud (id ?GenerationNoeudVal) (plateau $?plateau) (idPere ?idPere) (idfils $?idfils) (joueur ?joueur) (kalahaJoue ?kalahaJoueVal) (profondeur ?ProfondeurActuelleMinMaxVal)) ;noeud de la profondeur actuelle de MinMax
  ?noeudRacine <- (noeud (id 0) (plateau $?) (idPere ?) (idfils $?) (joueur ?joueurPapa) (kalahaJoue ?) (profondeur ?))
  =>
  (if (neq (length$ ?plateau) 15) ;si le dernier élément n'est pas encore inséré, ce noeud n'a donc pas encore été traité
    then
      (if (eq ?joueur ?joueurPapa) ;?si le numéro de joueur racine et celui du noeud actuel correspondent alors c'est une action max
        then
          (assert (MinMaxAction 0)) ;0 > max
          (modify ?noeud (plateau (insert$ ?plateau 15 0))) ; 0 car c'est max

        else ;action max
          (assert (MinMaxAction 1)) ;1 > min
          (modify ?noeud (plateau (insert$ ?plateau 15 99999999))) ;99999999 car c'est min
      )
      (assert (MinMaxNoeud ?idfils))
  )
)

;** remonté du min ou du max
; Décompose la liste des fils a remonter pour lancer la regle RemonteMinOuMaxNoeud sur chaque fils
(defrule RemonteMinOuMax
  (declare (salience 17))
  (MinMax)
  ?MinMaxNoeud <- (MinMaxNoeud $?idfils)
  =>
  (if (neq (length$ $?idfils) 0) ;s'il reste des noeud a explorer
    then
      (bind ?idfilsVal (first$ ?idfils))
      (bind ?idfils (rest$ ?idfils))
      ;lancement de la remonté de la valeur du noeud
      (assert (RemonteMinOuMaxNoeud ?idfilsVal))
      ;La remonté de la valeur sera faite, on le retire de la liste des noeud a remonter
      (assert (MinMaxNoeud ?idfils))
      (retract ?MinMaxNoeud)
    else ;il n'y a plus de noeud a explorer, suppression de minmaxnoeud
      (retract ?MinMaxNoeud)
  )
)

;**remonté d'un fils
(defrule RemonteMinOuMaxNoeud
  (declare (salience 16))
  (MinMax)
  (MinMaxAction ?MinMaxAction)
  ?RemonteMinOuMaxNoeud <- (RemonteMinOuMaxNoeud ?RemonteMinOuMaxNoeudVal)
  ?noeud <- (noeud (id ?RemonteMinOuMaxNoeudVal) (plateau $?plateau) (idPere ?idPere) (idfils $?idfils) (joueur ?joueur) (kalahaJoue ?kalahaJoueVal) (profondeur ?ProfondeurActuelleMinMaxVal))
  ?noeudPapa <- (noeud (id ?idPere) (plateau $?plateauPapa) (idPere ?) (idfils $?) (joueur ?) (kalahaJoue ?kalahaJoueValPere) (profondeur ?))
  =>
  (if (eq ?MinMaxAction 0) ;max
    then
      (if (eq  (length$ $?idfils) 0) ;c'est une feuille
        then ;utilisation de la fonction d'évaluation
          (bind ?plateauPapa (replace$ ?plateauPapa 15 15 (max (abs (- (nth$ 7 $?plateau) (nth$ 14 $?plateau))) (nth$ 15 $?plateauPapa))))
          ;(printout t "Eval max du noeud : " ?noeud " pere de " ?noeudPapa "dif = " (abs (- (nth$ 7 $?plateau) (nth$ 14 $?plateau))) " id pere" ?idPere t)
        else ;ce n'est pas une feuille
          ;(printout t "Comparaison max du noeud et de son pere : " ?noeud " pere de " ?noeudPapa t)
          (bind ?plateauPapa (replace$ ?plateauPapa 15 15 (max (nth$ 15 $?plateau) (nth$ 15 $?plateauPapa))))
      )
      (if (and (eq ?idPere 0) (eq ?kalahaJoueValPere -1))  ;si c'est la racine le pere, alors on fait remonter aussi la kalaha joué
        then
          (modify ?noeudPapa (plateau $?plateauPapa) (kalahaJoue ?kalahaJoueVal))
        else ;on ne modofie que le plateau
          (modify ?noeudPapa (plateau $?plateauPapa))
      )
    else
      (if (eq ?MinMaxAction 1) ;min
        then
          (if (eq  (length$ $?idfils) 0) ;c'est une feuille
            then ;utilisation de la fonction d'évaluation
              (bind ?plateauPapa (replace$ ?plateauPapa 15 15 (min (abs (- (nth$ 7 $?plateau) (nth$ 14 $?plateau))) (nth$ 15 $?plateauPapa))))
              ;(printout t "Eval min du noeud : " ?noeud " pere de " ?noeudPapa "dif = " (abs (- (nth$ 7 $?plateau) (nth$ 14 $?plateau))) " id pere" ?idPere t)
              (modify ?noeudPapa (plateau $?plateauPapa))
            else ;ce n'est pas une feuille
              ;(printout t "Comparaison min du noeud et de son pere : " ?noeud " pere de " ?noeudPapa t)
              (bind ?plateauPapa (replace$ ?plateauPapa 15 15 (min (nth$ 15 $?plateau) (nth$ 15 $?plateauPapa))))
              (modify ?noeudPapa (plateau $?plateauPapa))
          )
      )
  )
  (retract ?RemonteMinOuMaxNoeud) ; ?noeud)
)

;**Plus de noeud donc on décrémente
(defrule MinMaxDecrementeProfondeur
  (declare (salience 10))
  (MinMax)
  (not (MinMaxNoeud $?))
  (not (MinMaxNoeud))
  (not (RemonteMinOuMaxNoeud ?))
  (not (RemonteMinOuMaxNoeud))
  (not (phase iaGenerationArbre))
  (not (MinMaxProfondeurDecrementer)) ;pour pas que ça boucle sur cette regle
  ?MinMaxAction <- (MinMaxAction ?) ;supression de l'ancienne action pour savoir si c'était min ou max
  ?ProfondeurActuelleMinMax <- (ProfondeurActuelleMinMax ?ProfondeurActuelleMinMaxVal) ;récupere la profondeur MinMax a décrémenter
  (test (> ?ProfondeurActuelleMinMaxVal -1))
  =>
  ;(printout t "Profondeur de recherche minman décrémenté : " (- ?ProfondeurActuelleMinMaxVal 1) t)
  (assert (ProfondeurActuelleMinMax (- ?ProfondeurActuelleMinMaxVal 1)))
  (retract ?ProfondeurActuelleMinMax ?MinMaxAction) ;suppression des anciens faits
)

;** fin minmax
(defrule MinMaxFin
  ?ProfondeurActuelleMinMax <- (ProfondeurActuelleMinMax -1)
  (noeud (id 0) (plateau $?plateauPapa) (idPere ?) (idfils $?) (joueur ?joueur) (kalahaJoue ?kalahaJoueValPere) (profondeur ?))
  ?plateau <- (plateau $?plateauVal) ;plateau a supprimer
  ?plateauIA <- (plateauIA $?plateauIAVal) ;plateau a restaurer
  ?affichageInfos <- (affichageInfos ?) ;restauration de l'affichage
  ?kalahaJoueIAOld <- (kalahaJoueIA ?) ;ancien kalaha joué du calcul précédent
  ?profondeurMaxAtteinte <- (profondeurMaxAtteinte ?) ;ancien fait inutile maintenant mais a supprimer
  ?ia <- (ia)
  ?MinMax <- (MinMax)
  =>
  (printout t "L'ia " ?joueur " a décidé de jouer le kalaha : " ?kalahaJoueValPere "." t)
  (assert (RamasserMiettesIA)) ;lance le ramasse miettes de tous les faits de l'ia
  (assert (phase (sym-cat "FinIA" ?joueur))) ;déclare au moteur la fin de l'ia
  (assert (kalahaJoueIA ?kalahaJoueValPere)) ;kalaha joué par l'ia
  (assert (plateau $?plateauIAVal)) ;restaure le plateau de jeu d'origine
  (assert (affichageInfos 0)) ;On restaure l'affichage des infos
  (retract ?ia ?MinMax ?ProfondeurActuelleMinMax ?plateau ?affichageInfos ?kalahaJoueIAOld ?profondeurMaxAtteinte)
)

;************************************************
;**************     Ramasse miettes       **************
;************************************************

;** ramasse miettes de l'ia
(defrule RamasserMiettesIA
  (declare (salience 2))
  (RamasserMiettesIA)
  ?noeud <- (noeud (id ?) (plateau $?) (idPere ?) (idfils $?) (joueur ?) (kalahaJoue ?) (profondeur ?))
  =>
  (retract ?noeud)
)
(defrule RamasserMiettesIAFin ;il n'y a plus rien a supprimer
  (declare (salience 2))  
  ?RMIA <- (RamasserMiettesIA)
  (not (noeud (id ?) (plateau $?) (idPere ?) (idfils $?) (joueur ?) (kalahaJoue ?) (profondeur ?)))
  =>
  (retract ?RMIA)
)