In yesterChapter's lesson, you learned how Java supports network communications
through a client/server model. You even built a simple socket
class to help make network communications a little easier. In
toChapter's lesson, you carry the client/server approach a step forward
and build a complete network game supporting multiple players.
Actually, instead of writing a whole new game, you modify a game
you already wrote to support network play. By the end of toChapter's
lesson, you'll have the skills necessary to begin developing your
own network games.
ToChapter you take the Connect4 game you wrote on Chapter 16
and adapt it to network play between two players. In doing so,
you put the socket class developed yesterChapter to good use; you
use the socket class as a basis for implementing a complete network
game protocol facilitating game communication between multiple
clients and a server. Sounds like fun, right? You bet!
The following topics are covered in toChapter's lesson:
If you recall, the Connect4 game you wrote in Chapter 16's
lesson was a single-player game utilizing artificial intelligence
to simulate an intelligent computer player. The goal now is to
take that game and adapt it for two human players playing the
game over the Web. This task might sound a little daunting, but
keep in mind that the game itself is already written; you're just
adding the network support code.
As you learned yesterChapter, the core of Java network game programming
revolves around a client/server communication strategy. Knowing
this, you've probably guessed that a Java network game design
will involve some type of client/server arrangement. In fact,
the design of the NetConnect4 game can be divided cleanly into
the client side and the server side. These two components are
logically separate, communicating entirely through a game protocol
defined between them. Let's take a look at what each of these
pieces is responsible for.
In any Java game, the server side of the game acts almost like
a referee, managing the different players and helping them communicate
effectively. More specifically, a game server takes on the role
of handling the network connection for each player, along with
querying for and responding to events for the players. The role
of a generic game server can be broken down into the following
actions:
Initialize the server socket.
Wait for a client to connect.
Accept the client connection.
Create a daemon thread to support the client.
Go back to step 2.
The most crucial aspect of this series of events is step 4, when
the server creates a daemon thread to support the client. You're
probably wondering what I mean by "support." Well, in
a generic sense, I don't know what I mean. The reason is that
the daemon thread is where the applet-specific code goes. So a
generic server only knows that it needs to create a daemon thread;
it doesn't know or care about what that thread actually does.
You'll learn more about daemon threads a little later toChapter when
you actually get into the code for NetConnect4.
A daemon is a process that runs in the background of a
system performing some type of support function.
You now have an idea about the role a generic game server plays
in the context of a network game. The question, then, is what
role does such a server play in the context of a specific game,
namely NetConnect4? The role of the NetConnect4 server ends up
being not much different from that of the generic server, but
it is important that you understand exactly what it needs to do
differently.
Because Connect4 is a two-player game, the first job of the server
is to pair up players (clients) as they connect to the game. A
more limited approach would be to permit only the first two players
who connect to play the game. But you're smarter than that and
hopefully demand a more powerful game server. Your game server
enables multiple games to be played at once simply by pairing
additional client players together for each game. In this way,
a typical NetConnect4 server session might have six or eight players
playing at once. Of course, the players know only about the other
player in their immediate game.
Note
To keep things a little simpler, don't worry about players choosing who they play against; in other words, just pair players on a first-come first-served basis.
After the server has detected two players and paired them up for
a game, it becomes the responsibility of the server's daemon thread
to dictate the flow of the game between the players. The daemon
accomplishes this by informing each player of the state of the
game, while also modifying the state according to each player's
turn. The responsibilities of the NetConnect4 server and daemon
can be summarized as shown here:
Accept client player connections.
Pair up players to form separate games.
Manage the flow of the game.
Communicate each player's move to the other player.
The other side of a Java network game is the client. The client
portion of a network game corresponds to the applet being run
by each player. Because game players interact with the client,
the client program is usually much fancier than the server in
regard to how information is displayed. As a matter of fact, game
servers typically don't even have user interfaces; they crank
away entirely behind-the-scenes doing all the dirty work while
the client applets dazzle the users.
The basic responsibility of a game client is to connect to the
server and communicate the user's actions, along with receiving
game state information from the server and updating itself accordingly.
Of course, along with this comes the responsibility of displaying
the game graphics and managing the entire game interface for the
user. You can probably already see that game clients tend to require
the most work, at least from a strictly design perspective.
The good news is that you've already written most of the client
for NetConnect4. The Connect4 game you wrote on Chapter 16
is essentially a non-networking game client in that it handles
all the work of managing a game with a user; it displays the graphics,
interfaces with the user, and keeps up with the state of the game.
The focus of building a NetConnect4 client then becomes modifying
the original Connect4 code to transform it into a full-blown client
that can communicate with the NetConnect4 server. The following
is a summary of what functionality the NetConnect4 client needs
to provide:
Connect to the server.
Notify the player of the connection/game state.
Communicate the player's move to the server.
Receive the other player's move from the server.
Update the game with the state received from the server.
You might be wondering how this whole client/server game scenario
works in regard to a Web server, because it's apparent that the
game server must be running at all times. For a network game to
work, you must have the game server always running in the background,
meaning that it must somehow be launched by the Web server or
by some type of system startup feature. This makes it available
to connect clients who come along wanting to play.
When a Web client shows up to play a game, the game server accepts
the client's connection and then takes on the role of hooking
the client up with another client to play a game. The game server
is entirely responsible for detecting when clients arrive as well
as when they leave, creating and canceling game sessions along
the way. Because the game server is being run in the background
all the time, it must be extremely robust.
Because the game server is responsible for detecting and pairing clients, it is imperative that the server be running at all times. Without the server, you have no knowledge of or communication between clients.
The NetConnect4 sample applet demonstrates all the details of
using Internet network communication to develop a multiplayer
Java game. Even though the focus of toChapter's lesson is on the actual
programming involved in making NetConnect4 a reality, you'll probably
find the code a little easier to follow if you run the finished
product first. Knowing that, let's put NetConnect4 through its
paces and see how to use it. By the way, the complete source code,
executable, images, and sounds for the NetConnect4 game are located
on the accompanying CD-ROM.
This discussion on running the NetConnect4 sample game assumes
that you either have access to a Web server or can simulate a
network connection on your local machine. When I refer to running
the server side of the game, you need to run it in the way that
you typically execute a program based on your Web server configuration.
Note
I tested the game myself by simulating a network connection on my local Windows 95 machine. I did this by changing the TCP/IP configuration on my machine so that it used a specific IP address; I just made up an address. If you make this change to your
network configuration, you won't be able to access a real network using TCP/IP until you set it back, so don't forget to restore things when you're finished testing the game.
As you already know, the NetConnect4 game is composed of two parts:
a client and a server. The NetConnect4 server is the core of the
game and must be running in order for the clients to work. So
to get the game running, you must first run the server by using
the Java interpreter (java).
You do this from a command line, like this:
java NetConnect4Server
The other half of NetConnect4 is the client, which is an applet
that runs from within a browser such as Netscape Navigator. Incidentally,
the NetConnect4 client applet is called Connect4, to keep the
name consistent with the original single-player game. After you
have the server up and running, fire up a Java-compatible browser,
and load an HTML document including the NetConnect4 client applet.
On the CD-ROM, this HTML document is called Example1.asp,
in keeping with the standard JDK demo applets. After running the
Connect4 client applet, you should see something similar to what's
shown in Figure 19.1.
At this point, you have the server up and running with a single
client attached to it. Because two players are required to start
a game, the client is in a wait state until another player comes
along. Now, load a second instance of the Web browser with the
same Example1.asp document;
this is your second player. When the server detects this player,
it pairs the two players and starts the game. Figure 19.2 shows
this scenario.
By switching between the Web browsers, you can simulate a network
game between the two players. Go ahead and outwit yourself so
that you can see what happens when one of the players wins. This
situation is shown in Figure 19.3.
For another game to start between the same two players, each player
just needs to click once in the applet window. You can see now
how two players interact together in a game of NetConnect4. Now,
if you really want to test the game, try loading two more instances
of the Web browser and starting another game between two new players.
In this scenario, you have a total of four players involved in
two separate games, all running off the same server. The game
server supports an unlimited number of players and games, although
at some point it might be wise to impose a limit so that performance
doesn't start dragging. A couple of hundred players banging away
at your game server might tend to slow things down!
You now understand how the game plays, along with the roles of
the client and server, so you're ready to actually dig into the
source code and really see how things work. You've come to the
right place.
The client/server nature of NetConnect4 doesn't just apply at
the conceptual level, it also plays a role in how the code is
laid out for the game. Because the client and server components
function as separate programs, it makes sense to develop the code
for them as two different efforts. With that in mind, let's tackle
each part separately.
The Server
The NetConnect4 server is composed of four classes:
Connect4Server
Connect4Daemon
Connect4Player
Game
The Connect4Server class
serves as a stub program to get the server started. Check out
the source code for it:
class Connect4Server {
public static void main(String args[]) {
System.out.println("NetConnect4 server
up and running...");
new Connect4Daemon().start();
}
}
As you can see, the Connect4Server
class contains only one method, main,
which prints a message and creates a Connect4Daemon
object. The Connect4Daemon
class is where the server is actually created and initialized.
The Connect4Daemon class
is responsible for creating the server socket and handling client
connections. Take a look at the member variables defined in the
Connect4Daemon class:
public static final int PORTNUM = 1234;
private ServerSocket port;
private Connect4Player playerWaiting = null;
private Game thisGame
= null;
Other than the constant port number, Connect4Daemon
defines three member variables consisting of a ServerSocket
object, a Connect4Player
object, and a Game object.
The Connect4Player and Game
classes are covered a little later in the lesson. The ServerSocket
member object, port, is created
using an arbitrary port number above 1024. If you recall from
yesterChapter's lesson, all ports below 1024 are reserved for standard
system services, so you must use one above 1024. More specifically,
I chose 1234 as the port number, which is represented by the PORTNUM
constant.
Warning
Using a port number greater than 1024 doesn't guarantee that the port will be available. It does guarantee, however, that the port isn't already assigned to a common service. Nevertheless, any other extended services, such as game servers, could
potentially conflict with your port number. If your port number conflicts with another server, just try a different one.
The run method in Connect4Daemon
is where the details of connecting clients are handled:
public void run() {
Socket clientSocket;
while (true) {
if (port == null) {
System.out.println("Sorry,
the port disappeared.");
System.exit(1);
}
try {
clientSocket = port.accept();
new Connect4Player(this, clientSocket).start();
}
catch (IOException e) {
System.out.println("Couldn't
connect player: " + e);
System.exit(1);
}
}
}
The run method first retrieves
the socket for a connecting client via a call to the ServerSocket
class's accept method. If
you recall from yesterChapter's lesson, the accept
method waits until a client connects and then returns a socket
for the client. After a client connects, a Connect4Player
object is created using the client socket.
Note
Even though the Connect4Daemon class functions very much like a daemon thread, you don't specify it as a Java daemon thread because you don't want it to be destroyed by the runtime system. You might be wondering why the
Java runtime system would go around killing innocent threads. Because daemon threads always run as support for other non-daemon threads or programs, the Java runtime system kills them if there are no non-daemon threads executing.
The waitForGame method is
where players are paired up with each other. Listing 19.1 contains
the source code for the waitForGame
method.
Listing 19.1. The Connect4Daemon
class's waitForGame
method.
public synchronized Game waitForGame(Connect4Player
p) {
Game retval = null;
if (playerWaiting == null) {
playerWaiting = p;
thisGame = null; //
just in case!
p.send("PLSWAIT");
while (playerWaiting != null) {
try {
wait();
}
catch (InterruptedException
e) {
System.out.println("Error:
" + e);
}
}
return thisGame;
}
else {
thisGame = new Game(playerWaiting, p);
retval = thisGame;
playerWaiting = null;
notify();
return retval;
}
}
The waitForGame method is
called from within the Connect4Player
class, which you'll learn about in a moment. waitForGame
is passed a Connect4Player
object as its only parameter. If no player is waiting to play,
this player is flagged as a waiting player, and a loop is entered
that waits until another player connects. A null Game
object is then returned to indicate that only one player is present.
When another player connects and waitForGame
is called, things happen a little differently. Because a player
is now waiting, a Game object
is created using the two players. This Game
object is then returned to indicate that the game is ready to
begin.
The finalize method in Connect4Daemon
is simply an added measure to help clean up the server socket
when the daemon dies:
To clean up the server socket, finalize
simply calls the close method
on the port.
The Connect4Daemon class
made a few references to the Connect4Player
class, which logically represents a player in the game. Listing
19.2 contains the source code for the Connect4Player
class.
Listing 19.2. The Connect4Player
class.
class Connect4Player extends SocketAction
{
private Connect4Daemon daemon = null;
public void run() {
daemon.waitForGame(this).playGame(this);
}
public void closeConnections() {
super.closeConnections();
if (outStream != null) {
send("GAMEOVER");
}
}
}
The Connect4Player class
represents a player from the server's perspective. Connect4Player
is derived from SocketAction,
which is the generic socket class you developed yesterChapter. I told
you it would come in handy. The only member variable defined in
Connect4Player is daemon,
which holds the Connect4Daemon
object associated with the player.
The constructor for Connect4Player
takes Connect4Daemon and
Socket objects as its two
parameters. The Connect4Daemon
object is used to initialize the daemon
member variable, and the Socket
object is passed on to the parent constructor in SocketAction.
The run method for Connect4Player
calls back to the daemon's waitForGame
method to get a Game object
for the player. The playGame
method is then called on the Game
object to get the game underway. The closeConnections
method closes the client connection and is typically used to end
the game.
The last class the NetConnect4 server comprises is the Game
class, which handles the details associated with managing the
game logic and the communication between players. The Game
class takes on the bulk of the work involved in maintaining the
state of the game, as well as communicating that state between
the players. The Game class
contains a group of member constants that define the different
states in the game:
public static final int ERROR = -1;
public static final int IWON = -2;
public static final int IQUIT = -3;
public static final int ITIED = -4;
public static final int YOURTURN = -5;
public static final int SENTSTRING = -6;
Along with the constants, the Game
class has member variables representing each player, along with
an event queue for each player and a string used to send messages
to the other player:
An event queue is a list of events that take place within
a particular context.
In the case of NetConnect4, an event consists of player moves
and related game states. So the event queue is used to keep up
with the latest player moves and game states.
The workhorse method in the Game
class is playGame, which
essentially manages the game flow and logic for each player. Listing
19.3 contains the source code for the playGame
method.
if (playgame) {
me.send("THEIRTURN");
int stat = getStatus(me);
if (stat == IWON)
{
me.send("THEYWON");
if
(getStatus(me) != SENTSTRING) {
System.out.println("Received
Bad Status");
me.closeConnections();
}
me.send(sentString);
playgame
= false;
}
else if (stat
== ITIED) {
me.send("THEYTIED");
if
(getStatus(me) != SENTSTRING) {
System.out.println("Received
Bad Status");
me.closeConnections();
}
me.send(sentString);
playgame
= false;
}
else if (stat
== IQUIT) {
me.send("THEYQUIT");
playgame
= false;
}
else if (stat
== SENTSTRING) {
me.send(sentString);
}
else if (stat
== ERROR) {
me.send("ERROR");
me.closeConnections();
playgame
= false;
}
else {
System.out.println("Received
Bad Status");
sendStatus(me,ERROR);
me.closeConnections();
playgame
= false;
}
}
}
me.closeConnections();
return;
}
catch (IOException e) {
System.out.println("I/O Error: "
+ e);
System.exit(1);
}
}
The logic used in playGame
is fairly simple in that it models the way a game of Connect4
takes place; basically, each player waits while the other takes
her turn. The only potentially confusing aspect of playGame
is the mechanism it uses to communicate between the players. Each
player has an event queue, which contains game information sent
by the other player. The players communicate with each other in
an indirect fashion by using the event queue. The state of the
game is encoded into event messages using the state constants,
along with strings. The playGame
method interprets this information for each player.
The getStatus method gets
the status of the game for the player passed in the me
parameter. Listing 19.4 contains the source code for the getStatus
method.
Listing 19.4. The Game
class's getStatus
method.
private synchronized int getStatus(Connect4Player
me) {
Vector ourVector = ((me == player1) ? p1Queue : p2Queue);
while (ourVector.isEmpty()) {
try {
wait();
}
catch (InterruptedException e) {
System.out.println("Error:
" + e);
}
}
try {
Integer retval = (Integer)(ourVector.firstElement());
try {
ourVector.removeElementAt(0);
}
catch (ArrayIndexOutOfBoundsException
e) {
System.out.println("Array
index out of bounds: " + e);
System.exit(1);
}
return retval.intValue();
}
catch (NoSuchElementException e) {
System.out.println("Couldn't get
first element: " + e);
System.exit(1);
return 0; // never reached, just there
to appease compiler
}
}
The getStatus method waits
until the player's event queue contains status information, and
then it grabs the information and returns it.
The sendStatus method is
the complement of getStatus;
it's used to update a player's event queue with status information:
The integer status message passed in as the second parameter to
sendStatus is added to the
player's event queue. The notify
method is then called, which causes the wait
call in getStatus to return.
This shows the synchronized nature of these two methods: getStatus
waits until sendStatus provides
the information it needs.
That sums up the code for the server. At this point, you have
half a game. Too bad you can't do much with it yet; you still
need a client. Knowing that, let's take a look at the code involved
in making the client side work.
The Client
The client side of NetConnect4 consists of four classes, three
of which you've seen before:
Connect4State
Connect4Engine
Connect4
Connect4ClientConnection
The first two classes, Connect4State
and Connect4Engine, come
directly from the original Connect4 game. They provide the core
logic for establishing the rules of the game and determining whether
the game has been won, lost, or tied. These two classes require
no modification for NetConnect4, so refer to Chapter 16
if you need to refresh your memory on how they work.
The Connect4 applet class
should also be familiar from the original game. A few modifications
have been made to this version of Connect4
to accommodate the fact that the game is running over a network.
The primary changes to the Connect4
class are in the run method,
which handles establishing a server connection and coordinating
the state of the game with the graphics and user interface. Listing
19.5 contains the source code for the run
method.
Listing 19.5. The Connect4
class's run
method.
public void run() {
// Track the images
int gameState = 0;
newGame();
try {
tracker.waitForID(0);
}
catch (InterruptedException e) {
return;
}
try {
// Create the connection
connection = new Connect4ClientConnection(this);
while (connection.isConnected()) {
int istatus = connection.getTheirMove();
if (istatus == Connect4ClientConnection.GAMEOVER)
{
myMove = false;
gameState = 0;
return;
}
// Wait for the other player
else if (istatus == Connect4ClientConnection.PLSWAIT)
{
if (gameState
== 0) {
gameState
= Connect4ClientConnection.PLSWAIT;
status
= new String("Wait for player");
repaint();
} else {
System.out.println("Gameflow
error!");
return;
}
}
else if (istatus == Connect4ClientConnection.THEIRTURN)
{
status = new String("Their
turn.");
myMove = false;
gameState = Connect4ClientConnection.THEIRTURN;
repaint();
}
else if (istatus == Connect4ClientConnection.YOURTURN)
{
gameState = Connect4ClientConnection.YOURTURN;
status = new String("Your
turn.");
repaint();
myMove = true;
}
else if (istatus == Connect4ClientConnection.THEYWON)
{
gameState = Connect4ClientConnection.THEYWON;
}
else if (istatus == Connect4ClientConnection.THEYQUIT)
{
gameState = Connect4ClientConnection.THEYQUIT;
status = new String("Opponent
Quit!");
myMove = false;
repaint();
return;
}
else if (istatus == Connect4ClientConnection.THEYTIED)
{
gameState = Connect4ClientConnection.THEYTIED;
}
else if (istatus == Connect4ClientConnection.ERROR)
{
System.out.println("error!");
gameState = Connect4ClientConnection.ERROR;
status = new String("Error!
Game Over");
myMove = false;
repaint();
return;
}
else {
if (gameState
== Connect4ClientConnection.THEIRTURN) {
//
Note that we make the move, but wait for the *server*
//
to say YOURTURN before we change the status. Otherwise,
//
we have a race condition - if the player moves before
//
the server says YOURTURN, we go back into that mode,
//
allowing the player to make two turns in a row!
Point
pos = gameEngine.makeMove(1, istatus);
blueSnd.play();
repaint();
}
else if (gameState
== Connect4ClientConnection.THEYWON) {
status
= new String("Sorry, you lose!");
myMove
= false;
gameOver
= true;
repaint();
sadSnd.play();
return;
}
else if (gameState
== Connect4ClientConnection.THEYTIED) {
status
= new String("Tie game!");
myMove
= false;
gameOver
= true;
repaint();
sadSnd.play();
return;
}
else {
System.out.println("Gameflow
error!");
return;
}
}
}
}
catch (IOException e) {
System.out.println("IOException:
"+e);
}
}
The logic used in the run
method flows directly from the logic you just learned about in
the Game class. This logic
revolves around handling whose turn it is, along with communicating
whether a game has been won, lost, or tied.
The mouseDown method also
has been modified a little to accommodate sending game information
to the server. Listing 19.6 shows the source code for the mouseDown
method.
Listing 19.6. The Connect4
class's mouseDown
method.
public boolean mouseDown(Event evt, int
x, int y) {
if (gameOver) {
thread = null;
thread = new Thread(this);
thread.start();
}
else if (myMove) {
// Make sure the move is valid
Point pos = gameEngine.makeMove(0, x /
28);
if (pos.y >= 0) {
if (!gameEngine.isWinner(0))
if (!gameEngine.isTie())
{
redSnd.play();
status
= new String("Their turn.");
connection.sendMove(pos.x);
myMove
= false;
}
else {
sadSnd.play();
status
= new String("It's a tie!");
gameOver
= true;
connection.sendITIED();
connection.sendMove(pos.x);
}
else {
applauseSnd.play();
status
= new String("You won!");
gameOver
= true;
connection.sendIWON();
connection.sendMove(pos.x);
}
repaint();
}
}
else
badMoveSnd.play();
return true;
}
The mouseDown method is actually
where each player's physical move is sent to the server. Notice
that this information is sent using the client
member variable, which is a Connect4ClientConnection
object. This brings up a neat aspect of the design of the Connect4
client: The client communication details in the Connect4
class are hidden in the Connect4ClientConnection
class.
The Connect4ClientConnection
class is in charge of managing the client socket and ensuring
that information is sent back and forth to the server correctly.
Connect4ClientConnection
is derived from SocketAction,
which is another good example of code reuse. The constructor for
Connect4ClientConnection
takes an Applet object as
its only parameter:
Connect4ClientConnection(Applet a) throws
IOException {
super(new Socket(a.getCodeBase().getHost(), PORTNUM));
}
The Connect4ClientConnection
constructor creates a socket connection based on the applet parameter
and a port number. Note that this port number must match the port
number used by the server.
Warning
If the port numbers for the client and server don't match, none of the socket communication will be able to take place. In other words, the game won't run if the port numbers don't match.
The getTheirMove method in
Connect4ClientConnection
is used to get the other player's move so that the client game
can be updated. Listing 19.7 contains the source code for the
getTheirMove method.
Listing 19.7. The Connect4ClientConnection class's getTheirMove
method.
public int getTheirMove() {
// Make sure we're still connected
if (!isConnected())
throw new NullPointerException("Attempted
to read closed socket!");
try {
String s = receive();
System.out.println("Received: "
+ s);
if (s == null)
return GAMEOVER;
s = s.trim();
try {
return (new Integer(s)).intValue();
}
catch (NumberFormatException e) {
// It was probably a status
report error
return getStatus(s);
}
}
catch (IOException e) {
System.out.println("I/O Error: "
+ e);
System.exit(1);
return 0;
}
}
The getTheirMove method basically
just receives a string from the server and resolves it down to
an integer, which is then returned. The integer it receives is
a game state constant as defined in Connect4ClientConnection.
The following are the game state constants defined in Connect4ClientConnection:
static final int ERROR = -1;
static final int PLSWAIT = -2;
static final int YOURTURN = -3;
static final int THEIRTURN = -4;
static final int THEYWON = -5;
static final int THEYQUIT = -6;
static final int THEYTIED = -7;
static final int GAMEOVER = -8;
Although these game state constants are similar in function to
the ones defined on the server side in the Game
class, keep in mind that they are client-specific and make sense
only in the context of a client. The constants are all negative,
which is based on the fact that the integer state constant is
also used to convey the location of a player's move; all moves
are in the range 0 through 6, which corresponds to the column
into which a piece is being dropped.
The getStatus method resolves
a string status message into an integer game state constant. Listing
19.8 contains the source code for getStatus.
Listing 19.8. The Connect4ClientConnection
class's getStatus
method.
private int getStatus(String s) {
s = s.trim();
if (s.startsWith("PLSWAIT"))
return PLSWAIT;
if (s.startsWith("THEIRTURN"))
return THEIRTURN;
if (s.startsWith("YOURTURN"))
return YOURTURN;
if (s.startsWith("THEYWON"))
return THEYWON;
if (s.startsWith("THEYQUIT"))
return THEYQUIT;
if (s.startsWith("THEYTIED"))
return THEYTIED;
if (s.startsWith("GAMEOVER"))
return GAMEOVER;
// Something has gone horribly wrong!
System.out.println("received invalid status from
server: " + s);
return ERROR;
}
The getStatus method is used
by getTheirMove to convert
incoming text messages to their integer equivalent.
The sendMove method is pretty
straightforward; it simply sends the player's move to the server:
public void sendMove(int col) {
String s = (new Integer(col)).toString();
send(s);
}
Likewise, the sendIQUIT,
sendIWON, and sendITIED
methods are used to send the corresponding messages IQUIT,
IWON, and ITIED
to the server:
public void sendIQUIT() {
send("IQUIT");
}
public void sendIWON() {
send("IWON");
}
public void sendITIED() {
send("ITIED");
}
That wraps up the client side of NetConnect4. If you're still
a little dizzy from all the code, feel free to go through it again
and study the details until you feel comfortable with everything.
Trust me, it's perfectly normal to get confused during your first
dealings with network game programming-I sure did!
ToChapter you wrote your fourth complete Java game. Well, you actually
modified one of the games you had already written. However, turning
a single-player game into a two-player game that can be played
over the Web might as well constitute a whole new game. In learning
how the game was implemented, you saw how the client/server architecture
and Java socket services are used in a practical scenario. You
also reused the generic socket class you developed yesterChapter,
thereby reinforcing the values of applying OOP techniques to Java
game programming.
With four complete Java games under your belt, you're probably
ready to chart some new territory in regard to Java game development.
That's a good thing, because tomorrow's lesson focuses on a subject
that has long been crucial to successful game programming: optimization.
Tomorrow's lesson guides you through some tricks and techniques
for speeding up your Java game code.
I still don't follow the whole client/server strategy as it applies to NetConnect4. Can you briefly explain it again?
A
Of course. The game server sits around in a never-ending loop waiting for client players to show up. When a player connects to the server, the server spawns a daemon thread to manage the communications necessary
to support a single game. This daemon serves as a communication channel between the two clients (players) involved in the game, which is necessary because there is no facility to enable clients to communicate directly with each other.
Q
Would this same client/server strategy work for a game with more than two players?
A
Absolutely. The code would get a little messier because the daemon would have to manage a group of clients rather than just two, but conceptually there is no problem with adding more client players to
the mix.
Q
How do I incorporate NetConnect4 into a Web site?
A
Beyond simply including the client applet in an HTML document that is served up by your Web server, you must also make sure that the NetConnect4 server (NetConnect4Server) is
running on the Web server machine. Without the game server, the clients are worthless.
The Workshop section provides questions and exercises to help
you get a better feel for the material you learned toChapter. Try
to answer the questions and at least ponder the exercises before
moving on to tomorrow's lesson. You'll find the answers to the
questions in appendix A, "Quiz Answers."
Enhance the NetConnect4 game to display the name of each player
as he is making his move. Hint: To get the name of a player, use
the getHostName method after
getting an InetAddress object
for a socket using getInetAddress.
Modify the NetConnect4 game to enable players to choose who
they want to play with. Admittedly, this is a pretty big modification,
but I think you can handle it. Hint: This task involves modifying
the client/server design so that clients connect and are added
to a list of potential players. A player can then choose an opponent
from the list, in which case they are paired together normally.