FMNetGames Technical Notes

The applet communicate with the server indirectly, by posting to a cgi-bin. The cgi-bin reside on the same computer as the server and communicate with the server via TCP/IP sockets. This approach avoid using a dedicated port for communication over the net; only the standard http port (80) is used.

The applet consist of two components:

The game host component implement the following interface, contained in the file FMGameHost.java

// start of FMGameHost.java #################################################
public interface FMGameHost {
static final int NOT_CONNECTED=0;
static final int LISTENING=1;
static final int PLAYING_TO_MOVE=2;
static final int PLAYING_WAIT_MOVE=3;
static final int WAITING_SERVER_RESPONSE=4;
static final int GAME_ENDED=5;
void showDialog(String s);
void move(String s, boolean endGame);}
// end of FMGameHost.java ###################################################

The method showDialog can be called by the game when something must be notified about a certain situation. For example, when game is over, we might have:
showDialog("Congratulations! You win");

The method move is called by the game when the user moved. It is up to the game applet to process the mouse and keyboard input to detect when the user made a move. Then, after the user moved, the game applet must code the move in the string s and must set the second paramater to true if it detected that the game is over or false otherwise. The meaning of the string s is totally up to the game applet; the game host and the game server only forward this string between the instances of the game applet, without interpreting it. For example, in the game Xand0, a move in the cell in row 2 and column 1 is coded in the string "21"

Each game added to the game server must implement the following interface, contained in the file FMGame.java:

// start of FMGame.java ####################################################
public interface FMGame {
void enable();
void disable();
int receiveMove(String s);}
// end of FMGame.java ######################################################

The enable/disable methods are used for enabling/disabling the input for the game applet; the input must be enabled when the player is to move and disabled when waiting for the partner's move, or when game is ended because in such states the player isn't allowed to move.

The receiveMove method is called when the partner's move was received. The string argument s contain the partner's move, as sent by the partner using the move method.. The return value must be the new stare for the game after receiving the move. Usually, this state is PLAYING_TO_MOVE, but it may also be PLAYING_WAIT_MOVE when the player has no available move and must let the opponent to make a second move, or GAME_ENDED when the game was ended (of course, when return GAME_ENDED, receiveMove must also use showDialog to tell to the player that the game was ended and who, if any, won)

The Game class in the game applet must also have a constructor looking like:
public Game(FMGameHost win, boolean firstMove)
where win is the game host (the Game applet will call win.move and win.showDialog) and firstMove is true if the player has the first move.

Here follows the complete source code for the game called Xand0. It is a very simple game, nobody can win if correctly played, but is a very good chaice to show how an game applet must be written. Before looking at the following code, you probably want to read the help for that game, to learn the game rules and even to play the game in order to understand how it works.

// start of file Xand0/Game.java #############################################
ckage Xand0;
import FMGame;
import FMGameHost;
import java.awt.*;

// Game ######################################################################
public class Game extends Canvas implements FMGame {
static final int cw=20;
static final int ch=20;
boolean firstMove;
int status[][];
int numMoves=0;
FMGameHost win;

// Game::Game ================================================================
public Game(FMGameHost win, boolean firstMove) {
int i,j;
this.firstMove=firstMove;
this.win=win;
status=new int[3][3];
for (i=0;i<3;i++)
	for (j=0;j<3;j++)
		status[i][j]=0;}

// Game::size ================================================================
public Dimension size() {
return new Dimension(3*cw+1,3*ch+1);}

// Game::victory =============================================================
boolean victory() {
int retval;
retval=status[0][0]+status[0][1]+status[0][2];
if (retval==3 || retval==-3)
	return true;
retval=status[1][0]+status[1][1]+status[1][2];
if (retval==3 || retval==-3)
	return true;
retval=status[2][0]+status[2][1]+status[2][2];
if (retval==3 || retval==-3)
	return true;
retval=status[0][0]+status[1][0]+status[2][0];
if (retval==3 || retval==-3)
	return true;
retval=status[0][1]+status[1][1]+status[2][1];
if (retval==3 || retval==-3)
	return true;
retval=status[0][2]+status[1][2]+status[2][2];
if (retval==3 || retval==-3)
	return true;
retval=status[0][0]+status[1][1]+status[2][2];
if (retval==3 || retval==-3)
	return true;
retval=status[0][2]+status[1][1]+status[2][0];
if (retval==3 || retval==-3)
	return true;
return false;}

// Game::mouseDown ===========================================================
public boolean mouseDown(Event e, int x, int y) {
int i,j;
boolean endGame=false;
if (x%cw==0 || y%ch==0)
	return true;
i=x/cw; j=y/ch;
if (status[i][j]!=0)
	return true;
if (firstMove)
	status[i][j]=1;
else
	status[i][j]=-1;
numMoves++;
repaint();
if (victory()) {
	endGame=true;
	win.showDialog("Congratulations! You win");}
if (numMoves==9) {
	endGame=true;
	win.showDialog("Game ended!\nNobody won!");}
win.move(""+i+j,endGame);
return true;}

// Game::receiveMove =========================================================
public int receiveMove(String s) {
int i,j;
int retval=win.PLAYING_TO_MOVE;
i=s.charAt(2)-'0';
j=s.charAt(3)-'0';
if (firstMove)
	status[i][j]=-1;
else
	status[i][j]=1;
numMoves++;
repaint();
if (victory()) {
	win.showDialog("Sorry, you loose");
	retval=win.GAME_ENDED;}
if (numMoves==9) {
	retval=win.GAME_ENDED;
	win.showDialog("Game ended!\nNobody won!");}
return retval;}

// Game::paint ===============================================================
public void paint(Graphics g) {
int i,j;
g.drawRect(0,0,3*cw,3*ch);
g.drawLine(0,ch,3*cw+1,ch);
g.drawLine(0,2*ch,3*cw+1,2*ch);
g.drawLine(cw,0,cw,3*ch+1);
g.drawLine(2*cw,0,2*cw,3*ch+1);
for (i=0;i<3;i++)
	for (j=0;j<3;j++)
		switch (status[i][j]) {
			case 1:
				g.drawLine(cw*i+3,ch*j+3,cw*(i+1)-3,ch*(j+1)-3);
				g.drawLine(cw*i+3,ch*(j+1)-3,cw*(i+1)-3,ch*j+3);
				break;
			case -1:
				g.drawOval(cw*i+2,ch*j+2,cw-4,ch-4);
				break;}}}
// end of file Xand0/Game.java ###############################################