HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

Abstract
This is the second in a two-part series presenting a Java implementation of a real-time multi-user blackjack game based on a collaborative, active object framework. In the last article, we presented the design of an active object framework for developing collaborative client/server applications. In this article, we will use the active object framework to develop both the client and server components of a collaborative blackjack game.

Review of the Active Object Architecture
In last month's article, we presented an active object framework which supports event-based communication between concurrent objects. We defined an active object as a threaded object with an event queue capable of handling events from other active objects via both active waiting and passive callback handling. We also added support for client/server-based collaboration by defining the session concept, which is a group of active objects consisting of one server and one or more clients. We also simulated a multicast mechanism so that any active object within a session may send an event simultaneously to all other active objects within the session.

Simulating Client/Server Interaction
To demonstrate the use of our active object framework, we will keep the example simple by omitting some of the advanced functions of a blackjack game, such as betting, double-down and splitting. We will also not include any graphical interface in our simple example.

To begin, we define the blackjack server and blackjack client as active objects by extending ActiveObject.

class BJServer extends ActiveObject {
...
}
class BJClient extends ActiveObject {
...
}

Recall that our example active object architecture does not include networking capabilities. Therefore, for this example we will simulate multiple blackjack clients and one server all within one Java application as shown in Figure 1. This is accomplished by instantiating the server and all clients within a single application and calling each active object's start() methods. We will call this main application class Blackjack.

Figure 1
Figure 1:

As shown in Listing 2b, the Blackjack application instantiates the Blackjack server and four Blackjack clients. The Blackjack server also represents the dealer, which we will designate as seat number 0. The four clients represent four players occupying seats 1 through 4. Besides simplifying our example, this technique of running a group of interacting active objects within one application is a very useful technique to verify the correctness of a collaborative client/server protocol without the hassles of repeatedly restarting servers and clients during debugging.

Determining the Communication Protocol
The first step in creating a collaborative game such as blackjack is to define all of the events that will be necessary for communications between the clients and the server.

Following the active object design philosophy, the client and server will communicate through event passing between active objects. To design our blackjack client/server protocol, we will consider the mechanics of one session; that is, a client/server interaction between one server and up to four clients.

Recall that in our active object framework an active object may handle events from other active objects in two ways - using the active event handler or the passive callback handler. For blackjack we will use the active event handler to handle game control flow, and the passive callback handler to handle status update events (events that are used only for displaying status on the screen and has no effect on the game flow).

We'll first specify the events that are required to control one game (hand) of blackjack for a particular player. First, the dealer needs to deal two cards to the player, so let's define a deal_card event. Since this is a multi-player game, the player needs to wait until it is his turn to hit or stand. The dealer needs to notify the client with a request_action event. The player then chooses to hit or stand, so we need a hit and a stand event to reply to the server. During a sequence of hits, the player may bust, so the dealer needs to notify the client with a you_bust event. Finally, to conclude the game, the server needs to send a game_result event to notify the client that the current game is over.

In addition to control flow events, other events pertaining to the status of other players may occur during a game. These events will be handled by a separate entity, the passive callback handler, which is responsible for displaying information (or graphics) about all players during game play. In our simplified blackjack game, we only need two status events. First, the server needs to notify every client whenever a card is dealt to anyone, so that the card may be shown on the screen. This notification is represented by the show_card message. Note that the show_card message is used in addition to the deal_card control flow message because show_card is handled by the passive event handler while the deal_card message is handled by the active handler. We need another event to notify everyone that some player has bust. This is represented by the bust message. See Table 1 for a summary of the event definitions.

Table 1

All events are defined by first defining a unique integer constant for each event type, which we define in class Blackjack:

public static final int hit = 1;
public static final int stand = 2;
public static final int game_result = 3;
---

Then, each event is defined as a subclass of java.lang.Object. Each event object implements hashCode() to return the unique integer identifier that we defined in class Blackjack. To define any parameters in the event, just define them as data members of the event class.

class deal_card extends Object
{
int clientid;
Card card;
public deal_card(int clientid, Card card) {
this.clientid = clientid;
this.card=card;
}
public int hashCode()
{ return Blackjack.deal_card; }
}

The complete listing of event class definitions is given in Listing 2a.

We are now ready to code the server and client active objects, which will use the events we have defined to communicate.

Blackjack Server Logic
Using our active object framework, we can conceptualize a session as one blackjack table consisting of one dealer and up to four players. The blackjack session is identified by the session name "BJ." To allow for multiple concurrent blackjack tables, we can also assign each blackjack table a unique session ID. In our example here, the blackjack server is defined as class BJServer, extending ActiveObject. BJServer's constructor starts one session called "BJ" with a session ID of 0.

class BJServer extends ActiveObject
{
private ActiveSession session;
public BJServer()
{
this.session = new
ActiveSession(this,"BJ",0);
}
---

Though an active object may handle events in both the run() or handleEvent() methods depending on the nature of the events, in this case the blackjack server only requires the run() method to control game flow. Note that blackjack server's run() method may generate status events directed toward the passive handler on the client (BJClient.handleEvent()) in addition to control flow events directed at the client active handler (BJClient.run()), as shown in Figure 2. BJServer.handleEvent() is not used and is empty.

Figure 2
Figure 2:

As shown in Listing 2c, BJServer.run() is encased in an outermost while(true) loop so that the server may continuously serve games one after another. To begin a game, BJServer deals each player a card face up and a card face down.

for (int player=1;
player<=blackjack.max_players; player++)
{
dealCardTo(player,Card.FACE_DOWN);
dealCardTo(player,Card.FACE_UP);
}

This is accomplished in the dealCardTo() method by multicasting the status update event show_card to all clients and sending the deal_card to the client representing the player actually receiving the card.

session.mcast(new show_card
(player,card),0); //show card
to all except dealer session.send(new deal_card
(player,card),player); //deal
card to player

The dealCardTo() method also tracks the cards that each player has received so that the server may detect any player who has bust and the winner may be computed at the end of the game. See Listing 2c for details.

Next, it is required to serve each player. This involves sending a request_action event and waiting for a hit or stand event from the client. If a hit event is received, then a card is dealt and a check for a bust is performed. If a bust occurs, then a status event bust is multicast to all players using mcast() and a control event you_bust is sent to the player that has bust. This step is repeated until all of the players have properly been "served"; that is, until all players have requested to stand or have bust.

Finally, the server determines the winners and sends each player's game result using the event game_result.

Blackjack Client Logic
The Blackjack client also extends ActiveObject. Each Blackjack client is constructed with a clientID which corresponds to the player's seat number at the table. The first thing each client does is join the session, "BJ:0", which was started by the Blackjack server.

class BJClient extends ActiveObject
{
protected int clientID;
private ActiveSession session;

public BJClient(int clientID)
{
this.clientID = clientID;
this.session =
ActiveSession.join(this,"BJ",0); }

We also need to register all of the status update events that are to be handled by handleEvent(). This is done using ActiveObject's registerCallback() method.

int[] evts = {BlackJack.show_card,
BlackJack.bust};registerCallback(evts);

Both active control logic and status update logic events are required to be handled by the client; thus, both the run() method and handleEvent() method must be implemented.

Let's first look at the handleEvent() method. There are only two status update events that we need to be concerned with for this example: show_card and bust. These can be handled by handleEvent() by checking the event's hashCode() in a switch statement, similar to this:

switch (evt.hashCode())
{
case BlackJack.show_card: ...
case BlackJack.bust:...
}

In our example, we are just going to print out the status events that are received and their contents, since we are not implementing a graphical interface.

Now let's implement the game control flow in BJClient.run(). Like BJServer.run(), we encase the run() method in an outermost while(true) loop so that multiple games may be played continuously. During a particular game, the first step is to wait for two cards to be dealt. This is done by performing two waitFor() methods, each like this:

waitFor(BlackJack.deal_card);

Then we need to wait for the control event, request_action, to start the client's turn. This is where the player is prompted whether he wants to hit or stand. A hit action by the player generates the control event hit and a stand action generates stand. If the player hits then BJClient continues in a loop (which we call the ActionLoop), waiting for either request_action or you_bust. The client stays in the ActionLoop until either the player busts or the player requests to stand. In Listing 2d, we implement prompting for hit or stand by reading characters from the keyboard, so when a "stand" command is received, the thread needs to break out of the action loop. Since the action loop is not the immediate innermost loop at this point, we use

break ActionLoop;

to break out of the loop labeled with the ActionLoop label. Upon exiting the ActionLoop, all that's left to do is to wait for the control event game_result, which informs the client that the game is over and whether the player won or lost. The client then continues to wait for the start of the next game by restarting the outermost while loop.

Funtopia: A Real-World Implementation
In this two part article we have presented an active object framework suitable for developing collaborative client/server applications and we have demonstrated use of this framework through an example multi-player blackjack game. Although major aspects such as networking and graphics were left out for simplicity, the active object design philosophy is one of the key concepts to successfully developing such collaborative applications.

Avanteer, Inc., a company dedicated to Java-based collaborative solutions, has developed a full-scale multi-player game site called Funtopia using the active object design philosophy. Funtopia, at http://www.funtopia.com, includes a fully-featured multi-player blackjack game (Figure 3) which includes the networking and graphics aspects of a collaborative game that we have omitted in this article. All of the source code listings given in this two part article is available on the Avanteer web site, at http://www.avanteer.com.

Figure 3
Figure 3:

About the Authors
Larry T. Chen is a co-founder of Avanteer, Inc., a company focusing on developing Java-based collaborative software. He is also pursuing a Ph.D. in the area of distributed systems at the University of California, Irvine. Larry may be reached at [email protected]

Todd Busby is a project manager at Avanteer, Inc. Todd holds a Masters in Computer Science from the California State University, Fullerton. He may be reached at [email protected]

	

Listings 1a-1b:  Active Object Framework 
See Part 1 of this article in last month’s issue. 
Listing 2a: Blackjack Main
  
import java.awt.*; 

public class Blackjack extends Object 
{ 
 public static final int MAX_PLAYERS = 5; 
 public static BJServer server; 
 public static BJClient[] client = new BJClient[MAX_PLAYERS+1]; 

 public Blackjack() 
 { 
  server = new BJServer(); 
  for (int i=1; i<=MAX_PLAYERS; i++) client[i] = new BJClient(i); 
 } 
 public static void main(String[] args) 
 { 
  new Blackjack(); 
  server.start(); 
  for (int i=1; i<=MAX_PLAYERS; i++) client[i].start(); 
 } 

 public static final int hit = 1; 
 public static final int stand = 2; 
 public static final int game_result = 3; 
 public static final int request_action = 4; 
 public static final int bust = 5; 
 public static final int you_bust = 6; 
 public static final int deal_card = 7; 
 public static final int show_card = 8; 
} 

Listing 2b: Event Definitions
 
class deal_card extends Object  
{ 
 int clientid; 
 Card card; 
 public deal_card(int clientid, Card card) { 
  this.clientid = clientid; 
  this.card=card; 
 } 
 public int hashCode() { return Blackjack.deal_card; 
 } 
 public String toString() {return "Your card "+card.toString();
 } 
} 
class show_card extends deal_card 
{ 
 public show_card(int clientid, Card card) {super(clientid, card);
 } 
 public int hashCode() {return Blackjack.show_card;} 
 public String toString() {return "Player "+clientid+" received "+card.toString();
 } 
} 

class game_result extends Object 
{ 
 int winner; 
 public game_result(int winner) {this.winner = winner;} 
 public int hashCode() {return Blackjack.game_result;} 
 public String toString() {return "Player "+winner;} 
} 

Listing 2c: Blackjack Server
 
class BJServer extends ActiveObject 
{ 
 private ActiveSession session; 
 private int[] cardTotals = new int[Blackjack.MAX_PLAYERS+1]; 
 private int[] aceCount = new int[Blackjack.MAX_PLAYERS+1]; 

 public BJServer() 
 { 
  this.session = new ActiveSession(this,"BJ",0); 
 } 

 public void handleEvent(Object evt) {} 

 public void run() { 
  while (true) 
  { 
   //deal cards 
   for (int player=1; player<=Blackjack.MAX_PLAYERS; player++) 
   { 
    dealCardTo(player,Card.FACE_DOWN); 
    dealCardTo(player,Card.FACE_UP); 
   } 

   //serve each player 
   for (int player=1; player<=Blackjack.MAX_PLAYERS; player++) 
   { 
    Object evt = null; 
    do 
    { 
     session.send(new request_action(),player); 

     int[] evts = {Blackjack.hit, Blackjack.stand}; 
     evt = waitFor(evts); 
     if (evt.hashCode()==Blackjack.hit) 
     { 
      dealCardTo(player,Card.FACE_UP); 
      if (cardTotals[player] > 21)  
      { 
       cardTotals[player] = 0; 
       session.mcast(new bust(player),0); 
       session.send(new you_bust(),player); 
       break;  //breaks out of do loop 
      } 
     } 
    } while (evt.hashCode()==Blackjack.hit); 
   } 

   //send results 
   session.mcast(new game_result(winner()),0); 
  } 
 } 

 void dealCardTo(int player, int face) 
 { 
  Card card = Deck.dealCard(face); 
  if (card.getValue()==1) aceCount[player]++; 
  cardTotals[player]+= (card.getValue()==1 ? 11 : card.getValue()); 

  while (cardTotals[player] > 21 && aceCount[player] > 0) 
   if (cardTotals[player] > 21) { cardTotals[player]-=10; 
   aceCount[player]--;} 

  session.mcast(new show_card(player,card),0); 
  session.send(new deal_card(player,card),player); 
 } 

 int winner() 
 { 
  int max = 0; 
  int theWinner = 0; 
  for (int i=1; i<=Blackjack.MAX_PLAYERS; i++) 
   if (cardTotals[i] > max) {max = cardTotals[i]; theWinner = i;} 
  return theWinner; 
 } 
}
 
Listing 2d: Blackjack Client
 
class BJClient extends ActiveObject 
{ 
 protected int clientID; 

 private ActiveSession session; 
 private TextArea textBox; 

 public BJClient(int clientID) 
 { 
  this.clientID = clientID;   
  this.session = ActiveSession.join(this,"BJ",0); 
  Frame f = new Frame(this.getClass()+" "+clientID); 
  f.add("Center",textBox = new TextArea(20,40)); 
  f.pack(); f.show(); 
  int[] evts = {Blackjack.show_card,Blackjack.bust};  
  registerCallback(evts);   
 } 

 public void handleEvent(Object evt) 
 { 
  switch (evt.hashCode()) 
  { 
   case Blackjack.show_card:  
    print("    "+evt); 
    break; 
   case Blackjack.bust: 
    print("    "+evt); 
  } 
 } 

 public void print(Object o) {textBox.appendText(o.toString()+"\n");} 

 public void run() 
 { 
  while(true) 
  { 
   print(waitFor(Blackjack.deal_card)); 
   print(waitFor(Blackjack.deal_card)); 
ActionLoop: 
   while (true) 
   { 
    int[] evts = {Blackjack.request_action,Blackjack.you_bust}; 
    Object evt = waitFor(evts); 
    if (evt.hashCode() == Blackjack.you_bust) 
    { 
     print("You bust!"); 
     break; 
    } 

    print("[H]it or [S]tand?\n"); 
    char c = 's'; 
    do    {     try {c = (char) System.in.read();}  
     catch (Exception e) {e.printStackTrace();
	 }     
	 switch (c)     
	 {      
	 case 'H': case 'h':session.send(new hit(),0);        
print(waitFor(Blackjack.deal_card));       
break;      
case 'S': 
case 's': 
       session.send(new stand(),0);  
       break ActionLoop;      
	   default:      }    } while (c!='h' && c!='H');   
	   }   
	   print("The winner is "+waitFor(Blackjack.game_result)+"\n------------");  
	   } 
	   }} 
  

class bust extends Object 
{ 
 int player; 
 public bust(int player) {this.player = player;} 
 public int hashCode() {return Blackjack.bust;} 
 public String toString() {return "Player "+player+" busts!";} 
} 

class request_action extends Object  
 {public int hashCode() {return Blackjack.request_action;}} 
class hit extends Object 
{public int hashCode() {return Blackjack.hit;}} 
class stand extends Object 
{public int hashCode() {return Blackjack.stand;}} 
class you_bust extends Object 
{public int hashCode() {return Blackjack.you_bust;}}


 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.