Developing TicToe HTML5/Implementing Server Entities

From Arianne
Jump to: navigation, search

Development log of TicToe HTML5

Now we have a user interface on the client side and an empty server. The server is the place where all the interesting logic will happen. But before we can start coding the game rules, we need to implement the entities.

As we are using different programming languages for the client and the server, we have to do this again.

We start with a stub for the Entity class, which we will put into the folder net/sf/arianne/tictoe/server/entity. The prefix is generated by reversing our domain name arianne.sf.net to ensure a unique name space.


Entity class

We define two constructors: A simple one without parameters that is used by our code to create a new Entity. And a second one with an RPObject as parameter. This one is used when Marauroa creates an Entity based on information stored in the database:

package net.sf.arianne.tictoe.server.entity;

import marauroa.common.game.Definition.Type;
import marauroa.common.game.RPClass;
import marauroa.common.game.RPObject;

/**
 * the abstract base class of all Entities
 *
 * @author hendrik
 */
public abstract class Entity extends RPObject {

	/**
	 * creates a new Entity.
	 */
	public Entity() {
		// default constructor
		setRPClass("entity");
	}

	/**
	 * creates a new Entity based on the provided RPObject
	 *
	 * @param object RPObject
	 */
	public Entity(RPObject object) {
		super(object);
	}
}

Now we need to tell Marauroa which attributes our entity class will have. Depending on the flags they will be sent to the client and/or stored to the database. For now, we use the x, y, z coordinates without any special flags:

	/**
	 * generates the RPClass
	 */
	public static void generateRPClass() {
		RPClass clazz = new RPClass("entity");
		clazz.addAttribute("x", Type.INT);
		clazz.addAttribute("y", Type.INT);
		clazz.addAttribute("z", Type.INT);
	}

It is a good design practice to make the interface of a class explicit. Therefore we define get- and set- methods for those attributes:

	/**                                                     /**
	 * gets the x-coordinate                                 * sets the x coordinate
	 *                                                       *
	 * @return x-coordinate                                  * @param x x-coordinate
	 */                                                      */
	public int getX() {                                     public void setX(int x) {
		return super.getInt("x");                       	super.put("x", x);
	}                                                       }


	/**                                                     /**
	 * gets the y-coordinate                                 * sets the y coordinate
	 *                                                       *
	 * @return y-coordinate                                  * @param y y-coordinate
	 */                                                      */
	public int getY() {                                     public void setY(int y) {
		return super.getInt("y");                       	super.put("y", y);
	}                                                       }


	/**                                                     /**
	 * gets the z-coordinate                                 * sets the z coordinate
	 *                                                       *
	 * @return z-coordinate                                  * @param z z-coordinate
	 */                                                      */
	public int getZ() {                                     public void setZ(int z) {
		return super.getInt("z");                       	super.put("z", z);
	}                                                       }

These simple methods may seem like a waste of space, but they will make things a lot easier later.

Gameboard class

The game board class is rather simple for now. It is a specialisation of the Entity class both in the Java world as in the Marauroa type definition. It does not define any additional attributes for now.

package net.sf.arianne.tictoe.server.entity;

import marauroa.common.game.RPClass;
import marauroa.common.game.RPObject;

/**
 * a game board
 *
 * @author hendrik
 */
public class Gameboard extends Entity {
	/**
	 * creates a new Gameboard.
	 */
	public Gameboard() {
		// default constructor
		setRPClass("gameboard");
	}

	/**
	 * creates a new Gameboard based on the provided RPObject
	 *
	 * @param object RPObject
	 */
	public Gameboard(RPObject object) {
		super(object);
	}

	/**
	 * generates the RPClass
	 */
	public static void generateRPClass() {
		RPClass clazz = new RPClass("gameboard");
		clazz.isA("entity");
	}
}

This is the place where we will implement most of the game logic later.

Token class

The token class extends Entity like the Gameboard did. But it adds a tokenType attribute:

...
public class Token extends Entity {
	private static final String ATTR_TOKEN_TYPE = "tokenType";

...

	/**
	 * gets the token type.
	 *
	 * @return "x" or "o"
	 */
	public String getTokenType() {
		return super.get(ATTR_TOKEN_TYPE);
	}

	/**
	 * sets the token type.
	 *
	 * @param tokenType type of token ("x", "o")
	 */
	public void setTokenType(String tokenType) {
		super.put(ATTR_TOKEN_TYPE, tokenType);
	}

	/**
	 * generates the RPClass
	 */
	public static void generateRPClass() {
		RPClass clazz = new RPClass("token");
		clazz.addAttribute(ATTR_TOKEN_TYPE, Type.STRING);
		clazz.isA("entity");
	}

Note: We have left out the imports and constructors as they follow the pattern of the previous shown classes.

Player class

The last entity class is the one which represents players. For now, they just have a name, but later they will represent the clients and may be extended to full avatars with nice outfits.

...
public class Player extends Entity {
	private static final String ATTR_PLAYER_NAME = "playerName";

...
	/**
	 * gets the name of the player
	 *
	 * @return name of player
	 */
	public String getPlayerName() {
		return super.get(ATTR_PLAYER_NAME);
	}

	/**
	 * sets the name of the player
	 *
	 * @param playerName 
	 */
	public void setPlayerName(String playerName) {
		super.put(ATTR_PLAYER_NAME, playerName);
	}

	/**
	 * generates the RPClass
	 */
	public static void generateRPClass() {
		RPClass clazz = new RPClass("player");
		clazz.addAttribute(ATTR_PLAYER_NAME, Type.STRING);
		clazz.isA("entity");
	}
}

Again the imports and constructors have been left out.

Factory

The last step is to make our entities known to Marauroa. Therefore we create a new ObjectFactory implementation called net.sf.arianne.tictoe.server.core.TicToeObjectFactory. While this does the same thing as the object factory on the client side, the syntax is a bit different:

package net.sf.arianne.tictoe.server.core;

import marauroa.common.game.RPClass;
import marauroa.common.game.RPObject;
import marauroa.server.game.rp.RPObjectFactory;
import net.sf.arianne.tictoe.server.entity.Gameboard;
import net.sf.arianne.tictoe.server.entity.Player;
import net.sf.arianne.tictoe.server.entity.Token;

/**
 * Creates concrete objects of entities.
 */
public class TicToeObjectFactory extends RPObjectFactory {
	private static RPObjectFactory singleton;

	/**
	 * returns the factory instance (this method is called
	 * by Marauroa using reflection).
	 * 
	 * @return RPObjectFactory
	 */
	public static RPObjectFactory getFactory() {
		if (singleton == null) {
			singleton = new TicToeObjectFactory();
		}
		return singleton;
	}

	@Override
	public RPObject transform(final RPObject object) {
		final RPClass clazz = object.getRPClass();
		if (clazz == null) {
			return super.transform(object);
		}

		final String name = clazz.getName();
		if (name.equals("player")) {
			return new Player(object);
		} else if (name.equals("gameboard")) {
			return new Gameboard(object);
		} else if (name.equals("token")) {
			return new Token(object);
		}

		return super.transform(object);
	}
}

This is a very simple implementation of the transform method, but as we only have 3 entities, this is okay for now. Complex programs usually replace the if/else-if construct with a registration mechanism.

This new class has to be added to server.ini:

factory_implementation=net.sf.arianne.tictoe.server.core.TicToeObjectFactory

Rule Processor

The last basic class is the Rule Processor. It will receive all events from Marauroa and act in a game specific manner. We will register it in server.ini as:

ruleprocessor=net.sf.arianne.tictoe.server.core.TicToeRule

It is a singleton class, and therefore has to implement a get-method for the instance:

package net.sf.arianne.tictoe.server.core;

import marauroa.common.game.RPObject;
import marauroa.server.game.rp.IRPRuleProcessor;
import marauroa.server.game.rp.RPRuleProcessorImpl;
import net.sf.arianne.tictoe.server.entity.Entity;
import net.sf.arianne.tictoe.server.entity.Gameboard;
import net.sf.arianne.tictoe.server.entity.Player;
import net.sf.arianne.tictoe.server.entity.Token;


/**
 * provides the rules of the game.
 */
public class TicToeRule extends RPRuleProcessorImpl {
	private static RPRuleProcessorImpl instance;

	/**
	 * gets the Rule singleton object
	 *
	 * @return Rule
	 */
	public static IRPRuleProcessor get() {
		if (instance == null) {
			instance = new TicToeRule();
		}
		return instance;
	}

The constructor of the rule processor is a good place, to define the RPClasses:

	/**
	 * creates the rule processor
	 */
	public TicToeRule() {
		Entity.generateRPClass();
		Player.generateRPClass();
		Gameboard.generateRPClass();
		Token.generateRPClass();
	}

We override the method createCharacterObject, to tell Marauroa, that newly created character objects should use the class "Player":

	/**
	 * Creates an new character object that will used by createCharacter
	 *
	 * @param username the username who owns the account of the character to be added.
	 * @param character the character to create
	 * @param template the desired values of the avatar representing the character.
	 * @return RPObject
	 */
	@SuppressWarnings("unused")
	@Override
	protected RPObject createCharacterObject(String username, String character, RPObject template) {
		Player player = new Player();
		player.setPlayerName(character);
		return player;
	}
}