Jeu de morpions JavaScript 



Les règles simplissimes du morpions en font un exercice idéal de programmation. C'est en JavaScript que nous nous proposons de développer l'algorithme de ce jeu. Mais si JavaScript n'est pas votre tasse de... café, sachez que vous pouvez quand même, à l'aide d'une seule balise HTML, afficher ce jeu dans vos pages perso.

 

Mise en place du jeu en HTML

Le jeu doit s'afficher dans une page HTML. Nous utilisons un tableau de 12 cellules pour y insérer des champs de formulaire. Un simple éditeur de texte nous suffira pour le saisir. Le formulaire est baptisé "toto".

<HTML><HEAD></HEAD><BODY>
<FORM NAME="toto">
   <TABLE BORDER=0>
   <TR>
      <TD ALIGN=center>
       <INPUT TYPE=text VALUE="X" SIZE=1 MAXLENGTH=1 >
     </TD>
    <TD ALIGN=center>
       <INPUT TYPE=text VALUE="X" SIZE=1 MAXLENGTH=1 >
     </TD>
     <TD ALIGN=center>
       <INPUT TYPE=text VALUE="X" SIZE=1 MAXLENGTH=1 >
     </TD>
    </TR>
    <TR>
     <TD ALIGN=center>
       <INPUT TYPE=text VALUE="X" SIZE=1 MAXLENGTH=1 >
     </TD>
     <TD ALIGN=center>
       <INPUT TYPE=text VALUE="X" SIZE=1 MAXLENGTH=1 >
     </TD>
     <TD ALIGN=center>
       <INPUT TYPE=text VALUE="X" SIZE=1 MAXLENGTH=1 >
     </TD>
    </TR>
    <TR >
     <TD ALIGN=center>
       <INPUT TYPE=text VALUE="X" SIZE=1 MAXLENGTH=1 >
     </TD>
     <TD ALIGN=center>
       <INPUT TYPE=text VALUE="X" SIZE=1 MAXLENGTH=1 >
     </TD>
     <TD ALIGN=center>
       <INPUT TYPE=text VALUE="X" SIZE=1 MAXLENGTH=1 >
     </TD>
    </TR>
   <TR>
     <TD ALIGN=center COLSPAN=3>
        <INPUT TYPE=button VALUE="Effacer" >
     </TD>
</TR>
</TABLE>
</FORM></BODY></HTML>

 

Le code JavaScript

 La code HTML étant en place, nous devons maintenant inclure dans la balise HEAD, les fonctions qui vont animer notre jeu. Commençons par le contrôle le plus simple  : un bouton effaçant le contenu de chacune des 9 cellules afin de permettre de rejouer. Nous allons déclarer une fonction qui aura ce rôle et faire en sorte qu'un clic sur le bouton lance son exécution.

On insère une balise <SCRIPT> dans l'en-tête du document :

<HEAD><SCRIPT LANGUAGE="JavaScript">
  function efface(){
    for (n=0; n<=8; n++) {document.toto.elements[n].value="'' }
   }
</SCRIPT></HEAD>

Notre fonction boucle 9 fois en incrémentant une variable n. Chaque fois un élément du formulaire "toto", élément désigné par sa position dans le tableau "elements" est vidé de tout contenu. Rappelons que c'est le navigateur qui tient comptabilité des formulaires et des éléments de formulaire présents dans le document, en deux tableaux nommés "forms" et "elements". L'expression "document.forms[0]•elements[0] est du pur JavaScript et désigne toujours le premier objet (l'index commence à zéro) du premier formulaire d'un document.

Dès lors notre bouton de formulaire peut jouer son rôle . Il suffit d'inclure dans la balise qui le définit, un gestionnaire d'événement "onClick" qui appelle l'exécution de la fonction créée :

<INPUT TYPE=button VALUE="Effacer" onClick="efface()">

Ça marche ! Occupons nous maintenant de rendre ce jeu convivial. Un clic doit suffire à l'utilisateur pour placer sa croix dans une cellule. Les champs de formulaire ignorant l'événement onClick nous interceptons un autre type d'action : l'activation du champs par positionnement du curseur d'insertion. Cet événement est nommé "Focus". Pour le reste, le procédé d'écriture du script est le même : On déclare un fonction ("jeuPerso") exécutant l'action souhaitée, dans la balise script d'en-tête du document. Puis on veille à appeler cette fonction en modifiant le code HTML. La fonction d'abord : elle doit connaître la cellule cliquée afin d'en changer la valeur si elle est vide. Nous lui passerons donc cette information en paramètre (CaseDuClic) lors de son appel.

function jeuPerso(CaseDuClic){
   if ( CaseDuClic.value=="" ) { CaseDuClic.value='X' }
}

Pour appeler la fonction nous modifions comme suit le code HTML  définissant chaque champs de texte du formulaire.

  <INPUT TYPE=text VALUE="X" SIZE=1 MAXLENGTH=1 onFocus="jeuPerso(this)">

"This" désigne l'objet en cours, ici l'élément de formulaire cliqué. C'est lui qu'il nous faut passer à la fonction (dans la variable CaseDuClic).

Il ne nous manque plus qu'une chose mais de taille : un fonction permettant à la machine de jouer à son tour. Une telle fonction, nous pourrions la nommer "jeuAuto()" et faire en sorte qu'elle soit appelée immédiatement après jeuPerso(). C'est là un chose aisée :

function jeuPerso(CaseDuClic){
   if ( CaseDuClic.value=="" ) { CaseDuClic.value='X' }
    jeuAuto()
}

Plus difficile est de concevoir cette fonction jeuAuto(). Il s'agit ni plus ni moins que d'apprendre à une machine les règles du morpions ! Une analyse préalable s'impose.

 

L'algorithme du jeu

Avant d'écrire la fonction jeuAuto() nous devons nous demander comment NOUS jouons nous au morpions lorsque c'est notre tour de jouer.

Nous cherchons tout d'abord un coup gagnant qui mettrait fin à la partie. Avec la même urgence aussi, nous cherchons à parer un coup prochain de l'adversaire qui serait un coup gagnant pour lui. Notez que dans ces deux cas, nous cherchons sur tous les axes du jeu, deux cases remplies identiquement, accompagnées d'une troisième case vide. Dans tous les cas, que ces deux cases soient remplies par nous ou par notre adversaire, nous DEVONS occuper la troisième !

Ensuite, si notre première recherche n'aboutit pas, nous cherchons à effectuer un coup pertinent. Parler de tactique ici semble un peu forcé. Nous ne saurions donner qu'une seule case dont l'occupation est tactique  : le centre du plateau. Cette position est la seule qui entre dans la constitution de 4 lignes.

Enfin, si le centre est déjà occupé, il y-a -t'il autre chose à faire que de se placer sur la première case vide ? Certes un algorithme plus pertinent jouerais ce coup en fonction de celui de l'adversaire. Notre jeu deviendrait ainsi imbattable. Mais paradoxalement il ne nous semble pas inintéressant que notre jeu ait cette faiblesse. Qui voudrait jouer avec l'assurance qu'il va perdre ?

Notre fonctions jeuAuto() devra donc procéder de même. Chercher d'abord à occuper une place nécessaire (coup gagnant ou coup défensif obligé), chercher en un deuxième temps s'il est possible d'occuper la case centrale, enfin en désespoir de cause, occuper la première cellule vide. Notre fonction peut être décomposée en trois sous fonctions que nous nommerons : UnCoupObligé(), LaCaseCentrale(), UneCaseVide(). Nous ferons en sorte que ces fonctions renvoient une valeur "true" ou "false" afin de pouvoir écrire :

function jeuAuto() {
if ( UnCoupObligé() ) { }
  else {
       if ( LaCaseCentrale() ) { }
     else {
       UneCaseVide();
        return null
      }
   }
}

De la sorte, les deuxième fonction ne sera exécutée que si celle qui précède renvoie une valeur fausse. De même la dernière fonction de s'exécutera qu'à la condition que le centre est déjà occupé (la fonction LaCaseCentrale() renvoyant dans ce cas la valeur fausse).

Nous commençons par rédiger la fonction UneCaseVide(). Cette fonction appelée en dernier recours doit identifier la première case vide et y placer un "O" correspondant au coup joué par la machine. Toujours dans la balise <SCRIPT...> on insère maintenant les lignes suivantes :

function UneCaseVide() {
for (n=0; n<=8; n++) {            // pour chacune des 9 cases, on teste si elle est vide

if (document.toto.elements[n].value=="") {
      document.toto.elements[n].value="O";                //  auquel cas on s'y place
      return null                                                               //   et on sort de la boucle
     }
   }
}

La fonction qui vérifie si la case centrale est disponible et renvoie "false dans le cas contraire ne pose pas d'avantage de problème :

function LaCaseCentrale() {

if ( document.toto.elements[4].value=="" ) {         /*   Le cinquième objet du formulaire, indexé 4, est la cellule centrale */
      document.toto.elements[4].value="O";
    return true
     }
else { return false}        // en retournant "false, on donne lieu
}                                 // à l'exécution de la fonction UneCaseVide()

 

"Last but not least" la fonction UnCoupObligé() va nous contraindre à saisir beaucoup plus de code. Cette fonction doit très précisément prendre chacun des axes du jeu pour vérifier si s'y trouve deux valeurs identique (croix ou cercle) et une case vide. Dans cette configuration en effet, il FAUT jouer dans la case vide. La fonction renverra "false" si un tel état de fait n'est pas trouvé afin de laisser s'exécuter les fonctions suivantes LaCaseCentrale() ou UneCaseVide().

Il faut tester chaque axe du jeu c'est-à-dire qu'il faut observer plusieurs séries de cellules. Notre premier travail va consister à établir la liste de ces axes ou séries de cases. Nous nommerons ces 8 lignes ou séries possibles axeN.

function UnCoupObligé() {

                /* Création de 9 tableaux (axe1, axe2, axe3,...) de 4 entrées chacun (la première entrée étant indexée 0, nous ne l'utiliserons pas par commodité. */

for (n=1; n<=8; n++) { eval ("axe" + n +" = new Array(4)") }

 

//        population des tableaux axe1, axe2, axe3, séries horizontales

for (n=1; n<=3; n++) {
     axe1[n] = document.toto.elements[n-1]             /* rappel: l'élément Array est indexé depuis 0 */
}

for (n=4; n<=6; n++) {
   axe2[n-3] = document.toto.elements[n-1]
}

for (n=7; n<=9; n++) {
  axe3[n-6] = document.toto.elements[n-1]
}

 

//        population des tableaux axe4, axe5, axe6, séries verticales

for (n=1; n<=3; n++) {
  for (x=1 ; x<=3 ; x++) {
    eval ( "axe" + (x+3) + "[n] = axe" + n + "[x]")                  /* par exemple : axe"3+3" [2] = axe2 [3] * / 
  }
}

//      première diagonale 7ème axe

for (n=1; n<=3 ; n++) {
 eval ( "axe7[n]=axe" +n+ "[n]" )
}

//    population du huitième axe (du haut droit vers le bas gauche)
axe8[1] = axe1[3];
axe8[2] = axe2[2];
axe8[3] = axe3[1];

On dispose dès lors de 8 tableaux ou listes reprenant les axes ou lignes du jeu. Il nous est possible de tester une à une ces lignes afin de déterminer si l'une d'entre elles contient 2 valeurs identiques non vides et une troisième vide. Nous pourront ainsi agir en conséquence. L'algorithme est simple et pourtant efficace. Il pourra resservir à d'autre langage de programmation... ;-)

//     algorithme de test

for (n=1; n<=8; n++) {
  y = eval ("axe" + n)      /*    à chaque entrée dans la boucle "y" représente une ligne à tester  */

  if ( y[1].value == y[2].value && y[1].value != "" && y[3].value == "" ){
   y[3].value = "O";
   return true
   }

   if ( y[1].value == y[3].value && y[1].value != "" && y[2].value == "" ){
   y[2].value="O";
   return true
  }

 if ( y[3].value == y[2].value && y[2].value != "" && y[1].value == "" ){
   y[1].value="O";
   return true
  }
 }                     /* enfin si ce test des axes ne donne aucun resultat, la fonction retourne false */
return false
}

Notre jeu est prêt. Les fanatiques du morpions auront tôt fait de déceler les faiblesses de l'algorithme et de trouver les séquences gagnantes. B2,C1,C3 ou C1, A3, C3. Nous pourrions aménager aisément la fonction UneCaseVide() afin d'éviter à l'ordinateur une défaite mais il nous semble que notre jeu est plus intéressant ainsi.

 

 Utiliser le jeu sur votre site

Yazo.net vous offre d'afficher un jeu de morpions sur votre page perso sans écrire une ligne de JavaScript. Insérer seulement dans votre code HTML, à l'emplacement souhaité, la ligne suivante :

  <SCRIPT LANGUAGE="JavaScript" SRC="http://www.yazo.net/techniquest/morpions.js"></SCRIPT>

Prévoir 60 x 160 pixels minimun. Connexion rapide préferable ! Un exemple dans un cadre bleu :

<table bgcolor="blue">
<tr><td>
<script language="JavaScript" src="http://www.yazo.net/techniquest/morpions.js></script>
</td></tr></table>

Affichera sur votre homepage :

 

Dans un article ancien, le CrapoWeb présentait les notions nécessaires pour débuter en JavaScript.

Netscape propose le téléchargement du manuel de référence de JavaScript sur son site : http://developper.netscape.com/docs/manuals/
communicator/jsguide4/index.htm