Developing TicToe HTML5/Implementing Server Entities: Difference between revisions
imported>Hendrik Brummermann No edit summary |
imported>Hendrik Brummermann No edit summary |
||
| (33 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
<noinclude>{{Navigation TicToe HTML5}}</noinclude> |
<noinclude>{{Navigation TicToe HTML5}}__NOTOC__</noinclude> |
||
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. |
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. |
||
| Line 31: | Line 31: | ||
public Entity() { |
public Entity() { |
||
// default constructor |
// default constructor |
||
setRPClass("entity"); |
|||
} |
} |
||
| Line 58: | Line 59: | ||
</source> |
</source> |
||
It is a good design to make the interface of a class explicit. Therefore we define get- and set- |
It is a good design practice to make the interface of a class explicit. Therefore we define get- and set- methods for those attributes: |
||
<source lang="java"> |
<source lang="java"> |
||
| Line 114: | Line 115: | ||
public Gameboard() { |
public Gameboard() { |
||
// default constructor |
// default constructor |
||
setRPClass("gameboard"); |
|||
} |
} |
||
| Line 218: | Line 220: | ||
Again the imports and constructors have been left out. |
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: |
|||
<source lang="java"> |
|||
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); |
|||
} |
|||
} |
|||
</source> |
|||
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: |
|||
<source lang="ini"> |
|||
factory_implementation=net.sf.arianne.tictoe.server.core.TicToeObjectFactory |
|||
</source> |
|||
== 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: |
|||
<source lang="ini"> |
|||
ruleprocessor=net.sf.arianne.tictoe.server.core.TicToeRule |
|||
</source> |
|||
It is a singleton class, and therefore has to implement a get-method for the instance: |
|||
<source lang="java"> |
|||
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; |
|||
} |
|||
</source> |
|||
The constructor of the rule processor is a good place, to define the RPClasses: |
|||
<source lang="java"> |
|||
/** |
|||
* creates the rule processor |
|||
*/ |
|||
public TicToeRule() { |
|||
Entity.generateRPClass(); |
|||
Player.generateRPClass(); |
|||
Gameboard.generateRPClass(); |
|||
Token.generateRPClass(); |
|||
} |
|||
</source> |
|||
We override the method createCharacterObject, to tell Marauroa, that newly created character objects should use the class "Player": |
|||
<source lang="java"> |
|||
/** |
|||
* 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; |
|||
} |
|||
} |
|||
</source> |
|||
Latest revision as of 04:18, 4 January 2012
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:
<source lang="java"> 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); } } </source>
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:
<source lang="java"> /** * 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); } </source>
It is a good design practice to make the interface of a class explicit. Therefore we define get- and set- methods for those attributes:
<source lang="java"> /** /** * 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);
} }
</source>
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.
<source lang="java"> 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"); } } </source>
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:
<source lang="java"> ... 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"); } </source>
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.
<source lang="java"> ... 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"); } } </source>
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:
<source lang="java"> 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); } } </source>
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: <source lang="ini"> factory_implementation=net.sf.arianne.tictoe.server.core.TicToeObjectFactory </source>
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:
<source lang="ini"> ruleprocessor=net.sf.arianne.tictoe.server.core.TicToeRule </source>
It is a singleton class, and therefore has to implement a get-method for the instance:
<source lang="java"> 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; } </source>
The constructor of the rule processor is a good place, to define the RPClasses:
<source lang="java"> /** * creates the rule processor */ public TicToeRule() { Entity.generateRPClass(); Player.generateRPClass(); Gameboard.generateRPClass(); Token.generateRPClass(); } </source>
We override the method createCharacterObject, to tell Marauroa, that newly created character objects should use the class "Player":
<source lang="java"> /** * 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; } } </source>