Aegidius
 Plüss   Aplulogo
 
in examples/applications
Use the asterisk (*) character for wildcard searches
Searches on more than one word will be treated "as a phrase"
 
serverstatus
 www.aplu.ch
 www.java-online.eu
     Print Text 
© 2018, V10.0
 
  TcpJLib
 
 

The source code of all examples is included in the TcpJLib distribution.

Example 3: Development of a multi-room game server

The general network topology for Internet games is the following: A single game server manages several sessions, often called "rooms". In each room a number of players play a specific game. The game may be the same or different in different rooms and the number of players is normally restricted to a maximum number for the particular room. In the following example we will show the design principles for such a situation using the TcpJLib framework. For the sake of simplicity the game in each room is the same and the number of players in each room is restricted to two. For our pleasure, we use the famous "Memory Game", where card pairs with identical pictures are placed picture down on a table. On each move a player turns two cards. If the picture coincides, the player removes the cards and continues to play until there is no match or no cards left. When all cards are taken, the game is over and the player with more card pairs wins.

If the exchange of data in a two-players game is very simple, you may avoid the complexity of developing your own game server and adopt a peer-to-peer strategy using two TcpNodes or a TcpAgent/TcpBridge pair and distinguish the rooms by the session ID of the relay server.

The following topology is implemented:

tcpjlib5

As seen from this figure every player node and the memory server use the same session ID (for example "xyz"). For the TCP/IP communication every player uses a TcpAgent instance and the memory server uses aTcpBridge instance. Before rushing into coding you must decide how much "game awareness" is delegated to the game server and how much is kept locally within the player programs. Of course the decision depends on the specific game and is a matter of discussion. Some general guidelines are important:

  • The data exchange uses the following protocol: The first integer denotes the command type; if necessary, the following integers denotes data information. The commands are declared in an interface (because integers are transmitted):
  interface Command
{
  
int GAME_INIT = 0;
  
int REPORT_SHOW = 1;
  
int REPORT_HIDE = 2;
  
int REPORT_SCORE = 3;
  
int REQUEST_GAME_ENTRY = 4;
  
int TOO_MANY_PLAYERS = 5;
  
int PLAYER_NAMES = 6;
  
int WAIT_REMOTE_CONNECT = 7;
  
int REMOTE_MOVE = 8;
  
int LOCAL_MOVE = 9;
  
int DISCONNECTED = 10;
  
int GAME_OVER = 11;
}

  • The management of the rooms (login/logout of players, limiting number of players, start and end of game, etc.) is the duty of the game server. It keeps a collection of all rooms and of all players in each room. Because the relay handles name clashes by appending (n) trailers, it is simple to distinguish players that log in with the same name.
  • When the room is complete (two players entered), the game server sends the starting game situation to both of the players and decides that the player entered first will start to play. When the game is finished, the server decides to let start the loser in the next game.
  • To avoid a mess, the player's actions (mouse events) are disabled until he/she is allowed to make a move. To avoid uncertainness, the current status is displayed in a status bar.
  • During the game, most information is transmitted from one player to the other without intervention of the game server (pass-through). When a card is turned, the card coordinates are transmitted with a REPORT_SHOW command. The partner's callback is responsible for turning the same card.
  • After two cards are turned, the success or failure must be checked. There are in principle two possibilities where to do it: Locally (In the mouse callback) or by the game server. Because for complexity reasons we do not want to follow the complete game situation within the game server, we do the check in the mouse callback. On failure, the application thread is "woke up" and, after a short delay, it transmits the card coordinates to the waiting player using the REPORT_HIDE command. (Other solutions: Use the mouse callback or a new thread to transmit the information, or keep the locations of the two cards in memory and transmit only a failure command).
  • All command are received in the dataReceived() callback. Typically there is a switch statement to select the different actions:
  public void dataReceived(String source, int[] data)
{
  switch(data[0])
  {
    case Command.TOO_MANY_PLAYERS:
      setTitle("Room '"+ roomName +"' occupated");
      setStatusText("Connected. But room occupated.");
      break;

    case Command.WAIT_REMOTE_CONNECT:
      clear();
      setTitle("Waiting for partner in room '"+ roomName +"' ...");
      setStatusText("Wait for partner in room '"+ roomName +"'.");
      break;

    case Command.PLAYER_NAMES:
      playerNames = TcpTools.intAryToString(data, 1);
      setTitle("In game: "+ playerNames +" in room '"+ roomName +"'");
      break;

    case Command.DISCONNECTED:
      clear();
      setTitle("Waiting for partner in room '"+ roomName +"' ...");
      setStatusText("Partner disconnected. Wait for partner in room '"
        + roomName +"'.");
      break;

    case Command.REMOTE_MOVE:
      setStatusText("Wait for partner's move...");
      break;

    case Command.LOCAL_MOVE:
      isFirstMove =true;
      setStatusText("It's your move. Please click on two cards!");
      isMouseEnabled =true;
      break;

    case Command.GAME_INIT:
      init(data);
      break;

    case Command.REPORT_SHOW:
      showCard(data[1], data[2], true);
      break;

    case Command.REPORT_HIDE:
      showCard(data[1], data[2], false);
      break;

    case Command.REPORT_SCORE:
      rivalScore = data[1];
      if(isGameOver())
        showFinalScore();
      else
      {
        setStatusText("Score: "+ myScore +"/"+ rivalScore);
        tcpAgent.send("", Command.LOCAL_MOVE);
      }
      break;
  }
}
 
  • When a new player logs in, he/she transmits the user name with a REQUEST_GAME_ENTRY command to the game server. Because TcpAgent/TcpBridge restricts communications to integers, the name string must be coded to an integer array using TcpTools.stringToIntAry() and restored by the game server using TcpTools.intAryToString(). It is a somewhat tedious work to manage the players and the rooms when new players enters and quits, but even if a player disconnects unpolitely by closing the application, the game server is notified by triggering notifyAgentConnection() where a cleanup can be done.
  • Often game servers are highly multi-threaded. You should struggle to reduce parallel executions by separate threads as much as you can to avoid concurrency problems and dead-locks when synchronizing. TcpBridge helps you for serializing your code by calling all notification callbacks from the same thread. Evidently the pipeRequest() callback should not execute long lasting code for one specific node because this could starve out requests from other nodes. If you need to execute some longer lasting code, you are forced to do it in an new thread. In this case be very careful to synchronize critical sections to avoid side-effects, race conditions and concurrent access exceptions.

For all other details, consult the source code that is contained in the TcpJLib distribution (or download it from here).

tcpmemory

Execute the program locally using WebStart.