Tutorial 1: Tic Tac Toe (alpha 0.1)
|
|
Last Updated:
22nd June, 2005, 13:12 GMT
|
|
Contents
|
|
|
|
|
Introduction
|
|
|
Tic-tac-toe using the JOGRE API. This tutorial
includes all the source code and images used to create a simple server, client and the
various components. This tutorial is suitable for anyone who wants to create their
first game using the JOGRE games engine.
At the end of this tutorial you should have something which looks like the
following:
|
|
|
|
[ top ] |
|
|
Why Tic-Tac-Toe?
|
|
|
Tic-tac-toe, also called noughts and crosses and many other names, is a paper and
pencil game between two players, O and X, who alternate in marking the spaces in a
3×3 board. A player wins by getting three of their own marks in a horizontal,
vertical or diagonal row.
Tic-tac-toe is very simple and this simplicity makes it ideal to use as a tutorial
to illustrate the JOGRE API in action.
|
|
[ top ] |
|
|
Creating a New Project
|
|
|
The first step for creating a new project/game is to create a new directory underneath the
main "jogre" directory i.e. jogre/tictactoe
Inside the jogre/tictactoe directory create source and images folders i.e.
tictactoe/src and tictactoe/images. The tictactoe/images folder
should contain the following 4 images.
|
|
jogre_icon.gif |
tictactoe_icon.gif |
x.gif |
o.gif |
|
|
|
|
|
|
The source folders should be organised as follows:
|
|
TicTacToe src folders |
Description |
tictactoe/src/org/jogre/tictactoe/awt |
Location of tic-tac-toe graphical components |
tictactoe/src/org/jogre/tictactoe/client |
Location of tic-tac-toe client specific classes |
tictactoe/src/org/jogre/tictactoe/comm |
Client/Server tic-tac-toe communication objects |
tictactoe/src/org/jogre/tictactoe/server |
Location of tic-tac-toe server specific classes |
|
|
NOTE: All the files have been created for this tutorial have been placed in the
c:\Projects\jogre\tictacetoe directory although the jogre\tictacetoe
directory can be placed anywhere.
|
|
The next step is to create a small build file for this project which is stored
in the tutorial directory. The build program
ANT can be used to compile
and copy the properties files into a classes directory.
|
|
build.xml
<?xml version="1.0" encoding="utf-8"?>
<project name="TicTacToe" default="compile" basedir=".">
<!-- Source code directory -->
<property name="src.dir" value="${basedir}/src"/>
<!-- Class directory -->
<property name="class.dir" value="${basedir}/classes"/>
<!-- Path to JOGRE.JAR file which must be included in classpath -->
<path id="classpath">
<pathelement location="../api/jogre.jar"/>
</path>
<!-- Init. -->
<target name="init">
<!-- Create the build directory structure used by compile -->
<mkdir dir="${class.dir}"/>
</target>
<!-- Compile -->
<target name="compile" depends="init"
description="compile the source " >
<!-- Compile the java code from ${src} into ${classes} -->
<javac srcdir="${src.dir}" destdir="${class.dir}">
<classpath refid="classpath"/>
</javac>
<copy todir="${class.dir}">
<fileset dir="${basedir}">
<include name="*.properties"/>
</fileset>
</copy>
</target>
</project> |
|
|
The final step is to create two property files, game.properties
and game_labels.properties. These are both stored in the tictactoe directory.
|
|
game.properties
# Version
version=0.1 beta
# Declare minimum and maximum number of players for this game
min.num.players=2
max.num.players=2
# Must be a "colour.1" to "colour.x" where x = max.num.players
player.colour.0=255,255,255
player.colour.1=0,0,0 |
|
|
game_labels.properties
# Server
jogre.server.title=JOGRE TicTacToe Server
# Client
jogre.client.title=JOGRE TicTacToe Client
jogre.client.welcome.message=Welcome to JOGRE TicTacToe!
jogre.table.title=Jogre TicTacToe Table
# Must be a "player.label.0" to "player.label.x"
# where x = max.num.players - 1 (from game.properties)
player.label.0=X
player.label.1=O |
|
|
Most of these properties are self explanitory and simply set the name of the server, 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 Tic-tac-toe these are "X" and "O".
Chess for example is "White" and "Black".
|
|
[ top ] |
|
|
Creating a Server
|
|
|
To implement a server two classes must be created which are
TicTacToeServer and TicTacToeServerConnectionThread which extend the
JogreServer and ServerConnectionThread from the
JOGRE API respectively. Both these classes should be stored in the
src/org/jogre/tictactoe/server
directory. The following clickable image shows these two classes as a UML diagram.
|
|
|
|
As the diagram illustrates, both these classes extend classes from the JOGRE API. These
two 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.
|
|
TicTacToeServer.java
package org.jogre.tictactoe.server;
import java.net.Socket;
import org.jogre.common.JogreModel;
import org.jogre.server.JogreServer;
import org.jogre.server.ServerConnectionThread;
// TicTacToeServer Server.
public class TicTacToeServer extends JogreServer {
// Constructor
public TicTacToeServer (String [] args) {
super (args); // Call super constructor and pass in command line args
}
// Return the correct type of connection thread for this server.
public ServerConnectionThread connect (Socket connection, JogreServer server) {
return new TicTacToeServerConnectionThread (connection, server);
}
// Return the correct game model (ignore)
public JogreModel getJogreModel () {
return null;
}
// Main method.
public static void main (String [] args) {
TicTacToeServer server = new TicTacToeServer (args);
server.run (); // Run the server
}
} |
|
|
The TicTacToeServer class contains a main method which creates an instance
of itself and then runs. Arguments can passed to the server from the command line such as
-port 1790 (sets the port to listen on).
The class JogreServer which TicTacToeServer extends
contains two abstract methods connect (Socket connection, JogreServer server) and
getJogreModel ().
The connect method returns the correct type of connection thread
(TicTacToeServerConnectionThread)
for the server. This class is discussed next.
If a server stores the state of a game (and a user can join/view a game at any time)
then the getJogreModel method
sets the correct type of JogreModel. To make this tutorial simple we have decided not to store
the state of a game and so this method simply returns
null.
|
|
TicTacToeServerConnectionThread.java
package org.jogre.tictactoe.server;
import java.net.Socket;
import org.jogre.common.TransmissionException;
import org.jogre.server.JogreServer;
import org.jogre.server.ServerConnectionThread;
// Server thread which deals with any client requests.
public class TicTacToeServerConnectionThread extends ServerConnectionThread {
// Constructor
public TicTacToeServerConnectionThread (Socket clientSocket, JogreServer server) {
super(clientSocket, server);
}
// This method reads a message going to the server and does something with it
public void parse (String messageType, String inString) throws TransmissionException {
if (messageType.equals ("Undefined")) {
// Will fill this in later
}
else
super.parse (messageType, inString); // Super classes deals with most messages
}
}
|
|
|
Each time a client connects to a server a new Thread is created which sits and
deals with any requests coming from the client until the client logs off. The role of the
TicTacToeServerConnectionThread class
is to deal with any TicTacToe specific commands such as a player making a move.
Its constructor recieves a socket and a link to the JogreServer class which
are passed up to the super class.
This class extends ServerConnectionThread class
which processes client logons, chat messages, table requests etc. For this class
to process something from the client the extends parse (String messageType, String inString)
method is overwritten. The parameter messageType is then
checked and depending on what is the class processes it. This will be filled in later
as required.
The next step is to compile these two classes using the ANT
build.xml file or your favourite IDE.
The Open Source IDE Eclipse is very good (JOGRE
has been created primarily using Eclipse). If using an IDE / commmand line ensure
that the jogre.jar file is included in the CLASSPATH.
Also the compiled classes should be stored in the
classes folder and the two properties files (
game.properties and
game_labels.properties) are also
copied into this directory (the ANT build file does this automatically).
|
|
[ top ] |
|
|
Running a Server
|
|
|
Once the two server files have been compiled OK the next step is running the
server. This is achieved by going to the
jogre/tictactoe folder
and typing the following java command:
java -classpath classes;..\api\jogre.jar org.jogre.tictactoe.server.TicTacToeServer
If the command is sucessful then the Server has been created and will
list for client requests on the specified port (1790).
|
|
|
|
[ top ] |
|
|
Creating a Client Frame/Table
|
|
|
Once a server has been sucessfully created the next step is to create a tic-tac-toe client
frame which can connect to the server and interact with other users.
To create a client frame four classes must be created which are
TicTacToeClientConnectionThread, an interface ITicTacToeClientClient,
TicTacToeClientFrame and TicTacToeTableFrame.
These three classes and one interface extend classes / interfaces
from the JOGRE API. All these classes should be stored in the
src/org/jogre/tictactoe/client directory.
|
|
Location |
Class/Interface Name |
Synopsis |
org/jogre/tictactoe/client |
TicTacToeClientConnectionThread |
TicTacToe Client connection thread which sends/recieves information to/from the server. |
ITicTacToeClient |
Interface which TicTacToeClientFrame implements and the TicTacToeConnectionThread delegates calls through. |
TicTacToeClientFrame |
Tic-tac-toe client frame. |
TicTacToeTableFrame |
Tic-tac-toe table frame. |
|
|
The following clickable image shows these four classes as a UML diagram.
|
|
|
|
Once again, all these classes extend classes from the JOGRE API. These
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.
|
|
TicTacToeClientConnectionThread.java
package org.jogre.tictactoe.client;
import java.net.Socket;
import org.jogre.client.ClientConnectionThread;
// TicTacToe Client connection thread which sends/recieves information to/from the server.
public class TicTacToeClientConnectionThread extends ClientConnectionThread {
protected ITicTacToeClient clientInterface;
// Constructor for a client connection thread
protected TicTacToeClientConnectionThread (
Socket connection, String username, ITicTacToeClient clientInterface) {
super(connection, username, clientInterface);
this.clientInterface = clientInterface; // link to client frame (through interface)
}
// This method reads a message going to the server and does something with it
public void parse (String messageType, String inString) {
if (messageType.equals ("Undefined")) {
// Will fill this in later
}
else
super.parse (messageType, inString); // Super classes deals with most messages
}
} |
|
|
The TicTacToeClientConnectionThread class extends the
ClientConnectionThread from the JORE API which sits in its own thread
listening to Strings coming down the socket from the ServerConnectionThread (TicTacToeServer).
The important method here is the parse method as it parses the message coming from the
server. This will be filled in later in the tutorial when we create a small TicTacToe user move
communication object.
|
|
ITicTacToeClient.java
package org.jogre.tictactoe.client;
import org.jogre.client.IClient;
// Interface which sits between the ClientConnectionThread a frame.
public interface ITicTacToeClient extends IClient {
// Fill in later
}
|
|
|
The next file we create is an interface called ITicTacToeClient
which extends the JOGRE API interface IClient. When a String is sent
from the TicTacToeServer to a TicTacToeClientConnectionThread, it
is then passed to a method in the TicTacToeClientFrame which must implement
this interface. This inteface decouples the connection between the GUI and the network
connection.
|
|
TicTacToeClientFrame.java
package org.jogre.tictactoe.client;
import java.net.Socket;
import org.jogre.awt.JogreClientFrame;
import org.jogre.awt.JogreTableFrame;
import org.jogre.client.ClientConnectionThread;
import org.jogre.client.IClient;
// Tic-tac-toe client frame
public class TicTacToeClientFrame extends JogreClientFrame implements ITicTacToeClient {
public TicTacToeClientFrame () {
super ();
}
// Return the correct client connection thread
public ClientConnectionThread getClientConnectionThread (
Socket connection, String username, IClient clientInterface
) {
return new TicTacToeClientConnectionThread (
connection, username, (ITicTacToeClient)clientInterface);
}
// Return the correct table frame (each game).
public JogreTableFrame getJogreTableFrame (ClientConnectionThread conn, int tableNum) {
return new TicTacToeTableFrame (conn, tableNum);
}
// Main method where this class gets run.
public static void main (String [] args) {
TicTacToeClientFrame frame = new TicTacToeClientFrame ();
}
}
|
|
|
The TicTacToeClientFrame is class which is executed as it contains a main method.
This class extends JogreClientFrame and must implement 2 of its
abstract methods: 1) getClientConnectionThread which returns the
correct thread (a TicTacToeClientConnecionThread) to the super class and
2) getJogreTableFrame which will return a TicTacToeTableFrame where
each of the games are played.
|
|
TicTacToeTableFrame.java
package org.jogre.tictactoe.client;
import javax.swing.*;
import org.jogre.awt.JogreTableFrame;
import org.jogre.client.ClientConnectionThread;
// Jogre table frame
public class TicTacToeTableFrame extends JogreTableFrame {
// Constructor which passes a client connection thread and a table
public TicTacToeTableFrame (ClientConnectionThread conn, int tableNum) {
super(conn, tableNum);
// Set icon
ImageIcon barIcon = new ImageIcon ("images/tictactoe_icon.gif");
this.setIconImage(barIcon.getImage());
// Fill in rest later
}
} |
|
|
The TicTacToe table frame is where each game is going to be played. This
will be filled in later.
|
|
[ top ] |
|
|
Running a client
|
|
|
Once these files have been compiled OK and a server is
running then a client can execute and connect to the
server. This is achieved by going to the jogre/tictactoe folder
and typing the following java command:
java -classpath classes;..\api\jogre.jar org.jogre.tictactoe.client.TicTacToeClientFrame
If the command is sucessful then the client will be created and connect
to the server on the specified port (1790). 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 Tic-Tac-Toe MVC (Model/View/Controller)
|
|
|
Now that the server, client frame and table frame have been set up the next
step is to create the actual game of TicTacToe. This is achieved using a
MVC (model/view/controller) style architecture.
Click here for a
website which explains this architecture and its benefits.
To create the game of TicTacToe another three classes are going to be created
and initilised in the TicTacToeTableFrame class. These classes are as
follows:
|
|
Location |
Class/Interface Name |
Synopsis |
org/jogre/tictactoe/client |
TicTacToeModel |
Model which holds the data for a game of TicTacToe. |
TicTacToeController |
Controller which updates the model and listens to user input on the visual board. |
org/jogre/tictactoe/awt |
TicTacToeBoardComponent |
TicTacToe visual board component (view of the model). |
|
|
The following clickable image shows these three MVC classes as a clickable UML diagram.
|
|
|
|
Once again, all these classes extend classes from the JOGRE API. These
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.
|
|
TicTacToeModel.java
package org.jogre.tictactoe.client;
import org.jogre.common.JogreModel;
// Model which holds the data for a game of TicTacToe
public class TicTacToeModel extends JogreModel {
// Declare constans to define model
public static final int BLANK = -1;
public static final int X = 0;
public static final int O = 1;
// model (3x3 two dimensional integer array)
private int [][] data = new int [3][3];
// Constructor
public TicTacToeModel() {
super();
reset ();
}
// Start method which calls reset method (resets the model).
public void start() {
reset ();
}
// reset the model back to zeros
private void reset () {
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
data [x][y] = BLANK;
}
}
refreshObservers(); // inform view
}
// return data at a particular point
public int getData (int x, int y) {
return data [x][y];
}
// set data at a point
public void setData (int x, int y, int value) {
data [x][y] = value;
refreshObservers(); // update any views on this model
}
} |
|
|
The model class TicTacToeModel class extends the JogreModel and must implement
the abstract start () method which is called when a game starts (reset the data).
This class holds a 3x3 two dimensional integer array for holding the data at each
position on the TicTacToe board. The values in this array are either -1 (blank), 0 (X) or 1 (0). There are
also getter and setter methods for accessing / updating this array. An important
method call is the refreshObservers() in the setData method. This updates
any view which is registered with this model.
The next class discussed is the view part of the MVC architecture which is the
TicTacToeBoardComponent.java.
|
|
TicTacToeBoardComponent.java
package org.jogre.tictactoe.awt;
import java.awt.*;
import javax.swing.ImageIcon;
import org.jogre.awt.AbstractBoardComponent;
import org.jogre.tictactoe.client.TicTacToeModel;
// TicTacToe visual board component (view of the model).
public class TicTacToeBoardComponent extends AbstractBoardComponent {
// Declare constants which define what the board looks like
private static final int NUM_OF_ROWS_COLS = 3;
private static final int CELL_SIZE = 100;
private static final int CELL_SPACING = 1;
private static final int BORDER_SPACING = 0;
// Link to the model
protected TicTacToeModel model;
// Images for an X and an O
private static final ImageIcon [] images = {
new ImageIcon ("images/x.gif"),
new ImageIcon ("images/o.gif")};
// Constructor which creates the board
public TicTacToeBoardComponent (TicTacToeModel model) {
// Call constructor in AbstractBoardComponent
super (NUM_OF_ROWS_COLS, NUM_OF_ROWS_COLS, CELL_SIZE, CELL_SPACING,
0, 0, false, false, false);
this.model = model; // link to model
// set colours
setColours (Color.white, Color.white, 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 TicTacToe piece on the board (loop through the model)
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
// get piece from model
int piece = model.getData(x, y);
// Now get screen co-ordinates (AbstractBoardComponent)
Point screen = getScreenCoords (x, y);
// If piece isn't an blank piece then draw the image on the screen
if (piece != TicTacToeModel.BLANK) {
Image image = images [piece].getImage();
g.drawImage (image, screen.x, screen.y, null); // draw image
}
}
}
}
} |
|
|
The TicTacToeBoardComponent class extends an AbstractBoardComponent which is
a component for creating boards (ChessBoardComponent and CheckersBoardComponent
both extend this class). AbstractBoardComponent extends a JogreComponent which
extends a JComponent from the Java Swing classes and also implements the
Observer interface. This means a reference of this class can be added
TicTacToeModel class as an Observer (i.e. when the model changes this class
refreshes itself).
A reference to the TicTacToeModel is passed into the constructor of an
TicTacToeBoardComponent. To actually render the board the paintComponent
method from JComponent is overwritten. The data of the TicTacToeModel
is examined and images are rendered onto the component accordingly.
|
|
TicTacToeController.java
package org.jogre.tictactoe.client;
import java.awt.Point;
import java.awt.event.MouseEvent;
import org.jogre.client.JogreController;
import org.jogre.tictactoe.awt.TicTacToeBoardComponent;
// Controller which updates the model and listens to user input on the visual board.
public class TicTacToeController extends JogreController {
// links to game data and the board component
protected TicTacToeModel model;
protected TicTacToeBoardComponent boardComponent;
// Constructor
public TicTacToeController(TicTacToeModel model, TicTacToeBoardComponent boardComponent) {
super(model, boardComponent);
this.model = model; // set fields
this.boardComponent = boardComponent;
}
// Overwrite the mousePressed method
public void mousePressed (MouseEvent e) {
// ensure game is started and it is this players turn
if (isGamePlaying() && isThisPlayersTurn ()) {
// 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 < 3 && board.y >= 0 && board.y < 3) {
// check model at this point is BLANK and it is this persons go
if (model.getData(board.x, board.y) == TicTacToeModel.BLANK) {
int value = getCurrentPlayerSeatNum();
move (board.x, board.y, value); // move
checkGameOver (); // check to see if the games overv
}
}
}
}
// Update the model
public void move (int x, int y, int value) {
// update model and change player turn
model.setData (x, y, value);
// next player turn.
nextPlayer ();
}
// Check to see if the game is over
private void checkGameOver () { } // fill in later.
} |
|
|
The TicTacToeController controller class updates the model and
listens to user input on the TicTacToeBoardComponent. This class
extends the JogreController class which is an adapter class which
implements all the methods from MouseMotionListener,
MouseListener and KeyListener. Any of these methods can be
overwritten to do something.
In TicTacToeController the mousePressed method is
overwritten. This method starts with an if statement which checks
that the game is in play and it is this particular players turn to make a
move. Both of these useful methods exist in the super class
JogreController. The screen co-ordinate of the mouse click is
then converted into a board point (between 0 and 2). This is done using the
getBoardCoords of AbstractBoardComponent. The point is checked
to see that its range and then the TicTacToeModel is checked to ensure
that the use is clicking on a free space (TicTacToeModel.BLANK).
The model is then updated and the turn changes to the other player using the
nextPlayer method.
|
|
TicTacToeTableFrame.java
package org.jogre.tictactoe.client;
import javax.swing.*;
import org.jogre.awt.JogreTableFrame;
import org.jogre.client.ClientConnectionThread;
import org.jogre.tictactoe.awt.TicTacToeBoardComponent;
// Jogre table frame
public class TicTacToeTableFrame extends JogreTableFrame {
private TicTacToeModel model; // model reference
private TicTacToeBoardComponent boardComponent; // board reference
private TicTacToeController controller; // controller reference
// Constructor which passes a client connection thread and a table
public TicTacToeTableFrame (ClientConnectionThread conn, int tableNum) {
super(conn, tableNum);
// Set icon
ImageIcon barIcon = new ImageIcon ("images/tictactoe_icon.gif");
this.setIconImage(barIcon.getImage());
// Create model, view and register view to model
model = new TicTacToeModel (); // create model
boardComponent = new TicTacToeBoardComponent (model); // board
model.addObserver(boardComponent); // board observes model
// Create controller which updates model and controlls the view
controller = new TicTacToeController (model, boardComponent);
controller.setConnection(conn, tableNum); // connection
boardComponent.setController (controller);
// Add view to a panel and set on table frame
JPanel panel = new JPanel (); // create a panel
panel.add (boardComponent); // add board to panel
setGamePanel (panel); // add panel to table frame
// Set up MVC classes in super class (CONSTRUCTOR MUST CALL THIS)
setupMVC (model, boardComponent, controller);
}
} |
|
|
One of the key classes in the game TicTacToeTableFrame must be
updated now to hold references of the MVC classes, TicTacToeModel,
TicTacToeBoardComponent and TicTacToeController. These
are declare in the constructor. The most independent of these is the
model (TicTacToeModel) which is declared first. The view (TicTacToeBoardComponent)
is then declared and added as an observer to the model (when the model
changes the board will repaint automatically).
The controller (TicTacToeController) is then declared which
takes both the model and the view as parameters in its constructor. The
connection to the server is then set on the controller and controller is then
set on the view. Next the view is added to the TitTacToeTableFrame in the
middle of the screen using the setGamePanel method. Finally, the last statement
is an important one as it registers the MVC classes in the super class
which will make use of them.
To test this, recompile the classes again and run a 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 sucessful then
you should see the clients update themself but not each other.
|
|
|
|
[ top ] |
|
|
Server communication
|
|
|
For the clients to update each other the TicTacToe move must be sent
to the TicTacToeServerConnectionThread which will send the move to the
client (TicTacToeClientConnectionThread) which will call a method in
TicTacToeClientFrame through the ITicTacToeClient interface.
The TicTacToeClientFrame will examine the message, figure out which table
it is for and delegate to the correct table. The TicTacToeTableFrame
will then update the TicTacToeModel.
|
|
|
|
CommTicTacToeMove.java
package org.jogre.tictactoe.comm;
import java.util.StringTokenizer;
import org.jogre.comm.CommTableMessage;
import org.jogre.common.TransmissionException;
// Immutable communication object for a tic tac toe move.
public class CommTicTacToeMove extends CommTableMessage {
public static final String TIC_TAC_TOE = "TIC_TAC_TOE"; // header token
private int x, y, value; // position of move
// Constructor which takes a move from a player.
public CommTicTacToeMove (String username, int tableNum, int x, int y, int value) {
super (username, tableNum);
// set x and y
this.x = x;
this.y = y;
this.value = value;
}
// Constructor which takes a String from a flatten () method.
public CommTicTacToeMove (String inString) throws TransmissionException {
super ();
StringTokenizer st = new StringTokenizer (inString);
st.nextToken(); // read header token
try {
this.username = st.nextToken();
this.tableNum = Integer.parseInt(st.nextToken());
this.x = Integer.parseInt(st.nextToken());
this.y = Integer.parseInt(st.nextToken());
this.value = Integer.parseInt(st.nextToken());
}
catch (NumberFormatException nfEx) {
throw new TransmissionException ("Error parsing string: " + inString);
}
}
// Flatten the fields of this object into a String
public String flatten () {
return TIC_TAC_TOE + " " + username + " " + tableNum + " " +
x + " " + y + " " + value;
}
// get x
public int getX () { return x; }
// get y
public int getY () { return y; }
// get value
public int getValue () { return value; }
} |
|
|
The first class we will look at is CommTicTacToeMove which
is stored in the src\org\jogre\tictactoe\comm folder. CommTicTacToeMove
is a small class which can be transferred as a String across a Socket. This
class implements the ITransmittable interface i.e. contains a flatten method
which can turn the object into a String. The class must also contain
a constructor with a single String parameter for converting the String
back into an object.
This communication object contains the username of the player who made the move,
table number of game, x and y position of move and the value of the move (either X or O).
|
|
TicTacToeController.java
// (New import - don't copy line)
import org.jogre.tictactoe.comm.CommTicTacToeMove;
// (New method - don't copy line)
// Update the model
public void move (int x, int y, int value) {
// update model and change player turn
model.setData (x, y, value);
// next player turn.
nextPlayer ();
// send move to other user
CommTicTacToeMove move = new CommTicTacToeMove
(conn.getUsername(), tableNum, x, y, value);
conn.send(move);
}
|
|
|
The controller (TicTacToeController) is the class that creates
and sends the communication object. This is done in the move
method and is sent to the server via the TicTacToeClientConnectionThread using its
send method.
|
|
TicTacToeServerConnectionThread.java
// (New import - don't copy line)
import org.jogre.tictactoe.comm.CommTicTacToeMove;
// (Update method - don't copy line)
// This method reads a message going to the server and does something with it
public void parse (String messageType, String inString) throws TransmissionException {
if (messageType.equals (CommTicTacToeMove.TIC_TAC_TOE)) {
// Create communication object and send other table
CommTicTacToeMove move = new CommTicTacToeMove (inString);
transmitToTable (move.getUsername(), move.getTableNum(), move);
}
else
super.parse (messageType, inString); // Super classes deals with most messages
}
|
|
|
Once the communication object is sent to the server (TicTacToeServerConnectionThread) it
is passed to its parse method. The header token is checked and if it
is of type CommTicTacToeMove.TIC_TAC_TOE the CommTicTacToeMove
is reconstructed and sent to the correct table (omitting the person who sent the
message).
|
|
ITicTacToeClient.java
package org.jogre.tictactoe.client;
import org.jogre.client.IClient;
import org.jogre.tictactoe.comm.CommTicTacToeMove;
// Interface which sits between the ClientConnectionThread a frame.
public interface ITicTacToeClient extends IClient {
public void ticTacToeMove (CommTicTacToeMove move);
}
|
|
|
The interface (ITicTacToeClient) which TicTacToeClientFrame
implements must be updated so that it can read the move from the server.
|
|
TicTacToeClientConnectionThread.java
// (New import - don't copy line)
import org.jogre.tictactoe.comm.CommTicTacToeMove;
import org.jogre.common.TransmissionException;
// (Update method - don't copy line)
// This method reads a message going to the server and does something with it.
public void parse (String messageType, String inString) {
try {
if (messageType.equals (CommTicTacToeMove.TIC_TAC_TOE)) {
// Create communication object and send other table
CommTicTacToeMove move = new CommTicTacToeMove (inString);
clientInterface.ticTacToeMove (move);
}
else
super.parse (messageType, inString); // Super classes deals with most messages.
}
catch (TransmissionException transEx) {
transEx.printStackTrace();
}
}
|
|
|
Now the thread (TicTacToeClientConnection) which listens to
messages coming from the server (TicTacToeServerConnection) must
be updated so that it can read this communication object. Its parse
method is updated and the move is passed to the client
(TicTacToeClientFrame) through the interface (ITicTacToeClient).
|
|
TicTacToeClientFrame.java
// (New import - don't copy line)
import org.jogre.tictactoe.comm.CommTicTacToeMove;
// (New method - don't copy line)
// Tic tac toe move
public void ticTacToeMove (CommTicTacToeMove move) {
// retreive correct table and send message to it
int table = move.getTableNum();
TicTacToeTableFrame frame = (TicTacToeTableFrame)getTableFrame (table);
frame.ticTacToeMove (move); // delegate to the correct table
}
|
|
|
The client frame (TicTacToeClientFrame) must implement the new
ticTacToeMove method which has been added to the ITicTacToeClient interface.
The table number from the object is used to retrieve the correct
TicTacToeTableFrame and the method ticTacToeMove
in the table frame is then called.
|
|
TicTacToeTableFrame.java
// (New import - don't copy line)
import org.jogre.tictactoe.comm.CommTicTacToeMove;
// (New method - don't copy line)
// Tic tac toe move
public void ticTacToeMove (CommTicTacToeMove move) {
model.setData (move.getX(), move.getY(), move.getValue());
}
|
|
|
Finally the new ticTacToeMove method is inserted into the
TicTacToeTableFrame class which updates the model
(model.setData()) with the other players move.
To test this, recompile the classes again, run a new server, two new clients
and run a quick game. This time when a player makes a move the controller will
update the model and send the move to the server as a comm object which the
other user should recieve. The last thing to do is is to know when a
game is over. This is explained in the next section.
|
|
|
|
[ top ] |
|
|
Game Over
|
|
|
In TicTacToe a game can end with a player winning (getting 3 of his
pieces in row) or end in a draw (no spaces left on the board with noone winning). The following
images shows how a game can be won.
|
|
|
|
To implement game over checking, two methods were added to
TicTacToeModel and the checkGameOver
method in the TicTacToeController is implemented.
|
|
TicTacToeModel.java
// (2 New methods - don't copy line)
// return true if the game is won by this player
public boolean isGameWon (int player) {
// check y axis
for (int x = 0; x < 3; x++) {
int count = 0;
for (int y = 0; y < 3; y++) {
if (data[x][y] == player)
count++;
}
if (count == 3) return true;
}
// check x axis
for (int y = 0; y < 3; y++) {
int count = 0;
for (int x = 0; x < 3; x++) {
if (data[x][y] == player)
count++;
}
if (count == 3) return true;
}
// check diagonals
if (data[0][0] == player && data [1][1] == player && data [2][2] == player)
return true;
if (data[0][2] == player && data [1][1] == player && data [2][0] == player)
return true;
return false;
}
// return true if all 9 spaces are taken
public boolean isNoCellsLeft () {
int count = 0;
// check each cell
for (int x = 0; x < 3; x++) {
for (int y = 0; y < 3; y++) {
if (data[x][y] != BLANK)
count++;
}
}
return (count == 9); // if all 9 spaces filled return true
} |
|
|
The isGameWon method checks the int array in the TicTacToe
horizontally, vertically and diagonally. It returns true if a full 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).
|
|
TicTacToeController.java
// (New import - don't copy line)
import org.jogre.comm.CommGameOver;
// (Update method - don't copy line)
// 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
(conn.getUsername(), status, tableNum, 0);
conn.send (gameOver);
}
} |
|
|
The checkGameOver method in the TicTacToeController
is updated to check the TicTacToeModel 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.
This brings the TicTacToe tutorial to an end. Its aim is to illustrate implementation of
a simple game using the JOGRE API using screenshots, UML diagrams
and full source code listings. If you have any comments on this tutorial,
then feel free to contact its author
Bob Marks.
|
|
[ top ] |
|
|