TGB/Tutorials/TicTacToe

From TDN

TicTacToe Tutorial


This is a very simple tutorial that helps you understand the basics of TGB. We will make very simple but complete gameplay of Tic-tac-toe game.

1) First of all we need to create new empty project. I hope you knew how to do that.

2) Then we need to add some images that we will use.

Image:Tile.png

For this tutorial we need one image for background and one image for tiles that should have 3 frames. After you created these images we need to add them to TGB.

Image:Ticktaktoe1.jpg

Switch to the “Create” tab in right panel in TGB and click on “Create new ImageMap” button. Choose background and tile images to add them. As you can see, tile image added as 1-frame image. To fix this double-click on image and change "Image mode", "Cell count X" and "Cell count Y" in opened dialog like this:

Image:Tictactoe2.jpg

3) Now drag-and-drop background image to scene and set properties like this:

Image:Tictactoe4.jpg

4) Now drag-and-drop 2 tile images to scene. It will be indicators that shows current turn. Rename them to “guiXPlayerIndicator” and “guiOPlayerIndicator” and set Frame property of them to “1” and “2” accordingly.

Image:Tictactoe5.jpg

Before we start scripting we need one more thing to do.

5) Click on free space on scene to select scenegraph object and change it Class property to “TicTacToeScenegraph”.

Image:Tictactoe6.jpg

Now we are ready for scripting.

6) Create “./game/gameScripts/sceneGraph.cs”. In this file we will write all functions that we need for this tutorial. To make TGB run this script we need to add exec(“./sceneGraph.cs”); call to startGame() function in “./game/gameScripts/game.cs” file like this:

function startGame(%level)
{
   exec("./sceneGraph.cs"); // add this line
	
   Canvas.setContent(mainScreenGui);
   Canvas.setCursor(DefaultCursor);
   
   new ActionMap(moveMap);   
   moveMap.push();
   
   $enableDirectInput = true;
   activateDirectInput();
   enableJoystick();
   
   sceneWindow2D.loadLevel(%level);
}

7) In our “scenegraph.cs” add TicTacToeSceneGraph::onLevelLoaded(%this) function:

function TicTacToeSceneGraph::onLevelLoaded(%this)
{
	$sceneGraph = %this;
	
	// define some constant variables
	$nonePlayer = 0;
	$xPlayer = 1;
	$oPlayer = 2;
	
	$currentTurn = $nonePlayer; // this global variable will indicate what player should make turn
	
	$boardPos = "-8 -8";

	// initialize tile board
	for (%i=0; %i<9; %i++)
	{
		// get tile position in 2D array
		// 0 1 2
		// 3 4 5
		// 6 7 8
		%y = mFloor(%i/3);
		%x = %i - %y*3;
		
		// create tile with necessary properties
		%this.boardImagesArray[%i] =
			new t2dStaticSprite() {
				sceneGraph = %this;
				class = "BoardTile"; // we need setup class of tile to write custom onMouseUp() callback function
				imageMap = "tileImageMap";
				frame = $nonePlayer; // we will use frame to determine state of the tile
				canSaveDynamicFields = "1";
				Position = ($boardPos.x + %x*8) SPC ($boardPos.y + %y*8); // set up tile position
				size = "8 8";
				Layer = "20";
				tileId = %i; // custom variable that we will use to determine what tile was clicked
			};
		%this.boardImagesArray[%i].setUseMouseEvents(true); //allow use mouse events callbacks
	}


	%this.ResetGame(); // this is custom function that we will need to write later
}

8) Add following functions to “sceneGraph.cs”:

function TicTacToeSceneGraph::ResetGame(%this)
{
	for (%i=0; %i<9; %i++)
	{
		%this.boardImagesArray[%i].frame = $nonePlayer;
	}
	
	%this.SetCurrentTurn($xPlayer);
}

function TicTacToeSceneGraph::SetCurrentTurn(%this,%player)
{
	$currentTurn = %player;
	
	guiXPlayerIndicator.Visible = ($currentTurn == $xPlayer);
	guiOPlayerIndicator.Visible = ($currentTurn == $oPlayer);
	
}

ResetGame() function simply reset tiles’ frame to normal state and call SetCurrentTurn() function to give first turn to X-player. SetCurrentTurn() function changes value of global variable $currentTurn and visibility of images-indicators.

9) Add BoardTile::onMouseUp() function to end of “sceneGraph.cs”:

function BoardTile::onMouseUp(%this, %modifier, %worldPos, %mouseClicks)
{
	if (%this.frame==$nonePlayer)
	{
		if ($currentTurn==$xPlayer)
		{
			%this.frame = $xPlayer;
			$sceneGraph.SetCurrentTurn($oPlayer);

			$sceneGraph.CheckWinState();
		}
		else if ($currentTurn==$oPlayer)
		{
			%this.frame = $oPlayer;
			$sceneGraph.SetCurrentTurn($xPlayer);

			$sceneGraph.CheckWinState();
		}
	}

}

This function will be called when we click on tiles. First of all we make sure that tile is empty. If so, we set tile frame to appropriate value, change current turn and call CheckWinState() function that we will write right now.

10) The last function we need to add in “sceneGraph.cs” is TicTacToeSceneGraph::CheckWinState(%this) :

function TicTacToeSceneGraph::CheckWinState(%this)
{
	%winner = $nonePlayer;
	
	// check lines
	if (%this.boardImagesArray[0].frame!=$nonePlayer && %this.boardImagesArray[0].frame==%this.boardImagesArray[1].frame && %this.boardImagesArray[0].frame==%this.boardImagesArray[2].frame)
		%winner = %this.boardImagesArray[0].frame;
	else if (%this.boardImagesArray[0].frame!=$nonePlayer && %this.boardImagesArray[0].frame==%this.boardImagesArray[3].frame && %this.boardImagesArray[0].frame==%this.boardImagesArray[6].frame)
		%winner = %this.boardImagesArray[0].frame;
	else if (%this.boardImagesArray[0].frame!=$nonePlayer && %this.boardImagesArray[0].frame==%this.boardImagesArray[4].frame && %this.boardImagesArray[0].frame==%this.boardImagesArray[8].frame)
		%winner = %this.boardImagesArray[0].frame;
	else if (%this.boardImagesArray[2].frame!=$nonePlayer && %this.boardImagesArray[2].frame==%this.boardImagesArray[4].frame && %this.boardImagesArray[2].frame==%this.boardImagesArray[6].frame)
		%winner = %this.boardImagesArray[2].frame;
	else if (%this.boardImagesArray[2].frame!=$nonePlayer && %this.boardImagesArray[2].frame==%this.boardImagesArray[5].frame && %this.boardImagesArray[2].frame==%this.boardImagesArray[8].frame)
		%winner = %this.boardImagesArray[2].frame;
	else if (%this.boardImagesArray[3].frame!=$nonePlayer && %this.boardImagesArray[3].frame==%this.boardImagesArray[4].frame && %this.boardImagesArray[3].frame==%this.boardImagesArray[5].frame)
		%winner = %this.boardImagesArray[3].frame;
	else if (%this.boardImagesArray[1].frame!=$nonePlayer && %this.boardImagesArray[1].frame==%this.boardImagesArray[4].frame && %this.boardImagesArray[1].frame==%this.boardImagesArray[7].frame)
		%winner = %this.boardImagesArray[1].frame;
	else if (%this.boardImagesArray[6].frame!=$nonePlayer && %this.boardImagesArray[6].frame==%this.boardImagesArray[7].frame && %this.boardImagesArray[6].frame==%this.boardImagesArray[8].frame)
		%winner = %this.boardImagesArray[6].frame;

	if (%winner != $nonePlayer)
	{
		if (%winner == $xPlayer)
		{
			MessageBoxOK("Game Completed","X wins!","$sceneGraph.ResetGame();");
		}
		else if (%winner == $oPlayer)
		{
			MessageBoxOK("Game Completed","O wins!","$sceneGraph.ResetGame();");
		}
	}
	else
	{
		// check for drawn game
		%filled = true;
		for (%i=0; %i<9; %i++)
		{
			if (%this.boardImagesArray[%i].frame==$nonePlayer)
			{
				%filled = false;
				break;
			}
		}
		
		if (%filled)
		{
			MessageBoxOK("Game Completed","Nobody wins!","$sceneGraph.ResetGame();");
		}
	}
}

First of all we check if one of players wins. There are only 8 sets of tiles that can be filled in line and we simply check them.

Image:Tictactoe7.jpg

Then if we have winner we just show message box and reset game.

If we have no winner we check if all tiles are filled and game drawn. If so, we also show message box and reset game.

Now you can run your game and play with yourself.

Image:Tictactoe8.jpg


Now lets add some AI.

11) First of all we need to change BoardTile::onMouseUp() function and move most of its content to separate function TicTacToeSceneGraph::MakeTurn():

function BoardTile::onMouseUp(%this, %modifier, %worldPos, %mouseClicks)
{
	if ($currentTurn!=$xPlayer)
		return;

	$sceneGraph.MakeTurn($xPlayer,%this.tileId);
}

function TicTacToeSceneGraph::MakeTurn(%this,%player,%tile)
{
	if (%this.boardImagesArray[%tile].frame==$nonePlayer)
	{
		if ($currentTurn==$xPlayer)
		{
			%this.boardImagesArray[%tile].frame = $xPlayer;
			%this.SetCurrentTurn($oPlayer);

			%this.CheckWinState();
		}
		else if ($currentTurn==$oPlayer)
		{
			%this.boardImagesArray[%tile].frame = $oPlayer;
			%this.SetCurrentTurn($xPlayer);

			%this.CheckWinState();
		}
	}
}

12) Add following lines to end of TicTacToeSceneGraph::SetCurrentTurn() function:

	if ($currentTurn==$oPlayer)
		$aiTurnScheduled = %this.schedule(1000,"MakeAITurn");

Add following line

	cancel($aiTurnScheduled);

before each call of MessageBoxOK() in TicTacToeSceneGraph::CheckWinState() .

This things is need to control our AI actions that contained in one simple function TicTacToeSceneGraph::MakeAITurn() that we will write right now.

13) Add TicTacToeSceneGraph::MakeAITurn() function to "sceneGraph.cs":

function TicTacToeSceneGraph::MakeAITurn(%this)
{
	%tileIndex = getRandom(0,8);
	while (%this.boardImagesArray[%tileIndex].frame!=$nonePlayer)
	{
		%tileIndex = getRandom(0,8);
	}
	
	%this.MakeTurn($oPlayer,%tileIndex);
}

That’s all! Run game and try to win random AI :)

I hope that this lesson helps you understand TGB.

--Alexey Stashin 06:46, 1 December 2008 (PST)