Tutorial 3: Connect 4 (beta 0.3)
|
|
Last Updated:
8th Aug, 2008, 20:01 GMT
|
|
Contents
|
|
|
|
|
Introduction
|
|
|
This tutorial shows how the turn-based game of
Connect 4
can be created using the JOGRE 0.3 API. This tutorial has been updated from the old
0.2.3 to the newer 0.3 release which makes creating the games much easier and more powerful than before.
Connect 4 is a game where two players take turns to drop coloured pieces down columns.
The aim of the game is for a player to create a 4 in a row pattern vertically, horizontally
or diagonally.
At the end of this tutorial you should have something which looks like the
following:
|
|
|
|
This tutorial illustrates each part of creating this tutorial using
source code listings and UML diagrams which show how each new class extends
from the JOGRE API. The key of the UML diagrams are as follows:
|
|
|
|
[ top ] |
|
|
Getting started
|
|
|
Before you start this tutorial ensure you have the "Jogre Source, Beta 0.3"
distribution extracted somewhere (e.g. c:\) which is available on the
download page and then built using
the ANT utility.
To build JOGRE, open a command prompt, change directory to /jogre and type "ant" to do a complete
build.
The game key for this tutorial is c4tutorial - this is named
to avoid confusion from the existing connect4 game.
|
|
In the previous tutorials all the basic framework source / folders / ANT scripts had to be coded by hand.
In beta 0.3 there exists a code generator which does this for you, all you have to supply is the game key mentioned previously.
The game generator is located in the /jogre/util directory and be run either from the command line (java -jar gamegen.jar)
or using a GUI front end. To run the GUI click on gamegengui.bat or gamegengui.sh (depending on OS) in the
/jogre/util directory. It should look something like the following: -
|
|
|
|
Ensure the game_id is set to c4tutorial and the directory is specified correctly e.g. c:\jogre\games. You
can adjust the optional parameters also. Click the Generate button and the all the basic source files, ANT scripts, Eclipse project
files etc should all be created for you in the /jogre/games/c4tutorial folder.
The game generator creates various folders and 17 files which every game uses. For this tutorial, these files are:-
- build.xml - Build file which ANT uses to compile source files and create applet jar file.
- .classpath - Classpath file which Eclipse uses.
- client.bat - Windows batch file for running client.
- client.sh - Script file for running client.
- .cvsignore - CVS ignore file.
- .cvsignore - CVS ignore file.
- game_labels.properties - Properties file for storing language strings
- game.properties - Game specific properties e.g. number of players etc
- .project - Project file which Eclipse uses.
- C4tutorialApplet.java - Java class for creating an applet.
- C4tutorialClientFrame.java - Java class containing main method for running application.
- C4tutorialController.java - MVC controller class i.e. handles mouse clicks etc.
- C4tutorialModel.java - MVC model class i.e. holds data describing game state.
- C4tutorialServerController.java - Server controller class.
- C4tutorialTableFrame.java - Java class containing code for table frame.
- C4tutorialComponent.java - MVC view class i.e. renders the connect 4 graphics.
- C4tutorialModelTest.java - JUnit test class for testing model.
The c4tutorial/images folder should contain the following 6 images (right click and select "Save Pictures As...").
|
|
c4tutorial_icon.gif |
jogre_title.gif |
c4tutorial_title.gif |
no_piece.gif |
red_piece.gif |
yellow_piece.gif |
|
|
|
|
|
|
|
|
Load up Eclipse IDE and import the Jogre API, Server and newly generally C4tutorial game.
To import the API for example click "File - Import - Existing Projects Into Workspace". Select
Next and ensure the "Select root directory:" is set to /jogre/api. When you click finish the API should be a new project in
Eclipse. Do the same for the Jogre server (root dir = /jogre/server) and c4tutorial game (root dir = /jogre/games/c4tutorial).
Eclipse should look like the following screenshot.
|
|
|
|
Our first step is to edit the important game.properties file which every game has in its base folder.
Ensure the c4tutorial game.properties looks like the following: -
|
|
game.properties
# Game ID
game.id=c4tutorial
# Must be a "colour.1" to "colour.x" where x = max.num.players
player.colour.0=255,0,0
player.colour.1=255,255,0
# Other colours
background.colour=210,210,230
title.colour=0,0,0
# Declare images
image.1=images/no_piece.gif
image.2=images/red_piece.gif
image.3=images/yellow_piece.gif |
|
|
The game.property file contains game properties including
the important game.id property which sets the game key. It also
includes the colours of players (connect 4 is red and yellow) and background
colours using 0-255 RGB values. Finally, since applet support
is in Beta 0.3 the images must be defined in the property files.
This is necessary as the images are read from an applet jar file.
In JOGRE there are 3 special images that each game must have. They are: -
- jogre_title.gif - Global title image that each game displayed at the top left hand of screen.
- <game_id>_title.gif (e.g. c4tutorial_title.gif) - Game specific title graphic displayed at top right hand side of screen.
- <game_id>_icon.gif (e.g. c4tutorial_icon.gif) - Small icon graphic for each game.
In this connect 4 tutorial we have 3 additional images and these must be defined in the game.properties file.
Now update the game_labels.properties (multiple for each language) to the following.
It is also located in the /jogre/games/c4tutorial folder.
|
|
game_labels.properties
# Client
game.label=C4tutorial
jogre.client.title=Jogre C4tutorial Client
jogre.client.welcome.message=Welcome to Jogre C4tutorial!
jogre.table.title=Jogre C4tutorial Table
# Must be a "player.label.0" to "player.label.x"
player.label.0=Red
player.label.1=Yellow |
|
|
The game_labels.property files contains six labels which the API makes use of.
Most of the properties are self-explanatory and simply
set the name of the client and the welcome message that a client recieves. The
player.label.x properties are the names
of the player when they sit down at a table. For Connect 4 these are "yellow"
and "red". Additional labels can be inserted here if labels are required in the
game.
|
|
[ top ] |
|
|
Creating a Server
|
|
|
In JOGRE Beta 0.3 a single server exists which can handle multiple games.
To ensure that the server will support a game the game id
(from game.properties) must be inserted in the
/jogre/server/server.xml file. It also defines the minimum and maximum
number of players for a game. For connect 4 these are both 2.
|
|
server.xml
<!-- List of supported games. -->
<supported_games>
<game id="battleships" host="true" minPlayers="2" maxPlayers="2" />
<game id="checkers" host="true" minPlayers="2" maxPlayers="2" />
<game id="c4tutorial" host="true" minPlayers="2" maxPlayers="2" />
<game id="chess" host="true" minPlayers="2" maxPlayers="2" />
<game id="dots" host="true" minPlayers="2" maxPlayers="2" />
...
</supported_games> |
|
|
[ top ] |
|
|
Running a Server
|
|
|
To run the JOGRE server execute the /jogre/server/server.bat (windows) or
/jogre/server/server.sh (linux) file.
|
|
|
|
If the c4tutorial line was inserted correctly into the /jogre/server/server.xml you should see the
line in the server output. The server is now ready to accept connections from clients.
|
|
[ top ] |
|
|
Running a client
|
|
|
When changes are made to the jogre properties files it is good practise to run an ANT script.
|
|
|
|
The game client can be run from the command prompt or through eclipse. First of all
restart the Jogre Server. The c4tutorial client can now
execute and connect to the server. This is achieved by going to the jogre/games/c4tutorial folder
and executing the client.bat / client.sh file. These files contain the following: -
java -classpath .;classes;..\..\api\jogre.jar org.jogre.c4tutorial.client.C4tutorialClientFrame
If you prefer to run the client straight from Eclipse then create click "Run - Run configurations" and create
the following config: -
|
|
|
|
If the client loads successfully then the client will appear with a logon screen.
Use a username from the the users.xml file (located in the
jogre/server/data/xml folder). The default usernames are bob, dave, sharon and john.
You should see something like the following:
|
|
|
|
As you can see the JogreTableFrame is blank and does not contain the
actual game. This is created in the next section.
|
|
[ top ] |
|
|
The Connect 4 MVC (Model/View/Controller)
|
|
|
The next step is to create the actual game of "connect 4" using the MVC
design pattern (see the tic-tac-toe
tutorial for more information on MVC) by editting 3 of our generated classes. hese classes are as
follows:
|
|
Location |
Class/Interface Name |
API Extends |
Synopsis |
org/jogre/c4tutorial/client |
C4tutorialModel |
JogreModel |
Model which holds the data for a game of Connect4. |
C4tutorial4Controller |
JogreController |
Controller which updates the model and listens to user input on the visual board. |
C4tutorialComponent |
AbstractBoardComponent (-> JogreComponent) |
Connect4 visual board component (view of the model). |
|
|
The following clickable image shows these three MVC classes as a clickable UML diagram.
|
|
|
|
These classes all extend classes from the JOGRE API. The
three classes will now be explained in more detail using their source code. Any items on the
source which are in bold are of some interest and will be discussed further.
|
|
C4TutorialModel.java
...
public class C4tutorialModel extends JogreModel {
// Declare constants to define model
public static final int COLS = 7;
public static final int ROWS = 6;
public static final int BLANK = -1;
public static final int RED = 0;
public static final int YELLOW = 1;
// model (2 dimensional int array)
private int [][] data = new int [COLS][ROWS];
/**
* Constructor which creates the model.
*/
public C4tutorialModel () {
super (GAME_TYPE_TURN_BASED);
reset ();
}
/**
* Reset the pieces.
*/
public void reset () {
for (int x = 0; x < COLS; x++)
for (int y = 0; y < ROWS; y++)
data [x][y] = BLANK;
refreshObservers(); // inform any graphical observers
}
/**
* Return data
*
* @param x
* @param y
* @return
*/
public int getData (int x, int y) {
return data [x][y];
}
/**
* Set data at specified point.
*
* @param x
* @param value
*/
public void setData (int x, int value) {
// Find the next available empty space
for (int y = ROWS - 1; y >= 0; y--) {
if (getData(x, y) == BLANK) {
data [x][y] = value;
refreshObservers(); // update any views on this model
return;
}
}
}
public XMLElement flatten() {
return null;
}
public void setState(XMLElement message) {}
} |
|
|
NOTE: If you are getting "type cannot be resolved" errors in this tutorial then imports need to be added.
To organise imports in Eclipse press <cntl> + <shift> + o.
The model class C4tutorialModel is similar to the TicTacToe
model in the previous tutorial. It's data is stored in a 2 dimensional integer
array of 7 x 6 (7 columns by 6 rows). The data on each of these points can
be either -1, 0 or 1 (empty, red or yellow). The class also contains a reset ()
method which wipes all the data. There is also getter
and setter methods. The set method takes a value (red or yellow) and a column. The method
starts at the bottom of the board and loops until it finds an empty place and gives
it a value. The refreshObservers () method (from JogreModel) is then called
which will refresh any component registered with this data i.e. C4tutorialComponent.
The next class discussed is the view part of the MVC architecture.
|
|
C4tutorialComponent.java
...
public class C4tutorialComponent extends AbstractBoardComponent {
// Declare constants which define what the board looks like
private static final int NUM_OF_COLS = 7;
private static final int NUM_OF_ROWS = 6;
private static final int CELL_SIZE = 50;
private static final int CELL_SPACING = 0;
private static final int BORDER_WIDTH = 0;
// Link to the model
protected C4tutorialModel model;
protected int curMousePoint = -1;
/**
* Constructor which creates the board.
*
* @param model
*/
public C4tutorialComponent (C4tutorialModel model) {
// Call constructor in AbstractBoardComponent
super (NUM_OF_ROWS, NUM_OF_COLS, CELL_SIZE, CELL_SPACING,
BORDER_WIDTH, 0, false, false, false);
this.model = model; // link to model
// set colours
Color bgColor = new Color (34, 34, 102);
setColours (bgColor, bgColor, Color.black, Color.white);
}
// Update the graphics depending on the model
public void paintComponent (Graphics g) {
// Draw the board (AbstractBoardComponent)
super.paintComponent (g);
// draw each Connect 4 piece on the board (loop through the model)
for (int x = 0; x < C4tutorialModel.COLS; x++) {
for (int y = 0; y < C4tutorialModel.ROWS; y++) {
// get piece from model
int piece = model.getData(x, y);
// Now get screen co-ordinates (AbstractBoardComponent)
Point screen = getScreenCoords (x, y);
// Now get screen co-ordinates (AbstractBoardComponent)
Image image = GameImages.getImage(piece + 2);
g.drawImage (image, screen.x, screen.y, null);
}
}
// Draw the box around the board
if (curMousePoint != -1) {
g.setColor (new Color (200, 200, 200));
Point screen = getScreenCoords (curMousePoint, 0);
g.drawRect (screen.x, 0, CELL_SIZE - 1, (CELL_SIZE * NUM_OF_ROWS) - 1);
}
}
/**
* Return the current mouse point.
*
* @return
*/
public int getCurMousePoint() {
return curMousePoint;
}
/**
* Set the current mouse point to another point.
*
* @param newPoint
*/
public void setCurMousePoint (int newPoint) {
curMousePoint = newPoint;
}
} |
|
|
The C4tutorialComponent class extends an AbstractBoardComponent which is
a helper class used for creating game boards (e.g. chess, checkers, etc). This
class has a link to the connect 4 model which it renders. Its constructor calls
the super constructor in AbstractBoardComponent, sets the number of
rows, columns, cellspacing etc.
The most important method for any class which extends JogreComponent is the
paintComponent method as it is responsible for actually rendering the
graphics onto the screen. This method can be called from a model using the
refreshObservers() method call. The paintComponent method
starts with a call to super.paintComponent (g)
which renders board from AbstractBoardComponent. It then loops through the
rows / columns of the C4tutorialModel and gets the screen co-ordinate of each
position. It then draws a graphic onto the screen at this point depending on its
value in the model (no piece, red piece or yellow). Finally, if it is a players go
it draws a box around the position of the mouse to show which column the piece will drop down.
Getter and setter methods for setting this mouse point are included and these are called
from the controller (next).
|
|
C4tutorialController.java
...
public class C4tutorialController extends JogreController {
// links to game data and the board component
protected C4tutorialModel model;
protected C4tutorialComponent boardComponent;
/**
* Constructor.
*
* @param model
* @param boardComponent
*/
public C4tutorialController (C4tutorialModel model, C4tutorialComponent boardComponent) {
super(model, boardComponent);
this.model = model; // set fields
this.boardComponent = boardComponent;
}
/**
* Start method.
*
* @see org.jogre.client.JogreController#start()
*/
public void start() {
model.reset ();
}
// Overwrite the mousePressed method
public void mousePressed (MouseEvent e) {
if (isGamePlaying() && isThisPlayersTurn ()) { // ensure game has started
// get board point from screen position of mouse click
Point board = boardComponent.getBoardCoords (e.getX(), e.getY());
// ensure board point is in range (0 to 2)
if (board.x >= 0 && board.x < C4tutorialModel.COLS &&
board.y >= 0 && board.y < C4tutorialModel.ROWS) {
// check model at this point is BLANK and it is this persons go
if (model.getData(board.x, 0) == C4tutorialModel.BLANK) {
int value = getCurrentPlayerSeatNum();
move (board.x, value); // move
checkGameOver (); // check to see if the games over
}
}
}
}
// Show the column that the mouse is on.
public void mouseMoved (MouseEvent e) {
if (isGamePlaying () && isThisPlayersTurn ()) {
Point point = boardComponent.getBoardCoords (e.getX(), e.getY());
// If user has moved to a new column
if (point.x != boardComponent.getCurMousePoint()) {
// Check to see if there is room left
if (model.getData(point.x, 0) != C4tutorialModel.BLANK)
boardComponent.setCurMousePoint (-1);
else
boardComponent.setCurMousePoint (point.x);
// update the board
boardComponent.repaint();
}
}
}
/**
* Update the model
*
* @param x
* @param value
*/
public void move (int x, int value) {
boardComponent.setCurMousePoint (-1);
// update model and change player turn
model.setData (x, value);
// next players turn
nextPlayer ();
}
// Check to see if the game is over or not.
private void checkGameOver () {
// Do later
}
} |
|
|
The C4tutorialController controller class updates the model and
listens to user input on the C4tutorialComponent. This class
extends the JogreController class which is an adapter class which
implements all the methods from MouseMotionListener,
MouseListener and KeyListener.
The controller class has links to both the model
and the view (see the UML diagram
previous) which are both set in the constructor. The mousePressed and
mouseMoved methods are overwritten to response to user input. Both methods
start with the line:
if (isGamePlaying() && isThisPlayersTurn ()) { ...
This ensures that the method will not execute any further unless the game
has started and is this particular players turn to go. Both methods then convert
the large pixel co-ordinate into smaller board co-ordinate (0 .. 6) using the helper method
getBoardCoords() from AbstractBoardComponent. If the C4tutorialComponent
didn't extend from AbstractBoardComponent then this method would have be
created manually (e.g. check out Propinqity game which creates a hexagonal board).
In the mousePressed method the model is check to ensure there is space
for a piece to be dropped. If this is the case then the move is made.
The mouseMoved method simply sets the curMousePoint (this was
mentioned earlier in C4tutorialComponent) which draws a box to show
where the user has the mouse on the component.
Finally, the C4tutorialTableFrame must be filled in to create instances
of the model/view/controller classes.
|
|
C4tutorialTableFrame.java
...
public class C4tutorialTableFrame extends JogreTableFrame {
private C4tutorialModel model;
private C4tutorialComponent boardComponent;
private C4tutorialController controller;
/**
* Constructor which passes a client connection thread and a table.
*
* @param conn
*/
public C4tutorialTableFrame (TableConnectionThread conn) {
super (conn);
// Create model, view and register view to model
model = new C4tutorialModel (); // create model
boardComponent = new C4tutorialComponent (model); // board
model.addObserver(boardComponent); // board observes model
// Create controller which updates model and controlls the view
controller = new C4tutorialController (model, boardComponent);
controller.setConnection (conn); // connection
boardComponent.setController(controller);
// Add view to a panel and set on table frame
double pref = TableLayout.PREFERRED;
double [][] sizes = {{10, pref, 10, pref, 10}, // horizontal sizes
{10, pref, 10}}; // vertical sizes
JogrePanel panel = new JogrePanel (sizes);
panel.add (boardComponent, "1,1");
panel.add (new PlayerComponent (conn, 1, true), "3,1,l,t");
panel.add (new PlayerComponent (conn, 0, true), "3,1,l,b");
setGamePanel (panel); // add panel to table frame
// Set up MVC classes in super class
setupMVC (model, boardComponent, controller);
pack();
}
} |
|
|
The way this is done for Connect4 is identical as the
TicTacToe tutorial. The steps are summarised as follows:
- Create model.
- Create view and pass in the model.
- Ensure that the view observes the model.
- Create a controller and pass in the model and view.
- Register the controller on the view.
- Add view to a panel and set this as the main game panel using the setGamePanel method.
- Finally, call the important setupMVC method.
To test these changes, recompile the classes again and run the JogreServer
server and two clients. Get one of the clients to create a table which
the other client should join. Both players should then sit down and start
a game. If everything was successful then you should see the clients update
themselves but not each other.
|
|
|
|
For the clients to keep in sync requires communication to the server. This
is discussed in the next chapter.
|
|
[ top ] |
|
|
Server communication
|
|
|
All communication is transmitted as XML which is much more powerful than using
flat Strings in alpha 0.1.
In JOGRE Beta 0.3, communication is either a client message or a table message. A table
message always contains a table attribute and is delegated to the specified
table frame automatically. Click here
for the JOGRE communication protocol.
The JogreClientFrame now has a link to the ClientConnectionThread
and the JogreTableFrame has a link to the new TableConnectionThread
class. These classes automatically add table numbers
automatically to communication objects which makes the programmers life easier.
Also, using layers, the messy manual delegation between the ClientConnectionThread, the
JogreClientFrame and the JogreTableFrame is now unnecessary as the
(e.g. the method ticTacToeMove (CommTicTacToeMove move) from the TicTacToeClientFrame
class in tutorial 1).
These are the main differences between 0.1 and 0.2 communication. The following
UML diagram shows a class diagram of the JOGRE communication model.
|
|
|
|
Communication is sent:
-
client -> server - communication is sent using the send ()
methods from ClientConnectionThread and TableConnecionThread
(depending if it is a game message or a table message)
.
- server -> client - communication is received from the server
into the receiveMessages () method on the JogreClientFrame and
JogreTableFrame respectively.
New convience methods for communication also exist in
the JogreController class for
sending / recieving properties (and XML objects). A very easy way to send a property
to all the other players in a Connect 4 game is to use the sendProperty. To do this
update the move method in C4tutorialController and insert a new
receiveProperty method as follows.
|
|
C4tutorialController.java
// Update the model
public void move (int x, int value) {
boardComponent.setCurMousePoint (-1);
// update model and change player turn
model.setData (x, value);
// next player turn.
nextPlayer ();
// send move to other user
sendProperty ("move", x, value);
}
// Receive
public void receiveProperty (String key, int x, int value) {
model.setData (x, value);
}
|
|
|
The sendProperty method sends the String "move", the x board position and the
piece value (yellow / red) to the server. The server then sends this to all
the other clients through the recieveProperty method. This method simply
updates the connect 4 model so the 2 clients are in synch.
To test, restart the server, run 2 clients and create a new game. This time
you can see that each client look the same. However, you notice in the following
screenshot that both "Dave" and "Bob" have 4-in-a-row, but neither of them
have won the game. This is discussed later in the tutorial.
|
|
|
|
[ top ] |
|
|
Model State
|
|
|
Game state is now kept on the server side using server controllers.
This means that other users can now join a game in progress. The server controllers
keep a copy of the model for each table on each game. When a user joins a table where
the game is on progress they must be sent the state of the game as an XML
message. Two methods must now be implemented for the C4tutorialModel class.
These are XMLElement flatten () (turns state of model into an XML message)
and void setState (XMLElement message) (sets state of model from an XML
message).
The following is the flatten method in connect 4 model.
|
|
C4tutorialModel.java
// Flatten the state of the model into an XML object.
public XMLElement flatten () {
// Retrieve empty state message from super class
XMLElement state = new XMLElement(Comm.MODEL);
// Flatten 2d data to a 1d array
int [] data1D = JogreUtils.convertTo1DArray (data);
// Flatten 1d array into a space delimited string and set attribute
state.setAttribute ("pieces", JogreUtils.valueOf (data1D));
return state;
} |
|
|
From earlier we noticed how the state of a game of connect 4 was stored as a 2-dimensional
integer array. How do we store this as an attribute of an XML message? The first
thing we do is create an empty <model> message..
We then convert the data field (2D integer array) into a 1D integer array using the JogreUtils class.
We then convert this 1D int array into a spaced delimited string and set a "pieces"
attribute in the <model> message. If for example Bob and Dave are
playing a game connect 4 and another user shazza joins the table, the server will
sent shazza the following message (once the connect 4 server controller exists):
<join_table table="1" username="shazza">
<player_list owner="dave" curPlayer="dave">
<player name="shazza" state="viewing" seat="-1"/>
<player name="bob" state="started" seat="1"/>
<player name="dave" state="started" seat="0"/>
</player_list>
<model pieces="-1 -1 -1 -1 -1 0 -1 -1 -1 -1 -1 0 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1 -1 -1 1 1" />
</join_table>
The following is the setState which then takes the <model> message
and sets the state of the model accordingly.
|
|
C4tutorialModel.java
// Set the state of the model
public void setState (XMLElement message) {
// Retrieve "pieces" attribute into String
String pieces = message.getStringAttribute ("pieces");
// Convert space delimited string into an integer array
int [] data1D = JogreUtils.convertToIntArray (pieces);
// Convert from 1D to 2D and set data field
this.data = JogreUtils.convertTo2DArray (data1D, COLS, ROWS);
// If everything is read sucessfully then refresh observers
refreshObservers();
} |
|
|
The setState method does the reverse of the flatten method. It retrieves
the value of the "pieces" attribute into a String, converts this String to
an 1d integer array and finally from a 1d integer array into the data field
of C4tutorialModel (which is a 2d integer array). The last step is to call
the refreshObservers method which will refresh the C4tutorialComponent.
However the server must have a copy of the model otherwise none of this would work. This
is discussed in the next section.
|
|
[ top ] |
|
|
Server Controllers
|
|
|
On the client side we use controllers (i.e. C4tutorialController) to controll user input
and to send messages to the server to keep clients in sync. On the server side all standard messages
are parsed using the default server table and game controllers. However a custom server
controller can be installed which will receive all messages send from a particular game.
A controller must be located in the following location:
/jogre/games/<game key>/src/org/jogre/<game key>/server
i.e.
/jogre/games/c4tutorial/src/org/jogre/c4tutorial/server
The class name must also end in ServerController. When the Jogre Server loads up it will scan
the <game key>/classes/org/jogre/<game key>/server folder of each game
that is included in the server.xml file. If it detects a class ending in ServerController
it will try to create an instance of it using reflection. Thus, the Jogre Server loads server controllers
using a plug-in style architecture.
The next step is to add a receiveProperty method to the C4tutorialServerController class.
This method will intercept messages from the client and update the model on the server.
|
|
C4tutorialServerController.java
// Receive property from client and update model
public void receiveProperty (JogreModel model, String key, int x, int y) {
((C4tutorialModel)model).setData (x, y);
} |
|
|
Each server controller must implement a one parameter constructor and 2 abstract methods.
The constructor simply takes note of the gameKey. The startGame method is called when
a new game starts at a particular table. In this case a new connect 4 model is initilised on the server.
The gameOver method is called when a client requests that a game is over. This is verified on the
server side to prevent client side hacking. This method is filled in the next section.
The abstract class ServerController also contains a number of adapter methods
which can be overwritten if required. These adapater methods include userResigns, usersAgreeDraw
and a number of receiveProperty and receiveObject methods. On the client side, other clients
were kept in synch via the sendProperty method call and overriding the receiveProperty
adapter method to receive properties. The receiveProperty is similiar on the server side with the additional
JogreModel parameter as the server has to handle a number of different models for each table. When a client
makes a move (and sends a property to other clients) the server controller receives it in the receiveProperty
method. The model is then casted to a C4tutorialModel and kept in synch with the various clients models
using the setData (x, y) method call.
To ensure that everything is working properly run a Jogre Server, 2 clients (e.g. dave, bob) and start a game.
After a few moves run another user (e.g sharon) and join the game as a viewer. If everything is working the user sharon
should see the state of the game. This is because the server will send sharon the state of the game
in a <join_table> message. The state is created from the flatten() method in the previous section
and the model is updated on the server controller using the setState() method also from the previous section.
The final piece of the puzzle is determining when a game is over which is discussed in the next section.
|
|
[ top ] |
|
|
Game Over
|
|
|
In Connect 4 a game can end with a player winning (getting 4 of his
pieces in row) or end in a draw (no spaces left on the board with neither player winning).
It is quite similar to tic-tac-toe.
|
|
To implement game over checking, two methods were added to
C4tutorialModel and the checkGameOver
method in the C4tutorialController is implemented.
|
|
C4tutorialModel.java
// return true if the game is won by this player
public boolean isGameWon (int player) {
for (int x = 0; x < COLS; x++) {
for (int y = 0; y < ROWS; y++) {
// check horizontally, vertically a diagonally
int c1 = 0, c2 = 0, c3 = 0, c4 = 0;
for (int i = 0; i < 4; i++) {
if (x + i < COLS) {
if (data[x+i][y] == player)
c1 ++;
}
if (y + i < ROWS) {
if (data[x][y+i] == player)
c2 ++;
}
if (x + i < COLS && y + i < ROWS) {
if (data[x+i][y+i] == player)
c3 ++;
}
if (x - i >=0 && y + i < ROWS) {
if (data[x-i][y+i] == player)
c4 ++;
}
}
// Has any of these been 4 in a row
if (c1 == 4 || c2 == 4 || c3 == 4 || c4 == 4)
return true;
}
}
return false;
}
// return true if all spaces are taken
public boolean isNoCellsLeft () {
int count = 0;
// check each cell
for (int x = 0; x < COLS; x++) {
for (int y = 0; y < ROWS; y++) {
if (data[x][y] != BLANK)
count++;
}
}
return (count == ROWS * COLS); // if all 9 spaces filled return true
}
|
|
|
The isGameWon method checks the int array in the C4tutorial Model
horizontally, vertically and diagonally. It returns true if a 4 in a row is
detected. The isNoCellsLeft method does a count of all the
non blank cells in the int array. If its equal to nine it returns true (no empty cells).
|
|
C4tutorialController.java
// Check to see if the game is over or not.
private void checkGameOver () {
// Status is either -1, DRAW or WIN
int status = -1;
if (model.isGameWon (getCurrentPlayerSeatNum()))
status = CommGameOver.WIN;
else if (model.isNoCellsLeft ())
status = CommGameOver.DRAW;
// Create game over object if a win or draw
if (status != -1 && conn != null) {
CommGameOver gameOver = new CommGameOver (status);
conn.send (gameOver);
}
} |
|
|
The checkGameOver method in the C4tutorialController
is updated to check the C4tutorialModel for a win or a draw.
A status integer is then set and a CommGameOver
communications object is then created and sent to the server.
Previous to Beta 0.3 sending a <game_over> message to the
server resulting in a game ending. This would create problems in a real-life environment
as hackers would start sending fake <game_over> messages and
increase their score. Since Beta 0.3 when the server receives this message
it runs the abstract method gameOver in the correct server controller class to verify that
the game is actually over (and thus can update scores).
The implementation of this method is as follows:
|
|
C4TutorialServerController.java
// Game over on server controller
public void gameOver (ServerConnectionThread conn, int tableNum, int resultType) {
C4tutorialModel model = ((C4tutorialModel)getModel(tableNum));
int player = getSeatNum(conn.getUsername(), tableNum);
// Status is either -1, DRAW or WIN
int status = -1;
if (model.isGameWon(player))
status = IGameOver.WIN;
else if (model.isNoCellsLeft ())
status = IGameOver.DRAW;
// Create game over object if a win or draw
if (status != -1) {
// Update the server data
gameOver (conn, tableNum, conn.getUsername(), status);
}
} |
|
|
When a client informs the server that a game is over the abstract gameOver method is called on the
server controller. The model is retrieved for the specified table (getModel(int table)) and casted to a C4tutorialModel.
The seat number of the current player is also noted. A status variable is created and a gameover check
is performed. If the game is over a gameOver method is called which takes four parameters
(client connection, client username, status and table number). This method updates the
scores on the server persistent data. Currently this is stored as an XML file in the
/jogre/server/data/xml/users.xml file. The server finally sends a message to each client with the updated
scores.
Note: JOGRE can now store persistent data in a database. Switching to a database
will require changing the server_data attribute in the <configuration>
element in the server.xml file from "xml" to "database".
|
|
|
|
[ top ] |
|
|
Applet Support
|
|
|
Applet support is available in the Beta 0.3 release. The game generator has created
a C4tutorialApplet class which is similar to the C4tutorialClientFrame class.
To run the applet we must create a c4tutorial.jar file. This involves running the
package target in the ANT script.
|
|
|
|
This should create a jogre/games/c4tutorial/c4tutorial.jar file which is about 200KB in size.
To test the applet we can modify jogre/games/applet_test.html applet testing file. We need to
add a link to the c4tutorial.jar to the <select name="game"> html combobox (line 45).
Add the following option to the combobox:
<option value="c4tutorial/applet_c4tutorial.jar org.jogre.c4tutorial.client.C4tutorialApplet.class">C4Tutorial</option>
To test the applet run a server and open the applet_test.html file using your favourite browser
(e.g. Firefox 1.5 / IE 6.0). Select the C4Tutorial option from the "Game" combobox,
enter a username and click the "Connect" button. You should see something like the following:
|
|
|
|
The applet can also be run from Eclipse if you prefer which is tricker than running the client as an application.
This is achieved by selecting Run - Run Configurations...,
and selecting new Java Applet. In the "Main" tab ensure the Project is set to "JOGRE C4tutorial" and the Applet is
set to "org.jogre.c4tutorial.client.C4tutorialApplet".
In the "Parameters" tab set the width and height to 600 x 400. Add 4 parameters e.g.
(language=en, serverhost=localhost, serverport=1790 & username=bob). In the "Classpath" remove the "JOGRE C4tutorial" project
from the user entrie and click Add Jar's and add the "applet_c4tutorial.jar" file to the classpath so it looks like the
following: -
|
|
|
|
This brings the Connect4 tutorial to an end. Its aim is to illustrate the
implementation of game using the JOGRE API beta 0.3 using screenshots, UML diagrams
and full source code listings. More information is available in the other tutorials although they are out of date.
If you have any comments on this tutorial, then feel free to contact its author
Bob Marks.
|
|
[ top ] |
|
|