Battleship

I used to play this game a lot when I was younger; you place a few ships on "Your board" and then you try to guess the position of the ships on the "Enemy board". Run the game before reading the rest of this article; you will understand a lot of what's happening here without effort. If you don't know how to run the game read "Beginner's corner".

If you've never used multiplayer code until now you have to know that some of the code will run only on the server and some only on the client. You can't throw in single player code in a multiplayer game! It is always better to write new code from scratch.

Let's start the easy way, with some var definitions:

var number_of_boats = 3;

var server_boats[26];
var client_boats[26];

var server_guess = 0; // stores a number between 1 and 25; the server tries to guess the position of a boat on the client
var client_guess = 0; // stores a number between 1 and 25; the client tries to guess the position of a boat on the server

var server_ready = 0;
var client_ready = 0;
var server_plays = 0;
var client_plays = 0;

var killed_by_server = 0;
var killed_by_client = 0;

We are going to place 3 boats on our board and we are using 2 arrays to store the boat positions. The boats will be placed on a 5 x 5 = 25 squares grid but I have used arrays with 26 elements because I want to store square1 in server_boats[1] and client_boats[1], etc. I'm not using server_boats[0] and client_boats[0] to make the things easier for you.

Server_guess and client_guess store a number between 1 and 25; if the client has clicked a specific square on our grid, client_guess is set to that value and sent to the server, like in the picture below:

Server_ready (client_ready) wil be set to 1 when the server (client) is ready to play: it has placed its 3 boats and has pressed the "Start" button. Server_plays (client_plays) will be set to 1 while the server (client) is playing, having access to the mouse pointer. Finally, killed_by_server and killed_by_client shows the number of boats killed by the server and client.

I'm going to show you a lot of panel definitions this month:

panel main_pan
{
bmap = main_pcx;
pos_x = 0;
pos_y = 0;
layer = 10;

button = 50, 390, start1_pcx, start1_pcx, start2_pcx, null, start_game, null;
button = 50, 460, quit1_pcx, quit1_pcx, quit2_pcx, null, quit_game, null;
button = 386, 192, boat_pcx, boat_pcx, boat_pcx, null, place_boat, null;

flags = refresh, d3d, visible;
}

The main panel is the big picture that appears when you start the game; it has 3 buttons that are used to place the boats on your grid, start the game and quit.

panel disabled_pan
{
bmap = disabled_pcx;
pos_x = 595;
pos_y = 379;
layer = 30; // appears over the main panel
alpha = 40;
flags = transparent, refresh, d3d, visible;
}

Disabled_pan appears over the "Enemy board" and blocks access to it until we press the "Start" button.

panel enemyboard_pan
{
pos_x = 0;
pos_y = 0;
layer = 15; // appears over the main panel but it is covered by disabled_pan

button = 599, 383, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 631, 383, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 663, 383, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 695, 383, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 727, 383, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;

button = 599, 415, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 631, 415, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 663, 415, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 695, 415, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 727, 415, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;

button = 599, 447, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 631, 447, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 663, 447, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 695, 447, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 727, 447, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;

button = 599, 479, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 631, 479, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 663, 479, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 695, 479, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 727, 479, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;

button = 599, 511, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 631, 511, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 663, 511, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 695, 511, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;
button = 727, 511, bomb_pcx, invisible_pcx, invisible_pcx, fire_bomb, null, null;

flags = refresh, d3d, visible;
}

The "Enemy board" picture is part of the main panel so enemyboard_pan is an empty panel with 25 invisible buttons; when we click one of the squares on the "Enemy board", we click one of these buttons.

panel boat1_pan
{
bmap = boat_pcx;
pos_x = 0;
pos_y = 0;
layer = 15; // appears over the main panel
flags = refresh, d3d;
}

panel boat2_pan
{
bmap = boat_pcx;
pos_x = 0;
pos_y = 0;
layer = 15; // appears over the main panel
flags = refresh, d3d;
}

panel boat3_pan
{
bmap = boat_pcx;
pos_x = 0;
pos_y = 0;
layer = 15; // appears over the main panel
flags = refresh, d3d;
}

The 3 panels above are the boat pictures that can be placed on our board.

panel boathit1_pan
{
bmap = boathit_pcx;
pos_x = 0;
pos_y = 0;
layer = 20; // appears over the boat1_pan panel
flags = refresh, d3d;
}

panel boathit2_pan
{
bmap = boathit_pcx;
pos_x = 0;
pos_y = 0;
layer = 20; // appears over the boat2_pan panel
flags = refresh, d3d;
}

panel boathit3_pan
{
bmap = boathit_pcx;
pos_x = 0;
pos_y = 0;
layer = 20; // appears over the boat3_pan panel
flags = refresh, d3d;
}

The 3 panels above are the boats with flames that appear on our board if the enemy has guessed their position.

panel destroyed1_pan
{
bmap = destroyed_pcx;
pos_x = 0;
pos_y = 0;
layer = 17; // appears over the main panel
flags = refresh, d3d;
}

panel destroyed2_pan
{
bmap = destroyed_pcx;
pos_x = 0;
pos_y = 0;
layer = 17; // appears over the main panel
flags = refresh, d3d;
}

panel destroyed3_pan
{
bmap = destroyed_pcx;
pos_x = 0;
pos_y = 0;
layer = 17; // appears over the main panel
flags = refresh, d3d;
}

The 3 panels above are the boats with flames and the bomb that appear on the "Enemy board" when we manage to destroy an enemy boat.

panel youwon_pan
{
bmap = youwon_pcx;
pos_x = 250;
pos_y = 200;
layer = 30; // appears over all the panels
flags = refresh, d3d;
}

panel youlost_pan
{
bmap = youlost_pcx;
pos_x = 250;
pos_y = 200;
layer = 30; // appears over all the panels
flags = refresh, d3d;
}

These 2 panels appear at the end of the game and show the winner and the looser.

Function main is simple:

function main()
{
level_load (dummy_wmb);
fps_max = 40; // lock the frame rate to 40 fps
init_game();
}

Let's take a look at function init_game():

function init_game()
{
mouse_mode = 2;
mouse_map = cursor_pcx;
while (1)
{
mouse_pos.x = pointer.x;
mouse_pos.y = pointer.y;

if (server_ready + client_ready == 2)
{
if (killed_by_server != 3 && killed_by_client != 3)
{
disabled_pan.visible = off;
}
ifdef server; // server
if (server_plays == 1)
{
mouse_mode = 2;
}
else
{
mouse_mode = 0;
}
ifelse; // client
if (client_plays == 1)
{
mouse_mode = 2;
}
else
{
mouse_mode = 0;
}
endif;
}
wait (1);
}
}

This is the first function that includes multiplayer - specific code so be careful. At game start, the mouse pointer is made visible and the bitmap that will be used for the pointer is cursor_pcx; the pointer will change its coordinates because of the while (1) loop. If server_ready = 1 and client_ready = 1 (all the boats are placed and the server & client have pressed their "Start" buttons) and the game isn't over (the server or the client haven't destroyed all the enemy boats) we hide the disabled_pan that was covering our "Enemy board", so we can click on it from now on.

If our PC is the server (this PC ran the game using -sv -cl) and it is its turn to make a move (remember server_plays?) we show the mouse pointer on the server. If server_plays = 0 (the client must make its move) we hide the mouse pointer on the server. If our pc is the client (this PC ran the game using -cl) and the client should make a move, we show the mouse pointer on the client. If client_plays = 0 (the server must make its move) we hide the mouse pointer on the client.

Try to stop a little and take a look at the code that starts with ifdef server and ends with endif. This is the way to run different pieces of code on server and clients and if you want to create a multiplayer game you should code it this way.

Let's move on; we will look at the function that runs when the player (client or server) presses the "start" button:

function start_game()
{
if (number_of_boats != 0) {return;}
ifdef server;
server_ready = 1;
send_var(server_ready);
if (counter == 0)
{
counter = 1; // make sure that it runs only once
if (sys_seconds % 2 == 0) // generate a random value (0 or 1)
{
server_plays = 1; // shows the pointer on the server; the server has the first move
client_plays = 0; // the client hides its mouse pointer
}
else
{
client_plays = 1;
server_plays = 0; // the server hides its mouse pointer
}
send_var(client_plays); // shows or hides the mouse pointer on the client
}
ifelse;
client_ready = 1; // the client is ready to play
send_var(client_ready); // and sends this var to the server
endif;
}

If we haven't placed all the boats on our board the function does nothing. If we are the server, we show that we are ready to play and we send server_ready over the network to the client. Server_ready isn't updating the client by itself; we need to send it to the client using send_var. I thought that it would be nice to allow both player (client and server) to start the game so we generate a random value on the server and depending on it we allow the server or the client to make the first move. This part of the function runs only of the server; why would we want to have two random numbers when the decision is so simple? The player could press Start (by accident or not) during the game so the random number must be generated only once. I am using sys_seconds and I test it to see if we have an odd or even number. If the number is odd, the server will have the first move; if the number is even the client will be the first to move.

Please remember that all of this happens on the server and the client doesn't know if it should be the first to move or not, so we send client_plays (could be 1 or 0) from the server to the client to make the mouse pointer visible (or not) on the client.

If we are the client (the part after ifelse;) and we have pressed the "Start" button we set client_ready to 1 and we send this var to the server.

Starting to make sense? Let me show you my favorite function from battleship - it runs when you press the "Quit" button:
function quit_game()
{
exit; // shut down the engine
}
I'm sorry but the next function is simply HUGE, although it is really simple. It is the function that runs when you click the boat icon; its role is to place the boats on "Your board". I'm a nice guy so I have decided to show you only the part that deals with the first square (1) and the last square (25).

function place_boat()
{
if (number_of_boats == 0) {return;} // all the boats are placed

If all the boats are placed, we get out of this function

mouse_map = boat_pcx;

If we click the boat icon on the main panel, the mouse pointer changes to boat_pcx. This gives us the illusion that we are carrying the boat icon over the screen when all we do is to use a different mouse pointer.

while (mouse_left != 1)
{
if (mouse_right == 1)
{
mouse_map = cursor_pcx;
return;
}
wait (1);
}

As long as we haven't pressed the left mouse button (LMB) we can abort by pressing the right mouse button (RMB). If the RMB is pressed, we change back to the "normal" mouse pointer and we get out of this function.

Let me show you "Your board" here:

if (mouse_pos.x > 40 && mouse_pos.x < 230 && mouse_pos.y > 20 || mouse_pos.y < 200)
{

If the boat is placed somewhere inside "Your board" (I've got these coords in Paint Shop Pro, but you can use any painting program)

if (mouse_pos.x > 40 && mouse_pos.x < 65) // placed on the "A" column
{

If the mouse is within these coordinates the boat will be placed inside the "A" column; it could be any square that starts with A (A1..A5).

if (mouse_pos.y > 20 && mouse_pos.y < 55) // inside A1
{

If the mouse is within these coordinates the boat will be placed at A1.

if (number_of_boats == 1)
{
boat1_pan.pos_x = 51;
boat1_pan.pos_y = 41;
boat1_pan.visible = on;
number_of_boats -= 1;
}

If we are placing our first boat, we are going to use the first boat panel (boat1_pan).

if (number_of_boats == 2)
{
boat2_pan.pos_x = 51;
boat2_pan.pos_y = 41;
boat2_pan.visible = on;
number_of_boats -= 1;
}

If we are placing our second boat, we are going to use the second boat panel (boat2_pan).

if (number_of_boats == 3)
{
boat3_pan.pos_x = 51;
boat3_pan.pos_y = 41;
boat3_pan.visible = on;
number_of_boats -= 1;
}

If we are placing our third boat, we are going to use the third boat panel (boat3_pan).

ifdef server;
server_boats[1] = 1;
ifelse;
client_boats[1] = 1;
endif;
}

If we are the server, server_boats[1] will be set to 1; it is the way we are telling the engine that the first square on our board has a boat. If we are the client, client_boats[1] will be set to 1. Please note that server_boats will store the position of the boats only on the server and client_boats will store the position of the boats only on the client.

.............................................................

The dots above hide the code for the rest of the squares, but excepting the coordinates for the mouse and the panels everything is exactly the same. Let's see the rest of the code:

if (mouse_pos.y >= 155 && mouse_pos.y < 185) // inside E5
{
if (number_of_boats == 1)
{
boat1_pan.pos_x = 180;
boat1_pan.pos_y = 169;
boat1_pan.visible = on;
number_of_boats -= 1;
}
if (number_of_boats == 2)
{
boat2_pan.pos_x = 180;
boat2_pan.pos_y = 169;
boat2_pan.visible = on;
number_of_boats -= 1;
}
if (number_of_boats == 3)
{
boat3_pan.pos_x = 180;
boat3_pan.pos_y = 169;
boat3_pan.visible = on;
number_of_boats -= 1;
}
ifdef server;
server_boats[25] = 1;
ifelse;
client_boats[25] = 1;
endif;

If we are the server and we have placed a boat at E5 (the 25th square) we set server_boats[25] to 1. If we are the client, we set client_boats[25] to 1.

}
}
}
mouse_map = cursor_pcx;

The boat was placed so we restore the original mouse pointer (the arrow).

}

The function above makes sure that if you click "Your board" somewhere inside a square the boat will be placed at its correct position every time.

Every time we click the "Enemy board" trying to destroy a ship, we run the function named fire_bomb():

function fire_bomb(square_number)
{
snd_play(bomb_snd, 40, 0);
ifdef server;
server_guess = square_number;
send_var(server_guess);
server_plays = 0; // the server fired, now hide the pointer on the server
client_plays = 1; // and make it visible on the client
ifelse;
client_guess = square_number;
send_var(client_guess);
client_plays = 0; // the client fired, now hide the pointer on the client
server_plays = 1; // and make it visible on the server
endif;
send_var(server_plays);
send_var(client_plays);
}

When we click one of the squares inside the "Enemy board", a sound is played. If we are the server, square_number (and server_guess) is set to the number that is passed as a parameter to this function. This is an extremely important engine feature: all the buttons that belong to a panel have an ID number. If we click the first sqare on the "Enemy board", we click a button that will set square_number and server_guess to 1. If we click the 14th square, square_number and server_guess will be set to 14. This feature has allowed me to use a single function for all the 25 buttons that appear on the panel named enemyboard_pan.

Remember we're on the server, right? The server sends its guess (server_guess) to the client and then stops playing and tells the client to play. If we are on the client, we send client_guess to the server, we stop playing and tell the server that it is its turn to play. Finally, we send server_plays and client_plays over the network to make the mouse pointers visible or not.

I feel you're pretty tired but we only have 2 functions left and they're pretty similar so grab some whatever you want to grab and read on. The server and the clients have a dedicated function that runs whenever something is sent over the network: a new player has joined the game, a var was sent, etc.

on_server server_event;
on_client client_event;

This piece of code tells the server to run the server_event function and the client to run the client_event function every time something is sent over the network. Let's see these complicated event functions:

function server_event()
{
exclusive_global();

Only a copy of this function is allowed to run.

if (event_type == event_var)
{

If the client has sent a var to the server

if (client_ready == 1 && server_ready == 0)
{
snd_play(ready_snd, 50, 0);
}

If the client is ready and the server isn't ready, we play a sound on the server to let it know that the client has placed all the boats on his board and is waiting for the server.

if (server_ready + client_ready != 2) {return;}

The server and the client must be ready in order to start the game; if this isn't true we get out of this function

if (server_boats[client_guess] == 1)

Let's imagine that client_guess = 10 (the client has clicked on the 10th square). If the server was hiding a boat there, server_boats[10] is set to 1 because of that HUGE function we've talked about. Therefore, the line above checks if the client has guessed the position of a boat on the server.

{
snd_play(destroyed_snd, 100, 0); // a boat was hit!
killed_by_client += 1;

We play a sound and we increase killed_by_client

if (killed_by_client == 1)

If this is the first boat destroyed by the client

{
boathit1_pan.pos_x = 52 + ((client_guess - 1) % 5) * 32;
boathit1_pan.pos_y = 41 + 32 * (client_guess > 5 && client_guess <= 10) + 64 * (client_guess > 10 && client_guess <= 15)
+ 96 * (client_guess > 15 && client_guess <= 20) + 128 * (client_guess > 20);
boathit1_pan.visible = on; // appears on the server over its boat
}

We compute the position for boathit1_pan (boat + flames) that will be placed on the server, over the ship that was hit, on "Your board". Let's see what's happening with these panel coords:

boathit1_pan.pos_x = 52 + ((client_guess - 1) % 5) * 32;
boathit1_pan.pos_y = 41 + 32 * (client_guess > 5 && client_guess <= 10) + 64 * (client_guess > 10 && client_guess <= 15)
+ 96 * (client_guess > 15 && client_guess <= 20) + 128 * (client_guess > 20);

I know that the grid for the boards has a width and height of 32 pixels; if client_guess = 1, boathit1_pan will be placed with its pos_x = 52; if client_guess = 2, pos_x = 52 + 32 and so on. The same thing happens on the y axis but this time the z coordinate changes according to the intervals in the paranthesis. Please note that client_guess isn't a simple guess at this point because the client has guessed one of the boats on the server, so client_guess holds the coordinate of one of the enemy boats.

if (killed_by_client == 2)
{
boathit2_pan.pos_x = 52 + ((client_guess - 1) % 5) * 32;
boathit2_pan.pos_y = 41 + 32 * (client_guess > 5 && client_guess <= 10) + 64 * (client_guess > 10 && client_guess <= 15)
+ 96 * (client_guess > 15 && client_guess <= 20) + 128 * (client_guess > 20);
boathit2_pan.visible = on; // appears on the server over its boat
}

If we have destroyed the second boat, we show boathit2_pan.

if (killed_by_client == 3)
{
boathit3_pan.pos_x = 52 + ((client_guess - 1) % 5) * 32;
boathit3_pan.pos_y = 41 + 32 * (client_guess > 5 && client_guess <= 10) + 64 * (client_guess > 10 && client_guess <= 15)
+ 96 * (client_guess > 15 && client_guess <= 20) + 128 * (client_guess > 20);
boathit3_pan.visible = on; // appears on the server over its boat
}

If we have destroyed the third boat, we show boathit3_pan.

server_boats[client_guess] = 0;

The line above resets the element of server_boats to zero. This way a boat can't be hit twice; without this line we could click in the same square three times and we would win the game.

send_var(killed_by_client); // the client has destroyed one of the boats -> make it show destroyed_pcx on its enemy board
}

The client has destroyed one of the boats on the server, so the server sends killed_by_client to the client and the client will show destroyed_pcx (boat, fire and bomb) on its "Enemy board".

// killed_by_server is sent from the client
if (killed_by_server == 1 && destroyed1_pan.visible == off) // the first boat on the client was destroyed, runs only once
{
destroyed1_pan.pos_x = 599 + ((server_guess - 1) % 5) * 32;
destroyed1_pan.pos_y = 383 + 32 * (server_guess > 5 && server_guess <= 10) + 64 * (server_guess > 10 && server_guess <= 15)
+ 96 * (server_guess > 15 && server_guess <= 20) + 128 * (server_guess > 20);
destroyed1_pan.visible = on;
}
if (killed_by_server == 2 && destroyed2_pan.visible == off) // the second boat on the client was destroyed, runs only once
{
destroyed2_pan.pos_x = 599 + ((server_guess - 1) % 5) * 32;
destroyed2_pan.pos_y = 383 + 32 * (server_guess > 5 && server_guess <= 10) + 64 * (server_guess > 10 && server_guess <= 15)
+ 96 * (server_guess > 15 && server_guess <= 20) + 128 * (server_guess > 20);
destroyed2_pan.visible = on;
}
if (killed_by_server == 3 && destroyed3_pan.visible == off) // the third boat on the client was destroyed, runs only once
{
destroyed3_pan.pos_x = 599 + ((server_guess - 1) % 5) * 32;
destroyed3_pan.pos_y = 383 + 32 * (server_guess > 5 && server_guess <= 10) + 64 * (server_guess > 10 && server_guess <= 15)
+ 96 * (server_guess > 15 && server_guess <= 20) + 128 * (server_guess > 20);
destroyed3_pan.visible = on;
}

Killed_by_server is sent from the client every time the server has destroyed another boat on the client. If killed_by_server = 1 and destroyed1_pan isn't visible, we show it over server's "Enemy board" at the position that corresponds to the square that was clicked on. If killed_by_server = 2 we show destroyed2_pan and so on.

if (killed_by_server == 3) // game over, server wins
{
youwon_pan.visible = on;
disabled_pan.visible = on; // can't throw bombs from now on
server_plays = 1; // show the mouse pointer
client_plays = 1; // on the server and on the client because the game is over
}
if (killed_by_client == 3) // game over, client wins
{
youlost_pan.visible = on;
disabled_pan.visible = on; // can't throw bombs from now on
server_plays = 1; // show the mouse pointer
client_plays = 1; // on the server and on the client because the game is over
}
}
}

If killed_by_server = 3, the server wins so it displays the youwon_pan panel, makes disabled_pan visible (so that nobody can click on the "Enemy board" from now on) and shows the mouse pointer on the client and on the server, allowing them to exit the game by clicking on "Quit". If the client wins, the server displays the youlost_pan panel, makes disabled_pan visible and shows the mouse pointers.

The event associated to the client is similar but let's take a look at it:

function client_event()
{
exclusive_global();
if (event_type == event_var)

If the server has sent a var to the client

{
if (server_ready == 1 && client_ready == 0)
{
snd_play(ready_snd, 50, 0);
}

If the server is ready but the client isn't ready, play a sound on the client to let it know that the server is waiting

if (server_ready + client_ready != 2) {return;}

The server and the client must be ready in order to start the game

if (client_boats[server_guess] == 1)

If the server has guessed the position of one of the boats on the client

{
snd_play(destroyed_snd, 100, 0); // a boat was hit!
killed_by_server += 1;
if (killed_by_server == 1)
{
boathit1_pan.pos_x = 52 + ((server_guess - 1) % 5) * 32;
boathit1_pan.pos_y = 41 + 32 * (server_guess > 5 && server_guess <= 10) + 64 * (server_guess > 10 && server_guess <= 15)
+ 96 * (server_guess > 15 && server_guess <= 20) + 128 * (server_guess > 20);
boathit1_pan.visible = on;
}
if (killed_by_server == 2)
{
boathit2_pan.pos_x = 52 + ((server_guess - 1) % 5) * 32;
boathit2_pan.pos_y = 41 + 32 * (server_guess > 5 && server_guess <= 10) + 64 * (server_guess > 10 && server_guess <= 15)
+ 96 * (server_guess > 15 && server_guess <= 20) + 128 * (server_guess > 20);
boathit2_pan.visible = on;
}
if (killed_by_server == 3)
{
boathit3_pan.pos_x = 52 + ((server_guess - 1) % 5) * 32;
boathit3_pan.pos_y = 41 + 32 * (server_guess > 5 && server_guess <= 10) + 64 * (server_guess > 10 && server_guess <= 15)
+ 96 * (server_guess > 15 && server_guess <= 20) + 128 * (server_guess > 20);
boathit3_pan.visible = on;
}

Show the corresponding boathit_pan panel (over "Your board") depending on how many boats were destroyed by the server.

client_boats[server_guess] = 0;

A boat can't be hit twice

send_var(killed_by_server);

The server has destroyed one of the boats -> make it show destroyed_pcx on its "Enemy board".

}
if (killed_by_client == 1 && destroyed1_pan.visible == off) // the first boat on the server was destroyed, runs only once
{
destroyed1_pan.pos_x = 599 + ((client_guess - 1) % 5) * 32;
destroyed1_pan.pos_y = 383 + 32 * (client_guess > 5 && client_guess <= 10) + 64 * (client_guess > 10 && client_guess <= 15)
+ 96 * (client_guess > 15 && client_guess <= 20) + 128 * (client_guess > 20);
destroyed1_pan.visible = on;
}
if (killed_by_client == 2 && destroyed2_pan.visible == off) // the second boat on the server was destroyed, runs only once
{
destroyed2_pan.pos_x = 599 + ((client_guess - 1) % 5) * 32;
destroyed2_pan.pos_y = 383 + 32 * (client_guess > 5 && client_guess <= 10) + 64 * (client_guess > 10 && client_guess <= 15)
+ 96 * (client_guess > 15 && client_guess <= 20) + 128 * (client_guess > 20);
destroyed2_pan.visible = on;
}
if (killed_by_client == 3 && destroyed3_pan.visible == off) // the third boat on the server was destroyed, runs only once
{
destroyed3_pan.pos_x = 599 + ((client_guess - 1) % 5) * 32;
destroyed3_pan.pos_y = 383 + 32 * (client_guess > 5 && client_guess <= 10) + 64 * (client_guess > 10 && client_guess <= 15)
+ 96 * (client_guess > 15 && client_guess <= 20) + 128 * (client_guess > 20);
destroyed3_pan.visible = on;
}

Killed_by_client is sent from the server; show the corresponding destroyed_pan panel (over the "Enemy board") depending on how many boats were destroyed by the client

if (killed_by_server == 3) // game over, server wins
{
youlost_pan.visible = on;
disabled_pan.visible = on; // can't throw bombs from now on
server_plays = 1; // show the mouse pointer
client_plays = 1; // on the server and on the client because the game is over
}
if (killed_by_client == 3) // game over, client wins
{
youwon_pan.visible = on;
disabled_pan.visible = on; // can't throw bombs from now on
server_plays = 1; // show the mouse pointer
client_plays = 1; // on the server and on the client because the game is over
}
}
}

Show the panels for winner and looser, show disabled_pcx to prevent the players from clicking on the "Enemy board" after the game is over and show the mouse pointers on the server and on the client

You might have noticed that I'm using only event_var in these event functions; I could have used (for example) event_leave to display a panel on the server if the client gets disconnected and so on. The code isn't that difficult; if you have problems understanding it try to run the game and observe small features, then go to the code and see how I did it. Here's an example:

Q: How have I managed to play a sound on the client by clicking "Start" on the server?
A: When the server has placed its boats on "Your board" and the player has pressed "Start", server_ready is set to 1 and sent over the network to the client. Function client_event will run as soon as something is sent from the server, checking if server_ready = 1 (the server is ready) and client_ready = 0 (the client isn't ready); if both conditions are true it will play a sound.