informa
31 min read
Features

Java Network Game Programming

While Java's current performance rules it out as a possibility for high-performance, graphically intensive games, it is certainly capable of developing other types of games.

Java has grabbed the undivided attention of Web developers and shows no sign of disappearing. What does all this Java hype mean for game developers? It means the potential for creating cross-platform Internet games that can be played within the context of the familiar Web page format. In other words, you develop one set of source code and publish one executable on the Web.

Because of its power and ease of use, standard networking support in Java is probably the most compelling reason for game developers to give Java a closer look. This article explains networking support in Java and how it can be used to build multiplayer Web games. You'll see how Java makes it surprisingly easy to tackle many of the network challenges faced with other languages and development environments.

Is Java Ready for Games?

If you shudder thinking about the fact that Java executables are interpreted, you are probably a game developer who understands the critical issue of performance in games. Performance is so important in game programming that languages and development tools are regularly chosen solely based on their performance. This stands in stark contrast to other areas of software development, where maintainability and ease of use generally take precedence over raw performance.

How does Java fare as a game programming language and run-time environment? In its traditional interpreted form, Java is pretty weak. In fact, few commercial games relying heavily on high-performance graphics would run tolerably in a Java environment. However, a recent technology has greatly improved this scenario. I'm referring to just-in-time Java compilers, which eliminate the costly interpretation of generic Java bytecodes. A just-in-time (JIT) compiler is a back-end compiler that converts generic Java bytecode executables into native executables. Because JIT compilers generate native code, Java performance starts approaching that of natively compiled languages like C and C++.

Even though JIT compilers greatly improve the performance of Java programs, it's still hard to pit Java head-to-head with a platform-dependent program written in C or C++. This is mainly due to the API layering involved in making the Java API cross-platform. If you've ever been involved in a media-intensive cross-platform project, you probably understand how messy it can be. Java, on the other hand, provides one API and requires you only develop a single set of source code that can be executed on any Java supported platform. So, when assessing the performance limitations of Java, you must acknowledge this benefit.

Java in its current state is still a little on the slow side, which rules it out as a possibility for high-performance, graphically intensive games. However, Java is certainly capable of being used for other types of games, such as multiplayer strategy games. As this article culminates with the development of a complete Web-based multiplayer NetConnect4 Java game, you can judge for yourself where Java stands as a game development environment.

Internet Fundamentals

Before we look into the network support Java provides, it's important you understand some fundamentals about the structure of the Internet as a network. The only way to allow a wide range of computer systems to coexist and communicate with each other effectively is to hammer out some standards. Fortunately, plenty of standards abound for the Internet, and they share wide support across many different computer systems. Let's take a look at a few of them.

Addresses. One of the first areas of standardization on the Internet was in establishing a means to uniquely identify each connected computer. The solution that was implemented was IP addresses, which come in the form of a 32-bit number that looks like this: 243.37.126.82. You're probably more familiar with the symbolic form of IP addresses, which looks like this: gdmag.com. Without an addressing scheme, there would be no way to distinguish among different computers.

Protocols. Many different types of communication can take place on the Internet, so there must be an equal number of mechanisms for facilitating them. Protocols are sets of rules and standards that define certain types of Internet communication. More specifically, a protocol specifies the format of data being sent over the Internet, along with how and when it is sent. On the other end of the communication, the protocol also defines how the data is received along with its structure and what it means.

Without a doubt, the Internet protocol getting the most attention these days is the hyper-text transfer protocol (HTTP), which is used to transfer HTML documents on the web. The file transfer protocol (FTP) is a more general protocol used to transfer files over the Internet. These two protocols both have their own unique set of rules and standards defining how information is transferred, and Java provides support for both of them.

Ports. Internet protocols make sense only in the context of a service. For example, the HTTP protocol comes into play when you are providing Web content (HTML pages) through an HTTP service. Each computer on the Internet has the capability to provide a variety of services through the various protocols supported. There is a problem, however--the type of service must be known before information can be transferred. This is where ports come in. A port is a software abstraction that provides a means to differentiate between different services. More specifically, a port is a 16-bit number identifying the different services offered by a network server.

Each computer on the Internet has a bunch of ports that can be assigned different services. To use a particular service and therefore establish a line of communication via a particular protocol, you must connect to the correct port. Ports are numbered, and some of the numbers are specifically associated with a type of service. Ports with specific service assignments are known as standard ports, meaning that you can always count on a particular port corresponding to a certain service.

For example, the FTP service is located on port 21, so any other computer wanting to perform an FTP file transfer would connect to port 21 of the host computer. Likewise, the HTTP service is located on port 80, so any time you access a Web site, you are really connecting to port 80 of the host using the HTTP protocol behind the scenes. Figure 1 illustrates the relationship between ports and protocols.

Figure 1. Services are assigned to specific ports
02mof1.jpg

Protocols and Ports

All standard service assignments are given port values below 1024. Ports above 1024 are considered available for custom communications, such as those required by a network game implementing its own protocol. Keep in mind, however, that other types of custom communication also take place above port 1024, so you might have to try a few different ports to find an unused one.

One of Java's major strong suits as a programming language is its wide range of network support. Java has this advantage because it was developed with the Internet in mind. The result is that you have lots of options in regard to network programming in Java. Even though there are many network options, Java network game programming uses a particular type of network communication known as sockets. A socket is a software abstraction for an input or output communication medium.

Java performs all its low-level network communication through sockets. Logically, sockets are one step lower than ports; you use sockets to communicate through a particular port. So a socket is a communication channel enabling you to transfer data through a certain port. Check out Figure 2, which shows communication taking place through multiple sockets on a port.

Figure 2. Socket communication
02mof2.jpg

Socket Communication

This figure brings up an interesting point about sockets: data can be transferred through multiple sockets for a single port. Java provides socket classes to make programming with sockets much easier. Java sockets are broken down into two types: stream sockets and datagram sockets.

A stream socket, or connected socket, is a socket over which data can be transmitted continuously. By continuously, I don't necessarily mean that data is being sent all the time, but that the socket itself is active and ready for communication all the time. Think of a stream socket as a dedicated network connection in which a communication medium is always available for use. The benefit of using a stream socket is that information can be sent with less worry about when it will arrive at its destination. Because the communication link is always "live," data is generally transmitted immediately after you send it.

Java supports stream socket programming primarily through two classes: Socket and ServerSocket. Socket provides the necessary overhead to facilitate a stream socket client, and ServerSocket provides the core functionality for a server. Most of the actual code facilitating communication via sockets is handled through input and output streams connected to a socket. In this way, the communication itself is handled independently of the network socket connection. This might not seem like a big deal at first, but it is crucial in the design of the socket classes; after you've created a socket, you connect an input or output stream to it and forget about the socket.

The other type of socket supported by Java is the datagram socket. Unlike stream sockets, in which the communication is akin to a live network, a datagram socket is more akin to a dial-up network, in which the communication link isn't continuously active. A datagram socket is a socket over which data is bundled into packets and sent without requiring a live connection to the destination computer.

Because of the nature of the communication medium involved, datagram sockets aren't guaranteed to transmit information at a particular time or even in any particular order. The reason datagram sockets perform this way is that they don't require an actual connection to another computer; the address of the target computer is just bundled with the information being sent. This bundle is then sent out over the Internet, leaving the sender to hope for the best.

On the receiving end, the bundles of information can be received in any order and at any time. For this reason, datagrams also include a sequence number that specifies which piece of the puzzle each bundle corresponds to. The receiver waits to receive the entire sequence, then puts them back together to form a complete information transfer. As you might think, datagram sockets are less than ideal for network game programming because of the implied time delays, heightened reliability issues, and sequencing complexities.

A Reusable Socket Class

You've now learned enough about network theory and the Java networking support to write some code. Before you can think in terms of writing network game code, however, you need to develop some code that helps facilitate the core communications necessary for a game. In doing so, you'll have reliable, reusable code that can easily be applied to provide functionality specific to a particular game communication protocol.

The first layer of code necessary to facilitate network communications comes in the form of a socket helper class that handles the details of initializing a socket and managing the associated data streams. The SocketAction class was developed by Greg Turner to help ease the pain in establishing a communication channel using Java sockets. The SocketAction class is derived from the standard Thread class, so it has its own thread of execution. Let's start by looking at the member variables of SocketAction, which is shown in Listing 1.

The first two members, inStream and outStream, are the input and output streams used to receive and send data through the socket. The third member variable, socket, is the socket object. The constructor for SocketAction takes a Socket object as its only parameter, as shown in Listing 2.

The constructor creates the buffered data streams and initializes the socket member variable with the Socket object passed in. If there was a problem in initializing the streams, the constructor detects it by using the catch clause. If there is an error in creating the streams, something is seriously wrong, which explains why the entire program exits.

The send and receive methods are possibly the most useful methods in SocketAction, even though they contain very little code, as shown in Listing 3. The send method simply sends a string out over the socket connection by using the output data stream. Similarly, the receive method receives a string by using the input data stream.

The closeConnections method simply closes the socket, as shown in Listing 4. The isConnected method verifies that the input and output streams as well as the socket object are valid, as shown in Listing 5. Finally, the finalize method closes the socket as an added safety precaution, as shown in Listing 6.

And that's all there is to the SocketAction class, which is pretty elementary. Nevertheless, its simple function of providing a clean management class for sockets and their associated streams will make life much easier when building network Java games.

The core of Java network game programming revolves around a client/server communication strategy. 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.


The NetConnect4 Server

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:

  1. Initialize the server socket
  2. Wait for a client to connect
  3. Accept the client connection
  4. Create a daemon thread to support the client
  5. Go back to step 2.

The most crucial aspect of this series is Step 4, when the server creates a daemon thread to support the client. You're probably wondering what I mean by "support." 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 when you actually get into the code for NetConnect4.

You now have an idea about the role a generic game server plays in the context of a network game. The question 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 NetConnect4 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 more thorough than that and 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. To keep things a little simpler, don't worry about players choosing who they play against, 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 follows:

  • 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
  • Notify the players of the state of the game

Figure 3. Starting up NetConnect4 in a wait state.
02mof3.jpg

The Client

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.

Since the graphics, user interface, and game playing strategy aspects of NetConnect4 don't really impact the network code, I'll leave it for you to study on your own. You can check out the complete source code for the game by downloading it.

Following is a summary of what network functionality the NetConnect4 client needs to provide:

  • Connect to the server
  • Notify the player of the connection and 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

Figure 4. Pairing up players and starting the game.
02mof4.jpg

Pairing the Client and 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. It must somehow be launched by the Web server or by some type of system startup feature making 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 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. Also, since 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.

Figure 5. Winning and losing scenarios
02mof5.jpg

Running NetConnect4

The NetConnect4 sample applet demonstrates all the details of using Internet network communication to develop a multiplayer Java game. Even though the focus here 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.

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 for the clients to work. To get the game running, you must first run the server by using the Java interpreter (java); you do this from a command line:

java NetConnect4Server

The other half of NetConnect4 is the client, which is an applet that runs from within a Java-enabled browser, like Netscape Navigator 3.0 or Internet Explorer 3.0. After you have the server up and running, fire up a browser and load the HTML document containing the NetConnect4 client applet. After running the NetConnect4 client applet, you should see something similar to what's shown in Figure 3.

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 HTML document; this is your second player. When the server detects this player, it pairs the two players and starts the game. Figure 4 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 player wins. This situation is shown in Figure 5.

For another game to start between the same two players, each player just clicks 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 put the server to the test, 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!

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. The source code for it is shown in Listing 7. 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. The run method in Connect4Daemon, where the details of connecting clients are handled, is shown in Listing 8.

The run method first retrieves the socket for a connecting client via a call to the ServerSocket class's accept method. 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.

The waitForGame method is where players are paired up with each other. Listing 9 contains the source code for the waitForGame method.

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 Connect4Daemon class makes a few references to the Connect4Player class, which logically represents a player in the game. Listing 10 contains the source code for the Connect4Player class.

The Connect4Player class represents a player from the server's perspective. Connect4Player is derived from SocketAction, which is the generic socket class you developed earlier; 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 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 contains 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, in which case 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 11 contains the source code for the playGame method.

The logic used in playGame is fairly simple in that it models the way a game of NetConnect4 takes place; basically, each player waits while the other takes his or 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 12 contains the source code for the getStatus method. The getStatus method waits until the player's event queue contains status information, 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, as shown in Listing 13.

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, two of which are unrelated to networking:

  • Connect4State
  • Connect4Engine
  • Connect4
  • Connect4ClientConnection

The first two classes, Connect4State and Connect4Engine, handle the underlying Connect4 game logic and are totally unrelated to the networking aspect of the game. More specifically, they establish the rules of the game and determine whether the game has been won, lost, or tied. The Connect4 applet class primarily handles the graphics, sound, and user interface for the game. However, it also handles establishing a server connection for new clients.

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, as shown in Listing 14.

The Connect4ClientConnection constructor creates a socket connection based on the applet parameter and a port number. This port number must match the port number used by the server. If the port numbers for the client and server don't match, none of the socket communication will take place, and the game won't run.

The getTheirMove method in Connect4ClientConnection is used to get the other player's move, so the client game can be updated. Listing 15 contains the source code for the getTheirMove method.

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. 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 the 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 a piece is being dropped in.

The getStatus method resolves a string status message into an integer game state constant. Listing 16 contains the source code for getStatus. 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) {
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");
}

Wrapping It Up

That wraps up the client side of NetConnect4 and ultimately the entire NetConnect4 game. 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. Even though there is a fair amount of code, keep in mind that you just developed a multiplayer game that can be played over the Web on any platform that supports Java.

In other words, someone using a Sun workstation running the Solaris operating system can be playing a game of NetConnect4 with someone on the other side of the world using a Macintosh, and all through the familiar interface of the Web. That's a scenario hard to match with any other programming environment!


 

Code Listings

Listing 1. Member Variables of SocketAction

private DataInputStream inStream = null;
protected PrintStream outStream = null;
private Socket socket = null;

>

Listing 2. The Constructor for SocketAction

public SocketAction(Socket sock) {
super("SocketAction");
try {
inStream = new DataInputStream(new
BufferedInputStream(sock.getInputStream(), 1024));
outStream = new PrintStream(new
BufferedOutputStream(sock.getOutputStream(), 1024), true);
socket = sock;
}
catch (IOException e) {
System.out.println("Couldn’t initialize SocketAction:" + e);
System.exit(1);
}
}

Listing 3. Send and Recieve Methods

public void send(String s) {
outStream.println(s);
}
public String receive() throws IOException {
return inStream.readLine();
}

Listing 4. The closeConnections Method

public void closeConnections() {
try {
socket.close();
socket = null;
}
catch (IOException e) {
System.out.println("Couldn’t close socket:" + e);
}
}

Listing 5. The isConnected Method

<

public boolean isConnected() {
return ((inStream != null) && (outStream != null) && (socket != null));
}

Listing 6. The finalize Method

protected void finalize () {
if (socket != null) {
try {
socket.close();
}
catch (IOException e) {
System.out.println("Couldn’t close socket:" + e);
}
socket = null;
}
}

Listing 7. The Connect4Server Class

class Connect4Server {
public static void main(String args[ ]) {
System.out.println("NetConnect4 server up and running...");
new Connect4Daemon().start();
}
}

Listing 8. The run Method in Connect4Daemon

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);
}
}
}

Listing 9. 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;
}
}

Listing 10. The Connect4Player Class

class Connect4Player extends SocketAction {
private Connect4Daemon daemon = null;
public Connect4Player(Connect4Daemon server, Socket sock) {
super(sock);
daemon = server;
}
public void run() {
daemon.waitForGame(this).playGame(this);
}
public void closeConnections() {
super.closeConnections();
if (outStream != null) {
send("GAMEOVER");
}
}
}

Listing 11. The Game Class’s playGame Method

public void playGame(Connect4Player me) {
String instr;
boolean playgame = true;
boolean theirturn = false;
try {
if (me == player2) {
theirturn = true;
}
else if (me != player1) {
System.out.println("Illegal call to playGame!");
return;
}
while (playgame) {
if (!theirturn) {
me.send("YOURTURN");
instr = me.receive();
instr = instr.toUpperCase();
instr = instr.trim();
if (instr.startsWith("IQUIT")) {
sendStatus(me, IQUIT);
playgame = false;
}
else if (instr.startsWith("IWON")) {
sentString = me.receive();
sentString = sentString.toUpperCase();
sentString = sentString.trim();
sendStatus(me, IWON);
sendStatus(me, SENTSTRING);
playgame = false;
}
else if (instr.startsWith("ITIED")) {
sentString = me.receive();
sentString = sentString.toUpperCase();
sentString = sentString.trim();
sendStatus(me, ITIED);
sendStatus(me, SENTSTRING);
}
else {
sentString = instr;
sendStatus(me, SENTSTRING);
}
}
else {
theirturn = false;
}
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);
}
}

Listing 12. 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
}
}

Listing 13. The sendStatus Method

private synchronized void sendStatus(Connect4Player me, int message) {
Vector theirVector = ((me == player1) ? p2Queue : p1Queue);
theirVector.addElement(new Integer(message));
notify();
}

Listing 14. The Constructor for Connect4ClientConnection

Connect4ClientConnection(Applet a) throws IOException {
super(new Socket(a.getCodeBase().getHost(), PORTNUM));
}

Listing 15. 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;
}
}

Listing 16. 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;
}

Michael Morrison is the author of Teach Yourself Internet Game Programming with Java in 21 Days. You can reach Michael via his Web site at www.thetribe.com.

Latest Jobs

Treyarch

Playa Vista, California
6.20.22
Audio Engineer

Digital Extremes

London, Ontario, Canada
6.20.22
Communications Director

High Moon Studios

Carlsbad, California
6.20.22
Senior Producer

Build a Rocket Boy Games

Edinburgh, Scotland
6.20.22
Lead UI Programmer
More Jobs   

CONNECT WITH US

Register for a
Subscribe to
Follow us

Game Developer Account

Game Developer Newsletter

@gamedevdotcom

Register for a

Game Developer Account

Gain full access to resources (events, white paper, webinars, reports, etc)
Single sign-on to all Informa products

Register
Subscribe to

Game Developer Newsletter

Get daily Game Developer top stories every morning straight into your inbox

Subscribe
Follow us

@gamedevdotcom

Follow us @gamedevdotcom to stay up-to-date with the latest news & insider information about events & more