Multiplayer

Top  Previous  Next

This month we are going to create a turn-based multiplayer game, with two puppies that race, trying to get to the finish line as quickly as possible.

 

aum75_puppies1

 

It's a game with a server and a client, which throw a dice taking turns and move their puppies with a distance that depends on the dice value. 

I have included the published version of the game as well, so start server.bat, and then run client.bat to play the game for a bit - it will help you understand its inner works.

 

The game allows the server, as well as the client to start the game so the winning chances are equal, especially if you play several games in a row.

Let's discuss the code now. I won't show you the definitions for the variables, bitmaps, panels, texts and so on; however, we will discuss all the code in detail.

 

function main()

{

       randomize();

       fps_max = 60;

       vec_set(screen_color, vector(10, 10, 10));

       on_server = server_event;

       on_client = client_event;

       if (!connection)

       {

               str_cpy(messages_str, "Can't find the server. Please try again later.");

               wait (-4);

               sys_exit(NULL);

       }        

       if (connection == 2)

       {

               im_client = 1;

               str_cpy(messages_str, "Connected to the server");

       

       else // connection = 3

       {

               im_server = 1;

               str_cpy(messages_str, "Waiting for client to join in...");

       }

}

 

It all starts with function main, who uses randomize( ) to generate a random sequence of numbers each and every time - we will need them for the dice. We limit the frame rate to 60 fps (a simple method to limit the data that is sent over the network to 60 packets per second). We set a dark (RGB = 10 10 10) background color, and then we tell function "server_event" to be the event function for the server and function client_event to be the event function for the client. These event functions are run every time when the server or the client receive data.

 

If connection = 0 (!connection), the client can't find the server, so we display the "Can't find the server..." message for 4 seconds at the top of the screen, and then we shut down the engine. If connection = 2, this instance of the game is run by the client (client.bat was run) so we set im_client to 1 and then we display the "Connected to the server" message at the top of the screen. Finally, if connection is 3 (the game is run as a client and server at the same time, using server.bat), we set im_server to 1 and then we display the "Waiting for the client..." message at the top of the screen.

 

The variable named "im_client" will be set to 1 only on the client, while "im_server" will be set to 1 only on the server; this will helps us determine which parts of the code are supposed to run only on the client, only on the server and so on.

 

function server_event()

{

       if (event_type == EVENT_JOIN)

       {

               str_cpy(messages_str, "The client has joined in");

               current_player = integer(random(2)) + 1;

               if (current_player == 1)

               {

                       set(dice_pan, VISIBLE);

               }

               else

               {

                       reset(dice_pan, VISIBLE);

               }

               send_var_to(NULL, current_player);

       }

       if (event_type == EVENT_LEAVE)

       {

               str_cpy(messages_str, "The client has disconnected");

       }

       if (event_type == EVENT_VAR)

       {

               if (current_player == 1)

               {

                       set(dice_pan, VISIBLE);

               }

               else

               {

                       reset(dice_pan, VISIBLE);

               }

               puppy2_pan.pos_x = client_score * 20;

               if (puppy2_pan.pos_x > 600) puppy2_pan.pos_x = 600;

       }

}

 

The server event function will run every time when the server receives data from the client. There are 3 possible server events that can happen in our game:

1) The client has joined the game;

2) The client has left the game;

3) The client has sent a variable to the server.

 

Let's examine the corresponding sections of the code one by one:

 

       if (event_type == EVENT_JOIN)

       {

               str_cpy(messages_str, "The client has joined in");

               current_player = integer(random(2)) + 1;

               if (current_player == 1)

               {

                       set(dice_pan, VISIBLE);

               }

               else

               {

                       reset(dice_pan, VISIBLE);

               }

               send_var_to(NULL, current_player);

       }

 

If the client has joined the game, we display "The client has joined in" at the top of the screen, and then we generate a random number (could be 1 or 2) and store its value inside the "current_player" variable; this will tell us who should start the game (the server or the client). If current_player = 1, the server is supposed to start the game, so we show the dice panel; otherwise, if current_player = 2, the client will start the game, so we hide dice_pan. Finally, we send the value of current_player to the client as well; the client will use it to display or hide its own dice panel.

 

       if (event_type == EVENT_LEAVE)

       {

               str_cpy(messages_str, "The client has disconnected");

       }

 

This section of the server_event function will run if the client has left the game; if this happens, we display "The client has disconnected" at the top of the screen.

 

       if (event_type == EVENT_VAR)

       {

               if (current_player == 1)

               {

                       set(dice_pan, VISIBLE);

               }

               else

               {

                       reset(dice_pan, VISIBLE);

               }

               puppy2_pan.pos_x = client_score * 20;

               if (puppy2_pan.pos_x > 600) puppy2_pan.pos_x = 600;

       }

 

The last section of server_event will run whenever the server receives a variable from the client. If current_player = 1, it's server's turn, so we display dice_pan.

If current_player = 2, we hide dice_pan on the server. 

 

We are using puppy1_pan (the red puppy) as server's puppy and puppy2_pan (the blue puppy) as client's puppy; however, moving puppy2_pan on the client will not move 

puppy2_pan on the server automatically, so we have to displace it on the server as well, depending on the "client_score" value who is sent by the client over the network. 

I chose to multiply client_score with 20; this way, a game won't last too long. You can, of course, choose any other value, replacing "20" with your own values everywhere.

The last line of code makes sure that the blue puppy doesn't get out of the screen (client_score could be set to a big value if the client throws a "6" at the end, for example).

 

Let's examine client's event function now:

 

function client_event()

{

       if (event_type == EVENT_LEAVE)

       {

               str_cpy(messages_str, "The server has disconnected");

               wait (-4);

               sys_exit(NULL);

       }

       if (event_type == EVENT_VAR)

       {

               if (current_player == 1)

               {

                       reset(dice_pan, VISIBLE);

               }

               else

               {

                       set(dice_pan, VISIBLE);

               }

               puppy1_pan.pos_x = server_score * 20; 

               if (puppy1_pan.pos_x > 600) puppy1_pan.pos_x = 600;

       }

}

 

As you see, this event function contains only 2 sections: the first one is run when the server disconnects, displaying "The server has disconnected" for 4 seconds, and then shuts down the engine (the client can't play the game if the server disconnects).

 

The second section of the event runs as soon as the client receives a new variable value from the server; if current_player = 1 (it's server's turn to throw the dice), we hide dice_pan on the client; otherwise, if current_player = 2, we display dice_pan on the client. Moving puppy1_pan (server's puppy) on the server will not move it here on the client automatically, so we update puppy1_pan.pos_x on the client as well using server_score, which is sent by the server, and multiplying it by 20. Finally, we don't allow the red puppy to get out of the screen even if server_score is set to a big value.

 

What comes next? Our typical mouse function that runs by itself as soon as the game is started.

 

function mouse_startup()

{  

       mouse_mode = 2;

       mouse_map = arrow_tga;

       while (1)

       {  

               vec_set(mouse_pos, mouse_cursor);

               wait(1);

       }

}

 

There's nothing special about this function; it displays the mouse pointer, allowing us to click the dice panel. Here's the dice panel definition:

 

PANEL* dice_pan =

{

       bmap = dice1_pcx;

       pos_x = 80;

       pos_y = 130;

       on_click = dice_clicked;

}

 

As you see, function dice_clicked( ) is run as soon as we click dice_pan; it's the last function in our game, so let's examine its content attentively.

 

function dice_clicked()

{

       no_tricks += 1;

       if (no_tricks > 1) {return;}

       beep();

       dice_value = integer(random(6)) + 1;

       if (dice_value == 1) dice_pan.bmap = dice1_pcx;

       if (dice_value == 2) dice_pan.bmap = dice2_pcx;

       if (dice_value == 3) dice_pan.bmap = dice3_pcx;

       if (dice_value == 4) dice_pan.bmap = dice4_pcx;

       if (dice_value == 5) dice_pan.bmap = dice5_pcx;

       if (dice_value == 6) dice_pan.bmap = dice6_pcx;

 

This function is run as soon as the server or the client click their dice panels. I have defined a variable named no_tricks and I have initialized it to zero; this variable will be incremented every time when the player clicks dice_pan. If no_tricks is greater than 1, the function won't do anything; this way, the players can't click dice_pan several times in a row, moving their puppy faster than the other player.

 

I have used a beep( ) instruction to let the player know that it has clicked the dice; the rest of the code generates a random number between 1 and 6 (the value of the dice) and sets the corresponding dice panel bitmap (dice1_pcx... dice6_pcx).

 

       if (im_server)

       {

               server_score += dice_value;

               puppy1_pan.pos_x = server_score * 20;

               if (puppy1_pan.pos_x > 600)

               {

                       puppy1_pan.pos_x = 600;

                       send_var_to(NULL, server_score);

                       str_cpy(game_str, "THE SERVER WINS!");

                       snd_play(victory_wav, 100, 0);

                       send_string(game_str);

                       while (1) {wait (1);}

               }

               current_player = 2;

       }

 

The "if" branch above will only run on the server (im_server is set to 1 only on the server, remember?). The first line of code adds the new dice value to server_score, and then puppy1_pan.pos_x is displaced according to the new server_score value. If puppy1_pan.pos_x exceeds 600, the red puppy (the server) has won the race, so we limit puppy1_pan.pos_x to 600, we send the updated server_score value over the network for the last time, moving puppy1_pan on the client as well, we display "THE SERVER WINS!" on the screen, we play a victory_wav sound on the server, and then we send the "THE SERVER WINS!" string to the client as well.

 

aum75_puppies3

 

The "while (1) {wait (1);}" loop doesn't do anything useful; it's role is to pause the game, allowing you to do something else at the end of the game: display some stats, restart the game, etc. If puppy1_pan.pos_x doesn't exceed 600 (the game isn't over yet), current_player is set to 2, allowing the client to throw the dice (it was server's turn, so now it's client's turn).

 

       if (im_client)

       {

               client_score += dice_value;

               puppy2_pan.pos_x = client_score * 20;

               if (puppy2_pan.pos_x > 600)

               {

                       puppy2_pan.pos_x = 600;

                       send_var_to(NULL, client_score);

                       str_cpy(game_str, "THE CLIENT WINS!");

                       snd_play(victory_wav, 100, 0);

                       send_string(game_str);

                       while (1) {wait (1);}

               }

               current_player = 1;

       }

 

The "if" branch above will only run on the client. If the client clicks its dice_pan, we add the new dice value to client_score, and then we move puppy2_pan to its proper position, depending on client_score's value. If puppy2_pan.pos_x is greater than 600, puppy2 has won the game, so we limit puppy2_pan.pos_x to 600, we send the updated client_score value over the network for the last time, we display the "THE CLIENT WINS!" message, we play the victory_wav sound on the client, and then we send the "THE CLIENT WINS!" string to the server as well.

 

aum75_puppies2

 

If puppy2_pan.pos_x doesn't exceed 600 (the game isn't over yet), we set current_player to 1, allowing the server to throw the dice (it was client's turn, so now it's server's turn).

 

       send_var(current_player);

       wait (1);

       send_var_to(NULL, client_score);

       send_var_to(NULL, server_score);

       wait (-1);

       reset(dice_pan, VISIBLE);

       no_tricks = 0;

}

 

These lines end function dice_clicked( ) and (sadly) our multiplayer workshop; they are run on the client, as well as on the server. We send the current_player value (1 = server's turn or 2 = client's turn), we wait for 1 frame and then we send the updated client_score and server_score values over the network. We have to wait for a frame in order to make sure that current_player is properly set before sending the "client_score" and "server_score" values. The "wait (-1);" instruction allows the player 1 second to see the new dice value before hiding dice_pan; the rest of the code hides dice_pan and resets no_tricks, allowing the player to click dice_pan as soon as it appears again.

 

We could easily change this turn-based game into a mouse button destroyer, real-time action game. Just increment server_score and / or client_score every time when the server 

and / or the client click the left mouse button and the player with the higher mouse clicking rate will win the race! Don't forget to disable the "no_tricks" part of the game as well.

 

I'll see you all next month, when we will get to play with another exciting multiplayer project!