In this lesson we will write the code for the TicTacToe game model itself. This is just an abstract implementation that by itself isn’t playable, and doesn’t actually display anything. It simply has the idea of what the game is, what the rules are, and how to win, etc.
Tic Tac Toe
- Create a new project folder named “Model” as a subfolder of the “Scripts” folder
- Create a new C# script named “Game” in the “Model” folder
- Open the script for editing and replace the template code with the following:
using UnityEngine; using System.Collections; namespace TicTacToe { public enum Mark { None, X, O } public class Game { // Add Code Here } }
I like to use the most simple and intuitive name I can for my types. However, names like “Game” and “Mark” are so generic that it is important to place them within the “TicTacToe” namespace. This will help us to avoid naming conflicts and also provides a helpful context for understanding what kind of Game or what kind of Mark I am working with.
I defined a simple enumeration called “Mark”. Note that it is defined within the “TicTacToe” namespace, but outside of the “Game” class. The Mark enum defines three types: an ‘X’ and ‘O’ to represent the moves made by opposing players, and ‘None’ to indicate a spot which hasn’t been claimed yet. In a more complex game I might have stuck an enumeration like this into its own file, but this is fine for now.
I decided to create the “Game” as a normal class, not a subclass of MonoBehaviour. This makes the class much more reusable – even outside of Unity, and prevents me from tightly coupling the code to any idea of how it should be presented. When you treat your game model in an abstract way, it is much easier to adapt it to any sort of “view” whether you want a 2D or 3D implementation, etc. Really simple board games like this are easy to create this way. Other more complex games which rely on physics, etc aren’t quite as obvious.
public const string DidBeginGameNotification = "Game.DidBeginGameNotification"; public const string DidMarkSquareNotification = "Game.DidMarkSquareNotification"; public const string DidChangeControlNotification = "Game.DidChangeControlNotification"; public const string DidEndGameNotification = "Game.DidEndGameNotification";
There are several notifications I will want the model to be able to post. This includes posting a notice when a new game has begun, when a square has been marked, when a new turn has begun, and when the game ends.
public Mark control { get; private set; } public Mark winner { get; private set; } public Mark[] board { get; private set; } int[][] wins = new int[][] { // Horizontal Wins new int[] { 0, 1, 2 }, new int[] { 3, 4, 5 }, new int[] { 6, 7, 8 }, // Vertical Wins new int[] { 0, 3, 6 }, new int[] { 1, 4, 7 }, new int[] { 2, 5, 8 }, // Diagonal Wins new int[] { 0, 4, 8 }, new int[] { 2, 4, 6 } };
I have three public properties with private setters – this makes them “read only” to the perspective of other classes. The game model will handle its own logic and state and indicates this to consumers by protecting access to those fields.
The “control” field indicates which mark will be placed the next time an empty board square is chosen. Later we will create “Player” objects which will be assigned a Mark, and the player with the Mark which matches the “control” will be allowed to take a turn while the other player’s input will be ignored. When the game ends, “control” will be set to “None” and neither player will be able to take a turn.
The “winner” field indicates the mark of the “player” who won the game. For example if there are three X’s in a row, then the field will hold the “X” Mark. If the game is a tie, then the “winner” field will hold “None” while control is also “None”.
The “board” field holds an array of Marks. This is a “flattened” array which will have a length of 9 – one for each square on the board. If you prefer readability over speed then you can feel free to use a 2D array instead. Since I am not writing A.I. for this project, the speed is not a concern.
The final field “wins” is a convenient array of arrays which hold all of the possible places a win can occur, whether row, column, or diagonal line.
public Game () { board = new Mark[9]; }
In C# 6 you can provide a default value for a property. Unfortunately the version of C# used by unity is not up-to-date, so we will need to use the class constructor to initialize our “board” property.
public void Reset () { for (int i = 0; i < 9; ++i) board[i] = Mark.None; control = Mark.X; winner = Mark.None; this.PostNotification(DidBeginGameNotification); }
The “Reset” method wipes the board clean by setting all squares to a “None” Mark. In addition it hands control to the X mark – X will always go first in my implementation, although I decided to let the players control a different mark at random on each new game. The winner field also must be reset, just in case the game had previously been won, and finally, we post a notification that a new game has begun.
public void Place (int index) { if (board[index] != Mark.None) return; board[index] = control; this.PostNotification(DidMarkSquareNotification, index); CheckForGameOver(); if (control != Mark.None) ChangeTurn(); }
The “Place” method is used any time a player attempts to take a turn. The index of the desired square is passed as a parameter. If the spot is not vacant, then the call is ignored. Otherwise, the mark will be placed, and a notification will be posted so that the view can be updated to match. Next we check to see if the placement of a new mark caused the game to end. If not, then we hand control over to the next player.
void ChangeTurn () { control = (control == Mark.X) ? Mark.O : Mark.X; this.PostNotification(DidChangeControlNotification); }
The “ChangeTurn” method is very simple. It assigns the control to whichever mark was not currently in control, and then posts a relevant notification.
void CheckForGameOver () { if (CheckForWin() || CheckForStalemate()) { control = Mark.None; this.PostNotification(DidEndGameNotification); } }
Checking for a Game Over requires a few checks. First, we want to know if there are any winning patterns on the board. If the first check fails, then we will check to see if there are no empty squares. Either condition would cause the game to end, and a relevant notification to fire.
bool CheckForWin () { for (int i = 0; i < 8; ++i) { Mark a = board[wins[i][0]]; Mark b = board[wins[i][1]]; Mark c = board[wins[i][2]]; if (a == b && b == c && a != Mark.None) { winner = a; return true; } } return false; }
When checking to see if a player has won, we loop through the wins array. I grab the Marks located at each of the indices of the particular win pattern and see if they all match a single player mark. If so, we can update the winner mark with the same value, and set control to “None” which indicates that the game has ended.
bool CheckForStalemate () { for (int i = 0; i < 9; ++i) if (board[i] == Mark.None) return false; return true; }
When checking for a stalemate I am really checking for an open square. If I find a single square which still holds “None” then the game is allowed to continue.
Summary
In this lesson we created everything necessary to create a model of a playable TicTacToe game. Actually interacting with it and showing it to a user will come next. Don’t forget that if you get stuck on something, you can always check the repository for a working version here.
hi there, thanks for the Tutorial, I got a question:
thisPostNotification – I can’t find this method… I get an error:
‘Game’ does not contain a definition for `PostNotification’ and no extension method `PostNotification’
In part 1, there was a unity package for you to download within the “Project Setup” section. It contained a couple of scripts including “NotificationCenter” and “NotificationExtensions” that you need here.
thanks, I will take a look at bitbucket
i cant import it – my unity seems to be too new
I opened it in Unity 5.4 today without any issues. What problem are you seeing?
I’m getting an error on the strings that are found in the beginning of the code. I put them in the TicTacToe namespace, so if they belong in public Game(), then that is the problem. If not, I am getting the error message “parser error: unexpected symbol ‘const’, expecting ‘class’, ‘delegate’, etc…”. This leads me to believe that I must put these in a class, but the article never specifies this. Any help on how the completed code looks like is appreciated.
Yep, the const string notifications do belong inside the class. Some conventions I used to help hint at this are that the initial code snipped included a comment “Add Code Here” so that the next code snippets would would be inserted from that point on. Also, the value of the string is prefixed with “Game” which should indicate it being in the Game class.
Even with those hints it can be difficult to guess at my intentions, so the complete code exists in an online repository which I linked to in the Summary, but here it is again:
https://bitbucket.org/jparham/blog-tic-tac-toe