Skip to content

Network Tutorial

Mike Lilligreen edited this page Dec 9, 2013 · 1 revision

Overview

This tutorial will show you how to set up a network connection using Torque 2D MIT. I will also then move on to show you how to create a simple network game using that connection. Before starting this tutorial, you should have a good general knowledge of Torque 2D, Torque Script & a general knowledge of working over a network.

What you need

Table Of Contents

Chapter 1. Create our Base Project

Chapter 2. Setting up our Network scripts and gui’s

[Chapter 3. Network Checkers] (https://github.com/GarageGames/wiki/Network-Tutorial#chapter-3-network-checkers)

[Appendix A. TGB Gui conversion to T2D MIT] (https://github.com/GarageGames/wiki/Network-Tutorial#appendix-a-tgb-gui-conversion-to-t2d-mit)

Chapter 1. Create our Base Project

As has been seen in the Getting Started Guide we will set up a Base system and expand from there. Follow this guide through Chapter 3, there are only a few things to change within Chapter 3. Note, that I entitled my hub directory as T2D-Net. First, the Appcore’s main.cs file, in the create function initializeCanvas(“Torque 2D”); will change to initializeCanvas(“T2D Network”);, or if you want something else, feel free. Cornflower Blue is fine for a color for me, change at your heart’s content (but keep in mind later on we will be working with shades of brown, so maybe stay away from that). Also, we will change the line just like in Chapter 3 for ModuleDatabase.loadGroup(“gameBase”); to ModuleDatabase.loadExplicit(“T2DNetwork”);. We will also create a directory to house our new game instance, and copy in the necessary files, just like in Chapter 4, but in this case our module will be T2DNetwork. Within T2DNetwork we will need a few folders, in this case exactly the same as Chapter 4, assets, gui & scripts will be our folders. Your Directory structure should look as shown below:

Dir Start

The next thing we need to do is update our T2DNetwork module, we need to create a main.cs and module.taml. First up, module.taml:

<ModuleDefinition
	ModuleId="T2DNetwork"
	VersionId="1"
	Description="Network module"
	Group="game"
	ScriptFile="main.cs"
	CreateFunction="create"
	DestroyFunction="destroy">
	
	<DeclaredAssets
			Path="assets"
			Extension="asset.taml"
			Recurse="true"/>			
</ModuleDefinition>

This should be fairly straightforward, we specify our module, give it an id and description, specify out the create & destroy functions to be used, and set a path to our assets. The create and destroy functions reside within our main.cs:

function T2DNetwork::create( %this )
{
  // Load GUI profiles.
  exec("./gui/guiProfiles.cs");
  
  // Create the scene window
  exec("./scripts/scenewindow.cs");
  createSceneWindow();
  
  // Create the scene
  exec("./scripts/scene.cs");
  createScene();
  mySceneWindow.setScene(myScene);
  
  // Load and configure the console.
  exec("./scripts/console.cs");
  TamlRead("./gui/ConsoleDialog.gui.taml"); //Notice we are just reading in the Taml file, not adding it to the scene
  GlobalActionMap.bind( keyboard, "ctrl tilde", toggleConsole );
  
  // Network Prefs
   $pref::Master0 = "2:master.garagegames.com:28002";
   $pref::Net::LagThreshold = 400;
   $pref::Net::Port = 28000;
   $pref::Server::RegionMask = 2;
   $pref::Net::RegionMask = 2;
   
   $pref::Server::Name = "T2D Server";
   $pref::Player::Name = "T2D Player";

Most of this should look familiar to the Getting Started format, and I highly recommend you have worked through that before this, but to each their own. I will not explain all of it, as there is already a great explanation in that guide. The new things you will notice are the addition of the console as well as setting up Network preferences. The console is necessary for our first mock up of the network test, so we need to have access to it. Our network preferences will be needed later on when we start our connection process. Just as a note here, we’re using the master server from GarageGames for testing purposes; this will of course be what should be changed when hosting your own dedicated server. Continuing on with main.cs:

   // Set up networking
   setNetPort(0);
   
   // Gui Related scripts
   exec("./scripts/messageBox.cs");
   exec("./scripts/chatGui.cs");
   exec("./scripts/startServerGui.cs");
   exec("./scripts/joinServerGui.cs");
   
   // Network structure
   exec("./scripts/client/chatClient.cs");
   exec("./scripts/client/client.cs");
   exec("./scripts/client/message.cs");
   exec("./scripts/client/serverConnection.cs");
   
   exec("./scripts/server/chatServer.cs");
   exec("./scripts/server/clientConnection.cs");
   exec("./scripts/server/kickban.cs");
   exec("./scripts/server/message.cs");
   exec("./scripts/server/server.cs");

Next, we call the function SetNetPort(0);, this oddly enough initializes the call to open a network port. For those nostalgic enough to remember, this is what happens when you set $Game.UsesNetwork = true; in TGB. Then we follow up with loading up our taml configured gui’s, this is done by calling a TamlRead function of our taml files. Finally, we call our NetworkMenu to be brought up. Notice that we are not doing an add to scene with our Gui’s, as we will be using push/pop Canvas commands to add them as we need them, instead of loading them into our scene immediately. After this, is where some more of the heart of the Network system comes in, and understanding our network scripts is a lot to take in, but I’m going to do my best to cover the system as a whole.

   // Load the Network Gui's
   TamlRead("./gui/NetworkMenu.gui.taml");
   TamlRead("./gui/startServer.gui.taml");
   TamlRead("./gui/joinServer.gui.taml");
   TamlRead("./gui/chatGui.gui.taml");
   TamlRead("./gui/waitingForServer.gui.taml");
   TamlRead("./gui/messageBoxOk.gui.taml");

   // Simple Network
   Exec(“./scripts/simpleChat.cs”);
   
   Canvas.pushDialog(NetworkMenu);
}
function T2DNetwork::destroy()
{
  // Destroy the scene window
  destroySceneWindow();
}

You’ll notice I made reference to several files I haven’t told you about yet; we’re going to cover that next. Several files are identical to that which follows the Getting Started guide, but to be complete, here are the files for the scene creation. Here’s scenewindow.cs:

function createSceneWindow()
{
    // Sanity!
    if ( !isObject(mySceneWindow) )
    {
        // Create the scene window.
        new SceneWindow(mySceneWindow);

       // Set Gui profile. If you omit the following line, the program will still run as it uses                
       // GuiDefaultProfile by default

        mySceneWindow.Profile = GuiDefaultProfile;

        // Place the sceneWindow on the Canvas
        Canvas.setContent( mySceneWindow );                     
    }

    //Note that the SceneWindow's camera defaults to the following values, you could omit them entirely and       
    //obtain the same result.

    mySceneWindow.setCameraPosition( 0, 0 );
    mySceneWindow.setCameraSize( 100, 75 );
    mySceneWindow.setCameraZoom( 1 );
    mySceneWindow.setCameraAngle( 0 );
}

function destroySceneWindow()
{
    // Finish if no window available.
    if ( !isObject(mySceneWindow) )
        return;
    
    // Delete the window.
    mySceneWindow.delete();
}

This is described in the Getting Started tutorial. Next is scene.cs:

function createScene()
{
    // Destroy the scene if it already exists.
    if ( isObject(myScene) )
        destroyScene();
    
    // Create the scene.
    new Scene(myScene);
    
}

function destroyScene()
{
    // Finish if no scene available.
    if ( !isObject(myScene) )
        return;

    // Delete the scene.
    myScene.delete();
}

Again, this was covered in the Getting Started tutorial. There are a couple of extra files for the console that are simply easier to copy over from the master download that you copied AppCore from. The files needed for the console are: Sandbox\1\scripts\console.cs, Sandbox\1\gui\ConsoleDialog.gui.taml, and we also need Sandbox\1\assets\gui\bluegradient.asset.taml & blueGradient.png. Copy all of the files to the same paths except in our T2DNetwork module, the bluegradient files can just go into our assets folder. There is 1 line we have to change in the ConsoleDialog.gui.taml since we are working from our own project. Find this line within the GuiSpriteCtrl container: Image="@asset=Sandbox:blueGradient" and replace that with: Image="@asset=T2DNetwork:blueGradient", all we did was change which module we are referencing here. The network scripts & gui elements are still needed to be flushed out, and they require a bit of detail to go over.

Chapter 2. Setting up our Network scripts and gui’s

To start off with, it may benefit to know that T2D's network system has undergone no change since evolving from Torque Game Builder (TGB), this makes life, in essence easier to grasp. Providing of course, having some knowledge and background with that Network structure. The old system had a few things that weren’t complete, and it was also a little clunky in design purposes, but it does suffice the needs. An element to consider when I did this was the re-formatting of the gui elements, though that was easier to tackle than I had first thought, and I came up with a good conversion method. Since it isn’t required for what we’re doing, I’m going to stick it at the end of this guide as an extra, so in case others who have TGB gui’s might want to know what it takes to convert to MIT.
Now that you understand the conversion process I undertook, let’s take a look at what is required to grab. Note that all of the files will be provided as part of my example, but it’s good to understand the system mechanics behind it. Otherwise, you’re just copying information without retaining any knowledge of its usage. And, while it may not be important to all, it’s important for understanding, and also for documentation. So, for those who had access to a TGB build, I grabbed files that were always installed in projects under the “common” directory. There’s other files included here that serve different purposes, but that is beyond the scope of this tutorial, but may be worth exploration on your own time. Since there are certain copyright rules, and the fact that TGB requires a purchased license, I’ll just leave it at that, and I will explain the files that are now included with the T2DNetwork module.

  • chatGui – A windowed chat gui, that has a player list and a message area, the associated script file holds the power behind the updating of this.
  • joinServerGui – The gui required to join into a server connection, this holds a few buttons for joining servers through either a local LAN, a master, or to a direct IP address, the associated script file holds the specifics behind this and has some global engine function calls that deal with networking.
  • messageBoxOKGui – A very simple message box system that creates a message and an “OK” button.
  • NetworkMenuGui – This is brought up immediately and offers our main selection to choose to Start a Server, Join a Server, or Exit.
  • startServerGui – The gui required to start a server connection, asking for a port, a name, and some setups, the associated script files handles the processing to create the actual server.
  • waitingForServerGui – This gui is similar to the MessageBox, it’s usage is usually for latency/communication lag between clients and the server.

Also, there are several image files required for those gui elements, which will be provided. That covers up the Gui overlay system for ease of integration to our Network, next up we need to cover the specific Client & Server scripts. To understand what’s happening here, the user creating the Server is creating the base server, as well as a local Client to themselves. Each joining user then creates a new Client instance against the initial created Server, communication happens through commands from clients to the server, and the server to the clients. Ex: commandToClient(client, function, argument); & commandToServer(function, argument);. The respective calls are similar, serverCmdFunction(client, argument); & clientCmdFunction(argument). It pays to be familiar with the structure, we will cover it moreso in our scripts, and have more examples as we examine gameplay. For the sake of simplicity, and the fact that these are scripts from TGB that have been around for quite some time, I’m going to leave out the explanation of those scripts, and might later on provide a reference to them and explain their usage in further detail. Some of it isn’t for the timid, and you can just “trust” it to work. However, as things have changed a bit over time, the ChatGui has some oddities in it. But for now, it accomplishes our task. A brief overview of our files:

Client Scripts

  • chatClient.cs – This is the chat system integration, handling the list of players connected; there’s some functionality that doesn’t fully work with this system.
  • client.cs – This handles the connection/disconnection processes for the Client.
  • message.cs – This handles general messaging from the Server, utilizing the detag function to translate a tag to a string; this is common for Network messaging.
  • serverConnection.cs – This handles a Server connection to the Client, and also handles several onConnection necessities.

Server Scripts

  • chatServer.cs – This is the chat system integration, handling calls to the Chat Gui, again parts of this system aren’t fully working.
  • clientConnection.cs – This handles our Client’s connection processes, also is used for Name control.
  • kickban.cs – A fairly simple system to set up kicking & banning of players, we won’t really mess around with this for our example, but it is good to have the ability to block players on certain occasions.
  • message.cs – The message system for the server, similar to that of the Client, except this also handles messaging Teams (if enabled) and also for Spam control.
  • server.cs – This handles our server creation & destruction.

All of the files are required for our basic core network. These will be packaged in a zip file for convenience (netCore), and should be extracted to your module folder. This should update your gui & scripts folders for the core network scripts and gui interface that is required. Finally, we’ll add a few additional scripts to set up a simple system for understanding purposes mostly, and to get the general idea of what we need to do for Network calls. The files will be simpleSetup.cs & simpleChat.cs, also we will show how to load up our temporary ChatGui through the command line, and we need to add some guiprofiles, so let’s set that up as well. Take the file Guiprofiles.cs from the Sandbox/gui folder, and copy it over to our project, and do a find and replace on ^Sandbox to ^T2DNetwork, that will fix the module referencing, and add the following to it:

if(!isObject(GuiWindowProfile)) new GuiControlProfile (GuiWindowProfile)
{
   opaque = true;
   border = 1;
   text = "untitled";
   justify = "center";
};

if(!isObject(GuiMediumTextProfile)) new GuiControlProfile (GuiMediumTextProfile : GuiTextProfile)
{
   fontSize = 24;
};

if(!isObject(GuiBigTextProfile)) new GuiControlProfile (GuiBigTextProfile : GuiTextProfile)
{
   fontSize = 36;
};

if(!isObject(GuiTextArrayProfile)) new GuiControlProfile (GuiTextArrayProfile : GuiTextProfile)
{
   fontColorHL = "32 100 100";
   fillColorHL = "200 200 200";
};

if(!isObject(GuiTextListProfile)) new GuiControlProfile (GuiTextListProfile : GuiTextProfile) 
{
   tab = true;
   canKeyFocus = true;
};

if(!isObject(GuiProgressProfile)) new GuiControlProfile ("GuiProgressProfile")
{
   opaque = false;
   fillColor = "44 152 162 100";
   border = true;
   borderColor   = "78 88 120";
};

if(!isObject(GuiProgressTextProfile)) new GuiControlProfile ("GuiProgressTextProfile")
{
   fontColor = "0 0 0";
   justify = "center";
};

if(!isObject(GuiMLTextProfile)) new GuiControlProfile ("GuiMLTextProfile")
{
   fontColorLink = "255 96 96";
   fontColorLinkHL = "0 0 255";
   autoSizeWidth = true;
   autoSizeHeight = true;  
   border = false;
};

We will save this in our T2DNetwork/gui folder under the same name. Now for our simple network, we will make this file in our scripts folder, simpleChat.cs:

//How to use this example
//1. Launch two versions of T2D
//2. Click StartServer button. You should see a connected message (the server computer connects as a client to itself).
//3. In the console on your client click JoinServer button. Click QueryLan, then Join Server. You should see a connected message.
//4. In the console on the server run 'serversayhi("NAME")'. Where NAME is your name or whatever.
//5. Check the console on the client and you should see "NAME (server) says hi!"
//6. In the console on the client run 'clientsayhi("NAME")'. Where NAME is your name or whatever. 
//7. Check the console on the client and you should see "NAME (client) says hi!"
//8. To load up our chat gui, type 'loadChat' on the Server, then the Client.

//Create a server on this computer
function startserver()
{
   echo("creating server");
   createServer(true);
}

//Tell this client to connect to the server
function connect(%ip)
{  
   echo("connecting to server");
   connecttoserver(%ip);
}

//THIS IS CALLED ON THE CLIENT
//Tells the server to execute serverCmdHi with our name as an argument
function clientsayhi(%name)
{
//the function calls a command on the server called serverCmdHi below
//this converts to serverCmdHi function and executes on the server
   commandToServer('Hi', %name); 
}

//THIS EXECUTES ON THE SERVER FROM THE commandToServer CALL ABOVE
//we take the commandToServer 'Hi' argument from above and 
//append it to the serverCmd to create this callback
function serverCmdHi(%client, %name)
{
   echo(%name SPC "(client) says hi!");
}

//THIS EXECUTES ON THE SERVER
//this loops through all the clients and tells each to 
//execute the clientCmdHi function with our name as an argument
function serversayhi(%name)
{
   %count = ClientGroup.getCount();
   for(%i = 0; %i < %count; %i++)
   {
      %recipient = ClientGroup.getObject(%i);
      //this converts to clientCmdHi function and executes on the client
      commandToClient(%recipient, 'Hi', %name);
   }
}

//THIS EXECUTES ON EACH CLIENT FROM THE commandToClient CALL ABOVE
//we take the commandToClient 'Hi' argument from above and 
//append it to the ClientCmd to create this callback
function clientCmdHi(%name)
{
  echo(%name SPC "(server) says hi!");
}

As you can see here, I included direction in the file itself, but for review, this is what to do:

  • Launch two versions of T2D, this is done by launching Torque2D.exe in 2 separate windows.
  • On the first window, click the StartServer button. You should see a connected message (the server computer connects as a client to itself).
  • On the second window, click JoinServer button. Click QueryLan, then Join Server. You should see a connected message.
  • In the console on the server run 'serversayhi("NAME")'. Where NAME is your name or whatever.
  • Check the console on the client and you should see "NAME (server) says hi!"
  • In the console on the client run 'clientsayhi("NAME")'. Where NAME is your name or whatever.
  • Check the console on the client and you should see "NAME (client) says hi!"
  • To load up our chat gui, type 'loadChat' on the Server, then the Client.

That sums up our simple network. We have shown how to establish a connection between a server and a client, and how to send commands to one another as well as establishing a chat system. The display for the chat isn’t the greatest, and there are a couple means that could improve this. I had gone through part of this process, and thought it might be nice to revamp the chat system. In hind sight, I have realized that though beneficial to a handful, it can change quite a bit based on design. It’s not necessary, and I may provide another tutorial in the future for it. For reference, in case you wind up with any problems, I have included a zip file with this setup (SimpleNet).

Chapter 3. Network Checkers

So, I’ve shown you how to chat, and the core of the networking system, let’s put that knowledge to use and actually make a game! The first choice was to take an established network game from TGB, and convert that over to T2D MIT. The only well documented choice was Checkers. This was my choice, and I used the setup that was used and built and refined around it a bit as needed. There’s a lot to deal with when it comes to this, and it took me a fair bit of time to convert the project, as well as make a few things work properly. I strongly suggest going through this slowly, and I will provide test intervals at appropriate times so you don’t have to backtrack through a mile of code. First let’s set up a new folder for our game, within modules create a folder for NetCheckers. Also, for later purposes we can create a folder for assets & scripts. We will also want to create a couple of folders within scripts for our network split up, one for client and another for server. You should now have folders setup like below:

DirCheckers

Checkers Assets

The first thing we need to do is get all of the images needed and create the taml files for those assets so we can load them into the game. I have taken the images from the original checkers tutorial and manipulated them for usage here. Also, I had to create a few extras for the board itself, which was simply just making colored tiles for the board. By all means, for those eager and wanting to do everything on their own, go ahead and create your own stuff. I’m not much of an artist, but I can certainly use art programs for simple things. For those lazy or just wanting to move ahead, use what I have provided and deal with it. The images themselves are within the zip file, checkersAssets and should be placed within your assets folder for NetCheckers. The creation of the taml files for the images is fairly simple and is pretty well covered in other documentation, for reference I will show one here, the rest are setup the same with just changing the name on the asset and file.

<ImageAsset
     AssetName=”checker1”
     ImageFile=”checker1.png” />

The remaining assets are: checker2, moon_king, moon_neutral, sun_king & sun_neutral. Again the only changes would be the AssetName and associated ImageFile, and these should be named the same as the image file except instead of .png it should be .asset.taml.

Understanding the network structure

Setting up network calls back and forth can get pretty overwhelming at times, especially with a lot of back and forth going on between the client and the server. It’s important to understand the process, and to know the appropriate ways to call the functions. As will be seen here we have a couple of calls that should become common place in your mind with server & client scripts.

  • commandToClient( client, function [, arg1, … , argn]) Use the commandToClient function to issue a remote procedure call on a client. Note that the function is always contained within single quotes, and that we have to specify which client we are sending the command to. The server uses this to dispatch calls to the clients. It is then met with an appropriate function call by the client:
  • clientCmdFunction(arg1, …, argn) This is the function that gets processed by the above function call based on the function named within. Generally speaking, it is easier to then call a function from there so as to keep things organized and clean, as we will see more of as we go along.
  • commandToServer(function [, arg1, … , argn]) Use the commandToServer function to issue a remote procedure call on the server. Note that the function is always contained within single quotes, and there is only 1 server, so no need to distinguish where the command goes. The clients use the to dispatch calls to the server. It is then met with an appropriate function call by the server:
  • serverCmdFunction(arg1, …, argn) This is the function that gets processed by the above function call based on the function named within. Generally speaking, it is easier to then call a function from there so as to keep things organized and clean, as we will see more of as we go along.

Initialization & Setup

First things first, we need to create some files for our game to start, and set up some initialization that will help us as we go through the creation. As you should be familiar with by now, we need a main.cs and a module.taml file located within our hub NetCheckers folder, so let’s get those created So, let’s create our main.cs:

function NetCheckers::create( %this )
{
   // Checkers
   $red = 1;
   $blue = 2;
   $redKing = 3;
   $blueKing = 4;
   exec("./scripts/execs.cs");
}


function NetCheckers::destroy( %this )
{
}

Pretty simple setup, you should be familiar enough with the function calls for create and destroy if you’ve worked with other tutorials and have tinkered with the modules. But to elaborate, when the module is loaded we call the create function, and when it is exited we will call the destroy function. Within our create function I have setup a couple of global variables indicate by the $ in front of the name of those variables. We will use these throughout our scripts, and it’s really just a handy reference for information. Also, I’m calling to execute a script that I haven’t mentioned yet, but it will be coming soon. I prefer to group my scripts together in 1 file for several purposes, most of which involve debugging code, but also it provides a simple way to know where everything gets loaded, so I have called it execs. Onwards to our taml file:

<ModuleDefinition
	ModuleId="NetCheckers"
	VersionId="1"
	Description="Network checkers"
	Group="game"
	ScriptFile="main.cs"
	CreateFunction="create"
	DestroyFunction="destroy">
	
	<DeclaredAssets
			Path="assets"
			Extension="asset.taml"
			Recurse="true"/>			
</ModuleDefinition>

Again, this is fairly simple, and you should already be familiar with this layout, we simply define our module characteristics, as well as the functions for create and destroy (which we just created) and point a path to our required assets. Now that we have our initialization in place, we can proceed, but before we try launching up our game for test, we need to do a few more things. First we need to create our execs file I mentioned earlier, and have that start executing files for usage in our game. We will locate this file within our modules scripts folder we created earlier.

// Execs.cs – Execute game scripts
exec("./checkerBoard.cs");

// Server scripts
exec("./server/initServer.cs");

// Client scripts
exec("./client/initClient.cs");

That’s it for now… All we are going to do is call other scripts to be executed (hence the name execs), and that’s all we will do with this file throughout its lifetime, is call scripts to execute. Here we have called a new script called checkerboard, this is where we will setup our checkerboard and later we will use this to house some logic for our game. Also, we are going to call up initServer, and initClient which will initialize our client and server games (just like in the chat system). Let’s start with getting our board to display, and make sure we don’t have any problems so far.

// checkerBoard.cs - Set up our checker board and provide game logic for it
function createCheckerBoard(%board)
{
  // Use a composite Sprite to set up an 8x8 grid for our board
  %board.SceneLayer = 30;
  %board.setDefaultSpriteStride( 8, 8 );
  %board.setDefaultSpriteSize( 8, 8 );
  %board.SetBatchLayout( "rect" );
  // Use 3.5 to keep the board centered, so we start & end equal from 0
  for(%y=-3.5;%y<4;%y++)
  {
    for(%x=-3.5;%x<4;%x++)
    {
      %board.addSprite( %x SPC %y );
      
      if((%x + %y) % 2 == 0)
      {
        %board.setSpriteImage("NetCheckers:checker1");
        %board.setSpriteDepth(0); // Depth 0 = White
      }
      else
      {
        %board.setSpriteImage("NetCheckers:checker2");
        %board.setSpriteDepth(1); // Depth 1 = Black
      }
    }
  }
  return %board;
}

Okay, there’s a little bit going on here, so let’s go through this. We are creating a function call to createCheckerBoard with a variable %board, this will actually happen from our server and client inits that we will create shortly. We then set our board object (%board) to be on a layer that is at the bottom, because we want to see our pieces on top of it, and then we do a little fanciness to create a composite sprite for our board. We are creating an 8x8 board and keeping each tile to be 8x8 units itself. We are creating this under the “rect” layout (even though at the end of it, it will be a square). For more information, refer to the Composite Sprite module that comes with T2D. Next comes some for loops, this might seem a little odd at first, but as noted by the comment we are using the value of 3.5 to keep our board in the center of the screen. Now, here’s the first question that usually comes to mind… Why 3.5? Well, that’s because we need 8 tiles. Still confused? Math got you boggled? 3.5 + 3.5 = 7 right? Of course if does! Then add 1 more for the 0 position, as we go just like on a number line. (3.5, 2.5, 1.5, 0.5, -0.5, -1.5, -2.5, -3.5) See 8? Ok, I hope that makes sense now. Inside of the for loops, we add a Sprite at the logical positions called out as we iterate through our variable numbers, we check where our sprite is by doing another fancy math trick, of adding in our x & y coordinates and seeing if it can be divided by 2 or not, then add in an extra element to be used later.. Depth! Since we are in the process of creating our board, and we have access to this information, it’s easier later on to check what Depth a tile is as opposed to its Image… Well, truthfully you could do that, but it’s not a good practical approach, and requires more processing than setting it up this way. Lastly we return our board back to where it was called from, which we will be doing next. In our client folder create initClient.cs:

// initClient - Initialize client
function initClient()
{
   //Create an object to act as our server check board
   %board = new CompositeSprite(ClientCheckerBoard){
     class = "CheckerBoard";};
   %newBoard = createCheckerBoard(%board);
   myScene.add(%newBoard);
}

This one is fairly simple in structure, and we should be able to make sense out of it now that we have everything else in place. We are calling out initClient function (just like in the chat system) and creating a new Composite Sprite, giving it a class of CheckerBoard and calling upon our function createCheckerBoard (which we just went through), and then adding the board to our client’s scene. This process will act upon each client, in this case 2 instances. Now in our scripts/server folder create initServer.cs:

// initServer - initialize server
function initServer()
{
  //Create an object to act as our server check board
  %board = new CompositeSprite(ServerCheckerBoard){
    class = "CheckerBoard";};

  %newBoard = createCheckerBoard(%board);
  //init the inmatch value to false;
  $inMatch = false;
}

function onServerCreated()
{
  //toggle first connection to true
  $firstConnection = true;
}

function onClientConnected(%client)
{
  // If this is the first connection, it's local, second is client
  // any beyond is sent an "in match" message
  if($firstConnection)
  {
    $serverConnection = %client;
    
    $firstConnection = false;
    
    initClient();
    initServer();
  }
  else if(!$firstConnection)
  {
    if($inMatch)
    {
      commandToClient(%client, 'inMatch');
    }
    else
    {
      $playerConnection = %client;
      
      commandToClient(%client, 'initClient');
      serverLaunchGame();
    }
  }
}

This has a bit more to it, but a lot of this is the same as what we saw in our chat system, and our initClient setup. As well it should, we want our Server to be a master of our clients, and have the control aspect required to make sure we are doing what we are supposed to. The important part to notice here is we create a Composite Sprite and create our board just like we did for the clients. When a client connects to the system, we check if it’s the first connection and if so set up that as the server and a local client to itself. The initClient & initServer functions get called and create our boards. The next connection passes through this and checks to see if the match has started (sanity check to see if the game has started) and then runs initClient on that client and launches the game. As you may have noticed, there are quite a few function calls we haven’t developed yet. So, we better get to those so we can test. Note that some of this could be tested without some of the structure in place, but in the end it pays to get the client and server set up correctly. First, we need to go back to our execs.cs file and add a few more items. Here’s what we should have now:

// Execs.cs - Executes games scripts
exec("./checkerBoard.cs");

// Server scripts
exec("./server/initServer.cs");
exec("./server/serverCheckers.cs");
exec("./server/serverCommands.cs");

// Client scripts
exec("./client/initClient.cs");
exec("./client/clientCheckers.cs");
exec("./client/clientCommands.cs");

We are breaking up our scripts here between client & server, each with its own set of commands and game play itself. We will expand upon these scripts later as well, but for now let’s start with what we have in place so far, which is really just the creation of our checker board. First up is clientCommands:

// clientCommands - commands for the client to run
function clientCmdInitClient()
{
   initClient();
}

function clientCmdInMatch()
{
  //spawn the message telling a client they are rejected, we are in a game without you
  MessageBoxOK("Server in a Match...", "The server you are trying to connect to is already in a match.", "");
}

function clientCmdStartGame()
{
  //call the start game on the client side
  clientLaunchGame();
}

Here we setup some commands for our client to handle, the server sends in the values for the client so it has record of it, and keeps the server in charge. We initialize the Client, tell someone else trying to join the match is full and launch our local game. Take note that each function here starts with clientCmd, this is very important, this is how functions are recognized between the server and the client. Next up is clientCheckers.cs:

// clientCheckers - local client checkers game
function clientLaunchGame()
{
    //call the game started function on the server
  commandToServer('gameStarted');
}

Simple enough to start off with, all we are doing is telling the Server that the game has started. Note again the naming of this as starting with commandToServer, we will see this later as serverCmdGameStarted(%client). Another thing to note is the %client is the client id sending the information; this is required for all commands to the server, so it knows who has requested what and can determine if that request is valid. Next up, is the serverCommands.cs:

// serverCommands - commands for the server to run
function serverCmdGameStarted(%client)
{
  //tell the server to begin the game
  serverBeginGame();
}

As was just mentioned, here we receive the command from the client that the game has started, and the server then calls a function to begin the game, which is found in serverCheckers.cs:

// serverCheckers - master server checker game
function serverLaunchGame()
{
  //we are now in a match
  $inMatch = true;
  
  //tell the client to start the game
  commandToClient($playerConnection, 'StartGame');
}

function serverBeginGame()
{
}

Here we have the master server setting things up for us; we state our match is in place, tell the clients to start the game itself. Note that again we call commandToClient with the client id, the command and a value to send. Since we have 2 clients in this case, we send to each one, and set the values accordingly. We have left our serverBeginGame function empty for now, we will populate this later. There’s one last thing we need to do to get our game to load. In our T2DNetwork module, we want to load our NetCheckers module. In the file main.cs, change the line that says //Simple Network to // Network Checkers and change the next line that says exec(“./scripts/simpleChat.cs”); to ModuleDatabase.loadExplicit(“NetCheckers”); You should now be able to run 2 instances of T2D, setup a server, join it, and both instances should now display a checkerboard laid out for you! Just in case something went wrong, compare against the included zip file (NetCheckers_1).
Before we go any further, let’s take a look at how things are operating so far. It is important when designing your own games, to understand the code flow, or sequence of operations. We already had setup our core network structure, and saw a simple way to chat back and forth. This all started with the creation of a server, and the connection of clients to that server. Instead of loading up a chat setting, here we are preparing a game, and this is just the start of that foundation, so it’s better to grasp it before it all crumbles below you and you become lost. Take the time to look through the flow, and understand how things are being registered by the server, and then dispatched to the clients. As we progress, we will setup some rules which will be handled by clients, dispatched to the server, and then routed back to the clients for processing. This is a typical method of control for networking. So, let’s break it down a little more since we don’t have a lot going on. Once our server is created, and we have accepted our two client connections (note, that the first client actually hosts the server) we launch our initClient & initServer scripts that setup our checkerboard CompositeSprite and display it on the screen. During this process we create a Server Checkerboard along with a Client Checkerboard for each client. We ensure the match is stable by only allowing the 2 connections for this game. That more or less summarizes what is happening so far, as we continue we will add more to this structure.

Setting up our checker pieces

We have our board in place, the next thing we need to do is to populate it with checker pieces. We also want to set each side a team value so we can easily use that for setups between the two. Let’s set up our team values first, since we have most of the scripts in place already and we just need to add a few things to them. In our initServer function add the following to the onClientConnected function: Under the if($firstConnection), after $serverConnection = %client; add this line: setTeam(%client, $red); Still in the function, after the else, under $playerConnection = %client; add this line: setTeam(%client, $blue); Then we need to create the setTeam function itself here as well at the end of the file:

function setTeam(%client, %team)
{
  //store the team for the client
  %client.team = %team;
  
  //tell the client what team they are
  commandToClient(%client, 'setTeam', %team);
}

Now we need to setup the client command being issued here in clientCommands.cs:

function clientCmdSetTeam(%team)
{
  //set the player's team
  $playerTeam = %team;
}

All we did here was take our initial connection process and assign each player a team value (which we setup in main.cs, $red =1, $blue=2), and then inform the clients to track that value as a global $playerTeam, the server knows this information based on the team values for each client. Now, we want to setup our checker pieces. This will require a bit of work, so let’s set up some initialization for it. Starting in serverCheckers, add the following to the serverLaunchGame function after the line $inMatch=true;

  //init the server's boards
  ServerCheckerBoard.init();
  ClientCheckerBoard.init();

We will also need a similar call in clientCheckers, in the function clientLaunchGame, put this above the commadToServer(‘gameStarted’); call:

  //init the client checkerboard
  ClientCheckerBoard.init();

We will initialize placement for the server and the clients, but before we get to setting up those functions, we need to expand a little bit on our checkerboard.cs, add the following function:

function CheckerBoard::isBlack(%this, %pos)
{
   // Check the Depth of the sprite at selected position
   %this.selectSpriteId(%pos);
   if(%this.getSpriteDepth() == 1)
     return true;
   else
     return false;
}

I mentioned about using depth when we first set up our checkerboard sprite, here is where we get to use that information, we have set every other tiles depth to 1, so that way we can determine if we can put a checker piece there, and also later determine if we can make a valid move to a spot on the board. As you should know, we can only move checker pieces on the same color tiles. Since we’re here, let’s add one more function for setting our pieces on the board:

function CheckerBoard::setPiece(%this, %pos, %type)
{
   // Set the board position
   %this.board[%pos] = %type;
}

What we are doing with this is setting the tile on the board position to be the type passed in. You will see more of how this is used during our initialization; let’s start with the server, in serverCheckers:

function ServerCheckerBoard::init(%this)
{
  //init the team counts
  $redCount = 0;
  $blueCount = 0;
  
  //loop through and create the server board
  for(%i=1;%i<65;%i++)
  {
     //check if this is a black checkerboard spot
     if(%this.isBlack(%i))
     {
        //check if this is a checker's starting spot
        if(%i < 24)
        {
           //set the blue team's piece and add to the count
           %this.setPiece(%i, $blue);
           $blueCount++;
        }
        else if(%i > 41)
        {
           //set the red team's piece and add to the count
           %this.setPiece(%i, $red);
           $redCount++;
        }
        else
        {
           //set the blank spot
           %this.setPiece(%i, 0);
        }
     }
     else
     {
        //set the blank spot
        %this.setPiece(%i, 0);
     }
  }
}

First we start a loop process for our entire checker board, we check if a spot is black (by checking depth), Note that we could have avoided this here by simply changing our for loop to read as for(%i=1;%i<5;%i+=2), but we do have other reasons for having the isBlack check. Then we set up the checker positions on each side of the board, notice we’re not actually adding sprites here, just data. The server will track this data and only on the client side will we see our actual checker sprites. So, on to the clientCheckers:

function ClientCheckerBoard::init(%this)
{
  //create container object's for the team
  %this.redPieces = new SimSet();
  %this.bluePieces = new SimSet();
  //loop through and create the checker board and the initial checkers
  for(%i=1;%i<65;%i++)
  {
     //check if the checker board spot is black
     if(%this.isBlack(%i))
     {
        //check to see if we're populating the checker's starting area
        if(%i < 24)
        {
          //create a new blue checker piece
          %piece = %this.initPiece($blue);

          //set the piece to this location
          %this.setClientPiece(%i, $blue, %piece);
        }
        else if(%i > 41)
        {
          //create a new red piece
          %piece = %this.initPiece($red);
          
          //set this piece to this location
          %this.setClientPiece(%i, $red, %piece);
        }
        else
        {
          //set the empty space
          %this.setClientPiece(%i, 0);
        }
     }
     else
     {
        %this.setClientPiece(%i, 0);
     }
  }
}

This is very similar to the server init, except we’ve added a couple things, first we want to add a few container objects for our checker pieces, and this will help us out later on. Also, we’ve called upon a couple new functions; we need to init our checker pieces and set our client pieces. Let’s do the initialization of the checker pieces, add this new function to clientCheckers:

function ClientCheckerBoard::initPiece(%this, %type)
{
  //check which team the piece is
  if(%type == $red)
  {
    //get the count of current pieces of that color
    %num = %this.redPieces.getCount();
    
    //create a new piece as a clone of our stored piece
    %class = "RedChecker";
    %image = "NetCheckers:sun_neutral";
    %piece = createChecker(%type, %class, %image);
    %piece.setName("redPiece" @ %num);
    
    //add the piece to its color's container object
    %this.redPieces.add(%piece);
  }
  else if(%type == $blue)
  {
    //get the count of current pieces of that color
    %num = %this.bluePieces.getCount();
    
    //create a new piece as a clone of our stored piece
    %class = "BlueChecker";
    %image = "NetCheckers:moon_neutral";
    %piece = createChecker(%type, %class, %image);
    %piece.setName("bluePiece" @ %num);
    
    //add the piece to its color's container object
    %this.bluePieces.add(%piece);
  }
  
  return %piece;
}

Here we start our setup for our checker pieces, we will track each checker piece using our container objects, give our checkers a class, also there’s one more function addition for creating our checkers.

function createChecker(%type, %class, %image)
{
   %checker = new Sprite(){
      class = %class;};
   %checker.Size = "6 6";
   %checker.Image = %image;
   %checker.SceneLayer = 20;
   myScene.add(%checker);
   return %checker;
}

Here is where we add the actual Sprite, and set its properties. We’ve already set up most of the values before hand, we just need to set them to the sprite and define the size and layer as well. We have one more function to set the client pieces:

function ClientCheckerBoard::setClientPiece(%this, %pos, %type, %piece)
{
  //if the piece isn't empty we do some extra settings
  if(%type != 0)
  {
    //grab the position of the piece
    %this.selectSpriteId(%pos);
    %sPos = %this.getSpriteLocalPosition();
    
    //set the position of the piece
    %piece.setPosition(%sPos);
    //store this Sprite to the board index
    %this.checkerPiece[%pos] = %piece;
  }
  //set the piece's type
  %this.setPiece(%pos, %type);
}

Here we check the position of the checkerboards tile, set our checker piece to that position and set the data into our checkerboard. Test it! We should now be able to see our checker pieces populated on both sides of the board, the red should be on the top and the blue should be on bottom. This should all happen when both players are connected to the server. There was quite a bit of work involved for just getting our checkers on the screen, so let’s step back through what all we did so we understand the process. In order to create our checker pieces we had to use our checkerboard, and need a way to keep track of those pieces as well. We call an initialization to our checkerboards, the server sets up data information for each position on the checkerboards, and keep track of how many pieces are created. The clients have to do a little bit more, as they each need a physical representation (a sprite) of the checker pieces as well. We initialize the pieces themselves and track them through container objects; we also set them onto the checkerboard by checking against the checkerboards tile positions. We utilize much of the same setup as the server, creating a good data set that each system can use. Take the time to understand what’s happening again, and as usual I’ve provided a zip for the progress so far (NetCheckers_2). Next, we need to start our actual game, and start establishing some rules.

Setting up turns

Before we can get to the process of moving our checkers around we need to establish a turn system. It will become important later on that we have this structure in place as we begin to communicate with the server about processing information. In serverCheckers add the following:

function serverBeginGame()
{
  //set turn to server's turn
  setTurn($serverConnection);
}

function setTurn(%player)
{
  //get the other player
  %otherPlayer = getOtherPlayer(%player);
  
  //call the client's set turn function properly
  commandToClient(%otherPlayer, 'setPlayerTurn', 0);
  commandToClient(%player, 'setPlayerTurn', 1);
  
  //set the server's turn properly
  $playersTurn = %player;
}

function getOtherPlayer(%player)
{
  //check to see what play was passed and return the other player
  if(%player == $playerConnection)
  {
    %otherPlayer = $serverConnection;
  }
  else
  {
    %otherPlayer = $playerConnection;
  }
  
  return %otherPlayer;
}

Note, we had setup the first function to be empty before, now we want to call the setTurn function to have the server start the game. We then dispatch calls to our clients about the turn order, and define a global for the turn value, for getting the other player we use the pre-defined values from our connections. Now, let’s set up our client call in clientCommands:

function clientCmdSetPlayerTurn(%val)
{
  //set the player's turn to the value passed
  $myTurn = %val;
}
function clientCmdNotYourTurn()
{
  //tell the client that you -cannot- play when its NOT your turn
  MessageBoxOK("Not your turn...", "It is currently not your turn.", "");
}

Here again, we just use a global for myTurn so that way we have a means of checking our information. Also, we’ll add the message pop up box for when a client has acted out of turn. We haven’t had anything call on it yet, but we will soon. Now, we can proceed to checker selection.

The selection process – attempt and grab

We’ve got our pieces populated, but we can’t do anything with them yet. We need a system to be able to select our checkers, so we’ll need to enable a touch system. First, let’s add this to execs.cs after exec("./checkerBoard.cs"); add this line: exec("./touch.cs"); This is the last item we need for our executable files. Let’s now create touch.cs:

function mySceneWindow::onTouchUp(%this, %touchID, %worldPosition)
{
   // If the client checkerboard is created, pick the spot
   if(isObject(ClientCheckerBoard))
   {
      // Pick sprites
      %sprites = ClientCheckerBoard.pickPoint(%worldPosition);
      
      // Get count (more than 1 means selection is close to multiple)
      %spriteCount = %sprites.count;
      
      // Make sure a spot on the board is picked
      if(%spriteCount == 0)
        return;
        
      // Only 1 sprite selected
      if(%spriteCount == 1)
      {
         %pos = getWord(%sprites, 0);
         attemptSelectChecker(%pos);
      }
      else
      {
         echo("Invalid Selection - Touch");
      }
   }
}

function mySceneWindow::onTouchMoved(%this, %touchID, %worldPosition)
{
   if($touchObj == 0)
     return;
   // Set the position of the object to follow the touch control
   $touchObj.setPosition(%worldPosition);
}

T2D utilizes the scene window to register touch events in several ways, I decided that the best method for this game would be to have the player click and once the button is released, pick the spot, grab a checker if it’s there (or choose where to move once selected), and then assign that checker to follow the touch movement. An alternative to this would be to use onTouchDown with onTouchDragged, but for later usage with movement and jumping, it makes more sense this way. So, what are we doing here? We check once the touch input is released to see if we are first have a position over the checkerboard itself, see if we’ve chosen a corner spot, otherwise we can attempt to select a checker. Then as we move our touch device we set the position of a touch object to follow the device. Later on we will set our checker to be this touch object. First though, let’s attempt our selection in clientCheckers:

function attemptSelectChecker(%pos)
{
   //first we check if it is our turn
  if(!$myTurn)
  {
    echo("Not your turn");
  }
  else if($clientSelected)
  {
  }
  else
  {
    //grab the selected piece
    %selection = ClientCheckerBoard.getPiece(%pos);
    
    //if it's of the right team then attempt to check it
    if(%selection == $playerTeam)
    {
      //ask the server if we can select it
      commandToServer('attemptSelectChecker', %pos);
    }
    else
    {
      //let the console know this is an invalid selection
      echo("Invalid Selection - Attempt Selection");
    }
  }
}

Let’s add the function call into our checkerboard.cs for get Piece:

function CheckerBoard::getPiece(%this, %pos)
{
   // Return the piece at this position
   return %this.board[%pos];
}

Here we attempt to select a checker, first we’ll check if it’s our turn, otherwise we just echo the console, we’ll have the server inform the client later. Next, we check if we have one selected, and at this point, we won’t do anything if we do. Otherwise, we get the value of the piece (which is retrieving the information we had stored in setting our pieces). Then we will ask the server if we can select that checker, otherwise we don’t do anything but let the console know of an invalid selection, this could be changed to a message pop up, but since those tend to clutter up things, an echo works here. Now, for our server mechanics of selection, we go to serverCommands:

function serverCmdAttemptSelectChecker(%client, %pos)
{
  //pass the attempted selection on to the server function
  serverAttemptSelectChecker(%client, %pos);
}

Our matching serverCmd function simply passes the data into a real function in serverCheckers:

function serverAttemptSelectChecker(%client, %pos)
{
  //check if it's this client's turn
  if($playersTurn != %client)
  {
    //it is not this client's turn, nice try
    commandToClient(%client, 'notYourTurn');
  }
  else if($playersTurn == %client)
  {
    //grab the selection
    %selection = ServerCheckerBoard.getPiece(%pos);
    
    //check if this is the client's piece, or a king
    if(%selection == %client.team)
    {
      //proper move, process sit
      serverSelectChecker(%client, %pos);
    }
    else
    {
      //the client cannot move another team's piece
      commandToClient(%client, 'selectCheckerResponse', %pos, 0);
    }
  }
}

Here we check if the client acted in turn, and if not send a message pop up to let them know they acted out of turn. Then check the server checkerboard for the position the client has requested, determine if it’s their checker (as it should be), then we will select the checker on the server, if for some reason the information is bad, we tell the client they can’t move a piece that isn’t theirs. We still have to write both of those functions, but this is the first instance where we check for data that should never happen. If for any reason the function reaches this else statement we would assume some sort of corruption has occurred. Because this is just a simple tutorial, it’s not critical, but good to always ensure that the server is in charge. First, let’s have the server select the checker:

function serverSelectChecker(%client, %pos)
{
  //set the selected piece
  $playersTurn.selectedPos = %pos;
  $playersTurn.hasSelected = true;
  
  //respond to the client
  commandToClient(%client, 'selectCheckerResponse', %pos, 1);
}

Here we set a couple globals for the players selected position and that they have selected a checker, we then proceed to tell the client to select the checker in clientCommands:

function clientCmdSelectCheckerResponse(%pos, %answer)
{
  //select response, if no we tell client piece can't be selected, if yes select piece
  if(!%answer)
  {
    MessageBoxOK("Invalid Selection...", "You cannot select this checker piece!", "");
  }
  else
  {
    clientSelectChecker(%pos);
  }
}

Here we process our selected checker response from the server, if we’re not allowed to move the piece we get a message pop up, if we can, we then proceed to clientCheckers:

function clientSelectChecker(%pos)
{
  //set the selection with what's passed in
  $clientSelectedPos = %pos;
  $clientSelected = true;
  
  //have the checker follow the touch device
  ClientCheckerBoard.checkerFollowTouch(%pos);
}

Here we set up positional value, select our checker, and call another function to follow our touch device:

function ClientCheckerBoard::checkerFollowTouch(%this, %pos)
{
  //grab the checker
  %checker = %this.checkerPiece[%pos];
  
  //store the selected checker
  $clientSelectedPiece = %checker;
  
  //set the checker to the $touchObj
  $touchObj = %checker;
}

Here we call on our board to grab the checker piece, and assign it to our touchObject so it will follow our touch device. You should now be able to test it again, and the server should be able to select a red piece and grab onto it, and it will move around with the touch device! The client side won’t be able to do this due to setting up a turn structure. Included is the zip file for this point (NetCheckers_3), once again review through the scripts and understand how things are working, it’s starting to come to shape, and there’s a lot of back and forth going on. Next up, we can start getting to the heart of our game play and move pieces and swap turns back and forth.

You’ve got to move it, move it!

Ok, I couldn’t resist the titling of this section. We’ve been able to grab a piece, but that doesn’t do much good unless we can put it down and move it where we want. So, we need to set up a rule to determine where we can move and interpret what the user tries to do. We’ll start off simply, and then work into the game mechanics more so as we go along. First, let’s add a simple function to our checkerboard:

function CheckerBoard::removePiece(%this, %pos)
{
   // Set the board position to 0
   %this.board[%pos] = 0;
}

Just like our set piece, and get piece, we will want to clear out the data when we move, or remove a piece from the board, and this function will handle that. Next, we will add a check for a legal move; this will be a check when we start to attempt our movement of pieces:

function CheckerBoard::isLegalMove(%this, %from, %to)
{
   // Check if moved to same location
   if(%from == %to)
     return "same";
   
   // What color is it?
   %piece = %this.getPiece(%from);
   // Make sure it's a black square
   if(!%this.isBlack(%to))
     return false;
   
   // Make sure another piece isn't there
   %movePos = %this.getPiece(%to);
   // If a piece is there, we can't move there
   if(%movePos != 0)
     return false;
   // Check we are moving in the correct direction
   switch(%piece)
   {
      case 0: // Sanity
        return false;
      case 1: // Red team going up
        if(%from < %to)
          return false;
      case 2: // Blue team going down
        if(%from > %to)
          return false;
   }
   
   // Make sure we traveled a legal distance
   %dist = mAbs(%from - %to);
   // If we moved 1 position (which is 8 +/- 1)
   if(%dist == 7 || %dist == 9)
     return true;
   // Jump check (double the last check)
   else if(%dist == 14 || %dist == 18)
   {
      // First get the jump spot, which is the distance divided by 2
      if(%from < %to)
        %jumpSpot = %from + (%dist/2);
      else 
        %jumpSpot = %to + (%dist/2);
      %normal = getNormalPiece(%piece); 
      %vector = true SPC %jumpSpot;  
      // Check if jump spot is our own team or nothing, otherwise jump
      if((%jumpSpot == %normal) || (%jumpSpot == 0))
        return false;
      else
        return %vector;
   }
   // Moved more than allowed
   return false;
}

function getNormalPiece(%team)
{
   // Check what team is being passed and return proper normal piece
   if(%team == $red)
     %piece = $red;
   else if(%team == $blue)
     %piece = $blue;
   
   return %piece;
}

For the legal move, we will pass a to and from position, so first we check if it is the same position, then we check if we picked a black tile (darker tile, that we can’t move to), next we see if there is a piece in the spot we are trying to move to, finally we check how far we are moving, which requires a little more checking. First we check if we are moving in the correct direction, then we determine if we moved one position on the board, or if we tried to jump a piece. The check on getting the normal piece is another sanity check to first ensure it’s not an empty spot, or a spot occupied by the same color piece. For now, all we want to do is move to the other side of the board, later on, we will have to include kings, and also figure in multiple jumps. This requires some adjustments later on, but first thing first is to establish the core, and then expand on. Now, let’s add our client portion to attempt a move, first in clientCheckers, we have an empty call else if($clientSelected), within the curly brackets add these lines:

//this means we already have a checker selected, call attempt move
    attemptMovePiece(%pos);

At this point in the game we have selected a checker, and it should be following our touch device, so we should try to move it:

function attemptMovePiece(%pos)
{
   //check if the move is legal
  %legal = ClientCheckerBoard.isLegalMove($clientSelectedPos, %pos);
  //check if moving to the same position, if so reset the piece
  if(%legal $= "same")
  {
    //dismount the checker from the mouse object
    $touchObj = 0;
    //reset the checker's position to the original position
    ClientCheckerBoard.selectSpriteId(%pos);
    %sPos = ClientCheckerBoard.getSpriteLocalPosition();
    $clientSelectedPiece.setPosition(%sPos);
    
    //set selected to false
    $clientSelected = false;
  }
  else if(%legal)
  {
    //request a move from the server
    commandToServer('attemptMovePiece', %pos);
  }
}

The first thing we do when trying to move a piece is to determine if we really can move that piece where we have chosen to, so first, we check if we have chosen the same spot, and if so, we return our piece back where we started from. Otherwise, we ask the server if we can move our piece, in serverCommands:

function serverCmdAttemptMovePiece(%client, %pos)
{
  //pass the attempted move values on to the server function
  serverAttemptMovePiece(%client, %pos);
}

Pass along to the server command in serverCheckers:

function serverAttemptMovePiece(%client, %pos)
{
  //grab where the move is legal
  %legal = ServerCheckerBoard.isLegalMove($playersTurn.selectedPos, %pos);
  %jump = getWord(%legal, 1);
  //init jumped to false
  %jumped = false;
  
  //if we receive a value for %jump than that's the position of a jumped piece
  if(%legal && %jump > 0)
  {
    //grab the jumped piece
    %jumpedPiece = ServerCheckerBoard.getPiece(%jump);
    
    //grab the other player
    %otherPlayer = getOtherPlayer(%client);
    
    //remove the piece
    ServerCheckerBoard.removePiece(%jump);
    
    //tell the client's to remove the piece
    commandToClient(%client, 'destroyPiece', %jump);
    commandToClient(%otherPlayer, 'destroyPiece', %jump);
    
    //decrement the proper count
    if(%jumpedPiece == $red)
    {
      $redCount--;
    }
    else
    {
      $blueCount--;
    }
  }
  
  //check if the move is legal
  if(%legal)
  {
    //grab the type of piece that is selected
    %type = ServerCheckerBoard.getPiece($playersTurn.selectedPos);
    
    //grab the other player
    %otherPlayer = getOtherPlayer(%client);
    
    //tell the client to move the piece since this was a legal move
    commandToClient(%otherPlayer, 'moveCheckerPiece', %type, $playersTurn.selectedPos, %pos);
    commandToClient(%client, 'movePieceResponse', %pos, 1);
    
    //set the proper piece settings
    ServerCheckerBoard.setPiece($playersTurn.selectedPos, 0);
    ServerCheckerBoard.setPiece(%pos, %type);

    //reset the player's selection
    $playersTurn.selectedPos = "";
    $playersTurn.hasSelected = false;
    
    swapTurns();
  }
  else
  {
     // Client has bad information
     error("Warning! Corrupt client information passed to server.");
  }
}

Quite a bit to deal with here, first we do a check on the move being legal on the server checkerboard (remember, there are 2 client boards and 1 server board) Then we see if we got a jump value, and if so we will call to destroy the piece we jumped to both clients, so they can remove it from their boards. Also we drop our global count of pieces. If it’s just a move, and legal, we then tell the clients to move the checker accordingly, and lastly we swap turns, allowing the other player to go. Also, an extra else statement is thrown in just in case bad information exists, we send an error to the console stating bad information was passed from the client. Again, this is for sanity’s sake, and more actions would likely need to be taken. More than likely this would never happen, as it would require our game state to be changed that is not in the scope of the programming of it. Anyways, we’ve called upon a few function we haven’t written yet, since we’re still in serverCheckers, let’s add the swap turns function:

function swapTurns()
{
  //get the other player
  %player = getOtherPlayer($playersTurn);
  
  //set the turn to the other player
  setTurn(%player);
}

Here we just use our previously created call to grab the other player, and set the turn to them. Now we need to set up our client calls in clientCommands:

function clientCmdMovePieceResponse(%pos, %answer)
{
  //here is the server's response to trying to move a piece, if no tell client, if yes move it
  if(!%answer)
  {
    MessageBoxOK("You cannot move...", "You cannot move to the selected spot!", "");
  }
  else
  {
    clientMoveSelectedPiece(%pos);
  }
}

function clientCmdMoveCheckerPiece(%type, %from, %pos)
{
  //this moves the piece directly to a new location
  clientMovePiece(%type, %from, %pos);
}

function clientCmdDestroyPiece(%pos)
{
  //this destroy's a checker piece
  clientDestroyPiece(%pos);
}

Again, we’ll just pass along these calls over to clientCheckers:

function clientMoveSelectedPiece(%pos)
{
  //dismount the selection from the touch system and set its position to the new spot
  $touchObj = 0;
  ClientCheckerBoard.selectSpriteId(%pos);
  %sPos = ClientCheckerBoard.getSpriteLocalPosition();
  $clientSelectedPiece.setPosition(%sPos);
  
  //grab the position information
  %piece = ClientCheckerBoard.checkerPiece[$clientSelectedPos];
  ClientCheckerBoard.checkerPiece[$clientSelectedPos] = "";
  ClientCheckerBoard.checkerPiece[%pos] = %piece;

  //grab the object's type
  %type = ClientCheckerBoard.getPiece($clientSelectedPos);
  
  //set the pieces position
  ClientCheckerBoard.setPiece($clientSelectedPos, 0);
  ClientCheckerBoard.setPiece(%pos, %type);
  
  //reset the client's selection
  $clientSelectedPos = "";
  $clientSelected = false;
}

First up is to move the selected piece; we dismount it from the touch object, select the location from the checkerboard and set our sprite to move there. Next, we grab the logistical position of where we selected, clear the old data and set the piece position, we collect the object type by accessing our get piece function, and then set the data accordingly for the position we want. Finally, we clear out our global data for client selection. The next function also handles movement, but just for when we need to move a piece:

function clientMovePiece(%type, %from, %pos)
{
  //get the piece's image
  %piece = ClientCheckerBoard.checkerPiece[%from];
  
  //set the position of the piece
  ClientCheckerBoard.selectSpriteId(%pos);
  %sPos = ClientCheckerBoard.getSpriteLocalPosition();
  %piece.setPosition(%sPos);
  
  //reset the image in the previous slot and set the new one
  ClientCheckerBoard.checkerPiece[$clientSelectedPos] = "";
  ClientCheckerBoard.checkerPiece[%pos] = %piece;
  
  //reset the piece in the previous spot and set the new one
  ClientCheckerBoard.setPiece(%from, 0);
  ClientCheckerBoard.setPiece(%pos, %type);
}

Here, we grab the image at a checkerboard position, set the position of the piece, reset the old image and set the new, and then again for the piece. Finally we need to destroy a piece when we jump over one:

function clientDestroyPiece(%pos)
{
  //grab the team and image data of the piece to be destroyed
  %team = ClientCheckerBoard.getPiece(%pos);
  %image = ClientCheckerBoard.checkerPiece[%pos];
  
  //check which team the piece is
  if(%team == $red)
  {
    //remove the piece from the team's container
    ClientCheckerBoard.redPieces.remove(%image);
  }
  else if(%team == $blue)
  {
    //remove the piece from the team's container
    ClientCheckerBoard.bluePieces.remove(%image);
  }
  
  //remove the piece image from the image data
  ClientCheckerBoard.checkerPiece[%pos] = "";
  
  //remove the piece from the board data
  ClientCheckerBoard.removePiece(%pos);
  
  %image.safeDelete();
}

Here, we grab the piece using the team & image data, check which team the piece is and remove it from our sim set, then clear the data and delete the object. Test it again! You should now be able to move pieces appropriately, swap turns back and forth, jump over pieces and have them removed off the board. There was quite a bit of code added for this round, make sure to review it and understand what’s happening. Of course, double check work against the included packaged zip files (NetCheckers_4). This ends where the original tutorial left off. However, because I wanted to take it further, and I understand the logistics, I went ahead and added in some extra functionality to make the game more whole.

Kings

We’ve almost made our game whole, but of course as with real checkers, we need to set up kings, and be able to use them appropriately. The first thing we need is a check system in our checker board, so add this relatively simple function to checkerboard.cs:

function getKing(%team)
{
   // Check what team is being passed and return proper king
   if(%team == $red)
   {
      %king = $redKing;
   }
   else if(%team == $blue)
   {
      %king = $blueKing;
   }
   return %king;
}

Here we’re stepping way back to using some global variables we set up when we first started, and simply doing a check on which team is which and returning a king value back. Also, since we need to modify our isLegalMove function as well. In out switch(%piece) statement, we set up a case for red and blue, and since kings can go either way, we won’t add a case for them, but we’ll throw a comment in here after the last case statement: // If it's a king, it can go in either direction, just for documentation purposes. There’s a bit of code we need to add though in our jump check though, right above the line %normal = getNormalPiece(%piece); add this line: %king = getKing(%piece), we call upon our get king function to grab an appropriate king. Now we need to change our jump spot check, so modify the 2 lines below %vector = true SPC %jumpSpot; to read as:

// Check if jump spot is our own team/king or nothing, otherwise jump
      if((%jumpSpot == %normal) || (%jumpSpot == %king) || (%jumpSpot == 0))

Here we just to double check we don’t try to jump our own king, since it can now be a piece type on the board. The rest of the function can remain as is. One last thing in checkerboard, is the getNormalPiece function, let’s add our king values to that as well, replace the function to read as follows:

function getNormalPiece(%team)
{
   // Check what team is being passed and return proper normal piece
   if(%team == $red || %team == $redKing)
     %piece = $red;
   else if(%team == $blue || %team == $blueKing)
     %piece = $blue;
   
   return %piece;
}

Again, we’ve just added a case to include our king values, and return appropriately. Next, we need to set our king state when we reach the end of the board, so let’s modify serverCheckers.cs, inside the function serverAttempMovePiece, first let’s add a line at the the top of our if(%legal) statement: %setKing = false;,and then add the following lines after commandToClient(%client, 'movePieceResponse', %pos, 1);:

//king check, if red or blue normal piece reaches other side, set to king
    if(%pos < 8 && %type == $red)
    {
      %type = $redKing;
      %setKing = true;
    }
    else if(%pos > 57 && %type == $blue)
    {
      %type = $blueKing;
      %setKing = true;
    }
    
    if(%setKing)
    {
       commandToClient(%otherPlayer, 'setKing', %type, %pos);
       commandToClient(%client, 'setKing', %type, %pos);
    }

Here we check the last few spots on either side of the checkerboard, and if our piece has made it there, we tell the clients to set our king there. So, let’s create that call in clientCommands.cs:

function clientCmdSetKing(%type, %pos)
{
   setKing(%type, %pos);
}

Again, we’ll pass this on to clientCheckers.cs:

function setKing(%type, %pos)
{
   %piece = ClientCheckerBoard.checkerPiece[%pos];
   if(%type == $redKing)
     %piece.Image = "NetCheckers:sun_king";
   else
     %piece.Image = "NetCheckers:moon_king";
     
   ClientCheckerBoard.setPiece(%pos, %type);
}

Pretty simple really, all we need to do is pass in the position, change our image, and set our piece. We also need to add a small portion to our attemptSelectChecker function here as well. After the line %selection = ClientCheckerBoard.getPiece(%pos); add the following:

//get the possible king piece for that team
    %king = getKing($playerTeam);

Also, just below modify our if statement to read as follows: if(%selection == $playerTeam || %selection == %king) This handles all that we needed to get our kings acting appropriately. If you like, you can test again now, I won’t include a sample against this minor update, but we have a little bit more to add to this game to make it whole. We need to handle multiple jumps, as well as provide a means to call an end to the game.

Jump, jump, jump!

What kind of checkers game doesn’t let you jump over and over again? Not a great one! Well, let’s make it great! Before we tread down this path, we left out a way to end our game. Let’s quickly add that in first before diving into the jump system. In serverCheckers.cs, add these lines in serverAttemptMovePiece at the end of our if(%legal && %jump > 0) :

//check if this means the game is over
    serverEndGameCheck();

Then add the function for the check:

function serverEndGameCheck()
{
   if($redCount == 0)
   {
     commandToClient($serverConnection, 'youWin');
     commandToClient($playerConnection, 'youLose');
   }
   else if($blueCount == 0)
   {
     commandToClient($serverConnection, 'youLose');
     commandToClient($playerConnection, 'youWin');
   }
}

Here we check after we execute a legal jump, if there are no more red or blue pieces, and if so, tell the corresponding players they won or lost. We’ll need those functions in our clientCommands.cs:

function clientCmdYouWin()
{
   MessageBoxOK("You Won!!", "Congratulations!", "");
}

function clientCmdYouLose()
{
   MessageBoxOK("You Lost...", "Better luck next time.", "");
}

Very simple here, we just send a message box to the client with a simple message. You could do more with it if you want, but for practical purposes, it’s all that we need. Now for setting up our jump system, we’ll have to make several changes through several scripts, as well as create some new ones in order to include this feature. A design note that had to be considered for this system was how best to implement how a player could legitimately jump. A couple ideas came to mind, but in the end I decided that it should continue after a legal jump move has occurred, and then selecting the next jump position. Mount the checker after a valid jump, then doing the processes over again, except checking against only a valid multi-jump. A couple other notions sprang to my mind, but they seemed too confusing and most of which would lead to an extra interface that I did not find favorable. My idea works, it could certainly be altered, but that is up to discretion. So, to start with, let’s update our touch.cs onTouchUp function:

function mySceneWindow::onTouchUp(%this, %touchID, %worldPosition)
{
   // If the client checkerboard is created, pick the spot
   if(isObject(ClientCheckerBoard))
   {
      // Pick sprites
      %sprites = ClientCheckerBoard.pickPoint(%worldPosition);
      
      // Get count (more than 1 means selection is close to multiple)
      %spriteCount = %sprites.count;
      
      // Make sure a spot on the board is picked
      if(%spriteCount == 0)
        return;
        
      // Only 1 sprite selected
      if(%spriteCount == 1)
      {
         %pos = getWord(%sprites, 0);
         
         if($clientJumping)
           attemptJumpPiece(%pos);
         else
           attemptSelectChecker(%pos);
      }
      else
        echo("Invalid Selection - Touch");
   }
}

Here we’ve added a check for a global clientJumping, which we will set once we execute a legal jump. We will call anew attemptJumpPiece function, otherwise in a normal situation (no jumping) we proceed as before. Before we set up our new function, we need to set our global correctly so we know that we are jumping. In serverCheckers, in our function serverAttempt move piece after the line serverEndGameCheck(); add the following:

//toggle jumped to true;
    %jumped = true;

Next, at the end of the function is the line: swapTurns();, replace that line with the following:

//if this wasn't a jump then we just swap turns
    if(%jumped)
      commandToClient(%client, 'setClientJumping', %pos);
    else
      swapTurns()

We check if we jumped, otherwise swap turns as normal, if we did jump then we tell the client to set itself jumping in clientCommands.cs:

function clientCmdSetClientJumping(%pos)
{
   $clientJumping = true;
   $clientJumpingPos = %pos;
   
   commandToServer('attemptSelectChecker', %pos);
}

We set our global value for clientJumping that is used in our touch system, and set the jumpingPos to the passed in position, then tell the server to attempt to select the checker which will set the server up properly and mount our checker to the jumped piece. Now in clientChecker.cs we can add in our attemptJumpPiece function:

function attemptJumpPiece(%pos)
{
   %legal = ClientCheckerBoard.isLegalMove($clientJumpingPos, %pos);
   %jump = getWord(%legal, 1);
   if(%legal && %jump > 0)
   {
      $clientSelectedPos = $clientJumpingPos;
      attemptMovePiece(%pos);
   }
   else
   {
      //simple way to dismount the checker, get same legal move from attemptMovePiece
      %pos = $clientSelectedPos;
      attemptMovePiece(%pos);
      swapTurns();
   }
   
}

Here we check if we can legally jump only, set up the global values properly, then attempt to move, if it’s not legal we dismount the checker and swap turns. This should allow for the proper game rules of checkers to exist now, and we should have a relatively complete game. It’s not overly fancy, but it does cover a lot of ground and is a very good groundwork for what it takes to establish a network system using turn based mechanics. Review the code over carefully, the jumping mechanics could possibly change, but it seemed to work for me. The final code base is zipped up (NetCheckers_Final). Also it will be in the code base on my Git Hub page as well. I hope you were able to follow through this without problems. The conversion of this tutorial wasn’t too bad all in all, but there were quite a few changes to consider with porting to MIT. There are more things that I would like to cover with Networking, and I’ve got a few game ideas in mind. Since this covers the creation of a simple network, and a decent game as well, I will leave it as is for now.

Appendix A. TGB Gui conversion to T2D MIT

I mentioned this back in chapter 2, that I had devised a means to convert old TGB gui’s to the new style of T2D MIT. I thought it would be beneficial to share that information, since I found a relatively easy way to do that formatting. It requires a bit of hand editing, some global find & replaces, and then taking out any script associations. Since, at the time of writing this, the GUI Editor does not exist within T2D MIT, my method seemed the most sensible. Here’s an example:

// TGB Style Gui Elements
new GuiWindowCtrl(chatGui) {
profile = "GuiWindowProfile";
};

Would translate to:

<GuiWindowCtrl
   Name=”chatGui”
   Profile=”GuiWindowProfile”/>
  • So, to start with anytime you see a new, you replace with <, and since the Names of gui’s were part of creation, we have to create that line.
  • Followed by replacing the = by using a space before and after to just the equal sign (not really necessary, but it’s more consistent with formatting)
  • Then replacing “; with to get rid of all the semi-colons (and to keep the ones that come up with Command calls, that are still needed).
  • Ending with closing off our elements, here is where you need to be a little more careful. It’s comes down to inclusion within elements and what is a container of what.

In TGB, you didn’t have to do much special other than to make sure to include }; to close off the element, in taml format, we use a /> to close off an individual element, a > to encapsulate an element and continue to a new one. And, in cases where one was left open you have to declare the housing element (Ex: </GuiWindowCtrl>). All of this information is just for formatting, but it’s good to know how to convert things, as a lot of people have old projects and it’s beneficial to know the changes that required updates. Another thing to keep in mind through this process is the sizing of elements, as I did some conversions myself I noticed things didn’t exactly match what they were in TGB. It could take some extra tweaking in order to achieve the results intended, but this is a good way to start if you have an old project and want to start converting it. Of course, eventually (hopefully) the day will come when we have an editor at our disposal to use, and the process will be easier.

Clone this wiki locally