| Battleship -- CS 351 Fall 2002 Final Project | ||
|---|---|---|
| Prev | Next | |
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);
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
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).
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 requestGET /battleship/?action=register&player=your_name HTTP/1.0\n\nUnregister with the server request
GET /battleship/?action=unregister&player=your_name HTTP/1.0\n\nList available players request
GET /battleship/?action=list HTTP/1.0\n\nYou 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.193As 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:
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.
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 networkAnd 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 messageIdWhen 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 messageIdBoth 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 |