The following user tutorial was created by Kahshoo-heem, for the purpose of expanding upon the SDLTutorials.com series, and expounding upon the use of SDL. This tutorial, though not purposely a part of the SDLTutorials.com or created for the series, may be a branch or addition to the series. Please read notes by the author for any additional code and/or framework used by the author. If you wish to submit your own tutorial to this site, please visit the "User Tutorials" page.


Once we have made our common library, it's time to think on designing the game. Network games can have many architectures, but, normally, there are always at least two applications: a server and a client.

A server can be dedicated, like most online multiplayer games over the internet. But, in this case, the server will be playing too. To make things simpler, the server always will be "X", and the player that starts the game, too. It'll be responsible for waiting clients to connect, too.

At each player turn, the program sets the grid status, refreshes the screen, and sends a message to the other player. Receiving this message, the other player is now able to make valid inputs, as the first one can only exit or minimize his application.

At this point, we may have to create a state for the application: connected. Without it, one can start the game alone, and send messages to a person who is not online.

Now, let's take a look at the messages the program has to make. As we said in the first tutorial, we will send only a byte, that corresponds to the ID of the grid cell clicked. So we have to modify our CNetMessage class, as this byte can be zero, and the program can take the message as an empty string. Therefore, we will make the class CNetMessageApp. Here is the header file, CNetApp.h:
#include "CNet.h"

class CNetMessageApp : public CNetMessage {

 private:

//Virtual function that indicates how many bytes may have to be loaded onto the object. Overrides the parent class function member to work with only a byte

   virtual int NumToLoad();

//Virtual function that indicates how many bytes may have to be downloaded from the object. Overrides the parent class function member to work with only a byte

   virtual int NumToUnLoad();

public:

//Function simpler than LoadBytes(), inherited from the parent class, as it only works with one byte

   void LoadByte(char);

//Function simpler than UnLoadBytes(), inherited from the parent class, as it only works with one byte

   char UnLoadByte();

};
Here comes the implementation, at CNetApp.cpp:
#include "CNetApp.h"

int CNetMessageApp::NumToLoad(){

if (state == EMPTY)

 return 1;

else

 return 0;

}

int CNetMessageApp::NumToUnLoad() {

if (state == FULL)

 return 1;

else

 return 0;

}

void CNetMessageApp::LoadByte(char ID) {

   charbuf c;

   c[0] = ID;

LoadBytes(c, 1);

finish();

}

char CNetMessageApp::UnLoadByte() {

   charbuf c;

   UnLoadBytes (c);

   return c[0];

}
As we can see, very simple. Finally, we can modify the original files of Tim's TIC TAC TOE game.

At the header file CApp.h, in the client side, we will have to add the following lines:
//adding the network library

#include "CNetApp.h"

 .

 .

 .

   private:

// Network status indicator

       bool Connected;

// Network objects

       CClientSocket* tcpclient;

       CIpAddress* remoteip;

       CNetMessageApp msg;
And, in the server side, we don't need the help of CIpAddress object, so it can be deleted. However, we have to add a tcp socket that looks for - or, better saying, listen to - clients:
//network objects

       CHostSocket* tcplistener;
And we are able to get into the implementation code. Firstly, let's set this members when creating Capp object. At CApp.cpp, in the server side, we'll have to add these lines to the constructor:
   tcplistener = NULL;

   tcpclient = NULL;

   Connected = false;
And, in the client side,
   Connected = false;

   tcpclient = NULL;

   remoteip = NULL;
Now, let's see CApp_Oninit.cpp. We may have only to create the objects above, except CNetMessage, that is created automatically by CApp. In the server side, the code of CApp::OnInit() will be added by these lines:
//Creating host socket

   tcplistener = new CHostSocket (1234);

   if (!tcplistener->Ok())

       exit(EXIT_FAILURE);

//Creating client socket

   tcpclient = new CClientSocket();
And in the client side,
//Creating Ip and Socket objects

   tcpclient = new CClientSocket ();

   remoteip = new CIpAddress ("192.168.0.102", 1234);
Note that the client, in this example, will communicate with a determined machine. In my home network, "192.168.0.102" will be the server's address, and "1234" will be the port we have chosen to use. Otherwise, in a LAN, there is a manner to broadcast a required connection, but this method uses UDP sockets. Thus, someone can use this kind of protocol to get the server address, and, after that, work with TCP sockets to make reliable and simple communication over the network.

On the other hand, that is the code on CApp_OnCleanup - it will destroy these objects:

Server side:
   delete tcplistener;

   delete tcpclient;
Client side:
   delete remoteip;

   delete tcpclient;
Now, we can examine what happens when we click the mouse in our application. At CApp::OnLButtonDown() we'll change these lines:
if(CurrentPlayer == 0) {
 SetCell(ID, GRID_TYPE_X);
CurrentPlayer = 1;
 }else{
  SetCell(ID, GRID_TYPE_O);
 CurrentPlayer = 0;
 }
}
to these: CApp_OnEvent - Server side:
//Only starts to play once connected

   if (Connected) {

//Player == 0 will always be Server. Always starts to play and always be "X"

       if(CurrentPlayer == 0) {

           SetCell(ID, GRID_TYPE_X);

           CurrentPlayer = 1;

//Send a message to the client, telling the ID of the clicked cell

           msg.LoadByte((char) ID);

           tcpclient->Send(msg);

       }

   }
CApp_OnEvent - Client side:
//Player == 0 will always be Server. Always starts to play and always be "X". So if

//CurrentPlayer is not zero, the game is necessarily connected

if(CurrentPlayer != 0) {

 SetCell(ID, GRID_TYPE_O);

 CurrentPlayer = 0;

//Send a message to the server, telling the ID of the clicked cell

 msg.LoadByte((char) ID);

  tcpclient->Send(msg);

}
As we can see, no much changes have been made. If it's not the application's turn, it does nothing. If it is, the application sets the grid and player status, like it did priorly, and sends a message to the remote application. There's no need to modify the rendering behavior.

Lastly, we have to write CApp OnLoop() member function. We have to do two things: try to connect, or, if yet connected, receive the messages sent over the network. Below, the code of CApp_OnLoop.cpp, server side:
#include "CApp.h"

void CApp::OnLoop() {

//if not connected, listen to the port to detect if there is a client waiting there

   if (!Connected) {

       if (tcplistener->Accept (*tcpclient)) {

           Connected = true;

       }

   }

   else {

//if connected, checks the socket for messages ready to be read

       if (tcpclient->Ready()) {

// if there is a message, try to receive it. In case of disconnection, the TCP protocol sends

// a message with no bytes

           if (tcpclient->Receive (msg)) {

               //if there is a valid message, we can set the grid and player status

               SetCell((int) msg.UnLoadByte(), GRID_TYPE_O);

               CurrentPlayer = 0;

           }

           else

               Connected = false;

       }

   }

}
The commented lines say by themselves the program's flow. On the other side, the client's code:
//if not connected, try to connect to the server

   if (!Connected) {

       if (tcpclient->Connect(*remoteip)) {

           if (tcpclient->Ok()) {

               Connected = true;

           }

       }

}

   else {

//if connected, checks the socket for messages ready to be read

       if (tcpclient->Ready()){

// if there is a message, try to receive it. In case of disconnection, the TCP protocol sends

// a message with no bytes

           if (tcpclient->Receive (msg)) {

               //if there is a valid message, we can set the grid and player status

               SetCell((int) msg.UnLoadByte(), GRID_TYPE_X);

               CurrentPlayer = 1;

           }

           else {

               Connected = false;

           }

       }

   }

}
Then we have reached the end of this tutorial. I hope you enjoyed it, and feel free to use and modify the library shown here.