Background on NVEs


Comparing NetFourByFour and FourByFour

Many classes in NetFourByFour are similar to those in FourByFour; this is a consequence of keeping the game logic on the client side.

The Positions class, which manages the on-screen markers is unchanged from FourByFour. PickDragBehavior still handles user picking and dragging, but reports a selected position to NetFourByFour rather than to Board. The game data structures in Board are as before, but the tryMove( ) method for processing a move and the reportWinner( ) are different. WrapNetFourByFour is similar to WrapFourByFour but utilizes the OverlayCanvas class rather than Canvas3D.

The NetFourByFour class is changed since the networking code for the client side is located there. FBFWatcher is used to monitor messages coming from the server, so it is new.

Game Initialization in the Client

The network initialization done in NetFourByFour consists of opening a connection to the server and creating a FBFWatcher thread to wait for a response:

     // globals in NetFourByFour     private Socket sock;     private PrintWriter out;             private void makeContact(  )   // in NetFourByFour     {       try {         sock = new Socket(HOST, PORT);         BufferedReader in  = new BufferedReader(                                   new InputStreamReader( sock.getInputStream(  ) ));         out = new PrintWriter( sock.getOutputStream(  ), true );             new FBFWatcher(this, in).start(  );  // start watching server       }       catch(Exception e)       { System.out.println("Cannot contact the NetFourByFour Server");         System.exit(0);       }     }  // end of makeContact(  ) 

A consideration of Figure 31-8 shows that an ok or full message may be delivered from the server. These responses, and the other possible client-directed messages, are caught by FBFWatcher in its run( ) method:

     public class FBFWatcher extends Thread     {       private NetFourByFour fbf;    // ref back to client       private BufferedReader in;           public FBFWatcher(NetFourByFour fbf, BufferedReader i)       {  this.fbf = fbf;          in = i;        }           public void run(  )       { String line;         try {           while ((line = in.readLine(  )) != null) {             if (line.startsWith("ok"))               extractID(line.substring(3));                 else if (line.startsWith("full"))               fbf.disable("full game");             else if (line.startsWith("tooFewPlayers"))               fbf.disable("other player has left");             else if (line.startsWith("otherTurn"))               extractOther(line.substring(10));             else if (line.startsWith("added"))    // don't use ID               fbf.addPlayer(  );        // client adds other player             else if (line.startsWith("removed"))  // don't use ID               fbf.removePlayer(  );     // client removes other player             else   // anything else               System.out.println("ERR: " + line + "\n");           }         }         catch(Exception e) // socket closure will end while         {  fbf.disable("server link lost");  }   // end game as well        }  // end of run(  )           // other methods...     } // end of FBFWatcher class 

The messages considered inside run( ) match the communications that a player may receive, as given in Figures 31-8, 31-9, and 31-10.

An ok message causes exTRactID( ) to extract the player ID and call NetFourByFour's setPlayerID( ) method. This binds the playerID value used throughout the client's execution.

A full message triggers a call to NetFourByFour's disable( ) method. This is called from various places to initiate the client's departure from the game.

The handler for the player sends an added message to the FBFWatcher of the other player, leading to a call of it's NetFourByFour addPlayer( ) method. This increments the client's numPlayers counter, which permits game play to commence when equal to 2.

Game Termination in the Client

Figure 31-9 lists five ways in which game play may stop:

  • The player won.

  • The close box was clicked.

  • There are too few players to continue (i.e., the other player has departed).

  • The game has enough participants.

  • The handler or other player dies.

Each case is considered in the following sections.

The player has won

The Board object detects whether a game has been won in the same way as the FourByFour version and then calls reportWinner( ):

     private void reportWinner(int playerID)  // in Board     {       long end_time = System.currentTimeMillis(  );       long time = (end_time - startTime)/1000;           int score = (NUM_SPOTS + 2 - nmoves)*111 - int) Math.min(time*1000, 5000);           fbf.gameWon(playerID, score);     } 

reportWinner( ) has two changes: it's passed the player ID of the winner, and it calls gameWon( ) in NetFourByFour rather than write to a text field. gameWon( ) checks the player ID against the client's own ID and passes a suitable string to disable( ):

     public void gameWon(int pid, int score)  // in NetFourByFour     {       if (pid == playerID)   // this client has won         disable("You've won with score " + score);       else         disable("Player " + pid + " has won with score " + score);     } 

disable( ) is the core method for terminating game play for the client. It sends a disconnect message to the server (see Figure 31-9), sets a global Boolean isDisabled to true, and updates the status string:

     synchronized public void disable(String msg)  // in NetFourByFour     { if (!isDisabled) {    // client can only be disabled once         try {           isDisabled = true;           out.println("disconnect");  // tell server           sock.close(  );           setStatus("Game Over: " + msg);           // System.out.println("Disabled: " + msg);         }         catch(Exception e)         {  System.out.println( e );  }       }     } 

disable( ) may be called from the client's close box, FBFWatcher, or from Board (via gameWon( )), so it must be synchronized. The isDisabled flag means the client can only be disabled once. Disabling breaks the network connection and makes it so further selections have no effect on the board. However, the application is left running, and the player can rotate the game board.

The close box was clicked

The constructor for NetFourByFour sets up a call to disable( ) and exit( ) in a window listener:

     addWindowListener( new WindowAdapter(  ) {       public void windowClosing(WindowEvent e)       { disable("exiting");         System.exit( 0 );       }     }); 

Too few players, and the game is full

If FBFWatcher receives a tooFewPlayers or a full message from its handler, it will call disable( ):

     public void run(  )   // in FBFWatcher     { String line;       try {         while ((line = in.readLine(  )) != null) {           if (line.startsWith("ok"))             extractID(line.substring(3));           else if (line.startsWith("full"))             fbf.disable("full game");           else if (line.startsWith("tooFewPlayers"))             fbf.disable("other player has left");           : // other message else-if-tests         }       }       catch(Exception e)       {// exception handling... }     } // end of run(  ) 

The handler or other player has died

If the other player's client suddenly terminates, then its server-side handler will detect the closure of its socket and will send a removed message to the other player:

     public void run(  )    // in FBFWatcher     { String line;       try {         while ((line = in.readLine(  )) != null) {           if (line.startsWith("ok"))              extractID(line.substring(3));               // other message else-if-tests, and then...                else if (line.startsWith("removed"))  // don't use ID             fbf.removePlayer(  );     // client removes other player           // other message else-if-tests         }       }       catch(Exception e)    // socket closure will end while       {  fbf.disable("server link lost");  }  // end game as well         } // end of run(  ) 

FBFWatcher will see the removed message and call removePlayer( ) in NetFourByFour. This will decrement its number of players counter which prevents any further selected moves from being carried out.

If the server dies then FBFWatcher will raise an exception when it reads from the socket. This triggers a call to disable( ) which ends the game.

Game Play in the Client

Figure 31-7 presents an overview of typical game play using an activity diagram. Player 1 selects a move that is processed locally while being sent via the server to the other player to be processed.

A closer examination of this turn-taking operation is complex because it involves the clients and the server. I'll break it into three parts, corresponding to the swimlanes in the activity diagram. Each part will be expanded into its own UML sequence diagram, which allows more detail to be exposed.

Perhaps the most important point in this section is the usefulness of UML activity diagrams and sequence diagrams for designing and documenting network code. A networked application utilizes data and methods distributed across many distinct pieces of software, linked by complex, low-level message passing mechanisms. Abstraction tools are essential.

Player 1's client

The sequence diagram for the left side of Figure 31-7 (player 1's client) is shown in Figure 31-14.

Figure 31-14. Sequence diagram for player 1's client


The mouse press is dealt with by PickDragBehavior in a similar way to FourByFour, except that tryMove( ) is called in NetFourByFour and passed the selected position index.

tryMove( ) carries out simple tests before sending a message off to the server and calling doMove( ) to execute the game logic:

     public void tryMove(int posn)  // in NetFourByFour     {       if (!isDisabled) {         if (numPlayers < MAX_PLAYERS)           setStatus("Waiting for player " + otherPlayer(playerID) );         else if (playerID != currPlayer)           setStatus("Sorry, it is Player " + currPlayer + "'s turn");         else if (numPlayers == MAX_PLAYERS) {           out.println( "try " + posn );   // tell the server           doMove(posn, playerID);     // do it, don't wait for response         }         else          System.out.println("Error on processing position");       }     }  // end of tryMove(  ) 

TRyPosn( ) in Board is simpler than the version in FourByFour:

     public void tryPosn(int pos, int playerID)  // in Board     { positions.set(pos, playerID);  // change 3D marker shown at pos       playMove(pos, playerID);       // play the move on the board     } 

A gameOver Boolean is no longer utilized, the isDisabled Boolean has taken its place back in NetFourByFour. tryPosn( ) no longer changes the player ID since a client is dedicated to a single player.

The playerID input argument of tryPosn( ) is a new requirement since this code may be called to process moves by either of the two players.

set( ) in Positions is unchanged from FourByFour, and playMove( ) utilizes the same game logic to update the game and test for a winning move. reportWinner( ) is a little altered, as explained when considering the termination cases.

Server-side processing

The sequence diagram on the server side (Figure 31-15) shows how a try message from player 1 is passed to player 2 as an otherTurn message.

Figure 31-15. Sequence diagram for the server


I considered the coding behind these diagrams when I looked at the server-side classes.

The diagram shows that the otherTurn message is received by the FBFWatcher of player 2:

     public void run(  )   // in FBFWatcher     { String line;       try {         while ((line = in.readLine(  )) != null) {           if (line.startsWith("ok"))              extractID(line.substring(3));           :  // other message else-if-tests, and then...           else if (line.startsWith("otherTurn"))             extractOther(line.substring(10));           : // other message else-if-tests         }       }       catch(Exception e)       { // exception handling... }     } // end of run(  ) 

Player 2's client

The sequence diagram for the righthand side of Figure 31-7 (player 2's client) is shown in Figure 31-16.

The call to exTRactOther( ) in FBFWatcher extracts the player's ID and the position index from the otherTurn message. It then calls doMove( ) in NetFourByFour:

     synchronized public void doMove(int posn,int pid) //in NetFourByFour     {       wrapFBF.tryPosn(posn, pid);   // and so to Board       if (!isDisabled) {         currPlayer = otherPlayer( currPlayer );  // player's turn over         if (currPlayer == playerID)  // this player's turn now           setStatus("It's your turn now");         else    // the other player's turn           setStatus("Player " + currPlayer + "'s turn");       }     } 

doMove( ) and the methods it calls (e.g., tryPosn( ) in WrapNetFBF and Board) are used by the client to execute its moves and to execute the moves of the other player. The methods all take the player ID as an argument, so the owner of the move is clear.

As mentioned before, the client could still be processing its move when a request to process the opponent's move comes in. This situation is handled by the use of the synchronized keyword with doMove( ): a new call to doMove( ) must wait until the current call has finished.

Figure 31-16. Sequence diagram for player 2's client


Writing on the Canvas

OverlayCanvas is a subclass of Canvas3D which draws a status string onto the canvas in its top-left corner (see Figure 31-11). The display is implemented as an overlay, meaning that the string is not part of the 3D scene; instead, it's resting on "top" of the canvas. This technique utilizes Java 3D's mixed mode rendering, which gives the programmer access to Java 3D's rendering loop at different stages in its execution.

The client makes regular calls to setStatus( ) in NetFourByFour to update a global status string:

     synchronized public void setStatus(String msg) //in NetFourByFour     {  status = msg;  } 

This string is periodically accessed from the OverlayCanvas object by calling getStatus( ):

     synchronized public String getStatus(  )  // in NetFourByFour     {  return status; } 

The get and set methods are synchronizedthe calls from OverlayCanvas may come at any time and should not access the string while it's being updated.

The OverlayCanvas object is created in the constructor of WrapNetFBF, in the usual way that a Canvas3D object is created:

     GraphicsConfiguration config =            SimpleUniverse.getPreferredConfiguration(  );         OverlayCanvas canvas3D = new OverlayCanvas(config, fbf);     add("Center", canvas3D);     canvas3D.setFocusable(true);     // give focus to the canvas     canvas3D.requestFocus(  ); 

The only visible difference is the passing of a reference to the NetFourByFour object into the OverlayCanvas constructor (the fbf variable). This is used by OverlayCanvas to call the getStatus( ) method in NetFourByFour.

Mixed mode rendering

Canvas3D provides four methods for accessing Java 3D's rendering loop: preRender( ), postRender( ), postSwap( ), and renderField( ). By default, these methods have empty implementations and are called automatically at various stages in each cycle of the rendering loop. I can utilize them by subclassing Canvas3D and by providing implementations for the required methods.

Here are the four methods in more detail:


preRender( )

Called after the canvas has been cleared and before any rendering is carried out at the start of the current rendering cycle.


postRender( )

Called after all the rendering is completing but before the buffer swap. This means the current rendering has not yet been placed on screen.


postSwap( )

Called after the current rendering is on the screen (i.e., after the buffer has been swapped out to the frame) at the end of the rendering cycle.


renderField( )

Useful in stereo rendering. It's called after the left eye's visible objects are rendered and again after the right eye's visible objects are rendered.

Drawing on the overlay canvas

postSwap( ) is used to draw the updated status string on screen:

     // globals     private final static int XPOS = 5;     private final static int YPOS = 15;     private final static Font MSGFONT = new Font( "SansSerif", Font.BOLD, 12);         private NetFourByFour fbf;     private String status;             public void postSwap(  )     {       Graphics2D g = (Graphics2D) getGraphics(  );       g.setColor(Color.red);       g.setFont( MSGFONT );           if ((status = fbf.getStatus(  )) != null)  // it has a value         g.drawString(status, XPOS, YPOS);           // this call is made to compensate for the javaw repaint bug,       Toolkit.getDefaultToolkit(  ).sync(  );     }  // end of postSwap(  ) 

The call to getStatus( ) in NetFourByFour may return null at the start of the client's execution if the canvas is rendered before status gets a value.

The repaint( ) and paint( ) methods are overridden:

     public void repaint(  )     // Overriding repaint(  ) makes the worst flickering disappear     { Graphics2D g = (Graphics2D) getGraphics(  );       paint(g);     }         public void paint(Graphics g)     // paint(  ) is overridden to compensate for the javaw repaint bug     { super.paint(g);       Toolkit.getDefaultToolkit(  ).sync(  );     } 

repaint( ) is overridden to stop the canvas from being cleared before being repainted, which otherwise causes a nasty flicker.

The calls to sync( ) in postSwap( ) and paint( ) are bug fixes to avoid painting problems when using javaw to execute Java 3D mixed-mode applications.



Killer Game Programming in Java
Killer Game Programming in Java
ISBN: 0596007302
EAN: 2147483647
Year: 2006
Pages: 340

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net