Back to the Antelmann.com Java Framework Tutorial
The com.antelmann.game package provides extensive support for implementing strategy games (e.g. Chess, Checkers, Go-moku, FourConnects, Nine Men's Morris or Othello), puzzles (e.g. TilePuzzle) or in fact any type of simulation (e.g. games involving luck, such as BlackJack or other non-games not covered here). It is not really suitable for implementing action games of any sort.
Every game consists of the game representation itself (represented by the GamePlay interface) - which embeds the rules of the game - and the actions that can be applied to the game (represented by the GameMove interface) to alter its status to eventually win, loose or draw a game.
The GamePlay interface defines all
functionality necessary to play a game (see the chapter
below on how to best provide your own implementation for this interface). A game always has a defined number of
player roles (not to be confused with Player
objects covered later), which are represented by plain integers. For example,
the game of chess has 2 player roles, one for white and one for black. Other
games may have a varying number of player roles, such as BlackJack, which may be
played with multiple players against the bank (where the bank itself may or may
not be regarded as a separate player role; it could be regarded as just a part
of the rules). The method numberOfPlayers()
will tell how many player roles exist in a given game.
At any given point in a game, you can perform certain actions that will alter the status of the game; these actions are represented by GameMove objects (see the next chapter for more details). The GamePlay interfaces provides methods to retrieve all legal moves for a given game and to determine whether a specific action (i.e. GameMove) is legal given the current game status.
Given a legal move, you can then advance the game by applying that move with makeMove(GameMove
move); there are also methods for undoing and redoing - allowing you
to move back&forth within a game (provided that the implementation at hand
supports all this functionality; in a game of luck, for instance, - like BlackJack
- you may choose to disable undo for obvious reasons).
If no more moves are available in a given game, the game is over. Independent
from whether the game is over or not, you can check whether there are any
winners for the game by calling getWinner(),
which returns an array of the game roles that have won the game (note that -
particular in games with more than two players - a game can go on even there are
already winners determined). To be able to rank among multiple players or rank
one player on how great the victory really was, you can also call the method getResult(int
playerRole).
An important feature of the GamePlay is the
ability to spawnChild(GameMove move),
which allows game tree search algorithms to easily construct entire game trees
with separate GamePlay objects as nodes that
don't interfere with each other. This also allows game tree search with
backtracking for game implementations that do not support undoLastMove().
Other than that, the GamePlay also provides everything else that is required to plug a game into the other functionality of the com.antelmann.game package, such as GUIs with various options, AI (artificial intelligence) players and network games.
The GameMove interface is fairly simple and just defines the very basics that allow the concept of a move in a game to be used with several different algorithms of other classes in the package (such as game tree searches like best-first-search; see GameUtilities for examples for such algorithms). There is also a default implementation available (see MoveTemplate), allowing you to just focus on the game specific properties of a move by extending the MoveTemplate. The main feature of a GameMove is that it is always played by a specific game role (e.g. in a chess game, every move is either made by white or by black), which is pretty much the case for any conceivable game, puzzle or simulation.
The easiest starting point for implementing a new game of your choice is to extend the class AbstractGame. It provides a useful skeleton implementation of the methods defined in the GamePlay interface that can be handled without domain knowledge of the game at hand. All you will have to provide then is the actual domain knowledge (i.e. the rules of the game along with an appropriate game representation) and you're all set.
The complete source code example of a Tick-Tack-Toe implementation is available here for your reference (note that the TickTackToe class embeds the GameMove class used in the game as a nested class). The code is fairly well documented (and the game itself is simple enough), so it should be straight forward to see what's required to implement your own game. Also, carefully read the documentation for the AbstractGame class. You can always take a look at other game implementation provided by several sub-packages of com.antelmann.game.
Once you've implemented just the game logic as described above, you can take immediate advantage of the rich functionality provided by the com.antelmann.game package and play the game with a GUI along with some initial AI Player opponent. All you have to do is to start your game with a JGameFrame (which extends javax.swing.JFrame):
new JGameFrame(new TickTackToe()).setVisible(true);
As you have not specified anything else at this point (like some intelligence for the AI opponents or some GUI components that specify more sophisticated visualization), JGameFrame will use a bunch of default classes that help to visualize the game (like JDefaultGame) and provide some basic AI (like RandomPayer). These defaults can in fact be applied to any game, but you can certainly do better than that given some domain knowledge about the game at hand.
Once you have your game implemented in form of a GamePlay class, you still have to tell the object explicitly what move is to be performed to play the game 'properly' (at this point you can use your implementation to play the game e.g. human against human, though - or just by yourself if your game is more like an implementation of a puzzle). To automate the GamePlay, you need to specify how a move is to be selected, if all you want to do is just to make some move (an example of such a concept was already mentioned above - the RandomPayer).
You can do just that by implementing specialized AI Player objects (a convenient template for doing so is TemplatePlayer, which already provide several game tree search options for intelligently selecting moves, so all you have to provide is a useful heuristic for the game) that allow you to play against/with worthy opponents even if no other human is available to play with you. The main focus for making the Player intelligent here is always to find that 'killer-heuristic' for the game at hand.
Once you have a Player implementation suitable for your GamePlay implementation, you can merge the Player AI together with your GamePlay object into an AutoPlay object.
The AutoPlay interface provides the basis for running automated simulations of a game with methods like autoMove(). The AutoPlay interface also introduces concepts that specify the desired level of intelligence to be used, such as in fact a game level or a desired response time. In essence, the AutoPlay interface maps each game role (represented by a plain integer) to a Player object that assumes the specified game role. A single Player object could very well play more than just one game role, too (say you played a card game and one player plays multiple hands). This is why all methods that refer to the game roles of a Player return an array of integer rather than just one integer. In many games, you may ignore that flexibility by just considering the first given role that a Player plays in an AutoPlay.
A reference implementation for the AutoPlay interface can be found in the class GameDriver, which provides you will everything you need to run an automated simulation of your GamePlay along with Player implementations.
You can provide specialized visualizations (e.g. some nice graphics representing the game within a JComponent that also may allow you to perform moves by using drag&drop with your mouse) through a GUI wrapper for your game by implementing JGamePlay (or you can extend JDefaultGame for convenience).
Once you have provided some useful implementations for JGamePlay and Player for your game, you can then run a specialized version of your GamePlay by passing your JGamePlay object to the JGameFrame constructor.
Some game implementations included in the framework allow in fact for some cool pre-programmed customizations, such as JChess, which includes a method to customize the appearance of individual chess figures.
(c) Holger Antelmann <info@antelmann.com>
- all rights reserved