?? full tutorial.htm
字號:
player details to see if there is a free player position in thegame. The code is currently set to handle up to 6 players(<i>max_players</i>) but that is easy to change (increase<i>max_players</i> and create colours for the new players in the<i>Board</i> class). <p>Once a free slot has been found the details ofthe client connecting are stashed away and a new thread(<i>client_handler</i>) is started to look after the new player. Thegame handler then loops back to the listen call where it will wait formore clients to connect. So, to take an example of 2 clientsconnected we would have the following server threads running:<P><IMG SRC="Full Tutorial_files/image005.gif" ><p>The <i>client_handler</i> thread creates a socket (called<i>client</i>) and initialises it with the details saved in the gamethread so that it can talk to the newly joined client. <pre> Socket client; client.set_connection(clients[player].connection);</pre><p>Then the client handler sits in a loop that performs the following:<pre>while (true) { string command; client >> command; for (int i = 0; i < num_commands; i++) { if (command == command_list[i].command) { command_list[i].function(client, player); } }}</pre><p>This just reads a command text string from the client connected to thisthread, looks it up in a table of commands and, if it is found, callsthe appropriate function to deal with that command. From the clientend, the code to actually send some command is trivial, for example:<pre>server << "some-command\n";</pre><p>The very first thing that a client does when starting up is to start athread to receive updates from the server update handler (the code fordoing this is almost identical to that shown above). Once that threadis running, the client sends the following command to the server:<pre>connect computer_name port</pre><p>This command is used to tell the client handler which computer namethe client is running on and which port the server should use to talkto the client (we could have just hard-coded a value as we did for theserver but it's handy to run multiple clients on the same PC fortesting and this lets us do this without messing about in thecode). The server code for handling this command looks like this:<pre> int port; string hostname; client >> hostname >> port; client << player << "\n"; Socket * socket = (new Socket()); socket->connect(hostname.c_str(), port); clients[player].socket = socket; </pre><p>The first part of the code reads in the hostname and port and sendsthe player number back to the client. This tells the client whichplayer he is in the game and is used to select the colour of histroops as the board is updated (amongst other things). The <i>client_handler</i> thread then creates a socket which is connectedto the hostname and port which the client has just asked us touse. This will be used later to send the client the game updates andother commands. The socket we have just created is then stashed awayin a table where we keep the details of the connected clients.<p>So, once a client has fully started, we have the followingcommunication relationships between the threads of the client andserver:<P><IMG SRC="Full Tutorial_files/image006.gif" ><h3>Game Start</h3> <p>When the start button is pressed on the server dialog the followingcode is executed:<pre> board.initialise(exploring, hidden); for (int i = 0; i < max_players; i++) { if (clients[i].in_use) { board.setup_player(i, number_of_bases, group_bases); } } for (int k = 0; k < max_players; k++) { if (clients[k].in_use) { Socket * socket = clients[k].socket; (*socket) << "new-board\n"; board.transmit(socket); } } game_running = true;</pre><p>First, we ask the board to initialise itself, passing over some of theoptions from the dialog (this is when the terrain is generated). Then,for each player, we ask the board to set up that player (this is wherebases are created).<p>Finally, we ask the board to copy itself to each of the clients overthe socket connection we have established with the client. Thisdeviates from our preferred streaming approach and uses the socket<i>transmit</i> function. I did start out streaming the board into and outof the socket but it was just too slow.<p>It's important to point out that the server has one copy of the boardand every client has its own that is partly synchronised to the servermaster. This allows every client to have their own unique view on theboard relating to what they can see (this feature is used to implementexplore mode, for example).<p>On the client side, the code that responds to the new-board command isvery predictable:<pre> board.receive(inbound);</pre><h3>Client Commands</h3><p>When the game is running the client can send the commands below to theserver in response to mouse clicks and key presses (x and y are theWindows co-ordinates where the mouse is positioned:<p>mouse x y<br>key value x y<br><p>When a key is pressed the command key is followed by the ASCII code ofthe character pressed.<p>Why do we do it this way? We could, of course, build in knowledge inthe client that a left mouse click means add a vector and send acommand to that effect to the server. However, this is buildingknowledge of the game into the client, the server and the game classesthemselves. This means that there could be a lot of work should wewant to re-use the client or server code in a new project where keysand mouse buttons imply different commands.<p>The design aim is to make the client (and the server) as generic as wecan. All they need to know is that they are playing a multi-usergame. They don't need to know they are playing WinBattle or any othergame. Consequently the client and server are dumb. If somebody clicksa mouse button they just blindly pass it on to the game classes whoknow what to do with it. In an ideal world, if you want to write atotally different multi-player game all you would have to do is changethe game classes. You wouldn't have to touch the core client or servercode at all. <p>However, back in the real world this isn't completely practical(although it is possible with some work). For example, the serverdialog needs to know about the various game options so that they canbe displayed and set. So, the pragmatic design goal is to keep thedependencies as small as we can and if you look at the code in theclient and server directories you'll see that, although they know theyhave a game board, they know nothing else about it. For example, if wesuddenly decided to make the board cells tiled octagons instead ofhexagons then there would be no changes needed to either the client orthe server code. <h3>Game Updates</h3> <p>As the game progresses, the server <i>update_handler</i> threadperiodically sends the updated state of the board to the clients. Thecode is as follows:<pre> vector<Client>::iterator k; for (k = clients.begin(); k != clients.end(); k++) { if (k->in_use) { Socket * socket = k->socket; (*socket) << "update-board\n"; board.send_updates(socket); } } board.end_of_updates();</pre><p>This loops for each possible client. If a particular client isconnected then we warn it that an update is about to follow with the<i>update-board</i> command and then send across the updates. Once allupdates have been sent we let the board know that (with the call to<i>end_of_updates</i> so that it can prepare the next set).<p>The client code to deal with this is the rather boring:<pre> board.get_updates(&inbound);</pre><p>Note that neither the client nor the server has any idea what theseupdates are. They both just know that periodic updates are beingexchanged. <h3>Shut Down</h3><p>OK, we've got all these threads up and running how do we handleclients and servers being terminated? Quite simply, if either stopsit's polite enough to send a message to the other to that effect. Forexample, if the server is killed while clients are still running itruns through its list of connected clients and sends each one amessage so that the client server handler thread can exit gracefully:<pre> vector<Client>::iterator i; for (i = clients.begin(); i != clients.end(); i++) { if (i->in_use) { *(i->socket) << "shut-down\n"; } }</pre><p>Similarly, if a client terminates it sends a message to the server sothat the server client handler thread can close down and free up aslot for another client:<pre> server << "disconnect\n";</pre><p>The code that runs in the server when this command is received is asfollows:<pre> Socket * socket = (clients[player].socket); (*socket) << "shut-down\n"; client.close(); socket->close(); free(socket); clients[player].in_use = false; ExitThread(0);</pre><p>As the command to shut down comes from the main thread of the clientwe send a message to the client's other thread to ask it to shut downtoo. We close the sockets to release their resources and flag that wenow have a free slot in our list of clients.<p>Note that, in this simple example, it assumes that a server isstarted before any clients are and that if a server is shut-downclients are too. It wouldn't be hard for clients to carry on workingwhen the server is restarted but I'll leave that as "an exercise forthe reader". <h3>Summary</h3><p>So far we've described an almost generic client-server environmentthat can run any game of a certain class (it has a board, players andreal-time updates). The framework described so far should be a goodbase for implementing anything that fits into this model and the nextsection shows some of the additional techniques that were used toimplement WinBattle.<h3>Board Class</h3><p>All of the code that knows about the game can be found in the gamefolder. It is just two classes: <i>Board</i> and <i>Cell</i> (althoughthese classes are implemented using a number of separate files forreadability). The <i>Board</i> class implements the game itself, the<i>Cell</i> class is used to implement the individual cells on theboard. So, ignoring the functions for copying the board from theserver to the client which we've already covered, the <i>Board</i>class has the following additional functions we can call:<pre> draw Draws the whole game board. get_size Returns the window size of the board. initialise Prepares the board for a new game (generates terrain, etc.). key Deals with a key press.
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -