Dérivation de la classe Game

La classe Game permet de spécifier le mode de représentation interne du plateau de jeu et de définir les coups légaux pour le jeu.
Les sous-classes suivantes doivent également être dérivées :

Game ou DuelGame ?

La classe DuelGame dérive de la classe Game. La différence de DuelGame réside dans le fait qu'elle est déjà adaptée pour des jeux de stratégie à 2 joueurs.
A cet effet, la sous-classe DuelBoard comprend un membre protégé Player[] players qui est un tableau de taille 2 comprennant les deux joueurs. Les méthode addPlayer(Player) et removePlayer sont déjà implémentées, il est inutile de les surcharger.
Par ailleurs, la gestion des tours de jeu est automatique : il n'est pas nécessaire de mettre à jour les tours de jeu dans les méthodes abstraites applyMoveImplement(Move) et cancelMoveImplement(Move).
Il est donc conseillé de dériver DuelGame et ses sous-classes DuelBoard, DuelBoard.DuelMove et DuelResult pour implémenter un jeu à 2 joueurs, sauf si l'on souhaite gérer manuellement les tours de jeu et l'ajout et la suppression des joueurs.


Classe Game.Board: Implémentation du plateau

La classe représentant le plateau de jeu dérivée de Game.Board doit contenir tous les éléments capables de décrire exhaustivement une situation de jeu.
Il peut être en particulier utile d'utiliser une structure de tableau pour représenter le plateau : il est alors conseillé d'utiliser la classe game.HashableArray implémentant un tableau d'objets avec calcul de valeur de hachage basés sur tous ses éléments et support du clonage.
Avant de se lancer dans l'implémentation du plateau, il est nécessaire de considérer que celui-ci devra obligatoirement être clonable (i.e. copiable dans le jargon de Java) : Java ne réalise pas de clonage profond automatique des objets manipulés, il sera nécessaire d'implémenter soi-même le clonage profond pour le plateau.

Initialisation du plateau

Pour initialiser le plateau, il est nécessaire d'implémenter la méthode void Game.Board.initialization(Map) : cette méthode permet d'initialiser les membres internes de la classe Board avec différents paramètres liés au plateau exprimés dans l'instance de Map passée en paramètre.
En effet, il existe généralement certaines variantes pour un même jeu : il peut donc être nécessaire de préciser certains paramètres qui peuvent être utilisés pour la construction du plateau et/ou pour la vérification des coups légaux.

Par exemple, pour un jeu type Puissance4, il serait possible d'utiliser les paramètres suivants :

Chacun est donc libre lorsqu'il implémente son jeu de penser à mettre en place différents paramètres afin d'autoriser des variantes.

Si un paramètre indiqué dans la table de hachage Map n'est pas valide par son type (clé "width" associée à un Double pour le Puissance4 par exemple) ou par sa valeur, une exception BoardInitializationException doit être levée. Cette exception peut également être levée si un paramètre obligatoire pour l'initialisation du plateau ne figure pas dans l'instance Map fournie.

Il est nécessaire d'implémenter également la méthode abstraite Map Game.Board.getDefaultParameters(). Cette méthode construit une instance Map contenant les paramètres par défaut pour le plateau de jeu.
Dans l'exemple du Puissance4, nous retournerions un objet Map avec les couples clé-valeur suivants : {"width"=new Integer(7); "height"=new Integer(6); "align"=new Integer(4);}.

Vérification de la légalité d'un coup

Avant d'accepter l'application d'un coup sur le plateau, il est nécessaire de vérifier sa légalité : c'est là qu'intervient la méthode boolean Game.Board.isLegalMove(Game.Board.Move) : cette méthode retourne true si le coup passé en paramètre est légal, false sinon.

Implémenter une telle fonction peut être plus ou moins facile selon les jeux appréhendés. Il peut même quelquefois être nécessaire de conserver au niveau de la classe Board des informations sur l'historique du jeu : ainsi par exemple aux échecs, le roque (déplacement simultané d'une tour et du roi d'un joueur) est un coup légal uniquement si la tour et le roi n'ont pas encore été déplacés depuis le début de la partie. D'où la nécessité de maintenir pour le plateau une variable booléenne indiquant si la tour et le roi on été joués pour chaque joueur.

Gestion des joueurs

Les méthodes abstraites void Game.Board.addPlayer(Player p) et void Game.Board.removePlayer(Player p) permettent d'ajouter ou de supprimer un joueur. Théoriquement la méthode addPlayer() doit être appelée au début de la partie par une instance de Arbiter (la classe arbitre) pour que le plateau ait connaissance des joueurs. La méthode removePlayer() doit être utilisée pour signaler l'abandon d'un joueur (cet abandon ne pouvant provenir que d'un joueur humain).
.
En cas de mauvaise utilisation de ces deux méthodes (rajout par exemple d'un joueur en cours de partie), une exception BoardInitializationException doit être levée.

Les méthodes addPlayer(Player) et removePlayer(Player) sont déjà implémentées par GameDuel.DuelBoard, il est alors inutile de les surcharger.

Application et annulation d'un coup

Un plateau dans un état donné doit pouvoir évoluer afin d'intégrer les coups joués réellement ainsi que les coups simulés par le moteur de jeu générique avec l'algorithme de recherche de coup optimal alpha-bêta. A cet effet il est nécessaire de pouvoir appliquer un coup voire annuler le dernier coup réalisé sur le plateau.

Méthode void Game.Board.applyMoveImplement(Game.Board.Move)

Cette méthode applique le coup passé en paramètre sur le plateau : un certains nombre de modifications doivent être réalisées sur le plateau pour intégrer ce coup.
Notons que cette méthode est protégée : elle n'est jamais appelée directement mais par l'intermédiaire de la méthode applyMove(Game.Board.Move) après qu'elle ait vérifié la légalité du coup par la méthode isLegalMove(Game.Board.Move). En conséquence, il est inutile de réaliser une vérification de légalité de coup dans la méthode applyMoveImplement(Game.Board.Move).
Il est nécessaire de modifier le membre turn afin d'indiquer le tour du prochain joueur devant jouer uniquement si l'on dérive Game.Board : en cas de dérivation de DuelGame.DuelBoard, il ne faut en aucun cas modifier soi-même le tour de jeu, cette modification étant réalisée par DuelBoard.

Méthode void Game.Board.cancelMoveImplement(Game.Board.Move)

Grâce à cette méthode, il est possible d'annuler le coup passé en paramètre (qui doit être le dernier coup passé sur le plateau).
Pour certains jeux, il est nécessaire de stocker certaines informations spécifiques dans l'objet Move afin de pouvoir annuler le coup. Par exemple aux échecs, si une pièce est capturée lors d'un coup, il est nécessaire de l'indiquer dans l'objet Move afin de pouvoir réintégrer la pièce sur l'échiquier lors de l'annulation du coup.
Le membre turn doit être modifié pour indiquer le prochain joueur devant jouer après annulation du coup. Par contre, en cas de dérivation de DuelGame.DuelBoard, il ne faut pas modifier manuellement le tour de jeu, la modification étant réalisée automatiquement par DuelBoard

Détection de fin de partie

La méthode boolean Game.Board.isOver() détecte la fin de partie à partir du plateau. Une partie est terminée lorsque l'état du plateau permet de déduire la victoire d'un des adversaires ou alors le match nul si plus aucun coup n'est possible.

Pour illustrer la détection de la fin de partie, nous prenons comme exemple le jeu d'échecs. Pour ce jeu, la détection de fin de partie est particulièrement complexe et peu intervenir dans les cas suivants :

Dans le cas des échecs, la fin de partie nécessite dans certains cas la décision d'un des joeurs : la demande de nullité devient alors un des coups possibles.

Dans certains cas, il peut être intéressant de détecter la fin de partie par l'usage d'une fonction d'évaluation.

Rendre le jeu jouable par le moteur générique

Afin que le moteur de jeu générique puisse jouer au jeu, il est nécessaire d'implémenter la méthode Game.Board.Move[] getLegalMoves() de l'interface Evaluable. Ainsi un plateau de jeu manipulable par le moteur générique doit obligatoirement implémenter l'interface Evaluable.

Lister les coups possibles

La méthode getLegalMoves() appelée depuis un plateau de jeu doit retourner un tableau de tous les coups jouables. L'algorithme d'exploration de l'arbre des coups parcourera l'arbre dans l'ordre des coups retournés : il est ainsi conseillé si possible de retourner un tableau comportant les coups potentiellement les meilleurs (killer moves) en tête. Par exemple, pour le jeu d'échecs, retourner les coups pouvant permettre la capture de pièces en premier lieu peut être intéressant.

Classe Game.Board.Move: implémention du coup

La classe Move a pour objectif de représenter un coup sur le plateau : il s'agit d'une action entreprise par un joueur lors de son tour (déplacer une ou plusieurs pièces, passer le tour, demander la nullité de la partie,...).

Il est important de prévoir la possibilité d'annulation du coup : à partir des informations sur le coup, il doit être possible de modifier le plateau pour intégrer le coup (évidemment) mais aussi, une fois le plateau modifié, de procéder à l'annulation du coup.
Il est donc nécessaire pour certains jeux d'indiquer deux types d'informations pour le coup :

Ces deux types d'informations doivent être spécifiés en deux temps : dans un premier temps, seule la commande du coup est utile. Ensuite, au moment de la vérification de la légalité du coup ou lors de son application sur le plateau, il sera nécessaire d'indiquer les conséquences de celui-ci.

Initialisation du coup

Une méthode abstraite doit être implémentée pour créer une classe dérivée de Game.Board.Move : il s'agit de Game.Board.Move.initialization(Game.Player,Map).
A partir de la donnée du joueur et des informations figurant dans l'instance de Map, le coup doit pouvoir être initialisé. Si des erreurs sont relevés dans les paramètres du map, une exception MoveWrongParametersException doit être levée.

Initialisation de Game

Lors du commencement de la partie, la classe Game doit être initialisée. Cette initialisation est réalisée grâce à la méthode void Game.initialization(Map).
Cette méthode doit créer le plateau de jeu (Game.Board) et lui passer les paramètres d'initialisation nécessaires par un map.

Classe Game.Result

La classe Game.Result permet d'indiquer des informations concernant l'achèvement d'une partie. Le programmeur a toute liberté pour indiquer les informations qu'il souhaite. Une information est néanmoins obligatoire : le joueur vainqueur (membre winner de la classe). Dans un cas de match nul, winner=null.
Dans la plupart des jeux, seul l'indication du vainqueur est intéressante, dans d'autres indiquer un score pour chaque joueur peut également être intéressant.

L'instance de Result correspondant au résultat du jeu doit être construite par la méthode Game.getResult() qui doit être implémentée dans la classe Game dérivée.


Félicitations, vous savez désormais implémenter un jeu en utilisant la librairie de Sgamaja. Cependant certaines optimisations sont possibles afin d'améliorer le parcours de l'arbre des coups par le moteur générique.
Il est ainsi conseillé de lire la page suivante : Optimisations pour la classe Game.


Revenir à l'introduction
2004, The Sgamaja project <http://sgamaja.sf.net/>