Loops

As a programmer you will frequently be working with a “group” of data (like an array which I presented in the previous lesson). Tic Tac Toe for example has a 3×3 board with nine total cells. If you were creating a method to operate on that group of data, such as to wipe a board clean for a new game, you wouldn’t want to have to manually apply the changes to each and every value in the array. Instead, you can write something called a loop, and let the computer handle the tedious work for you. In this lesson, we will create Tic Tac Toe, and show how loops can help make our code more elegant.

Life Without Loops

Create a new script called “TicTacToe” . Without loops, you might go about trying to implement this game with something like the following:

using UnityEngine;
using UnityEngine.UI;
using System.Collections;

public class TicTacToe : MonoBehaviour 
{
	[SerializeField] Text[] cells;

	void Start ()
	{
		NewGame();
	}

	public void NewGame ()
	{
		cells[0].text = "";
		cells[1].text = "";
		cells[2].text = "";
		cells[3].text = "";
		cells[4].text = "";
		cells[5].text = "";
		cells[6].text = "";
		cells[7].text = "";
		cells[8].text = "";
		cells[9].text = "";
	}
}

This code declares an array of Text components representing the cells of our tic tac toe board. It also defines the first method we will need – one which clears the board to ready it for a new game. In that method we assign the text of each cell to an empty string so that the cell is unused. Everything written here so far is functional, but not elegant, or easily expandable to games with larger boards. Imagine a similar setup for Chess, where you had to manually assign 64 tiles instead of the 9 we have here!

The Wonder of Loops

Each of the nine statements in the NewGame method are identical with one exception, the index of the cell in the array. As a programmer, you will often hear about keeping your code “DRY” which means, “Don’t repeat yourself.” That can often refer to a need of putting bits of logic into smaller more reusable methods, but it can also apply here. Look how the NewGame method could be implemented with some new vocabulary:

public void NewGame ()
{
	for (int i = 0; i < cells.Length; ++i) {
		cells[i].text = "";
	}
}

In this example we were able to replace nine separate statements of the “NewGame” method with a single statement wrapped in a “for loop“. Besides being more compact, this code snipped is also dynamic and expandable. We could change from a normal 3×3 tic tac toe board to a 5×5 board and not need to change or add any lines of code, whereas the previous implementation would have required an extra 16 lines!

The keyword “for” marks the beginning of our loop. An initializer, condition, and iterator “expression” appear inside of its parenthesis (note that they are separated by semicolons, but the last one does not end with a semicolon), and a body (the statements to repeatedly execute) appear between the open and close brackets ‘{‘ and ‘}’.

The statements inside of the parenthesis determine the “rules” of how we loop and deserve a bit more discussion:

  • I declare a temporary variable called “i” in the “initializer” statement and assign its default value to 0. This variable’s scope is constrained to the loop itself, and won’t be visible outside of its declaration and body. The initializer only executes once – at the very beginning of this block of code.
  • The “condition” determines whether or not to execute the code in its body. In this example, we will continue looping for as long as the value of “i” is less than the length of our cells array. This statement is checked once before each loop cycle.
  • The “iterator” provides us an opportunity to modify the variable we declared in the initializer. In this example, we increment the value of “i” by one after every loop. Note that “++i” is a shorthand way to write “i = i + 1”. The iterator executes after every loop cycle.

In the body of our loop we pass along the variable “i” which we defined in the loop initializer, as the index into our cells array. By using the variable, the cell which we are modifying is dynamic and will be different on each run through the loop.

Tip:
There are additional features of the for loop, such as not providing one or more rules for your loop. See the reference for more.

There are multiple other types of loops in C# . I commonly use the “while” loop, although I tend to avoid the “foreach” loop due to memory issues in Unity

Board Interaction

Now that we have a board, and can get it ready to play on, let’s add some logic for actually playing. We will need two things: a variable marking whether it is time to place an “X” or an “O” and an event handler method to determine when and where to make a move on the board. Add the following variable declaration beneath our cells array:

string mark;

Let’s make it so that X’s always go first. To do that, add the following statement inside of the NewGame method, just after the close bracket of our loop:

mark = "X";

When the user clicks one of the buttons of our board, we will need a method for it to call. That will be defined as follows:

public void SelectCell (int index)
{
	if (!string.IsNullOrEmpty(cells[index].text))
		return;
	cells[index].text = mark;
	mark = (mark == "X") ? "O" : "X";
}

This method begins with a check to see if the cell has already been marked or not (because we don’t want to allow a player to overwrite another player’s move). The exclamation mark means “Not” so the whole statement is basically read “if the cell’s text is not empty”. When the condition is true, the method calls a “return” statement so that the rest of the method is ignored. Note that this example does not wrap the return statement in brackets. Brackets are only required when you need more than one statement to be treated as the body. Normally you would only use a return statement at the end of a method, but occasionally you will see it at the beginning of a method as a way to “abort” early.

When the condition is false, the rest of the method can execute normally. In this case it means that the cell is empty and is therefore a legal place to make a “move”. We make our “move” by assigning the value of “mark” to the label.

Finally, we change turns by toggling the mark from X to O and vice versa. This statement can be thought of as a variation of an “if statement”. It has a condition (the code in the parenthesis) followed by a question mark. If the result of the condition is true, the value to the left of the colon is used, otherwise the value to the right of the colon is used. In order to see what we have so far, lets start building the scene:

Scene Setup

  1. To begin, create a new scene called “TicTacToe”.
  2. Add a new Panel (from the menu bar choose “GameObject -> UI -> Panel”).
  3. Remove the “Image” and “Canvas Renderer” components from that panel (select the gear in the inspector and then “Remove Component”) because we won’t be needing them.
  4. On the Panel’s “Rect Transform” component, enter a value of “0.5” for each of the four anchors (Min and Max, X and Y) as well as the Pivot (X and Y). Set the Position to zero on all three axis (X, Y and Z) and set the Width and Height to “300”.
  5. Add the component “Grid Layout Group” (from the menu bar choose “Component -> Layout -> Grid Layout Group”). Set its cell size to 100 for X and Y.
  6. Add a Button (from the menu bar choose “GameObject -> UI -> Button”) and parent it to the panel (drag and drop it on top of the Panel object in the hierarchy panel so that it becomes nested underneath it).
  7. Duplicate the button until you have nine buttons total (from the menu bar choose “Edit -> Duplicate”). If you have followed along correctly, you should see a 3×3 board of buttons centered in the middle of the camera.
  8. Attach our TicTacToe script to the Canvas.
  9. Make sure the canvas is selected and then lock the inspector (click the lock icon in the upper right).
  10. Expand each of the buttons in the hierarchy so you can see the text labels. Multi-select the text objects, and drag them onto the cells array variable of our script. Unity will automatically resize the array to hold all of the values and assign the objects to the array.
  11. When all the cells are assigned, unlock the inspector.
  12. Collapse the buttons in the hierarchy and then multi-select the buttons. Use the inspector to add an OnClick handler. Drag the Canvas object to the target object field, and select “TicTacToe -> SelectCell(int)” as our function handler.
  13. You will have to assign the value to pass on each button individually (there are better ways but this will serve for now) Starting from the top, set the values to pass from 0-8 (we will be using this value as the index into an array).

Play the scene and click each of the buttons. You should see each button set an alternating X or O as its label as you click them. If the button you click causes a different button’s label to update, then you have linked something incorrectly. Verify that the array of Text labels are in order (you can click them in the inspector and it will highlight the match in the hierarchy panel) from top to bottom. Also verify that the button’s OnClick parameter is marked in order according to step 13.

Game State

The last step is to make our game watch for a victory / loss condition. After every turn we need to make this check, and when found, congratulate the winner and begin a new game.

Add another variable which indicates when the game is actually over. We will use it to make sure extra moves won’t be played when a victory condition is found. We will also create and initialize a multi-dimension array, where each sub-array is a list of location indices which form a line on the board (rows, columns, and diagonals) from which we will check for wins. Add these just beneath the declaration of the “mark” variable:

bool gameOver;
int[,] wins = new int[,]
{
	{0,1,2},
	{3,4,5},
	{6,7,8},
	{0,3,6},
	{1,4,7},
	{2,5,8},
	{0,4,8},
	{2,4,6}
};

In the NewGame method we will need to make sure to set our gameOver variable to false, or no new moves will be able to be made. Add this statement at the end of that method:

gameOver = false;

We will determine whether or not the game has ended by calling a new method:

void CheckGameState ()
{
	for (int i = 0; i < wins.GetLength(0); ++i)
	{
		int j = wins[i,0];
		int k = wins[i,1];
		int l = wins[i,2];
		if (cells[j].text == cells[k].text && 
		    cells[k].text == cells[l].text &&
		    !string.IsNullOrEmpty(cells[j].text)) {
			gameOver = true;
			Debug.Log(cells[j].text + " wins!");
			Invoke("NewGame", 3f);
			break;
		}
	}
}

In this method, we loop over the array of lines where a win could occur. Inside of the loop we have a compound “if statement” that requires three things to be true (this happens by using “&&” which means “AND”:

  1. The value of the first checked cell must match the value of the second checked cell.
  2. The value of the second checked cell must match the value of the third checked cell.
  3. The value of the first checked cell must not be empty.

If those three conditions are simultaneously met, then we set gameOver to “true”, print a message indicating who won, set our NewGame method to trigger in 3 seconds, and then call “break” which exits the loop early. We don’t need to keep looking for victories once one has been found.

Next we need to modify the SelectCell method. We don’t want to allow moves when the gameOver variable is true OR when the cell is already taken. We can make a compound check with OR by using two vertical lines – “||”. We also call our CheckGameState after applying a move.

public void SelectCell (int index)
{
	if (gameOver || !string.IsNullOrEmpty(cells[index].text))
		return;
	cells[index].text = mark;
	mark = (mark == "X") ? "O" : "X";
	CheckGameState();
}

Save your script and return to Unity. Play the game now and trigger a win condition. You should see a congratulatory message in the console and then be unable to make new moves until the board resets.

Summary

In this lesson we created a human playable version of Tic Tac Toe. We were able to keep our script short and sweet by using loops to iterate over our game board’s cells. We learned how to control where loops start and end, what conditions they require, and how they iterate. The break statement was introduced as a way to exit loops early. We also introduced some variations to previous items such as compound statements with “AND” and “OR” and used multi-dimensional arrays.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s