Le jeu Othello : première partie

Programmer un jeu - OTHELLO - Première partie

Découvrir Visual Basic Express Edition dans une première application.

Description

Ce texte, au travers de la construction d'un programme de jeu, explique les premiers pas dans Visual Basic Express Edition de Microsoft. Il faut chercher dans les programmes en basic pour avoir le source complet.

Introduction

Programmer un jeu comme Othello est, pour le novice, faire que l'ordinateur devienne le partenaire plus ou moins « doué » contre lequel il va falloir jouer de son talent pour gagner des parties ! Mais avant d'en arriver à cette finalité, il y a beaucoup d'étapes à franchir dans la connaissance de la programmation. Il faut d'abord appréhender un langage de programmation, puis traduire nos idées et nos raisonnements en algorithmes de programmes. C'est une étape nécessaire et fastidieuse car il faut isoler et conscientiser toutes les opérations implicites qui inondent notre pensée. Car, pour que l'ordinateur soit capable d'exécuter une tache, même toute simple en sa forme, il faut tout lui dire, lui expliquer tout ! Rien n'est évident pour lui...

Sur ces bases, dans un premier temps, nous allons réduire nos ambitions. Nous nous proposons seulement de réaliser un programme qui nous aide à jouer, en vérifiant le respect des règles, la légalité des coups et en affichant l'état courant du jeu, et laissant à deux partenaires humains le soins de jouer proprement dit, en choisissant, eux mêmes, les coups et la stratégie.

Pour réaliser ce projet, c'est Visual Basic qui est retenu comme outil et langage de programmation. Il existe une version Express Edition que l'on peut charger gratuitement sur le site Microsoft et qui fonctionne sur les différentes versions de Windows, de XP à Windows 7, de plus, cet outil existe en version française avec sa documentation incorporée. Les versions 2008 et 2010 sont actuellement disponibles, par exemple à l'adresse http://msdn2.microsoft.com/fr-fr/express/aa975050.aspx. La dernière version, en phase d'expérimentation, n'est pas forcément la meilleure pour un débutant. Il est plus sage de choisir la version précédente qui a déjà fait ses preuves, mais il faut la chercher sur le site avec plus de perspicacité !... Elle est aussi gratuite.

Si c'est une image ISO qui est trouvée et téléchargée, il est inutile de la graver sur un DVD pour faire l'installation, il faut télécharger gratuitement l'utilitaire « Daemon Tools » sur http://www.daemon-tools.fr . Installez cet utilitaire et ensuite montez l'image ISO de Visual Studio pour l'installer en final. Si vous avez assez de place sur votre disque dur, n'omettez pas les options permettant d'avoir aussi la documentation.

Une fois installée, pour accéder à l'application Microsoft Visual Basic, Il suffit de lancer la commande adéquate à partir du menu Démarrer.

Premiers contacts avec Visual Basic

Laissez le système se présenter puis, à l'aide du menu, lancer la commande Fichier, Nouveaux Projets... qui charge la fenêtre suivante :

Choisissez Application Windows Forms et définissez son nom : « Othello », puis validez en appuyant sur OK. Cette fonction permet de créer le squelette d'une application Windows qui se présente sous la forme d'un panneau de control.

Cette validation crée, entre autre, un panneau de contrôle « Form1 » que l'application est susceptible de présenter. C'est possible de le vérifier en lançant une première fois la génération par la commande Déboguer, Démarrer le débogage. Un panneau, dont le titre est « Form1 » est ainsi généré. Il ne comporte rien d'autre, mais toutefois, il est possible de le déplacer sur l'écran de l'ordinateur par capture avec la souris. Il est même possible de terminer son affichage par un clic sur le X situé dans le coin haut-droite.

Fort de cette première expérience, nous allons compléter et aménager notre première application en modifiant un peu notre « forme ». D'abord, nous devons redimensionner notre objet et lui affecter le bon titre et lui incorporer l'image de notre Othellier. Pour cette opération il faut faire apparaitre la fenêtre « propriété » correspondant à Form1. A cette fin, il faut activer le menu contextuel en cliquant avec la souris, touche droite, au milieu de la forme, puis choisir propriétés dans ce menu. Une table de propriétés, conforme à l'image ci-dessous, apparaît. Il ne reste plus qu'à la compléter en conformité avec le tableau ci-dessous. Ce tableau ne comporte seulement que les lignes à modifier, les valeurs par défaut des autres postes étant tout à fait acceptables.

Propriétés Observations
Désignation Valeur
Text Othello Titre de la forme
Size 416; 304 Taille fenêtre
BackgroundImage ... Cliquez sur les points
Backgro..eLayout None Une fois l'image

Dans notre mise à jour, il y a une petite difficulté avec le poste BackgroundImage où il faut d'abord cliquer une fois sur la case à compléter pour faire apparaître une touche avec des points, puis cliquer sur cette touche pour faire apparaître une nouvelle fenêtre « Sélectionner une ressource », qui va nous permettre de sélectionner l'image à placer dans notre forme. Dans cette nouvelle fenêtre, nous choisissons Ressource locale, Importer. Ce qui nous permet de sélectionner le fichier contenant notre image « BmOthellier.bmp ». Normalement, les fichiers contenant les images nécessaires à cette application sont fournies avec ce fichier d'instruction. Mais pour les puristes, il y a vers la fin de cet exposé un chapitre consacré au comment créer ces images. Dans un premier temps, il sera plus sage de se satisfaire des images déjà réalisées.

Il est à noter aussi que le poste suivant, BackgroundImageLayout, où il faut inscrire « none », permet d'éviter ainsi que l'image se démultiplie ou s'étende sur tout l'espace de la forme.

Toutes ces opérations réalisées, il est possible de visualiser le résultat en relançant la génération par la commande Déboguer, Démarrer le débogage.

Nos premiers efforts ont permis de personnaliser notre forme. Elle prend l'allure de notre réalisation finale. Il nous reste à la compléter en lui ajoutant les zones de dialogue et les boutons nécessaires, ce qui fait l'objet de la prochaine étape.

Zones de dialogue et boutons

Nous allons meubler notre face de différents objets, destinés soit à afficher des messages, soit à recevoir des commandes. Pour afficher les messages nous choisirons les objets de type TextBox et pour les commandes, des objets de type Button et de type NumericUpDown. Pour ces opérations, il faut utiliser l'éditeur graphique de Visual Basic. C'est normalement la fenêtre principale de l'application ; si elle n'apparaît pas, il faut cliquer sur l'onglet « Form1.vb [Design] » ou, en désespoir de cause, sur le menu Affichage, Concepteur.

Nous devons d'abord disposer sur la forme les trois objets de type TextBox. Pour chacun, il faut sélectionner l'outil TextBox dans la Boite à outils, puis ensuite dessiner un rectangle sur la forme à l'emplacement approximatif où l'on veut placer l'objet. On peut juger des possibilités de positionnement et d'alignement disponibles pour ces opérations. Dans notre cas, nous pouvons ignorer ces facilités car nous positionnerons ces objets avec précision par l'initialisation des propriétés (autre méthode). De la même manière, nous plaçons aussi six buttons et un NumericUpDown. En final, la fenêtre principale de Visual Basic peut ressembler à ceci :

Maintenant, il nous faut modifier individuellement les propriétés de chacun de ces objets créés pour obtenir la disposition exacte et la fonctionnalité souhaitée. Normalement, dans le cadre du développement d'un projet classique, le choix de la disposition des objets se fait plutôt avec les fonctions graphiques de Visual Basic, la modification des propriétés ne sert qu'à définir des fonctionnalités particulières.

Pour faire apparaître le tableau de propriétés spécifique à un objet, il suffit de sélectionner cet objet sur l'éditeur graphique. Si cela ne fonctionne pas, un clic de la touche droite sur l'objet fait apparaître le menu contextuel dans lequel on choisi la ligne propriété, normalement, la dernière de la liste. Les tableaux qui suivent indiquent les valeurs à modifier pour chaque objet.

Pour les TextBox

Propriétés TextBox1 TextBox2 TextBox3
Name Messages ScoreBlack ScoreWhite
Location 238; 12 238; 102 238; 128
Multiline True
ReadOnly True True True
Size 156; 84 156; 20 156; 20
TabStop False False False

Pour les Buttons

Propriétés Button1 Button2 Button3 Button4 Button5 Button6
Name BtnZero BtnFor BtnBack BtnMax BtnWrite BtnRead
Location 29; 236 75; 236 121; 236 167; 236 285; 195 342; 195
Size 40; 22 40; 22 40; 22 40; 22 52; 22 52; 22
Text "0 <<" ">>" "<<" Write Read

Pour le NumericUpDown

Propriétés NumericUpDown1
Name RecNb
Enabled False
Location 342; 223
Minimum 1
Size 52; 20
Value 1

Nos différentes mises à jour permettent d'obtenir le résultat suivant sur la forme affichée dans l'éditeur graphique de Visual Basic :

Si nous lançons l'exécution pour le test de l'état actuel de notre programme, nous obtenons une forme qui a pratiquement l'aspect définitif de notre application, il ne manque seulement ! Que les pions de départ sur le jeu, et aucun des boutons n'est actifs. C'est bien normal, car aucune ligne de programme n'est encore écrite ! Mais ce n'est qu'une illusion, car toutes nos initialisations ont déjà produit la construction d'un programme d'initialisation, opération qui s'est faite automatiquement par l'outil. Pour s'en convaincre, il faut sauvegarder l'application par la commande Fichier, Enregistrer tout, et rechercher dans les dossiers écrits, le fichier « Form1.Designer.vb ». Il se trouve dans un dossier nommé : Projets\Othello\. Une commande Affichage, Explorateur de solutions ouvre une fenêtre où tous ces fichiers créés apparaissent.

Une connaissance approfondie du tableau de propriétés

Après ces dernières mises à jour, nous avons fait un usage intensif des tableaux de propriétés des différents objets. Pourtant pour la suite de nos travaux, il nous faut accéder à d'autres informations que recèle ce type de fenêtres : Les événements. En effet chaque objet et capable de « déclencher » des événements en fonction de différentes situations internes ou externes. C'est à partir de ces événements que notre application va prendre vie et réagir !

En passant en revue les différents objets créés, les événements suivant sont ajoutés :

Sur la forme elle même, Form1, on ajoute les événements : Load, Paint et Mousse_Click.

Note : pour créer l'événement il faut faire un double click sur son nom dans le tableau. Cette opération crée un nouveau nom, le nom de la procédure qui sera activée par cet événement, comme par exemple Form1_Load lorsqu'on clique sur load. A chacune de ces opérations, Visual basic présente la source du programme avec cette nouvelle procédure ajoutée. Il faudra re-sélectionner « Form1.vb [Design] » pour revenir à l'affichage du tableau de propriétés/événements de l'objet suivant.

Pour les six boutons, il faut par le même procédé ajouter à chacun l'événement Click. Cet événement peut aussi être créé par un double click sur l'objet choisi dans l'éditeur graphique. Comme on peut le remarquer dans le cours de cet exposé, un effort particulier a été mené pour rendre l'outil convivial et il existe toujours plusieurs manières pour obtenir un même résultat !

Enfin, c'est le tour de l'objet NumericUpDown1, RecNb, il faut ajouter l'événement Click. Cet objet sera utilisé par la suite pour désigner le numéro de la partie à lire dans la Base de Données.

Une autre manière d'accéder à nos informations, c'est à partir de la fenêtre « Form1.vb » telle que présentée ci-dessous :

Dans le haut de la fenêtre, se trouve deux ComboBox permettant d'accéder à toutes les informations nécessaires. Celle de droite permet de naviguer dans un premier niveau d'objets, dans notre cas :
Général, Form1, <Form1_événements>, puis la liste des objets implémentés dans Form1.

Celle de gauche contient le détail de l'item sélectionné à droite. Si nous sélectionnons un objet à droite, il apparaît à gauche la liste des événements possibles pour cet objet, avec en caractères gras ceux qui ont déjà un traitement, et miracle, si on clique dessus, l'éditeur se positionne au code correspondant. Un clic sur un nom non encore en gras, crée les lignes de code adéquates à la suite dans Form1.vb. Enfin, il faut remarquer que la sélection d'un objet à droite nous ouvre la liste de gauche sur le premier événement traité (s'il en existe un) !

Si dans la liste de droite nous sélectionnons Form1, à droite apparaît entre autre la liste des différents Sub et Function déjà programmés dans Form1 et bien sûr un clic sur un de ces noms positionne l'éditeur à ce nom dans le code source.

Des objets supplémentaires

Il est nécessaire de compléter notre forme d'objets particuliers car ils ne se voient pas directement. Pourtant on les trouve, comme les précédents, dans la Boite à outils. Lorsqu'on les dépose sur la forme, ils glissent pour se placer dans une zone en dessous. Il s'agit de ImageList, OpenFileDialog et SaveFileDialog. Il nous en faut un de chaque dans notre application :

Comme les autres objets, des propriétés sont associées. Le tableau suivant indique les initialisations à exécuter :

Propriétés ImageList1 OpenFileDialog1 SaveFileDialog1
Image ...
ImageSize 23; 23
Transparent Color
Filter Othello (*.oth)|*.oth|Base de données (*.wtb)|*.wtb|Texte (*.txt)|*.txt|All (*.*)|*.* Othello (*.oth)|*.oth|Texte (*.txt)|*.txt|All (*.*)|*.*
FileName Othello.oth Othello.oth

Pour la case ImageList1, Image, le clic sur la case, puis sur les petits points ouvre la fenêtre de Editeur de collections Images dans lequel il faut Ajouter, dans l'ordre, les fichiers d'images : BmOthelloEmpty.bmp, BmOthelloBlack.bmp, BmOthelloWhite.bmp. Ces fichiers complètent la fourniture comprenant déjà BmOthellier.bmp, chargé pour l'image de fond du jeu.

Ces nouvelles images vont nous permettre de placer ou effacer les pions sur le jeu.

Note : pour la case Filter, dont le contenu est assez ésotérique et pour éviter les erreurs, il est plutôt judicieux de recopier le texte par un copier-coller. Pour mémoire ce contenu est composé de différents champs séparés par le caractère '|' (barre verticale). Ces champs vont par pair : le premier est susceptible d'être affiché dans la fenêtre type de fichier de la boite de dialogue, le second décrit l'extension de fichier correspondant à ce type. Par exemple pour les fichiers de type texte nous avons :
Texte (*.txt)|*.txt

Le premier champ est un commentaire, destiné à être affiché, le deuxième champ décrit l'extension utilisée (.txt). Le formalise de cette partie est imposé.

Début de la programmation

Les choses sont prêtes maintenant pour attaquer la phase de programmation proprement dite. Dans la réalité de la conception et de la réalisation, ces phases sont souvent plus mélangées, car le besoin de disposer d'un nouvel objet ou de modifier ses propriétés peut résulter de l'évolution de la programmation. Il n'est pas nécessaire de tout prévoir avant !

Les opérations de création d'événements ont façonné le squelette du programme de l'application. On peut le voir par un clic sur l'onglet Form1.vb. Si cet onglet n'existe pas encore, on peut double cliquer sur la partie bleue de la forme affichée dans la fenêtre Form1.vb [Design] pour le faire apparaître.

'
Public Class Form1


Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

End Sub


Private Sub Form1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint

End Sub


Private Sub Form1_MouseClick(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseClick

End Sub


Private Sub BtnZero_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnZero.Click

End Sub


Private Sub BtnFor_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnFor.Click

End Sub


Private Sub BtnBack_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnBack.Click

End Sub


Private Sub BtnMax_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnMax.Click

End Sub


Private Sub BtnWrite_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnWrite.Click

End Sub


Private Sub BtnRead_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles BtnRead.Click

End Sub


Private Sub RecNb_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles RecNb.ValueChanged

End Sub

End Class

Le travail du programmeur est donc de compléter l'espace vide entre les lignes : Private Sub... et End Sub en fonction de la cause de l'événement.

Bien sûr, les actions risquent d'être complexes et nécessitent de nombreuses lignes de programme. Pour améliorer la lisibilité, il faut découper chacune de ces actions en sous actions qui seront décrites dans des structures Sub ... End Sub, décomposées éventuellement, elles aussi. Une règle de bonne programmation étant que la structure de l'événement ne comporte qu'une ou deux lignes de programme et qu'une structure Sub...End Sub ne comporte pas plus d'un vingtaine de lignes. Cette stratégie permet de faire apparaître trois zones dans le programme (l'espace entre Public Class Form1 et End Class) :

  • Zone de déclarations, définissant les types et les noms des variables globales utilisées
  • Zone de programme comportant les différentes Sub, décrivant les traitements et sous-traitements
  • Zone des événements.

Rien n'interdit de mélanger ces zones, sinon que le programme est moins lisible. Car à l'usage un programme est écrit une fois mais il est lu un grand nombre de fois, lors de la mise au point et les évolutions.

Le squelette, tel que présenté ci dessus, ne représente que la troisième zone, la zone événements ; il reste donc à composer les deux premières, et bien sûr, compléter la troisième.

Afficher la position initiale du jeu

La Form1 initialisée par les opérations précédentes est incomplète, il lui manque les quatre pions de départ sur la piste du jeu. C'est par cette fonction que démarre notre exercice de programmation.

Dans ce but, la première idée serait de dessiner simplement les pions sur notre Othellier à la bonne place car nous disposons, grâce à l'objet ImageList, d'une procédure capable de cette action :

ImageList.Draw(g As System.Drawing.Graphics, pt As System.Drawing.Point, index As Integer)

Pour mémoire, le premier nom indique le nom de l'objet, puis attaché au nom suivant par un point pour indiquer qu'il s'agit d'une composante, suit le nom de la fonction ou la procédure (action souhaitée), dans notre cas, dessiner (Draw), puis suivent, entre parenthèses, les paramètres nécessaires à l'action. Dans notre cas, c'est le sur quoi on veut dessiner, puis l'endroit (le point x, y) et enfin un indice désignant le numéro de l'image à dessiner, contenue dans ImageList. Dans notre cas, c'est l'une des trois images (BmOthelloEmpty.bmp, BmOthelloBlack.bmp, BmOthelloWhite.bmp) que nous avons déjà chargées dans l'objet. Si le nom du départ (nom objet) est omis, cela signifie que nous faisons référence à Form1, donc les programmes écrits dans Form1 (par nous !... entre autre), ou à une fonction globale système !

Pour le premier paramètre, il est facile d'obtenir un objet « Graphics » correspondant à la fenêtre d'affichage, la forme que nous venons d'initialiser : g = Me.CreateGraphics(). Pour le deuxième paramètre, il faut calculer le point en position xy pixels où nous voulons placer notre image de pion (le coin haut-gauche). Pour le troisième paramètre, c'est l'indice dans ImageList de l'image à afficher. D'après l'initialisation faite plus haut : 0 = BmOthelloEmpty.bmp, 1=BmOthelloBlack.bmp et 2=BmOthelloWhite.bmp.

Cette première idée pourrait être acceptable, si l'intention finale n'était pas d'afficher le jeu avec ses 64 cases, et qui peuvent évoluer à chaque coup. Il serait donc intéressant de disposer d'un tableau à deux dimensions (ligne, colonne) où chaque case contiendrait la couleur du pion à afficher. Il faudrait alors disposer d'un sous-programme capable d'afficher ce tableau.

La seule objection, à cette solution, qui sur le plan théorique est satisfaisante, c'est sa lourdeur sur le plan pratique, parce que pour désigner une place sur l'Othellier il faut deux variables x et y. Il serait plus efficace d'utilser un tableau à une dimension de 64 cases, où les lignes de 8 cases sont rangées à la suite.

Nos dernières périgrinations permettent de concevoir une Function (c'est une Sub qui retourne un paramètre) qui convertisse un Index (0 .. 63), représentant le numéro de la case du jeu, en une position en xy pixels sur l'Othellier. Ensuite, une Sub, constituée d'une boucle de 64 pas, recopie le contenu de notre table de jeu dans l'affichage et permet d'afficher un état quelconque du jeu. Enfin il ne reste qu'à concevoir une dernière Sub capable d'initialer les 64 cases d'état du jeu en position initiale et le tour est joué !

Déclarations nécessaires pour cette phase

Pour rendre plus lisible les programmes, comme nous avons attribué des noms plus explicites aux objets, nous singulariserons les différentes variables nécessaires. Nous utilisons l'Enumération Index pour désigner le nom des cases du jeu et l'Enumération Piece pour le contenu d'une case. L'état du jeu est mémorisé dans un tableau baptisé « table » de 65 cases, la dernière étant réservée pour désigner le hors jeu dans nos opération de déplacement qui nous intéresseront dans les chapitres suivants.

'
Dim g As Graphics

' position Othellier, en pixels, dans l'image (et dans Form1)
Const ofsX = 17, OfsY = 17, oWidth = 208, oHeight = 208
'======================================================================

' Etat du jeu
Enum Index As Byte ' positions caracteristiques sur l'Othellier
a1 = 0 ' premiere case du jeu
h8 = 63 ' derniere case du jeu
d4 = 8 * 3 + 3 ' cases centrales de depart
d5 = 8 * 3 + 4
e4 = 8 * 4 + 3
e5 = 8 * 4 + 4
ovf = 64 'valeur utilisee pour signifier le bord du jeu, hors Othellier
End Enum

Enum Piece As Byte ' differentes valeurs possibles d'une case du jeu
Empty
Black
White
Border ' valeur de la case Index.ovf
End Enum

' Etat courant du jeu : etat des 64 cases du jeu, plus une, reservee pour signaler
Dim table(Index.ovf) As Piece 'le bord de l'Othellier lors des déplacements
Dim Player As Piece 'la couleur du pion qui doit jouer (Piece.Empty = partie terminée)
'======================================================================

Sous programmes et fonctions nécessaires

'
    ' Conversion d'un index (dans la table de jeu) en position dans l'affichage
    Function ToPosition(ByVal idx As Index) As Point
        If idx = Index.ovf Then
            ToPosition.X = -1 : ToPosition.Y = -1
        Else
            ToPosition.X = ((idx Mod 8) * oWidth) \ 8 + ofsX
            ToPosition.Y = ((idx \ 8) * oHeight) \ 8 + OfsY
        End If
    End Function

    ' Initialisation de la table du jeu
    Sub InitTable()
        For i As Index = Index.a1 To Index.h8
            table(i) = Piece.Empty
        Next
        table(Index.d4) = Piece.White
        table(Index.d5) = Piece.Black
        table(Index.e4) = Piece.Black
        table(Index.e5) = Piece.White
        table(Index.ovf) = Piece.Border

        Player = Piece.Black        ' Noir commence toujours la partie
    End Sub

    ' Affiche le jeu
    Sub Display()
        ' Affiche l'etat des cases du jeu
        For i As Index = Index.a1 To Index.h8
            ImageList1.Draw(g, ToPosition(i), table(i))
        Next
    End Sub

Activation de ces programmes (événements)

Enfin, pour donner vie à notre application, il faut donner l'ordre d'exécution des Sub qui nous intéressent. Nous choisissons l'événement Form1_Load pour initialiser la table du jeu et l'événement Form1_Paint pour afficher le jeu.

'
Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        InitTable()
End Sub

Private Sub Form1_Paint(ByVal sender As System.Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
        g = Me.CreateGraphics()
        Display()
End Sub

Après avoir introduit toutes ces informations dans Form1.vb, entre Public Class Form1 et End Class, il suffit de lancer l'exécution par notre commande habituelle pour voir s'afficher le jeu avec les pions en position départ.

Placer un pion sur l'Othellier

Il ne faut pas réfléchir bien longtemps pour réaliser que cette opération n'est pas simple. Car, qui dit placer un pion, dit aussi retourner les pions adverses qui doivent être retournés. L'art de l'informaticien, est que, lorsqu'il est confronté à un problème complexe, il sait le décomposer en problèmes plus simples qu'il est capable de résoudre ensuite séparément.

La difficulté de notre cas est de savoir se déplacer dans une direction donnée, à partir d'un point quelconque du jeu et dans l'une des huit directions possibles, pour savoir si les conditions sont remplies pour retourner des pions dans cette direction.

L'idée de départ, pour résoudre ce problème, est de concevoir une fonction qui donne le numéro de la case suivante dans le jeu en fonction de la case de départ et de la direction. A chaque appel, cette fonction avance d'une case dans la direction choisie, ce qui permet, au fur et à mesure, de faire le test de la couleur du pion rencontré, s'il existe !... Bien sûr, il faut aussi considérer le cas où la case de départ est sur un bord et que la direction nous amène à sortir du jeu !

Nous choisissons un type de variable pour signifier la direction d'un déplacement :

'============================================================
    ' Mouvements sur l'Othellier dans les 8 directions
    Enum Direction As Byte  ' Les 8 directions de mouvements sur l'Othellier (0..7)
        North
        NorthEast
        East
        SouthEast
        South
        SouthWest
        West
        NorthWest
        last = 7
    End Enum

Ce qui nous permet de définir la fonction assurant le déplacement sur l'Othellier :

    ' Calcul de la position suivante pour un deplacement sur l'Othellier
    Function NextPos(ByVal idx As Index, ByVal dir As Direction) As Index
        'suivant la position de depart et la direction, on peut sortir de l'Othellier
        Select Case dir
            Case Direction.North
                NextPos = If(idx \ 8 = 0, Index.ovf, idx - 8)

            Case Direction.NorthEast
                NextPos = If(idx Mod 8 = 7 Or idx \ 8 = 0, Index.ovf, idx – 8 + 1)

            Case Direction.East
                NextPos = If(idx Mod 8 = 7, Index.ovf, idx + 1)

            Case Direction.SouthEast
                NextPos = If(idx Mod 8 = 7 Or idx \ 8 = 7, Index.ovf, idx + 8 + 1)

            Case Direction.South
                NextPos = If(idx \ 8 = 7, Index.ovf, idx + 8)

            Case Direction.SouthWest
                NextPos = If(idx Mod 8 = 0 Or idx \ 8 = 7, Index.ovf, idx + 8 - 1)

            Case Direction.West
                NextPos = If(idx Mod 8 = 0, Index.ovf, idx - 1)

            Case Direction.NorthWest
                NextPos = If(idx Mod 8 = 0 Or idx \ 8 = 0, Index.ovf, idx – 8 - 1)

        End Select
    End Function

Comme déjà prévu, la 65eme case de notre Othellier permet de signifier les cas où le déplacement nous fait sortir du jeu. Une valeur de case est même prévue à cet effet : Piece.Border.

Comme on peut le constater, l'utilisation d'un tableau à une dimension simplifie plutôt les calculs, même si l'analyse du problème est un peu plus ardue que dans un tableau à deux dimensions. Les mouvements Est-Ouest se font par variation d'une unité alors que les mouvements Nord-Sud introduisent des variations de 8 unités (Est=+1, Ouest=-1, Sud=+8, Nord = -8).

Pour continuer notre jeu, il faut aussi pour assurer le retournement des pions, une fonction capable d'inverser la couleur des pions :

    ' Retournement d'un pion
    Function NotColor(ByVal pion As Piece) As Piece
        Select Case pion
            Case Piece.Black
                NotColor = Piece.White

            Case Piece.White
                NotColor = Piece.Black

            Case Else ' pour les autres etats pas de changement
                NotColor = pion
        End Select
    End Function

Fort de ces premières briques, nous pouvons envisager la fonction qui teste la validité d'un coup, dans un premier temps, pour une case et une direction données, puis pour une case et toutes les directions :

'
' Recherche si, dans une direction donnee, le retournement de pions adverses est possible
    Function Search(ByVal idx As Index, ByVal dir As Direction, ByVal pion As Piece) As Boolean
        Dim notPion As Piece = NotColor(pion)   ' couleur pion adverse
        'il faut, au moins, qu'a la premiere case parcourue, le pion rencontre soit adverse
        idx = NextPos(idx, dir)
        If table(idx)  notPion Then Return False
        Do   'cherche la fin des pions adverses dans la direction
            idx = NextPos(idx, dir)
        Loop While table(idx) = notPion
        'la case suivante, dans la direction, doit etre occupee par un pion de la couleur
        Return CBool(table(idx) = pion)
    End Function

    ' Teste si le jeu de cette case avec cette couleur de pion est possible
    Function CaseCheck(ByVal idx As Index, ByVal pion As Piece) As Boolean
        If table(idx)  Piece.Empty Then Return False 'la case n'est pas vide
        For dir As Direction = 0 To Direction.last 'dans les 8 directions
            If Search(idx, dir, pion) Then Return True 'cherche une ou le jeu est possible
        Next
        Return False
    End Function

Nous pouvons envisager enfin le programme capable de jouer une case et retourner les pions en conséquence si l'opération est légale :

'
    ' Place un nouveau pion dans le jeu (si possible), indique le nombre de pions gagnes
    Function Playing(ByVal idx As Index, ByVal pion As Piece) As Integer
        Playing = 1                         ' on compte le pion joue
        If table(idx) = Piece.Empty Then    ' La case est vide
            For dir As Direction = 0 To Direction.last ' pour les 8 directions
                If Search(idx, dir, pion) Then  ' Cette direction est valide
                    Dim i As Index = NextPos(idx, dir) ' premiere case dans la direction
                    'le premier est toujours a retourner, puisque la direction est valide
                    Do
                        table(i) = pion 'retourne les pions dans cette direction
                        Playing += 1      'compte les pions retournes
                        i = NextPos(i, dir) 'extrait la position suivante
                    Loop While table(i)  pion 'termine sur un pion de sa couleur
                End If
            Next
        End If
        'Si la case de depart (idx) est mal choisie, aucun pion n'est retourne
        If Playing > 1 Then table(idx) = pion Else Playing = 0
    End Function

Pour tester toutes ces nouvelles fonctionnalités, il faut relier tout ceci à l'événement « Form1_MouseClick » et convetir la position écran en index de position sur le jeu :

    ' Conversion d'une position dans l'affichage en index dans la table (de jeu)
    Function ToIndex(ByVal pos As Point) As Index
        pos.X -= ofsX : pos.Y -= OfsY
        If pos.X >= 0 And pos.X < oWidth And pos.Y >= 0 And pos.Y < oHeight Then
            ToIndex = ((pos.X * 8) \ oWidth) + 8 * ((pos.Y * 8) \ oHeight)
        Else
            ToIndex = Index.ovf
        End If
    End Function

    ' Place le pion sur l'Othellier a la suite d'un clic souris
    Private Sub Form1_MouseClick(ByVal sender As System.Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles MyBase.MouseClick
        Dim idx As Index = ToIndex(e.Location)
        If idx  Index.ovf Then Playing(idx, Player) : Display()
    End Sub

Nous pouvons tester notre nouvelle version, et miracle elle permet de placer un premier pion sur le jeu ! Mais, déception, ça ne marche pas pour les suivants car il faudrait changer la couleur du pion qui joue après chaque coup... Et pas dans tous les cas ! Car cela est impossible vers la fin d'une partie, où il existe des cas où le pion prévu ne peut pas jouer. Et puis, en plus de ce « bug », le jeu ne dit pas son état, il manque de moyens de communications !

Cette première phase de programmation appelle une remarque sur un aspect qui est manifeste dans la fonction Playing. La programmation est un exercice précis où on ne teste pas des variables plus que de raison, au cas où comme on dit ! Par exemple après le test de la fonction Search, on est sûr ensuite que la direction en cours est valide, qu'il y a des pions à retourner, ce qui permet de réduire les tests de la fonction de retournement. Une programmation approximative, se manifestant par des redoublements de tests, ne reflète qu'une analyse superficielle du problème à traiter. Elle sera source de fonctionnements erratiques et risque d'introduire de gros déboires lors des opérations de maintenance sans compter l'effet sur les temps d'exécution. L'ordinateur ne se trompe pas, il exécute strictement ce que le programme lui impose ! C'est une sacrée école de rigueur !

Puisque je parle de test, c'est le moment d'aborder l'aspect test d'un programme qui consiste à vérifier que ce dernier se comporte bien comme prévu ! Je réserve un chapitre à la fin de cet exposé sur ce sujet avec le parti à tirer de la documentation fournie. Dans un premier temps on peut apprécier la convivialité de l'outil de développement aves ses différentes aides à l'écriture et la correction immédiate des fautes de frappe...

Dans la seconde partie de ce tutoriel, nous allons voir, entre autre, comment améliorer ce programme, le rendre plus convivial, plus exploitable, et; pourquoi pas, faire jouer l'ordinateur...
Pour la suite, veuillez cliquer ICI.

Ce document intitulé « Le jeu Othello : première partie » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous