CMPSCI 377: Operating Systems
Lab 3 Hints

Here are a few thoughts on game design that have come out of my experience in building my poker player. You are welcome to use snippets of code from this example.

Communication Model

I strongly recommend using the RMI communication model, since you do not have to think about the details of moving the bytes around the network. In this model, your game server is implemented as the remote object and the clients execute methods on this object in order to change (or attempt to change) the state of the game. The set of methods provided by the remote poker object is defined in the GameServer class.

The difficulty with this form of communication is that although clients are able to execute methods on the remote object, the server object does not have a direct way to initiate a message back to the clients. In the poker server, we solve this problem by maintaining a queue of messages on the server for each client. One thread of the client is responsible for picking up these messages and processing them appropriately. These messages are all implemented as extentions of the absract ReturnMessage class. Here is the set of extentions that we are currently using:

We represent the message queue on the server side using the ArrayList class, which is a dynamically allocatable array class. There is a queue associated with each game player (see the Player class).

On the client side, once the game has started, the job of the main thread is to continuously obtain these messages as they become available (see the PokerClient class). The thread will block until a message is available; once a message is returned from the game server, it is interpreted and serviced. Because we are dealing with an object-oriented language, the different message classes implement their own unique service() methods. Thus, all the client program has to do is to call this service method without having to explicitly differentiate between the different message classes (this is done automagically for us at run time).

Knowing What Time It Is

We say that we are dealing with an asynchronous programming problem because multiple threads are operating concurrently, all of which can potentially affect the state of the game. In most cases, you will only allow one client to actually change the game state at any one time. However, all of the clients are interacting in real time with their users, and this interaction may lead to requests for changes in game state (whether or not the game server is ready for these requests). So - in the design of your program, you will want to think carefully about how to enforce this turn taking and how the game state should evolve.

It is common in distributed gaming systems to employ a finite state machine model to describe both the high-level state (e.g., what phase of the game are you in?) and the transitions from one state to another. This way, when a remote method is called by a client, that method is able to know how to respond to the client's request. In this poker server, we have an explicit representation of the high-level state (as stored in the state component of the Game class).

Note that each player also has a notion of state (see the Player class).

Robustness and Testing

Your program should enforce your defined game rules. If a user tries to make a move out of turn, your program must deal appropriately with the request.

One test that we will perform on your program is to kill both the server and one of the clients to see how the remaining processes respond. It is important that you detect and respond appropriately to these 'unfriendly' conditions. On the client side, it is easy to know that the server has disconnected, since calling a method on a remote object that no longer exists will result in a RemoteException being thrown. Detecting the disconnection of a client by the server is a bit more challenging. In the poker player, this is implemented by noting the time at which a client last picked up a message (using the GregorianCalendar class). If this time is too long (in our case, 2 seconds), then the server assumes that the client has died. In order to ensure that the clients always have a message to process, a special "ping" thread on the server side sends the PingReturnMessage to each active player at 1 second intervals. These messages do not cause the client to do anything other than to pick up another message (hence their service() method is empty).

Note that it is not necessary that a game continue when a client disconnects unexpectedly. In this case, the game could simply end with an apology to the remaining users.

Other Notes

Andrew H. Fagg