{"id":34,"date":"2011-03-01T11:50:47","date_gmt":"2011-03-01T10:50:47","guid":{"rendered":"http:\/\/www.blaess.fr\/christophe\/blog\/?page_id=34"},"modified":"2011-03-01T11:50:47","modified_gmt":"2011-03-01T10:50:47","slug":"programmer-un-jeu-de-reflexion","status":"publish","type":"page","link":"https:\/\/www.blaess.fr\/christophe\/articles\/programmer-un-jeu-de-reflexion\/","title":{"rendered":"Programmer un jeu de r\u00e9flexion"},"content":{"rendered":"<p>Janvier 2003<\/p>\n<p>&nbsp;<\/p>\n<p style=\"text-align: justify;\">Cet article se propose d&rsquo;offrir une introduction aux techniques d&rsquo;Intelligence Artificielle les plus simples que l&rsquo;on utilise pour la r\u00e9solution des jeux de r\u00e9flexion. Cette pr\u00e9sentation vous donnera envie, esp\u00e8rons-le, de mettre en pratique ces concepts, en programmant vous-m\u00eame des jeux de r\u00e9flexion, existants ou invent\u00e9s pour l&rsquo;occasion. Nous allons nous int\u00e9resser ici \u00e0 un passionnant jeu de strat\u00e9gie d&rsquo;origine africaine\u00a0: l&rsquo;Aw\u00e9l\u00e9.<\/p>\n<p style=\"text-align: justify;\"><a href=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2011\/03\/awele.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-full wp-image-35\" title=\"Awele\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2011\/03\/awele.jpg\" alt=\"Awele\" width=\"400\" height=\"300\" \/><\/a><\/p>\n<h2>Le jeu d&rsquo;Aw\u00e9l\u00e9<\/h2>\n<p>&nbsp;<\/p>\n<p style=\"text-align: justify;\">L&rsquo;<em>Aw\u00e9l\u00e9<\/em> fait partie de la famille des jeux de semaille, o\u00f9 des graines circulent alternativement sur les domaines des deux adversaires, et o\u00f9 la victoire revient \u00e0 celui dont la r\u00e9colte est la plus avantageuse. Appartenant \u00e0 une culture de tradition orale, l&rsquo;Aw\u00e9l\u00e9 est un jeu vivant, dont les r\u00e8gles \u00e9voluent selon les r\u00e9gions et selon les \u00e9poques. Nous utiliserons les r\u00e8gles les plus commun\u00e9ment admises, mais des variations sont donc tout \u00e0 fait imaginables.<\/p>\n<p style=\"text-align: justify;\">L&rsquo;Aw\u00e9l\u00e9 se joue sur un plateau o\u00f9 sont creus\u00e9s douze trous, organis\u00e9s en deux rang\u00e9es appartenant aux deux adversaires. On place initialement quatre graines dans chaque trou. Les trous peuvent \u00eatre simplement creus\u00e9s dans le sable et les graines figur\u00e9es par de petits cailloux, ou au contraire le plateau peut \u00eatre une oeuvre d&rsquo;art richement d\u00e9cor\u00e9e. Alternativement, chaque joueur saisit tout le contenu de l&rsquo;un de ses propres trous et le s\u00e8me, graine par graine, dans le sens inverse des aiguilles d&rsquo;une montre, dans chaque trou &#8211; y compris ceux de son adversaire. La seule exception est le trou de d\u00e9part qui, st\u00e9rile pendant une saison, doit rester vide. Il est donc saut\u00e9 lors de la semaille. Lorsque la derni\u00e8re graine est pos\u00e9e dans un trou de l&rsquo;adversaire comportant pr\u00e9alablement une ou deux graines, le contenu de ce trou peut \u00eatre captur\u00e9, c&rsquo;est-\u00e0-dire mis en r\u00e9serve hors du jeu. De plus tous les trous pr\u00e9c\u00e9dents de l&rsquo;adversaire r\u00e9pondant aux m\u00eames crit\u00e8res (contenant apr\u00e8s semis deux ou trois graines) peuvent \u00e9galement \u00eatre captur\u00e9s. Les graines ne ressortent pas de la r\u00e9serve, elles seront seulement comptabilis\u00e9es pour d\u00e9terminer le vainqueur.<\/p>\n<p style=\"text-align: justify;\">Le but de l&rsquo;Aw\u00e9l\u00e9 est une domination \u00e9conomique et non pas une victoire guerri\u00e8re, contrairement aux autres principaux jeux de strat\u00e9gie comme le Go ou les \u00e9checs. Aussi est-il interdit d&rsquo;affamer l&rsquo;adversaire\u00a0: on doit toujours lui laisser au moins une graine dans son camps. Si c&rsquo;est impossible, la partie se termine. Le vainqueur est le joueur qui a mis le plus de graines en r\u00e9serve. En pratique, la fin de la partie est g\u00e9n\u00e9ralement d\u00e9termin\u00e9e bien avant d&rsquo;en arriver \u00e0 cette extr\u00e9mit\u00e9, puisque d\u00e8s qu&rsquo;un joueur poss\u00e8de plus de vingt-quatre graines dans sa r\u00e9serve, il ne peut plus \u00eatre d\u00e9pass\u00e9.<\/p>\n<p style=\"text-align: justify;\">L&rsquo;Aw\u00e9l\u00e9 est un jeu complexe, comportant des retournements de situation tr\u00e8s rapides, et n\u00e9cessitant une dose importante de calculs et de strat\u00e9gies, reposant entre autres sur la constitution de \u00ab\u00a0greniers\u00a0\u00bb, c&rsquo;est-\u00e0-dire de trous comportant un nombre important de graines (une vingtaine par exemple). Pour un joueur d\u00e9butant toutefois, l&rsquo;ordinateur peut repr\u00e9senter un adversaire int\u00e9ressant par sa facult\u00e9 d&rsquo;explorer rapidemment un nombre important de combinaisons.<\/p>\n<h2>R\u00e9solution de jeux<\/h2>\n<p style=\"text-align: justify;\">L&rsquo;<em>Intelligence Artificielle<\/em> est une branche de l&rsquo;informatique recouvrant de tr\u00e8s nombreux domaines d&rsquo;application, (reconnaissance des formes, expertise et diagnostique automatiques, mod\u00e9lisation, logiques, traitement du langage, etc.) mais l&rsquo;un de ses sujets de pr\u00e9dilection fut d\u00e8s ses d\u00e9buts la r\u00e9solution des jeux de r\u00e9flexion. Des m\u00e9canismes divers ont \u00e9t\u00e9 mis au point \u00e0 cette fin, mais le plus courant, et le plus simple \u00e0 comprendre, est l&rsquo;exploration en force brute. Il s&rsquo;agit, partant d&rsquo;une position de jeu, de tester successivement tous les mouvements possibles, puis pour chacun d&rsquo;eux tous les mouvements de l&rsquo;adversaire, en it\u00e9rant le processus pour d\u00e9terminer quel mouvement imm\u00e9diat est le plus \u00e0 m\u00eame de nous apporter finalement la victoire.<\/p>\n<p style=\"text-align: justify;\">Pour certains jeux, comme le tic-tac-toe \u00e0 neuf cases, le nombre de situations est suffisament faible pour \u00eatre explor\u00e9 enti\u00e8rement par un ordinateur (et m\u00eame par un humain compte tenu des sym\u00e9tries r\u00e9duisant consid\u00e9rablement le nombre de branches \u00e0 \u00e9tudier). En revanche, pour un jeu offrant une certaine complexit\u00e9, l&rsquo;exploration exhaustive est impossible pour des raisons d&rsquo;explosion combinatoire. On a recours alors \u00e0 une\u00a0<em>fonction d&rsquo;\u00e9valuation<\/em>. On commence l&rsquo;exploration de tous les mouvements possibles, puis de tous ceux de l&rsquo;adversaire, et ainsi de suite jusqu&rsquo;\u00e0 une profondeur maximale. La situation r\u00e9sultante est soumise \u00e0 une fonction qui va la noter, lui affectant une valeur positive si elle nous est favorable, ou n\u00e9gative si elle est favorable \u00e0 l&rsquo;adversaire.<\/p>\n<p style=\"text-align: justify;\">L&rsquo;id\u00e9e consiste alors \u00e0 faire \u00ab\u00a0remonter\u00a0\u00bb jusqu&rsquo;\u00e0 la position de d\u00e9part les notes obtenues pour chaque s\u00e9quence de mouvements, afin de d\u00e9terminer le mouvement initial le plus int\u00e9ressant. Consid\u00e9rant que l&rsquo;adversaire joue aussi bien que nous, la remont\u00e9e des notes s&rsquo;effectue en utilisant un algorithme nomm\u00e9\u00a0<em>minimax<\/em>, qui prend alternativement le coup le plus favorable (lorsque nous jouons) et le plus d\u00e9favorable (lorsque l&rsquo;autre joue). Cet algorithme peut s&rsquo;exprimer de diff\u00e9rentes mani\u00e8res, en voici une repr\u00e9sentation r\u00e9cursive\u00a0:<\/p>\n<pre>minimax (position, joueur, profondeur)\n\u00a0 si profondeur = profondeur_maximale\n\u00a0\u00a0\u00a0 renvoyer evaluation (position)\n\u00a0 pour tous les mouvements possibles\n\u00a0\u00a0\u00a0\u00a0\u00a0 nouvelle = appliquer_mouvement (position, mouvement)\n\u00a0\u00a0\u00a0\u00a0\u00a0 valeur = minimax (nouvelle, adversaire(joueur), profondeur+1)\n\u00a0 si joueur = \"nous\"\n\u00a0\u00a0\u00a0 renvoyer maximum des valeurs\n\u00a0 sinon\n\u00a0\u00a0\u00a0 renvoyer minimum des valeurs<\/pre>\n<p style=\"text-align: justify;\">En supposant que la fonction d&rsquo;\u00e9valuation accepte un argument indiquant pour quel joueur elle doit \u00eatre calcul\u00e9e (et donc le sens positif ou n\u00e9gatif qu&rsquo;elle doit prendre), on peut simplifier l&rsquo;expression de l&rsquo;algorithme ainsi\u00a0:<\/p>\n<pre>minimax (position, joueur, profondeur)\n\u00a0 si profondeur = profondeur_maximale\n\u00a0\u00a0\u00a0 renvoyer evaluation(position, joueur)\n\u00a0 maximum = -infini\n\u00a0 pour tous les mouvements possibles (adversaire(joueur))\n\u00a0\u00a0\u00a0 nouvelle = appliquer_mouvement(position, mouvement)\n\u00a0\u00a0\u00a0 valeur = - minimax(nouvelle, adversaire(joueur), profondeur+1)\n\u00a0\u00a0\u00a0 si valeur &gt; maximum\n\u00a0\u00a0\u00a0\u00a0\u00a0 maximum = valeur\n\u00a0 renvoyer maximum<\/pre>\n<p style=\"text-align: justify;\">En fait, toute l&rsquo;intelligence du syst\u00e8me repose finalement dans la fonction d&rsquo;\u00e9valuation, qui doit indiquer &#8211; avec la meilleure pr\u00e9cision possible &#8211; l&rsquo;avantage qu&rsquo;offre une position pour un joueur donn\u00e9. Nous reviendrons sur ce sujet.<\/p>\n<p style=\"text-align: justify;\">Comme c&rsquo;est fr\u00e9quemment le cas en IA, on repr\u00e9sente usuellement le principe de r\u00e9solution des jeux par un arbre. On d\u00e9place ainsi les probl\u00e8mes, en les ramenant \u00e0 des manipulations (parcours, tri, etc.) de graphes pour lesquels on dispose de nombreux algorithmes. Chaque noeud de l&rsquo;arbre repr\u00e9sente une position du jeu, et chaque arc qui en sort est assimil\u00e9 \u00e0 un mouvement menant \u00e0 une nouvelle position. \u00e0 chaque tour de jeu, il y a, au plus, six mouvements possibles (chacun des six trous). Ceci est tr\u00e8s faible en regard d&rsquo;autres jeux comme les \u00e9checs qui peuvent en proposer plusieurs dizaines, ou le Go qui peut atteindre plusieurs centaines\u00a0! L&rsquo;arbre \u00e0 explorer sera donc relativement \u00ab\u00a0pointu\u00a0\u00bb, et l&rsquo;on essayera de privil\u00e9gier la profondeur de la descente.<\/p>\n<p style=\"text-align: justify;\">Pour cela, il existe une technique nomm\u00e9e\u00a0<em>\u00e9lagage alpha-b\u00e9ta<\/em>. Il s&rsquo;agit d&rsquo;am\u00e9liorer l&rsquo;algorithme en renon\u00e7ant \u00e0 \u00e9tudier les mouvements ne permettant pas d&rsquo;am\u00e9liorer le r\u00e9sultat d\u00e9ja obtenu pour une position donn\u00e9e. Sachant qu&rsquo;\u00e0 partir d&rsquo;une position, on a d\u00e9j\u00e0 atteint une certaine valeur gr\u00e2ce aux mouvements explor\u00e9s, il est inutile d&rsquo;examiner plus longuement les mouvements ne permettant pas de monter au-dessus de cette valeur. Or, au tour suivant c&rsquo;est l&rsquo;adversaire qui joue, et en tentant de maximiser son avantage, il diminue le n\u00f4tre, et l&rsquo;exploration de ce niveau ne pourra que faire diminuer l&rsquo;avantage obtenu au niveau sup\u00e9rieur.<\/p>\n<p style=\"text-align: justify;\">Il est facile d&rsquo;exprimer l&rsquo;\u00e9lagage alpha-b\u00e9ta dans un algorithme utilisant une fonction d&rsquo;\u00e9valuation sym\u00e9trique comme nous l&rsquo;avons fait plus haut. En effet, lorsqu&rsquo;on invoque\u00a0<code>minimax()<\/code>, on lui transmet un argument suppl\u00e9mentaire correspondant \u00e0 l&rsquo;oppos\u00e9 de la meilleur valeur d\u00e9ja obtenue \u00e0 notre niveau. Si l&rsquo;exploration au niveau suivant d\u00e9passe cet argument, son oppos\u00e9 sera n\u00e9cessairement inf\u00e9rieur \u00e0 la valeur d\u00e9j\u00e0 obtenue, et il n&rsquo;est pas utile d&rsquo;\u00e9tudier les autres mouvements.<\/p>\n<pre>minimax (position, joueur, profondeur,alpha)\n\u00a0 si profondeur = profondeur_maximale\n\u00a0\u00a0\u00a0 renvoyer evaluation(position, joueur)\n\u00a0 maximum = -infini\n\u00a0 pour tous les mouvements possibles (adversaire(joueur))\n\u00a0\u00a0\u00a0 nouvelle = appliquer_mouvement(position, mouvement,maximum)\n\u00a0\u00a0\u00a0 valeur = - minimax(nouvelle, adversaire(joueur), profondeur+1, -maximum)\n\u00a0\u00a0\u00a0 si valeur &gt; maximum\n\u00a0\u00a0\u00a0\u00a0\u00a0 maximum = valeur\n\u00a0\u00a0\u00a0 si valeur &gt; alpha\n\u00a0\u00a0\u00a0\u00a0\u00a0 sortir boucle pour\n\u00a0 renvoyer maximum<\/pre>\n<p style=\"text-align: justify;\">Pour bien saisir le fonctionnement de l&rsquo;algorithme minimax et l&rsquo;\u00e9lagage alpha-b\u00e9ta, il est recommand\u00e9 de le faire \u00ab\u00a0tourner\u00a0\u00bb manuellement sur un arbre construit pour l&rsquo;occasion. On trouvera \u00e9galement des explications plus claires et plus d\u00e9taill\u00e9es dans de nombreux ouvrages d&rsquo;introduction \u00e0 l&rsquo;IA comme [WINSTON 92] ou \u00e0 l&rsquo;algorithmique, comme [GOLDSCHLAGER 86] par exemple.<\/p>\n<h2>Impl\u00e9mentation<\/h2>\n<h3>Algorithme minimax<\/h3>\n<p style=\"text-align: justify;\">L&rsquo;impl\u00e9mentation d&rsquo;un algorithme minimax est relativement simple. Il faut d&rsquo;abord trouver une bonne repr\u00e9sentation de l&rsquo;\u00e9tat du jeu. Pour l&rsquo;Aw\u00e9l\u00e9, il suffit d&#8217;employer un tableau de deux fois six cases, plus deux cases contenant les graines captur\u00e9es. Il faut ensuite choisir un langage de d\u00e9veloppement. Ici, nous ferons preuve d&rsquo;originalit\u00e9, en nous tournant vers le langage Tcl, ceci pour trois raisons\u00a0:<\/p>\n<ul>\n<li style=\"text-align: justify;\">en tant que langage de script, Tcl est simple \u00e0 lire et \u00e0 programmer, la traduction de l&rsquo;algorithme exprim\u00e9 ci-dessus se fera tr\u00e8s facilement\u00a0;<\/li>\n<li style=\"text-align: justify;\">l&rsquo;interpr\u00e9teur Tcl est disponible sur toutes les plateformes Unix et la biblioth\u00e8que Tk livr\u00e9e avec lui nous permettra de disposer tr\u00e8s facilement d&rsquo;une interface graphique pour le jeu\u00a0;<\/li>\n<li style=\"text-align: justify;\">enfin, il est toujours amusant d&rsquo;essayer d&#8217;employer un langage au-del\u00e0 de ses limites habituelles. Tcl est souvent utilis\u00e9 comme un langage de regroupement de commande (une sorte de super script shell), mais rarement pour de v\u00e9ritables programmes de calcul. D\u00e9montrons donc qu&rsquo;il en est capable\u00a0!<\/li>\n<\/ul>\n<p style=\"text-align: justify;\">Pour les lecteurs d\u00e9sireux de s&rsquo;initier au langage Tcl ou \u00e0 l&rsquo;utilisation de la biblioth\u00e8que Tk, je ne peux que vous conseiller la lecture de [BLAESS 01]\u00a0!<\/p>\n<p style=\"text-align: justify;\">L&rsquo;interpr\u00e9teur Tcl \u00e9tant livr\u00e9 avec toutes les distributions Linux, aucun probl\u00e8me ne devrait se poser pour tester les routines ci-dessous. La premi\u00e8re routine que nous examinerons est le coeur m\u00eame de l&rsquo;algorithme minimax. On peut d&rsquo;ailleurs presque reconna\u00eetre ligne-\u00e0-ligne son expression ci-dessus.<\/p>\n<pre>proc evaluation_mouvement {ref_jeu joueur profondeur mvt alpha} {\n\u00a0 upvar $ref_jeu jeu\n\u00a0 global profondeur_maxi liste_mouvements erreur\n\u00a0 if {[effectuer_mouvement jeu $joueur $mvt] &lt; 0} {return $erreur}\n\u00a0 if {[incr profondeur] &gt;= $profondeur_maxi} {\n\u00a0\u00a0\u00a0 return [evaluation_position jeu $joueur]\n\u00a0 }\n\u00a0 set joueur [expr 1 - $joueur]\n\u00a0 set meilleure_evaluation $erreur\n\u00a0 foreach mvt $liste_mouvements {\n\u00a0\u00a0\u00a0 array set copie_jeu\u00a0 [array get jeu]\n\u00a0\u00a0\u00a0 set valeur [evaluation_mouvement copie_jeu $joueur $profondeur $mvt [expr - $meilleure_evaluation]]\n\u00a0\u00a0\u00a0 if {$valeur == $erreur} {continue}\n\u00a0\u00a0\u00a0 set valeur [expr - $valeur]\n\u00a0\u00a0\u00a0 if {$valeur &gt; $meilleure_evaluation} {\n\u00a0\u00a0\u00a0\u00a0\u00a0 set meilleure_evaluation $valeur\n\u00a0\u00a0\u00a0\u00a0\u00a0 if {$valeur &gt; $alpha} {break}\n\u00a0\u00a0\u00a0 }\n\u00a0 }\n\u00a0 return $meilleure_evaluation;\n}<\/pre>\n<p style=\"text-align: justify;\">La premi\u00e8re instruction de cette routine &#8211;\u00a0<code>upvar<\/code> &#8211; indique que le premier argument est une r\u00e9f\u00e9rence symbolique, ou plus pr\u00e9cis\u00e9ment le nom d&rsquo;une variable appartenant \u00e0 la routine appelante. C&rsquo;est donc le moyen employ\u00e9 en Tcl pour passer des arguments par r\u00e9f\u00e9rence. Le tableau\u00a0<code>jeu<\/code>repr\u00e9sente le contenu des diff\u00e9rents trous.\u00a0<code>jeu(0,\u2026)<\/code> correspond au joueur Nord (par d\u00e9faut l&rsquo;ordinateur) et\u00a0<code>jeu(1,\u2026)<\/code> au joueur Sud. Pour chacun d&rsquo;eux,\u00a0<code>jeu(\u2026,i)<\/code> avec i dans [0,6[ correspondent aux six trous, num\u00e9rot\u00e9s dans l&rsquo;ordre inverse des aiguilles d&rsquo;une montre. Enfin<code>jeu(0,6)<\/code> et\u00a0<code>jeu(1,6)<\/code> correspondent aux deux r\u00e9serves de graines captur\u00e9es. La num\u00e9rotation des trous est repr\u00e9sent\u00e9e sur le tableau ci-dessous.<\/p>\n<table border=\"3\">\n<tbody>\n<tr>\n<td rowspan=\"2\" align=\"center\">jeu(0,6)<\/td>\n<td align=\"center\">jeu(0,5)<\/td>\n<td align=\"center\">jeu(0,4)<\/td>\n<td align=\"center\">jeu(0,3)<\/td>\n<td align=\"center\">jeu(0,2)<\/td>\n<td align=\"center\">jeu(0,1)<\/td>\n<td align=\"center\">jeu(0,0)<\/td>\n<td rowspan=\"2\" align=\"center\">jeu(1,6)<\/td>\n<\/tr>\n<tr>\n<td align=\"center\">jeu(1,0)<\/td>\n<td align=\"center\">jeu(1,1)<\/td>\n<td align=\"center\">jeu(1,2)<\/td>\n<td align=\"center\">jeu(1,3)<\/td>\n<td align=\"center\">jeu(1,4)<\/td>\n<td align=\"center\">jeu(1,5)<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p style=\"text-align: justify;\">Il est important de remarquer que les premi\u00e8res routines que nous d\u00e9veloppons consid\u00e8rent la variable\u00a0<code>jeu<\/code> dans son ensemble comme une repr\u00e9sentation de la situation, sans faire r\u00e9f\u00e9rence \u00e0 son contenu. Nous pourrons r\u00e9utiliser ces fonctions directement avec d&rsquo;autres jeux de r\u00e9flexion.<\/p>\n<p>Nous pouvons remarquer l&rsquo;existence des variables globales suivantes\u00a0:<\/p>\n<ul>\n<li style=\"text-align: justify;\"><code>profondeur_maxi<\/code> : le nombre de niveaux \u00e0 explorer dans l&rsquo;arbre des possibilit\u00e9s, chaque niveau de cet arbre correspondant \u00e0 un\u00a0<em>demi-coup<\/em> ;<\/li>\n<li style=\"text-align: justify;\"><code>liste_mouvements<\/code> : une liste contenant tous les mouvements possibles pour chaque joueur. Ici il ne s&rsquo;agit que de la liste\u00a0<code>{0 1 2 3 4 5}<\/code> correspondant aux six trous\u00a0;<\/li>\n<li style=\"text-align: justify;\"><code>erreur<\/code> : il s&rsquo;agit d&rsquo;une valeur num\u00e9rique qui doit \u00eatre inf\u00e9rieure \u00e0 la pire des \u00e9valuations possibles. Nous choisissons arbitrairement -100000.<\/li>\n<\/ul>\n<p style=\"text-align: justify;\">La routine\u00a0<code>effectuer_mouvement<\/code> devra appliquer le mouvement d\u00e9sir\u00e9 au jeu, et renvoyer une valeur n\u00e9gative en cas d&rsquo;impossibilit\u00e9. Nous voyons que le reste de la fonction est assez proche de l&rsquo;algorithme pr\u00e9c\u00e9dent, avec quelques remarques\u00a0:<\/p>\n<ul>\n<li style=\"text-align: justify;\">La boucle\u00a0<code>foreach<\/code> permet de balayer la liste des mouvements de mani\u00e8re plus lisible qu&rsquo;une boucle\u00a0<code>for<\/code> ;<\/li>\n<li style=\"text-align: justify;\">L&rsquo;instruction Tcl\u00a0<code>array<\/code> permet de manipuler les tableaux, nous l&rsquo;utilisons ici pour dupliquer la table repr\u00e9sentant le jeu.<\/li>\n<\/ul>\n<p style=\"text-align: justify;\">Nous savons donc \u00e9valuer un mouvement donn\u00e9. Nous devrons utiliser une routine permettant de d\u00e9terminer le meilleur mouvement pour une position de d\u00e9part donn\u00e9. Cette fonction s&rsquo;\u00e9crit de mani\u00e8re assez \u00e9vidente.<\/p>\n<pre>proc meilleur_mouvement {ref_jeu joueur} {\n\u00a0 upvar $ref_jeu jeu\n\u00a0 global liste_mouvements erreur\n\u00a0 set meilleure_evaluation $erreur\n\u00a0 set meilleur $erreur\n\u00a0 foreach mvt $liste_mouvements {\n\u00a0\u00a0\u00a0 array set copie_jeu\u00a0 [array get jeu]\n\u00a0\u00a0\u00a0 set valeur [evaluation_mouvement copie_jeu $joueur 0 $mvt [expr - $erreur]]\n\u00a0\u00a0\u00a0 if {$valeur == $erreur} {continue}\n\u00a0\u00a0\u00a0\u00a0\u00a0 if {$valeur &gt; $meilleure_evaluation} {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 set meilleure_evaluation $valeur\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 set meilleur $mvt\n\u00a0\u00a0\u00a0 }\n\u00a0 }\n\u00a0 return $meilleur\n}<\/pre>\n<p>On notera que si aucun mouvement n&rsquo;est acceptable, elle renvoie la valeur d&rsquo;<code>erreur<\/code>.<\/p>\n<h3>Routines de jeu<\/h3>\n<p style=\"text-align: justify;\">Nous pouvons \u00e0 pr\u00e9sent \u00e9crire les routines encadrant le d\u00e9roulement la partie. Il nous faut disposer d&rsquo;une variable globale stockant le num\u00e9ro du joueur en cours. La premi\u00e8re routine correspond au jeu de l&rsquo;ordinateur.<\/p>\n<pre>proc ordinateur_joue {ref_jeu} {\n\u00a0 upvar $ref_jeu jeu\n\u00a0 global joueur_en_cours erreur\n\u00a0 set mvt [meilleur_mouvement jeu $joueur_en_cours]\n\u00a0 if {$mvt == $erreur} {\n\u00a0\u00a0\u00a0 afficher_resultats jeu\n\u00a0 } else {\n\u00a0\u00a0\u00a0 effectuer_mouvement jeu $joueur_en_cours $mvt\n\u00a0\u00a0\u00a0 afficher_jeu jeu\n\u00a0\u00a0\u00a0 indiquer_coup_joue $joueur_en_cours $mvt\n\u00a0\u00a0\u00a0 passer_la_main\n\u00a0\u00a0\u00a0 if [partie_terminee jeu] {afficher_resultats jeu\u00a0; return}\n\u00a0 }\n}<\/pre>\n<p style=\"text-align: justify;\">Nous voyons qu&rsquo;elle invoque plusieurs routines concernant l&rsquo;interface graphique (comme \u00a0<code>afficher_resultats<\/code>, \u00a0<code>afficher_jeu<\/code>, \u00a0<code>indiquer_coup_joue<\/code>, \u00a0<code>passer_la_main<\/code>). Elles seront d\u00e9velopp\u00e9es plus bas. La routine suivante est invoqu\u00e9e lorsque l&rsquo;humain demande \u00e0 jouer un certain trou. Elle v\u00e9rifie que le mouvement soit correct en passant par une copie interm\u00e9diaire du jeu.<\/p>\n<pre>proc humain_joue {ref_jeu j c} {\n\u00a0 upvar $ref_jeu jeu\n\u00a0 global joueur_en_cours\n\u00a0 if {$j\u00a0!= $joueur_en_cours} { bell; return }\n\u00a0 array set copie [array get jeu]\n\u00a0 if {[effectuer_mouvement copie $j $c] &lt; 0} {bell; return}\n\u00a0 array set jeu [array get copie]\n\u00a0 afficher_jeu jeu\n\u00a0 passer_la_main\n\u00a0 if [partie_terminee jeu] {afficher_resultats jeu\u00a0; return}\n\u00a0 after 10 ordinateur_joue jeu\n}<\/pre>\n<p style=\"text-align: justify;\">La derni\u00e8re ligne de cette routine lance la r\u00e9flexion de l&rsquo;ordinateur au bout de 10 millisecondes. Cet intervalle permet \u00e0 la biblioth\u00e8que Tk de traiter sa boucle d&rsquo;\u00e9v\u00e8nements, rafraichissant ainsi l&rsquo;affichage graphique.<\/p>\n<p style=\"text-align: justify;\">Nous avons besoin d&rsquo;une routine permettant de basculer d&rsquo;un joueur \u00e0 l&rsquo;autre. Elle met \u00e0 jour la variable globale concern\u00e9e et invoque la fonction d&rsquo;interface s&rsquo;occupant de l&rsquo;affichage du nom du joueur attendu.<\/p>\n<pre>proc passer_la_main {} {\n\u00a0 global joueur_en_cours\n\u00a0 set joueur_en_cours [expr 1 - $joueur_en_cours]\n\u00a0 afficher_joueur_en_cours $joueur_en_cours\n}<\/pre>\n<h3>Jeu d&rsquo;Aw\u00e9l\u00e9<\/h3>\n<p style=\"text-align: justify;\">Jusqu&rsquo;\u00e0 pr\u00e9sent, les fonctions d\u00e9velopp\u00e9es sont totalement ind\u00e9pendantes du jeu impl\u00e9ment\u00e9, nous pourrions les employer pour un grand nombre de jeux de r\u00e9flexion. Voyons maintenant les fonctions sp\u00e9cifiques au jeu d&rsquo;Aw\u00e9l\u00e9. Tout d&rsquo;abord, la fonction initialisant toutes les donn\u00e9es du jeu, conform\u00e9ment au tableau suivant\u00a0:<\/p>\n<table border=\"3\">\n<tbody>\n<tr>\n<td align=\"center\">jeu(j,i)<\/td>\n<td align=\"center\">i=0<\/td>\n<td align=\"center\">i=1<\/td>\n<td align=\"center\">i=2<\/td>\n<td align=\"center\">i=3<\/td>\n<td align=\"center\">i=4<\/td>\n<td align=\"center\">i=5<\/td>\n<td align=\"center\">i=6<br \/>\n(r\u00e9serve)<\/td>\n<\/tr>\n<tr>\n<td align=\"center\">j=0<br \/>\n(Nord)<\/td>\n<td align=\"center\">4<\/td>\n<td align=\"center\">4<\/td>\n<td align=\"center\">4<\/td>\n<td align=\"center\">4<\/td>\n<td align=\"center\">4<\/td>\n<td align=\"center\">4<\/td>\n<td align=\"center\">0<\/td>\n<\/tr>\n<tr>\n<td align=\"center\">j=1<br \/>\n(Sud)<\/td>\n<td align=\"center\">4<\/td>\n<td align=\"center\">4<\/td>\n<td align=\"center\">4<\/td>\n<td align=\"center\">4<\/td>\n<td align=\"center\">4<\/td>\n<td align=\"center\">4<\/td>\n<td align=\"center\">0<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<pre>proc reinitialiser {ref_jeu} {\n\u00a0 upvar $ref_jeu jeu\n\u00a0 global joueur_en_cours\n\u00a0 foreach j {0 1} {\n\u00a0\u00a0\u00a0 foreach c {0 1 2 3 4 5} {\n\u00a0\u00a0\u00a0\u00a0\u00a0 set jeu($j,$c) 4\n\u00a0\u00a0\u00a0 }\n\u00a0\u00a0\u00a0 set jeu($j,6) 0\n\u00a0 }\n\u00a0 afficher_jeu jeu\n\u00a0 set joueur_en_cours 1\n\u00a0 afficher_joueur_en_cours $joueur_en_cours\n}<\/pre>\n<p style=\"text-align: justify;\">La routine suivante r\u00e9alise v\u00e9ritablement le mouvement propos\u00e9, en distribuant les graines extraites d&rsquo;un trou. Il s&rsquo;agit probablement de la fonction n\u00e9cessitant le plus d&rsquo;adaptation d&rsquo;un jeu \u00e0 l&rsquo;autre. N&rsquo;oublions pas qu&rsquo;elle doit renvoyer une valeur n\u00e9gative si le mouvement indiqu\u00e9 conduit \u00e0 une position inacceptable (dans d&rsquo;autres circonstances cela pourrait repr\u00e9senter une mise en \u00e9chec-au-roi).<\/p>\n<pre>proc effectuer_mouvement {ref_jeu joueur mvt} {\n\u00a0 upvar $ref_jeu jeu\n\u00a0 set j $joueur\n\u00a0 set c $mvt\n\u00a0 set nb $jeu($joueur,$mvt)\n\u00a0 set jeu($joueur,$mvt) 0\n\u00a0 if {$nb &lt; 1} {return -1}\n\u00a0 while {$nb &gt; 0} {\n\u00a0\u00a0\u00a0 incr c\n\u00a0\u00a0\u00a0 if {$c &gt; 5} { # Passage de l'autre c\u00f4t\u00e9\n\u00a0\u00a0\u00a0\u00a0\u00a0 set c 0\n\u00a0\u00a0\u00a0\u00a0\u00a0 set j [expr 1 - $j]\n\u00a0\u00a0\u00a0 }\n\u00a0\u00a0\u00a0 if {($c\u00a0!= $mvt) || ($joueur\u00a0!= $j)} {\n\u00a0\u00a0\u00a0\u00a0\u00a0 # on saute le trou de d\u00e9part\n\u00a0\u00a0\u00a0\u00a0\u00a0 incr nb -1\n\u00a0\u00a0\u00a0\u00a0\u00a0 incr jeu($j,$c)\n\u00a0\u00a0\u00a0 }\n\u00a0 }\n\u00a0 if {$j\u00a0!= $joueur} { # captures dans le terrain adverse\n\u00a0\u00a0\u00a0 while {$c &gt;= 0} {\n\u00a0\u00a0\u00a0\u00a0\u00a0 if {($jeu($j,$c) &lt; 2) || ($jeu($j,$c) &gt; 3)} { break }\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 incr jeu($joueur,6) $jeu($j,$c)\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 set jeu($j,$c) 0\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 incr c -1\n\u00a0\u00a0\u00a0 }\n\u00a0 }\n\u00a0 # il faut laisser au moins une graine chez l'adversaire\n\u00a0 set j [expr 1 - $joueur]\n\u00a0 foreach c {0 1 2 3 4 5} {\n\u00a0\u00a0\u00a0 if {$jeu($j,$c) &gt; 0} {return 0}\n\u00a0 }\n\u00a0 return -1\n}<\/pre>\n<p style=\"text-align: justify;\">Nous avons invoqu\u00e9 pr\u00e9c\u00e9demment une routine\u00a0<code>partie_terminee<\/code>, qui v\u00e9rifiera simplement si un joueur a atteint les vingt-quatre graines en r\u00e9serve.<\/p>\n<pre>proc partie_terminee {ref_jeu} {\n\u00a0 upvar $ref_jeu jeu\n\u00a0 if {($jeu(0,6)&gt;=24) || ($jeu(1,6)&gt;=24)} {return 1}\n\u00a0 return 0\n}<\/pre>\n<p style=\"text-align: justify;\">Enfin, avant d&rsquo;aborder l&rsquo;interface graphique du jeu, nous devons \u00e9crire la fonction d&rsquo;\u00e9valuation. Comme nous l&rsquo;avons sugg\u00e9r\u00e9 plus haut, c&rsquo;est un \u00e9l\u00e9ment crucial pour l&rsquo;intelligence du syst\u00e8me. C&rsquo;est justement parce que le jeu est complexe qu&rsquo;il est impossible de l&rsquo;explorer int\u00e9gralement et que l&rsquo;on doit avoir recours \u00e0 une fonction capable d&rsquo;estimer avec pr\u00e9cision l&rsquo;avantage d&rsquo;un joueur par rapport \u00e0 l&rsquo;autre.<\/p>\n<p style=\"text-align: justify;\">Nous allons \u00e9crire une fonction d&rsquo;\u00e9valuation simple, mais chacun pourra facilement l&rsquo;adapter pour exp\u00e9rimenter de nouveaux param\u00e8tres. Notre routine prendra en argument le nom du joueur pour lequel l&rsquo;\u00e9valuation doit \u00eatre faite. En r\u00e9alit\u00e9, les m\u00eames calculs seront ex\u00e9cut\u00e9s pour les deux joueurs, mais cet argument permettra de d\u00e9terminer dans quel sens soustraire les deux valeurs obtenues.<\/p>\n<p style=\"text-align: justify;\">Le premier param\u00e8tre permettant de d\u00e9crire l&rsquo;avantage d&rsquo;un joueur est le nombre de graines qu&rsquo;il a captur\u00e9es. Le contenu de sa r\u00e9serve va \u00eatre multipli\u00e9 par une valeur arbitraire (300) afin de lui donner un poids important par rapport aux autres graines pr\u00e9sentes dans le jeu. On constate rapidement que les trous se trouvant \u00e0 droite de la rang\u00e9e d&rsquo;un joueur ont une importance strat\u00e9gique plus \u00e9lev\u00e9e que ceux de gauche. En effet, ils sont plus difficilement touch\u00e9s par l&rsquo;adversaire pour les captures, et \u00e0 l&rsquo;inverse offrent un acc\u00e8s plus facile vers le jeu de l&rsquo;opposant. Nous allons donc pond\u00e9rer leur contenu par une valeur croissante de gauche \u00e0 droite (1, 2, 3\u20266).<\/p>\n<pre>proc evaluation_position {nom_jeu joueur} {\n\u00a0 upvar $nom_jeu jeu\n\u00a0 foreach j {0 1} {\n\u00a0\u00a0\u00a0 set evaluation($j) [expr 300 * $jeu($j,6)]\n\u00a0\u00a0\u00a0 foreach i {0 1 2 3 4 5} {\n\u00a0\u00a0\u00a0\u00a0\u00a0 set n $jeu($j,$i)\n\u00a0\u00a0\u00a0\u00a0\u00a0 incr evaluation($j) [expr $n * ($i + 1)]\n\u00a0\u00a0\u00a0 }\n\u00a0 }\n\u00a0 return [expr $evaluation($joueur) - $evaluation([expr 1 - $joueur])]\n}<\/pre>\n<p style=\"text-align: justify;\">Lorsqu&rsquo;on veut \u00e9crire une fonction d&rsquo;\u00e9valuation, il est difficile de savoir o\u00f9 s&rsquo;arr\u00eater. Nous n&rsquo;avons pris en consid\u00e9ration que les param\u00e8tres les plus \u00e9vidents. D&rsquo;autres pourraient \u00e9galement \u00eatre utilis\u00e9s (\u00ab\u00a0y a-t-il des graines menac\u00e9es de capture\u00a0?\u00a0\u00bb, \u00ab\u00a0dispose-t-on de greniers suffisants pour faire deux fois le tour du jeu\u00a0?\u00a0\u00bb, etc.) Le probl\u00e8me est d&rsquo;arriver \u00e0 distinguer les \u00e9l\u00e9ments qui rel\u00e8vent v\u00e9ritablement de l&rsquo;\u00e9valuation et ceux qui tentent de pr\u00e9voir les coups suivants. V\u00e9rifier si des graines sont menac\u00e9es de capture revient simplement \u00e0 essayer de repousser l&rsquo;horizon de l&rsquo;exploration d&rsquo;un demi-coup suppl\u00e9mentaire.<\/p>\n<p style=\"text-align: justify;\">Notons que l&rsquo;on se rapproche du domaine de l&rsquo;apprentissage automatique, et que l&rsquo;on pourrait imaginer un syst\u00e8me o\u00f9 la fonction d&rsquo;\u00e9valuation \u00e9voluerait en s&rsquo;appuyant sur les m\u00e9canismes auto-adaptatifs habituels (r\u00e9seaux connexionistes, algorithmes g\u00e9n\u00e9tiques, etc.), d&rsquo;autant que le nombre de param\u00eatres d&rsquo;entr\u00e9e dans le jeu d&rsquo;Aw\u00e9l\u00e9 est relativement restreint.<\/p>\n<h3>Interface graphique<\/h3>\n<p>Pour construire l&rsquo;interface graphique de notre programme, nous nous appuyons sur la biblioth\u00e8que Tk. Ceci nous permettra de r\u00e9aliser une interface tout \u00e0 fait acceptable en quelques lignes de code. La premi\u00e8re proc\u00e9dure sert \u00e0 construire la fen\u00eatre du jeu.<\/p>\n<p><a href=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2011\/03\/capture-ecran-awele-tcl.gif\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-36\" title=\"capture-ecran-awele-tcl\" src=\"http:\/\/www.blaess.fr\/christophe\/wp-content\/uploads\/2011\/03\/capture-ecran-awele-tcl-300x124.gif\" alt=\"Capture \u00e9cran Awele en TCL\" width=\"300\" height=\"124\" \/><\/a><\/p>\n<pre>proc creer_fenetre {ref_jeu} {\n\u00a0 global widget\n\u00a0 pack [frame .f -relief raised -bd 2]\n\u00a0 pack [frame .f.b ] -fill x\n\u00a0 pack [button .f.b.quitter -text \"Quitter\" -command {exit}] -side left\n\u00a0 pack [button .f.b.reinit -text \"Nouveau\"\u00a0 -command \"reinitialiser $ref_jeu\"] -side left\n\u00a0 pack [button .f.b.jouer -text \"Jouer\"\u00a0\u00a0\u00a0\u00a0 -command \"ordinateur_joue $ref_jeu\"] -side left\n\u00a0 pack [label .f.b.txt -text \"Joueur en cours\u00a0: Sud\"]\n\u00a0 pack [frame .f.d -relief sunken -bd 1] -fill both -expand 1 -padx 5 -pady 5\n\u00a0 pack [canvas .f.d.c -relief sunken -bd 1 -height 100 -width 390 -background brown4]\n\u00a0 for {set j 0} {$j &lt; 2} {incr j 1} {\n\u00a0\u00a0\u00a0 for {set c 0} {$c &lt; 6} {incr c 1} {\n\u00a0\u00a0\u00a0\u00a0\u00a0 set x1 [expr 50 + 50 * $c]\n\u00a0\u00a0\u00a0\u00a0\u00a0 set y1 [expr 10 + 50 * $j]\n\u00a0\u00a0\u00a0\u00a0\u00a0 set x2 [expr 90 + 50 * $c]\n\u00a0\u00a0\u00a0\u00a0\u00a0 set y2 [expr 40 + 50 * $j]\n\u00a0\u00a0\u00a0\u00a0\u00a0 set xt [expr 70 + 50 * $c]\n\u00a0\u00a0\u00a0\u00a0\u00a0 set yt [expr 25 + 50 * $j]\n\u00a0\u00a0\u00a0\u00a0\u00a0 if {$j &gt; 0} {set i $c} else { set i [expr 5 - $c] }\n\u00a0\u00a0\u00a0\u00a0\u00a0 .f.d.c create oval\u00a0 $x1 $y1 $x2 $y2 -fill SandyBrown -tags \"jeu($j,$i)\"\n\u00a0\u00a0\u00a0\u00a0\u00a0 set widget($j,$i) [.f.d.c create text $xt $yt -anchor center -tags \"jeu($j,$i)\"]\n\u00a0\u00a0\u00a0\u00a0\u00a0 .f.d.c bind \"jeu($j,$i)\" &lt;Button&gt; \"humain_joue $ref_jeu $j $i\"\n\u00a0\u00a0\u00a0 }\n\n\u00a0\u00a0\u00a0 set x1 [expr 10 + 340 * $j]\n\u00a0\u00a0\u00a0 set x2 [expr 40 + 340 * $j]\n\u00a0\u00a0\u00a0 set xt [expr 25 + 340 * $j]\n\u00a0\u00a0\u00a0 .f.d.c create oval\u00a0 $x1 10 $x2 90 -fill SandyBrown\n\u00a0\u00a0\u00a0 set widget($j,6) [.f.d.c create text\u00a0 $xt 50\u00a0 -anchor center]\n\u00a0 }\n}<\/pre>\n<p style=\"text-align: justify;\">Les premi\u00e8res lignes de cette routine cr\u00e9ent une barre de boutons en haut de l&rsquo;\u00e9cran, pour quitter, r\u00e9initialiser le jeu, ou pour demander \u00e0 l&rsquo;ordinateur de joueur le coup suivant. Ensuite, une zone de dessin (<code>canvas<\/code>) est remplie avec des ovales repr\u00e9sentant les trous et les r\u00e9serves, ainsi que des zones de texte indiquant le contenu des trous. Ces zones de texte sont r\u00e9f\u00e9renc\u00e9es par le tableau de variables globales\u00a0<code>widget()<\/code>, que nous mettrons \u00e0 jour dans les routines ci-dessous. L&rsquo;instruction\u00a0<code>bind<\/code> permet d&rsquo;associer une action (<code>humain_joue<\/code>) \u00e0 un \u00e9v\u00e8nement (pression d&rsquo;un bouton de la souris). Les deux routines suivantes servent \u00e0 afficher l&rsquo;\u00e9tat des trous. Pour indiquer le dernier coup jou\u00e9 par l&rsquo;ordinateur, le texte repr\u00e9sentant le contenu de la case jou\u00e9e passera en rouge.<\/p>\n<pre>proc afficher_jeu {ref_jeu} {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 upvar $ref_jeu jeu\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 global widget\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 for {set j 0} {$j &lt; 2} {incr j 1} {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 for {set i 0} {$i &lt;= 6} {incr i 1} {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .f.d.c itemconfigure $widget($j,$i) -fill black\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .f.d.c itemconfigure $widget($j,$i) -text $jeu($j,$i)\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\n}\nproc indiquer_coup_joue {joueur coup} {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 global widget\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .f.d.c itemconfigure $widget($joueur,$coup) -fill red\n}<\/pre>\n<p style=\"text-align: justify;\">Enfin, les deux derni\u00e8res routines de l&rsquo;interface graphique affichent respectivement le nom du joueur attendu, et les r\u00e9sultats de la partie.<\/p>\n<pre>proc afficher_joueur_en_cours {joueur} {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if {$joueur} { set nom Sud } else { set nom Nord }\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 .f.b.txt configure -text \"Joueur en cours\u00a0: $nom\"\n}\nproc afficher_resultats {ref_jeu} {\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 upvar $ref_jeu jeu\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 afficher_jeu jeu\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if {$jeu(0,6) == $jeu(1,6)}\u00a0 { set msg \"Match nul\u00a0!\"}\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if {$jeu(0,6) &gt;\u00a0 $jeu(1,6)}\u00a0 { set msg \"Joueur Nord gagne\u00a0!\"}\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 if {$jeu(0,6) &lt;\u00a0 $jeu(1,6)}\u00a0 { set msg \"Joueur Sud gagne\u00a0!\"}\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 tk_messageBox -message $msg -title \"R\u00e9sultat\" -type ok\n}<\/pre>\n<h3>Fonctions centrales<\/h3>\n<p style=\"text-align: justify;\">Voyons pour terminer les instructions centrales du programmes, celles qui vont invoquer les routines ci-dessus et initialiser les variables globales. Notre fichier g\u00e9n\u00e9ral &#8211; que vous pouvez trouver int\u00e9gralement sur mon site (voir paragraphe\u00a0<code>Liens<\/code>) &#8211; sera\u00a0:<\/p>\n<pre>#! \/usr\/bin\/wish\n\u00a0 <em>\u2026routines d\u00e9crites plus haut\u2026<\/em>\n\n\u00a0 set liste_mouvements {0 1 2 3 4 5}\n\u00a0 set erreur -100000\n\u00a0 set profondeur_maxi\u00a0\u00a0 6\n\u00a0 creer_fenetre jeu\n\u00a0 reinitialiser jeu<\/pre>\n<p style=\"text-align: justify;\">La variable globale\u00a0<code>profondeur_maxi<\/code> permet de configurer la profondeur de r\u00e9flexion, donc le niveau de jeu de l&rsquo;ordinateur. La profondeur de six demi-coups permet d&rsquo;avoir des r\u00e9sultats corrects sur une machine de capacit\u00e9 moyenne. Sur un processeur rapide, il est possible d&rsquo;augmenter nettement cette valeur.<\/p>\n<h2>Conclusion<\/h2>\n<p style=\"text-align: justify;\">Le but de cet article n&rsquo;\u00e9tait pas de r\u00e9aliser un v\u00e9ritable programme champion d&rsquo;Aw\u00e9l\u00e9, et nous en sommes loin, mais simplement de pr\u00e9senter les m\u00e9canismes les plus courants pour programmer des jeux de r\u00e9flexion. Pour am\u00e9liorer les performances d&rsquo;un tel logiciel, deux pistes peuvent \u00eatre suivies\u00a0:<\/p>\n<ul>\n<li style=\"text-align: justify;\">augmenter la profondeur de la recherche\u00a0: pour cela il faut am\u00e9liorer les performances du programme afin de conserver un temps de r\u00e9ponse acceptable. On l&rsquo;optimisera donc en l&rsquo;\u00e9crivant dans un langage compil\u00e9 par exemple\u00a0;<\/li>\n<li style=\"text-align: justify;\">am\u00e9liorer l&rsquo;intelligence du syst\u00e8me\u00a0: cela s&rsquo;obtient en travaillant sur la fonction d&rsquo;\u00e9valuation, en recherchant les param\u00e8tres les plus pertinents, et en comparant les r\u00e9sultats. Il peut \u00eatre int\u00e9ressant d&rsquo;opposer automatiquement plusieurs versions de fonction d&rsquo;\u00e9valuation en les faisant joueur les unes contre les autres.<\/li>\n<\/ul>\n<p style=\"text-align: justify;\">L&rsquo;impl\u00e9mentation en Tcl est amusante, mais n&rsquo;est \u00e9videmment pas suffisament efficace pour un v\u00e9ritable programme de jeu de r\u00e9flexion. L&rsquo;avantage de ce script est de pouvoir modifier tr\u00e8s facilement les param\u00e8tres et la fonction d&rsquo;\u00e9valuation, avant d&rsquo;en \u00e9crire une dans un langage compil\u00e9 plus rapide. On notera malgr\u00e9 tout que le langage Tcl, associ\u00e9 \u00e0 la biblioth\u00e8que Tk permet d&rsquo;impl\u00e9menter un jeu complet avec une interface utilisateur graphique en utilisant seulement 200 lignes de code environ\u00a0!<\/p>\n<h2>Bibliographie<\/h2>\n<ul>\n<li>[BLAESS 2001] Christophe Blaess \u00ab\u00a0<em>Langages de Scripts sous Linux<\/em>\u00a0\u00bb Editions Eyrolles 2001.<\/li>\n<li>[GOLDSCHLAGER 86] Les Goldschlager &amp; Andrew Lister \u00ab\u00a0<em>Informatique et Algorithmique<\/em>\u00a0\u00bb InterEditions 1986. Traduction par Virginie Sumpf, titre original \u00ab\u00a0<em>Computer Science: A Modern Introduction<\/em>\u00ab\u00a0<\/li>\n<li>[WINSTON 92] Patrick Henry Winston \u00ab\u00a0<em>Artificial Intelligence<\/em> Addison-Wesley 1992.<\/li>\n<\/ul>\n<h2>Liens<\/h2>\n<ul>\n<li>Le script complet\u00a0:\u00a0<a href=\"http:\/\/www.blaess.fr\/christophe\/logiciels.php?pg=003\">http:\/\/www.blaess.fr\/christophe\/logiciels\/src\/awele.tcl<\/a><\/li>\n<li style=\"text-align: justify;\">Les r\u00e8gles de l&rsquo;Aw\u00e9l\u00e9, ainsi qu&rsquo;une applet Java qui impl\u00e9mente un jeu d&rsquo;Aw\u00e9l\u00e9 employant probablement la m\u00eame fonction d&rsquo;\u00e9valuation que nous, car les niveaux de jeu sont \u00e0 peu pr\u00e8s \u00e9quivalents (mais plus rapide en Java qu&rsquo;en Tcl\u00a0!)\u00a0:http:\/\/www.sdv.fr\/pages\/casa\/html\/awele.html<\/li>\n<\/ul>","protected":false},"excerpt":{"rendered":"<p>Janvier 2003 &nbsp; Cet article se propose d&rsquo;offrir une introduction aux techniques d&rsquo;Intelligence Artificielle les plus simples que l&rsquo;on utilise pour la r&eacute;solution des jeux de r&eacute;flexion. Cette pr&eacute;sentation vous donnera envie, esp&egrave;rons-le, de mettre en pratique ces concepts, en programmant vous-m&ecirc;me des jeux de r&eacute;flexion, existants ou invent&eacute;s pour l&rsquo;occasion. Nous allons nous int&eacute;resser [&hellip;]<\/p>","protected":false},"author":1,"featured_media":0,"parent":20,"menu_order":2,"comment_status":"open","ping_status":"open","template":"","meta":{"footnotes":""},"class_list":["post-34","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/pages\/34","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/comments?post=34"}],"version-history":[{"count":0,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/pages\/34\/revisions"}],"up":[{"embeddable":true,"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/pages\/20"}],"wp:attachment":[{"href":"https:\/\/www.blaess.fr\/christophe\/wp-json\/wp\/v2\/media?parent=34"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}