Battleship -- CS 351 Fall 2002 Final Project
Prev Next

4. Implementation

4.1 General Concepts

During the course of execution of your program, it will transition between several states which dictate exactly what is possible and what is not. For instance, it doesn't make sense to be bombing your opponent when you're not even connected yet. For the sake of simplicity we've defined the following states which your program should use (the game should be able to exit in any given state):

Go ahead and #define those so you can conviently keep track of what state you're really in.

Developing Battleship in an object oriented fashion is, of course, encouraged. There are certain subtleties concerning OOP programming and the Win32 API you should know before you begin.

Please note that if you decide to write your project in pure C, it's perfectly OK. You will not be penalized in any way whatsoever should you choose to do so. The OOP method is just a suggestion for those who can't live without C++.

The main problem arises with callback functions. Callback functions are functions in your program that are called by the operating system, not by other functions in your program. One of such functions is the window procedure. Anytime a message is to be delivered to your application, Windows knows that there is a WndProc defined within your program that it can call and deliver the message.

When you call a method on an object, the compiler actually includes the pointer to the object you're calling the method on as one of the parameters to the function. This pointer is in fact the 'this' pointer available to you within the method. Because of this, regular, non-static functions declared inside your classes cannot be used as callback functions. Windows doesn't have any pointer to any object if it decided to call such a method. Thus, anytime you provide a callback function pointer (for example, the window procedure pointer when initializing your window class, or the thread function pointer when starting a thread) it has to be a pointer to a method declared as static within your class (i.e. a class method). Some of you are probably asking right now: If this is so, how do I access my non-static members and functions inside my class? One of the ways to do this is to initialize a static 'self' member variable in the constructor of the class that contains the window procedure. The self member variable is in reality a pointer to the instance of that class, and since it is static, it will be accessible to you in any static function declared within your classes used as a callback function. A note is in place: such a class should only ever be instantiated once (this would be the Singleton pattern, for those of you who know about such things). This makes sense if you think about it: there should ever only be one instance of the class handling messages for the main window. If we assume there will, in fact, only ever exist one object, the usage of the static 'self' pointer is safe.

For example, when initializing the window class, you may use something like this:

wcex.lpfnWndProc = (WNDPROC) Battleship::WndProc;
And your WndProc may look like this:
LRESULT CALLBACK Battleship::WndProc (HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
	return self->ProcessMessage (hWnd, iMessage, wParam, lParam);
}
with a header file declaration looking similar to this:
static LRESULT CALLBACK WndProc (HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam);

4.2 Threads

Threads are an integral part of this project. This section will explain the way you'll use them, and exactly how to go about it.

Your main thread will be the one running the main application. You don't need to worry about it, since Windows creates it itself when you launch Battleship. Since the networking involved in Battleship is synchronous (and would thus block the main thread when waiting for data on a socket (i.e., the user interface would be unresponsive), you will create and run two new threads of execution to handle the networking part of Battleship.

At any point of time, only one other thread besides the main thread will run.

The first thread will simply sit on a socket and await connections once the player registered with the server. As soon as the socket the thread is waiting on receives a connection, the connection will be registered by Battleship, a message will be sent to the main window's window procedure informing it of the new connection, and the thread will exit. As you can see, the lifecycle of this thread is pretty short.

The second thread is more important. It will be executing alongside the main thread throughout the whole game (from the very beginning when a player connects, till the very end when the player disconnects). The purpose of this thread is to sit on the socket connected to your opponent, and receive any data that arrives on it. Depending on the type of data is receives (i.e., depending on the message the opponent sends), the thread will read the appropriate amount of data as described above, dispatch a message to the main window informing it of the event (and passing any necessary data received), and block until there is more data available on the socket once again.

The process of creating threads isn't compliated. This is how a thread is created:

receiveThreadHandle = CreateThread (NULL, 0, BattleshipNetwork::ReceiveThreadProc, (PVOID) hWnd, 0, &receiveThreadId);
The parameters passed to CreateThread are For more information about the CreateThread function, consult the MSDN Library. Also note the function pointer we use as one of the CreateThread parameters. If you decide to program Battleship in a non-OO manner (strongly discouraged), you don't need to worry about this. However, if you use OOP, anytime you are required to pass a function pointer, it'll have to be a pointer to a function defined as static in your class. This is because an extra parameter (pointer to the object we're calling the function on) is automatically appended to the parameter list of non-static functions. As this information is not available for functions that are 'called back' by the operating system, a static function has to be provided.

How must the thread function be defined? If it is a member function, it will be defined as such in the header file:

static DWORD WINAPI ReceiveThreadProc (LPVOID lpParameter);
and as such in the source file:
DWORD WINAPI BattleshipNetwork::ReceiveThreadProc (LPVOID lpParameter) { ... }

Remember that the second thread which continuously listens for messages from your opponent, has to keep running. You will most likely need a do-while loop that runs as long as the DISCONNECT message hasn't been received. During every iteration of the loop, the thread will read one byte from the socket (the message id), and depending on what it is, read any other data excepted, if any. Once the data is read off the socket, the thread will send an appropriate message to the main window, sending any relevant information with it (in lParam or wParam).

4.3 Networking

As was hinted before, there are 2 major things that your program must be able to do with respect to the network: 1) Communicate successfully with a HTTP server using HTTP protocol and 2) successfully implement the Battleship protocol which we have defined for this project. Just remember that all a protocol really is is a standard way of two or more clients to talk to each other.

HTTP Server Interaction

HTTP stands for Hyper Text Transfer Protocol and defines a certain standardized way of sending data across a network. 99.9% of the time you request an HTML webpage on the Internet, your browser constructs and sends an HTTP request to the remote server. The HTTP request identifies the file we are asking for, and contains other useful information, such as the browser identifier, the different MIME types the browser accepts, and the hostname of the server we are connecting to. Once the connection is established and the webserver receives the request, it locates the file (or generates it dynamically), and sends it back to us in its response. A response consists of two parts: a header and a body. The two are separated using the "\r\n\r\n" character sequence, and it is the body you need to worry about for this project.

You will need the following information to initialize the sockaddr_in structure needed to connect to the HTTP server in order to either register yourself or download the list of players:

Socket family: AF_INET
Port: 80
Hostname: 216.47.129.194

After you have initialized the socket and connected, you will need to issue the appropriate HTTP request. The three types of requests Battleship will send once a connection is established are the following:

Register with the server request
GET /battleship/?action=register&player=your_name HTTP/1.0\n\n
Unregister with the server request
GET /battleship/?action=unregister&player=your_name HTTP/1.0\n\n
List available players request
GET /battleship/?action=list HTTP/1.0\n\n
You only need to worry about the server's response after issuing the third request. The server will send something like this back to you:
HTTP/1.1 200 OK
Date: Mon, 14 Oct 2002 07:03:55 GMT
Server: Apache/1.3.26 (Unix) Debian GNU/Linux PHP/4.1.2 mod_jk/1.1.0
Connection: close
Content-Type: text/plain; charset=iso-8859-1

Bot 2.0 216.47.129.194
Player	216.47.129.193
As you can tell, the actual body of the response is what you care about. You can simply discard the header (which, as we mentioned previously is separated from the body by "\n\r\n\r", and read the needed information into a character array. Then you'll need to parse that information, and the following should help you: You will use the name of the player in the dialog listbox once a player requests player listing. The IP address will be saved in the background, as looked up once a player selects an opponent and clicks Connect.

Remember that the recv() function returns 0 when all data was received and the remote side closed the connection. This is your way of telling all the data has been downloaded and can be now processed.

If you want to learn more about HTTP, read the HTTP RFC (Request For Comments) 1945 located at http://rfc.net/rfc1945.html.

Battleship Protocol Specification

Here we need to define our own networking protocol. Once you have the protocol and have implemented it correctly, you can connect to any other client who also correctly implements the protocol and successfully play the game.

The Battleship protocol is extremely simple. Each message is comprised of at most 2 parts.

The first part is the message type. The second part is what we can call the message payload. Note that not all messages require payload to be sent. In fact, by careful study of the problem, the only messages that need the payload is MSG_TORPEDO and MSG_CONNECT. All other messages will be a simple byte of data being sent across the wire containing the type of the message.

#define these as well, with the numbers you see in red. Why? You will notice that those specific numbers correspond to the ASCII character codes for '1' through '8'. These numbers will aid you enormously during your debugging sessions. It's far easier to look at the character (which the messageId will actually be) and see the number '7', indicating a MSG_SUNK, then it is to look at the bits of messageId and determine that "Yup! That's binary for number 7..." But even if you are good at converting, you must stick with those exact numbers because the Battleship network protocol only knows about those numbers. And you will most definetly be using the associated Battleship Bot (which of course implements the protocol correctly) to test your code without us being there (more on that later).

For all messages besides MSG_CONNECT and MSG_TORPEDO, the protocol requires you to do something like the following:

	char messageId = MSG_HIT;
	...
	Send messageId across the network
And that's it! MSG_CONNECT and MSG_TORPEDO are a little more involved but not by much.

For MSG_CONNECT you also need to send a maximum of 32 bytes worth of character array which contains your name. So if i wanted to connect, I'd send [MSG_CONNECT  +  "CS 351 TA"]; a total of 1 + 9 = 10 bytes.

	char messageId = MSG_CONNECT;
	...
	Send messageId across the network
	Send up to 32 bytes right after messageId
When you receive a MSG_CONNET the protocol requires that you send back an identical MSG_CONNECT back to your opponent along with your name. If you are the sender of the MSG_CONNECT, it is therefore up to you to be expecting a MSG_CONNECT with the other client's information. Once both clients have sent and received MSG_CONNECT the handshake is over.

It should also be noted that this should happen with MSG_DISCONNECT as well.

For MSG_TORPEDO you will need to send the row and column too for a total of 1 + 8 = 9 bytes of data.

	messageId = MSG_TORPEDO;
	...
	Send messageId across the network
	Send a buffer containing 2 4 byte integers across right after messageId
Both row and col are zero-based meaning that the rows and cols should go from 0-9 and not 1-10.


Prev Home Next
Requirements Extra Credit