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 :
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.
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.
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);}.
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.
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.
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.
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 certains cas, il peut être intéressant de détecter la fin de partie par l'usage d'une fonction d'évaluation.
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.
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 :
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.
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.
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.