Click on the title image to go to the main page
 
 Main Page
 Download
 Documentation
 F.A.Q.
 Contact
 
 
 
SourceForge.net Logo
 
   
Tutorial 2: Connect 4 (alpha 0.2.3)
Last Updated: 3rd Oct, 2005, 14:18 GMT
 

Contents

 
 

Introduction

 

This tutorial shows how the turn-based game of Connect 4 can be created using the JOGRE 0.2.3 API. This tutorial has been updated from the old 0.2.1 to the newer 0.2.3 release which adds more functionality on the server side such as game state, server side game over detection etc.

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 ]
 
 

Creating a New Project

 

Before you start this tutorial ensure you have the "Jogre Source, Alpha 0.2.3" distribution extracted somewhere (e.g. c:\) which is available on the download page and then built using the ANT utility. To ensure we are following the tutorial properly we have created a new folder under /jogre/games called c4tutorial to avoid confusion from the existing /jogre/games/connect4.

The first step for creating a new project/game is to create a new directory underneath the main "jogre/games" directory i.e. jogre/games/c4tutorial

Inside the jogre/games/c4tutorial directory create source and images folders i.e. c4tutorial/src and c4tutorial/images. The c4tutorial/images folder should contain the following 5 images.

 
jogre_icon.gif connect4_icon.gif no_piece.gif red_piece.gif yellow_piece.gif
 

The source folders should be organised as follows:

 
Connect 4 src folders Description
c4tutorial/src/org/jogre/c4tutorial/client Location of client specific files
 

The next step is to create a 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. Since applet support in JOGRE 0.2.3 this build file has got much larger in size but is very similar and straightforward for each game.

 
build.xml
<?xml version="1.0" encoding="utf-8"?>
<project name="Connect4 Tutorial" default="compile" basedir=".">

    <!-- Source code directory -->
    <property name="src.dir" value="${basedir}/src"/>
    <!-- Class directory -->
    <property name="class.dir" value="${basedir}/classes"/>
    <!-- Image directory -->
    <property name="image.dir" value="${basedir}/images"/>
    <!-- JAR file -->
    <property name="jar.file" value="c4tutorial.jar"/>
    <!-- Temp JAR directory -->
    <property name="temp.jar.dir" value="${basedir}/tempjardir"/>
    <!-- Classpath -->
    <path id="classpath">
        <pathelement location="${basedir}/../../api/jogre.jar"/>
        <pathelement location="${basedir}/../../server/server.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>

        <uptodate property="jar.notRequired" targetfile="${jar.file}" >
            <srcfiles dir= "${class.dir}" includes="**/*.class"/>
            <srcfiles file= "${classpath}" />
        </uptodate>

    </target>

    <!-- All  -->
    <target name="all" depends="compile" unless="jar.notRequired"
            description="compile and make jar">
        <echo/>
        <echo>--------- making a jar for connect4 applet ---------</echo>

        <mkdir dir="${temp.jar.dir}"/>
        <copy todir="${temp.jar.dir}">
            <!-- Copy from API -->
            <fileset dir="${basedir}/../../api/classes">
            	<include name="**/*.class"/>
            </fileset>
            <fileset dir="${basedir}/../../api">
		        <include name="**/*.gif"/>
		        <include name="*.properties"/>
            </fileset>

            <!-- Copy from Game -->
            <fileset dir="${class.dir}">
            	<include name="**/*.class"/>
            </fileset>
            <fileset dir="${basedir}">
               <include name="images/*.gif"/>
               <include name="*.properties"/>
            </fileset>
        </copy>

        <jar jarfile="${jar.file}" basedir="${temp.jar.dir}" compress="yes">
            <exclude name="**/*.html"/>
        </jar>

    	<delete dir="${temp.jar.dir}"/>

    </target>
</project>
 

The final step is to create two property files, game.properties (one exists for each game) and game_labels.properties (multiple for each language). These are both stored in the c4tutorial directory.

 
game.properties
# Game ID (game)
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

# Declare background colour
background.colour=210,210,230

# Declare images
image.0=images/connect4_icon.gif, 1052
image.1=images/no_piece.gif, 1440
image.2=images/red_piece.gif, 2278
image.3=images/yellow_piece.gif, 2270
 
game_labels.properties
# Client
jogre.client.title=JOGRE Connect 4 Client
jogre.client.welcome.message=Welcome to JOGRE Connect 4!
jogre.table.title=Jogre Connect 4 Table

# Must be a "player.label.0" to "player.label.x"
player.label.0=Red
player.label.1=Yellow
 

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 in Alpha 0.2.3 the images must be defined in the property files along with the image sizes (in bytes). This is necessary as the images are read from an applet jar file.

The game_labels.property files contains five 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 Alpha 0.2.3 a single server exists which can handle multiple games. To ensure that the server will support a game the game key (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" minPlayers="2" maxPlayers="2" />
        <game id="checkers" minPlayers="2" maxPlayers="2" />
        <game id="c4tutorial" minPlayers="2" maxPlayers="2" />
        <game id="chess" minPlayers="2" maxPlayers="2" />
        <game id="dots" minPlayers="2" maxPlayers="2" />

        ...

    </supported_games>
 
[ top ]
 
 

Running a Server

 

As mentioned earlier there is only one server which can be run through the /jogre/server/server.bat batch file. Release alpha 0.2.1 also has a GUI wrapper for the server which is very useful as it shows messages sent / received from clients and the various users / tables and players in a nice GUI tree. To run the GUI wrapper execute the /jogre/server/servergui.bat batch file.

If the server runs OK you will see the following screenshot and notice the c4tutorial key under the Games being served.

 
 

Note:The c4tutorial gamekey will not have a [controller] string to its left.

If you are running the GUI wrapper ensure that you copy the connect4_icon.gif image from /jogre/games/connect4/images/ to /jogre/server/images and rename it to c4tutorial.gif (game key with extension .gif). You should then see something like the following:

 
 
[ top ]
 
 

Creating a Client Frame/Table

 

Once a server has been successfully created the next step is to create a client frame which can connect to the server and interact with other users.

To create a client frame two classes must now be created (alpha 0.1 required four). These Connect4ClientFrame and Connect4TableFrame which extend extend JogreClientFrame and JogreTableFrame from the JOGRE API.

These classes should be stored in the src/org/jogre/c4tutorial/client folder under /jogre/games/c4tutorial.

 
Location Class/Interface Name API Extends Synopsis
org.jogre.c4tutorial.client Connect4ClientFrame JogreClientFrame Connect 4 client frame for creating tables.
Connect4TableFrame JogreTableFrame Connect 4 table frame for playing a game.
 
The following clickable image shows these classes as a UML diagram.
 
 

The UML diagram illustrates both the Connect4ClientFrame and Connect4TableFrame inheriting the JogreClientFrame and JogreTableFrame classes from the API which in turn extend the Swing class JFrame

The architecture of alpha 0.2 has changed a great deal with regards to client / server communication. Clients don't need to create their own connection threads anymore but communicate to the server through the ClientConnectionThread and TableConnectionThread classes. Communication is sent from the server back to the ClientConnectionThread which automatically delegates it to the correct client frame through the IClient and ITable interfaces depending of the layer (game or table) of the communication message. No manual delegation of communication is required as of alpha 0.2.

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.

 
Connect4ClientFrame.java
package org.jogre.c4tutorial.client;

import org.jogre.client.TableConnectionThread;
import org.jogre.client.awt.JogreClientFrame;
import org.jogre.client.awt.JogreTableFrame;

// Tic-tac-toe client frame.
public class Connect4ClientFrame extends JogreClientFrame {

  // Constructor
  public Connect4ClientFrame (String [] args) {
    super (args);
  }

  // Return the correct table frame
  public JogreTableFrame getJogreTableFrame (TableConnectionThread conn) {
    return new Connect4TableFrame (conn);
  }

  // Main method where this class gets run
  public static void main (String [] args) {
    Connect4ClientFrame frame = new Connect4ClientFrame (args);
  }
}
 

The Connect4ClientFrame is the client class which is executed as it contains a main method. This class extends JogreClientFrame and must implement one abstract method (alpha 0.1 required two methods to be filled). This method is getJogreTableFrame which is filled in to return the correct Connect4TableFrame where each of the games of connect 4 are played.

 
Connect4TableFrame.java
package org.jogre.c4tutorial.client;

import javax.swing.*;

import org.jogre.client.awt.JogreTableFrame;
import org.jogre.client.awt.GameImages;
import org.jogre.client.TableConnectionThread;

// Jogre table frame
public class Connect4TableFrame extends JogreTableFrame {

  // Constructor which passes a table connection thread.
  public Connect4TableFrame (TableConnectionThread conn) {
    super (conn);
    
    // Set icon
    this.setIconImage (GameImages.getImage (0));
    
    // Fill in rest later with instances of MVC classes
  }
}
 

The Connect4TableFrame is where each game is going of connect 4 is to be played. The class must consist of a constructor which matches that of the getJogreTableFrame (TableConnectionThread conn) method of Connect4ClientFrame. This class also keeps instances of the MVC (model/view/controller) classes. These will be filled in later in the tutorial.

 
[ top ]
 
 

Running a client

 

The next step is to compile these files by running the ANT script or using an IDE like Eclipse and manually copying the properties files to the classes folder. If you are using Eclipse (or any IDE) ensure that the build path has a link to the API (either the jogre/api/jogre.jar file or a link to a Jogre API project). Once the classes are compiled OK run the Jogre Server. The connect 4 client can now execute and connect to the server. This is achieved by going to the jogre/games/connect4 folder and typing the following java command:

java -classpath .;classes;..\..\api\jogre.jar org.jogre.c4tutorial.client.Connect4ClientFrame

If the command is sucessful then the client will appear with a logon screen. Ensure you have a username in the the users.xml file (located in the jogre/server/data/xml folder). 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).

Of all the changes from alpha 0.1 to the alpha 0.2 API, the MVC part of the JOGRE API has changed the least (although new convenience functionality has been added). This section is quite similar to the same section in the "tic-tac-toe" tutorial.

To create the game of connect 4 another three classes are going to be created and initialised in the ConnectTableFrame class. These classes are as follows:

 
Location Class/Interface Name API Extends Synopsis
org/jogre/c4tutorial/client Connect4Model JogreModel Model which holds the data for a game of Connect4.
Connect4Controller JogreController Controller which updates the model and listens to user input on the visual board.
Connect4BoardComponent AbstractBoardComponent
(-> JogreComponent)
Connect4 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.

 
Connect4Model.java
package org.jogre.c4tutorial.client;

import org.jogre.common.JogreModel;

// Model which holds the data for a game of Connect4
public class Connect4Model 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
  public Connect4Model() {
    super();
    reset ();
  }

  // reset the model back to zeros
  public void reset () {
    for (int x = 0; x < COLS; x++) {
      for (int y = 0; y < ROWS; y++) {
        data [x][y] = BLANK;
      }
    }
    refreshObservers();
  }

  // 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 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;
      }
    }
  }
}
 

The model class Connect4Model 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 start () method which calls the 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. Connect4BoardComponent.

The next class discussed is the view part of the MVC architecture which is the Connect4BoardComponent.java.

 
Connect4BoardComponent.java
package org.jogre.c4tutorial.client;

import java.awt.*;
import javax.swing.ImageIcon;

import org.jogre.client.awt.AbstractBoardComponent;
import org.jogre.client.awt.GameImages;

// Connect4 visual board component (view of the model)
public class Connect4BoardComponent 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 Connect4Model model;

  protected int curMousePoint = -1;

  // Constructor which creates the board
  public Connect4BoardComponent (Connect4Model 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 < Connect4Model.COLS; x++) {
      for (int y = 0; y < Connect4Model.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.
  public int getCurMousePoint() {
    return curMousePoint;
  }

  // Set the current mouse point to another point.
  public void setCurMousePoint (int newPoint) {
    curMousePoint = newPoint;
  }
}
 

The Connect4BoardComponent 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 class the super constructor in AbstractBoardComponent which 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 Connect4Model 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).

 
Connect4Controller.java
package org.jogre.c4tutorial.client;

import java.awt.Point;
import java.awt.event.MouseEvent;

import org.jogre.client.JogreController;
import org.jogre.common.comm.CommGameOver;

// Controller for the
public class Connect4Controller extends JogreController {

  // links to game data and the board component
  protected Connect4Model model;
  protected Connect4BoardComponent boardComponent;

  // Constructor
  public Connect4Controller (Connect4Model model, Connect4BoardComponent boardComponent) {
    super(model, boardComponent);

    this.model = model;            // set fields
    this.boardComponent = boardComponent;
  }

  // Start method which calls reset method (resets the model).
  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 < Connect4Model.COLS &&
        board.y >= 0 && board.y < Connect4Model.ROWS) {

        // check model at this point is BLANK and it is this persons go
        if (model.getData(board.x, 0) == Connect4Model.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) != Connect4Model.BLANK)
          boardComponent.setCurMousePoint (-1);
        else
          boardComponent.setCurMousePoint (point.x);

        // update the board
        boardComponent.repaint();
      }
    }
  }

  // Update the model
  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 Connect4Controller controller class updates the model and listens to user input on the Connect4BoardComponent. 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 Connect4BoardComponent 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 Connect4BoardComponent) which draws a box to show where the user has the mouse on the component.

Finally, the Connect4TableFrame must be filled in to create instances of the model/view/controller classes.

 
Connect4TableFrame.java
package org.jogre.c4tutorial.client;

import org.jogre.client.TableConnectionThread;
import org.jogre.client.awt.JogrePanel;
import org.jogre.client.awt.JogreTableFrame;
import org.jogre.client.awt.GameImages;

// Jogre table frame
public class Connect4TableFrame extends JogreTableFrame {

  private Connect4Model model;
  private Connect4BoardComponent boardComponent;
  private Connect4Controller controller;

  // Constructor which passes a client connection thread and a table
  public Connect4TableFrame (TableConnectionThread conn) {
    super (conn);

    // Set icon
    this.setIconImage (GameImages.getImage (0));

    // Create model, view and register view to model
    model = new Connect4Model ();    // create model
    boardComponent = new Connect4BoardComponent (model);  // board
    model.addObserver(boardComponent);   // board observes model

    // Create controller which updates model and controlls the view
    controller = new Connect4Controller (model, boardComponent);
    controller.setConnection (conn);       // connection
    boardComponent.setController(controller);

    // Add view to a panel and set on table frame
    JogrePanel panel = new JogrePanel ();   // create a panel
    panel.add (boardComponent);     // add board to panel
    setGamePanel (panel);           // add panel to table frame

    // Set up MVC classes in super class
    setupMVC (model, boardComponent, controller);
  }
}
 

The way this is done for Connect4 is identical as the TicTacToe tutorial. The steps are summarised as follows:

  1. Create model.
  2. Create view and pass in the model.
  3. Ensure that the view observes the model.
  4. Create a controller and pass in the model and view.
  5. Register the controller on the view.
  6. Add view to a panel and set this as the main game panel using the setGamePanel method.
  7. 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

 

As mentioned previously, the MVC part of JOGRE has changed very little so the previous section was quite similar to the Tic-Tac-Toe tutorial. However, with regards to communication, alpha 0.2 has changed considerably.

The old server per game model has disappeared and so has the need for classes to extend ClientConnectionThread and ServerConnectionThread (e.g. TicTacToe tutorial required a TicTacToeClientConnectionThread). All communication is now transmitted as XML (much more powerful than using flat Strings in alpha 0.1).

In JOGRE Alpha 0.2.3, communication is either a base 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 Connect4Controller and insert a new receiveProperty method as follows.

 
Connect4Controller.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);
  }

  // recieve
  public void receiveProperty (String key, int x, int value) {
    model.setData (x, value);
  }
 

As you can see, it is extremely easy to communicate now in alpha 0.2. 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

 

Since Alpha 0.2.3 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 Connect4Model 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.

 
Connect4Model.java
// New imports at top of file
import nanoxml.XMLElement;
import org.jogre.common.util.JogreUtils;

  // Flatten the state of the model into an XML object.
  public XMLElement flatten () {

    // Retrieve empty state message from super class
    XMLElement state = super.flatten();

    // 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 retrieve an empty <model> message from the JogreModel super class. 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.

 
Connect4Model.java
// New import at top of file
import org.jogre.common.TransmissionException;

  // Set the state of the model
  public void setState (XMLElement message) throws TransmissionException {

    // 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 Connect4Model (which is a 2d integer array). The last step is to call the refreshObservers method which will refresh the Connect4BoardComponent.

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. Connect4Controller) 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.

To create a controller it must be created 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 create a new class called C4TutorialServerController (we cannot name it Connect4ServerController as this already exists).

 
C4tutorialServerController.java
package org.jogre.c4tutorial.server;

import java.awt.Point;

import org.jogre.common.IGameOver;
import org.jogre.common.JogreModel;
import org.jogre.c4tutorial.client.Connect4Model;
import org.jogre.server.ServerConnectionThread;
import org.jogre.server.ServerController;

// Create chess server controller.
public class C4tutorialServerController extends ServerController {

  // Constructor
  public C4tutorialServerController (String gameKey) {
    super (gameKey);
  }

  // Override startGame method to create a new model when a new game starts.
  public void startGame (int tableNum) {
    setModel (tableNum, new Connect4Model ());
  }

  // Receive property from client and update model
  public void receiveProperty (JogreModel model, String key, int x, int y) {
    ((Connect4Model)model).setData (x, y);
  }

  // Game over
  public void gameOver (ServerConnectionThread conn, int tableNum, int resultType) {
    // Fill in later.
  }
}
 

NOTE: This will compile OK if you run the ANT script as its classpath points to the server.jar file which contains server specific classes including ServerController and ServerConnectionThread. If you are developing using Eclipse you will have to add the server.jar (or a link to the Jogre Server project if one is created) to the project classpath. Otherwise this file will not compile in Eclipse.

Once the file is compiled, restart Jogre Server again and if everything is OK it should pick up the controller as follows:

 
 

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 Connect4Model 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 Connect4Model and the checkGameOver method in the Connect4Controller is implemented.

 
Connect4Model.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 Connect4Model 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).

 
Connect4Controller.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 Connect4Controller is updated to check the Connect4Model 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 alpha 0.2.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 alpha 0.2.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) {
    Connect4Model model = ((Connect4Model)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 Connect4Model. 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: later versions of JOGRE will 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 alpha 0.2.3 release. To run the connect 4 tutorial as an applet we must create the following file.

 
Connect4Applet.java
package org.jogre.c4tutorial.client;

import org.jogre.client.TableConnectionThread;
import org.jogre.client.awt.JogreTableFrame;
import org.jogre.client.awt.JogreClientApplet;

// Connect 4 applet
public class Connect4Applet extends JogreClientApplet {

  // Constructor
  public Connect4Applet () {
    super ();
  }

  // Return the correct table frame
  public JogreTableFrame getJogreTableFrame (TableConnectionThread conn) {
    return new Connect4TableFrame (conn);
  }
}
 

The Connect4Applet file is very similar to the Connect4ClientFrame we created earlier. To run the applet we must create a c4tutorial.jar file. This involves running the ANT script we created at the top of the tutorial (delete the classes folder and run ant all at the command line). 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/c4tutorial.jar org.jogre.c4tutorial.client.Connect4Applet.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:
 
 

This brings the Connect4 tutorial to an end. Its aim is to illustrate the implementation of game using the JOGRE API alpha 0.2.3 using screenshots, UML diagrams and full source code listings. It should also illustrate the differences between JOGRE alpha 0.1 and alpha 0.2.3. If you have any comments on this tutorial, then feel free to contact its author Bob Marks.

 
[ top ]
 
 
 
Copyright 2004-2008, JOGRE API and JOGRE Games, by Bob Marks