Concevoir un jeu d’Echecs en VBA

Première architecture en terme d’échiquier, joueur, pièce et case : Type, With, "."

Première architecture en terme d’échiquier, joueur, pièce et case : Type, With, "."


Comment aborder la conception d’un jeu d’Echecs ?

On peut concevoir une architecture autour de quatre notions : l’échiquier, la case, la pièce et le joueur.

Architecture du jeu d’Echecs : approche statique

Modèle statique du jeu d'Echecs

Partons de l’échiquier Board qui sera la racine de notre architecture. Détaillons chacun de ses champs :

Un Board c’est d’abord un tableau de cases : arrSquare(). On utilise le préfixe "arr" (array) pour le distinguer d’une Function qui a également des parenthèses en fin de nom mais pour la liste de ses paramètres.

Il y a deux joueurs playerWhite et playerBlack. Le joueur qui a le trait est playerToPlay.

Sur la feuille de partie papier, on trouve le n° de coup turnNo et la liste des coups movesHistory.

En VBA on parle de collection plutôt que de liste.

La collection movesRedoList servira pour sauvegarder les coups supprimés de movesHistory quand on navigue en sens inverse vers le début de la partie.

isGameOver pose la question la partie est-elle terminée ?

Le résultat final de la partie est dans resultGame.

La position de départ de la partie est au format FEN dans le cas où le PGN commencerait dans une position donnée.

Revenons sur le tableau de cases : arrSquare(256) :

On indique la dimension maximum (16 * 16 voir L’échiquier &H88) entre parenthèses. Le 1er élément du tableau a pour indice 0.

Une case a un n° appelé ordinal dont on déduit les coordonnées fileSqr et rankSqr.

La couleur de la case ne sert pas que pour l’échiquier GUI. Elle est utile pour identifier la couleur des Fous.

Sur une case, on place une piece.

Une piece a un type : Roi, Dame, Tour, Fou, Cavalier, Pion.

Une piece a une couleur isWhite = True pour Blanche, False pour Noire.

Une piece appartient au joueur player qui a pour adversaire opponent.

Une piece est sur une case que l’on identifie par un ordinal.

Finalement un joueur Player a une couleur isWhite, la collection de ses pièces colPieces et des pièces adverses capturées colCaptures.

La caractéristique d’un joueur est le déplacement de ses pions en terme d’offset à ajouter à l’ordinal de la case de départ du pion.

Finalement le dernier champ d’un Player est : Le Roi de ce joueur a-t’il roqué hasCastled ?


Type de pièces, leurs valeurs, type de coups et résultat du jeu

Ajoutons de nouvelles constantes dans le module ModChess montrant que le projet se concrétise progressivement :

Public Const typeKing As Byte = 1
Public Const typeQueen As Byte = typeKing + 1
Public Const typeRook As Byte = typeQueen + 1
Public Const typeBishop As Byte = typeRook + 1
Public Const typeKnight As Byte = typeBishop + 1
Public Const typePawn As Byte = typeKnight + 1

Public Const valuePawn = 100
Public Const valueKnight = 300
Public Const valueBishop = 325
Public Const valueRook = 500
Public Const valueQueen = 975
Public Const valueKing = 1000

Public Const moveTypeNull As Byte = 0
Public Const moveStandard As Byte = moveTypeNull + 1
Public Const movePromotionQueen As Byte = moveStandard + 1
Public Const movePromotionRook As Byte = movePromotionQueen + 1
Public Const movePromotionBishop As Byte = movePromotionRook + 1
Public Const movePromotionKnight As Byte = movePromotionBishop + 1
Public Const moveEnPassant As Byte = movePromotionKnight + 1
Public Const moveCastleQueenSide As Byte = moveEnPassant + 1
Public Const moveCastleKingSide As Byte = moveCastleQueenSide + 1

Public Const statusNormal As Byte = 1
Public Const statusInCheck As Byte = statusNormal + 1
Public Const statusDrawStaleMate As Byte = statusInCheck + 1
Public Const statusInCheckMate As Byte = statusDrawStaleMate + 1

Public Const strAbbreviationPiecesEn = "KQRBNP"
Public Const strAbbreviationPiecesFr = "RDTFCP"

Définir une constante par incrément de la précédente constante nommée permet d’insérer une nouvelle constante où on veut sans devoir renuméroter les anciennes.


Implémentation VBA du modèle statique du jeu d’Echecs

Le codage de l’architecture statique du jeu d’Echecs peut se faire avec des types utilisateurs (user defined type), c-a-d définis par le développeur grâce au mot-clé Type ... End Type.

Dans la fenêtre projet du VBE, cliquez avec le bouton droit de la souris sur "Modules" ou "ModChess", contextuel menu "Insérer" > "Module".

Dans la fenêtre "Propriétés", renommer "Module1" en "ModBoard".

Dans la fenêtre d’Edition de ModBoard :

Option Explicit
Const board0x88Height = 16
Const board0x88Width = 16

Type PlayerType
    isWhite As Boolean
    colPieces As Collection
    colCaptures As Collection
    PawnForwardOffset As Integer
    PawnForward2Offset As Integer
    PawnAttackRightOffset As Integer
    PawnAttackLeftOffset As Integer
    hasCastled As Boolean
End Type

Type PieceType
    typePiece As Byte
    isWhite As Boolean
    player As PlayerType
    opponent As PlayerType
    ordinal As Byte
End Type

Type SquareType
    ordinal As Byte
    fileSqr As Byte
    rankSqr As Byte
    isWhite As Boolean
    piece As PieceType
End Type

Type BoardType
    arrSquare(board0x88Height * board0x88Width) As SquareType
    playerWhite As PlayerType
    playerBlack As PlayerType
    playerToPlay As PlayerType
    turnNo As Integer
    movesHistory As Collection
    movesRedoList As Collection
    isGameOver As Boolean
    resultGame As Integer
    strFENStartPosition As String
End Type

Public boardMain As BoardType

Après la déclaration des nouveaux types, on déclare la variable boardMain de type BoardType.

C’est la première fois que l’on déclare une variable à l’extérieur d’une Function ou Sub.

Qui dit à l’extérieur dit que la variable n’est pas locale à une procédure. La variable est donc globale ce qui nous permettra de l’utiliser dans la fenêtre d’Exécution immédiate même après l’exécution de la procédure d’initialisation NewGame() :


Initialisation de la structure globale boardMain

Sub NewGame()
Dim ordSqr As Byte ' ordinal

    ' Le joueur des Blancs
    boardMain.playerWhite.isWhite = True
    boardMain.playerWhite.PawnForwardOffset = 16 ' e2-e3
    boardMain.playerWhite.PawnForward2Offset = 32 ' e2-e4
    boardMain.playerWhite.PawnAttackRightOffset = pawnWhiteAttackRightOffset
    boardMain.playerWhite.PawnAttackLeftOffset = pawnWhiteAttackLeftOffset
    
    ' Initialisation des 64 cases
    For ordSqr = a1 To h8
        If ((ordSqr - a1) And &H88) = 0 Then
            With boardMain.arrSquare(ordSqr)
                .ordinal = ordSqr
                .fileSqr = FileFromOrdinal(ordSqr)
                .rankSqr = RankFromOrdinal(ordSqr)
                .isWhite = (.fileSqr And 1) Xor (.rankSqr And 1)
            End With
        End If
    Next
    
    ' Création des huit pions des Blancs
    For ordSqr = a2 To h2
        With boardMain.arrSquare(ordSqr).piece
            .ordinal = ordSqr
            .isWhite = True
            .player = boardMain.playerWhite
            .opponent = boardMain.playerBlack
            .typePiece = typePawn
        End With
    Next
End Sub

On constate la répétition du préfixe "boardMain.playerWhite" pour chacun des champs du joueur des Blancs tel que sa couleur, l’avance d’un pion d’une case puis de deux cases, etc. Il est plus rapide en terme d’exécution de factoriser ce préfixe grâce à l’instruction With .. End With.

Dans la fenêtre d’Edition, remplacez l’initialisation du joueur Blanc par :

    With boardMain.playerWhite
        .isWhite = True
        .PawnForwardOffset = 16 ' e2-e3
        .PawnForward2Offset = 32 ' e2-e4
        .PawnAttackRightOffset = pawnWhiteAttackRightOffset ' e4xf5
        .PawnAttackLeftOffset = pawnWhiteAttackLeftOffset ' e4xd5
    End With

A l’intérieur du bloc With ... End With, chaque champ commence maintenant directement par le "." séparateur.

Aller à la fin du commentaire ’ e4xd5 et tapez ENTER pour créer une ligne vide.

Tapez deux TAB, pour s’aligner sur les précédentes instructions.

Expérimentez l’autocomplétion en ajoutant manuellement avant End With : le Roi des Blancs n’a pas encore roqué :

        .hasCastled = False
    End With

Saisir le "." plutôt que de faire un copier-coller.

Puis la première lettre "h" : hasCastled est sélectionné dans l’info-bulle.

Puis "=" le symbole d’affectation. HasCastled s’affiche tout seul suivi du "=".

L’autocomplétion propose False / True en info-bulle.

Choisir False avec flèche vers le bas. Valider par ENTER ou SPACE.

Notez que VBA va automatiquement insérer un espace blanc séparateur autour du "=".


Test hors rangée avec l’échiquier &H88

Dans l’initialisation des 64 cases dans le tableau boardMain.arrSquare(ordSqr), on retrouve le And avec la constante &H88 de l’échiquier 0x88.

Dans la fenêtre d’Exécution immédiate :

? ((h1 - a1) And &H88) = 0

True

h1 est bien sur l’échiquier.

? ((h1 + 1 - a1) And &H88) = 0

False

Dans le balayage horizontal d’une case vers la droite (offset +1), la Tour ne doit pas sortir de la première rangée au-delà de h1. Le test ((ordSqr - a1) And &H88) = 0 est peut-être moins parlant que (indFile >= 8) or (indRank >= 8) or (indFile < 0) or (indRank < 0) mais il est plus efficace.


Le premier pion blanc en a2

Lors de la création des huit pions dans la boucle For ordSqr = a2 To h2, dans le bloc par pièce With boardMain.arrSquare(ordSqr).piece, l’appartenance à un joueur .player = boardMain.playerWhite pose question. Car cette affectation va copier tous les champs du playerWhite. Une fois que l’on aura initialisé toute la structure globale boardMain, que peut-on en faire ?

Dans la fenêtre d’Exécution immédiate :

NewGame

Aucun résultat ne s’affiche car c’est une Sub.

? boardMain.arrSquare(a2).piece.typePiece

6

C’est le type d’un pion. Retrouvons son initiale :

? Mid(strAbbreviationPiecesEn, boardMain.arrSquare(a2).piece.typePiece, 1)

P

L’intérêt pédagogique est plus de saisir manuellement l’expression plutôt que de la copier-coller pour apprendre à utiliser l’autocomplétion quand on tape le séparateur "." avec ses propres types utilisateurs. Voir ci-dessus la saisie de hasCastled et, au tout début, Initiation à Excel VBA : la fenêtre Exécution.


En conclusion, on a décrit un modèle statique du jeu d’Echecs. L’utilisation de Types utilisateurs a permis de définir une première approche des champs de l’échiquier, joueur, pièce et case.

Pour rendre dynamique le modèle, il faudrait pouvoir lui intégrer des actions comme générer la collections des coups dans une position donnée ou jouer un coup et donc gérer la collection des pièces du joueur et des pièces capturées.


Précédent : VBA & l’échiquier 0x88

Suivant : Paradoxe de la gallinacée et de l’œuf en VBA

Posté le 18 juillet 2011 par Matt