Tic-Tac-Toe Tutorial (aka Noughts and Crosses) Q&A

This is the Official Q&A thread for the Tic-Tac-Toe (aka Noughts and Crosses) Tutorial in our UI Module.

Please use this thread if you have any questions, issues or feedback on this project.

The Tic-Tac-Toe Tutorial is a learning project on our Learn Site:

2606328--183224--001-FinalGame.png

hey, i think I found a bug? if i put an x in all 4 corners and o in all 4 edges (or vice versa, the final x makes the game a draw? Iā€™m gonna do the last bit of the tutorial tomorrow, (the pick a player part) but just wondering about that.

Interesting. Iā€™ll test that today and see what happens.

Looking at my final project, it works as expected:

2612655--183222--slack-imgs.png

Now, Iā€™m having a hard time (without redoing the project) checking to see if thereā€™s an issue at your stepā€¦ but first:

Iā€™d check the code in EndTurn to make sure that all the test are correct. Thoā€™ that should trigger 4 separate lines of code and 4 possible wins (1 row, 1 column, 2 diagonals). Youā€™d have to have 4 lines wrong to make that not a win.

The other thing to check is to make sure that the center grid space is correctly assigned and in the correct orderā€¦

Can you win with a diagonal, row or column that goes thru the center before the last turn?

Can you have a win with any last place turn? EG: From this setup, starting with X, placing the last X should create another 9th turn win.

2612655--183223--slack-imgs.png

It only really seems to happen as

xox
oxo
xox
(invertable)

and only when the last square is the middle square.

Ive included a few wins through the middle square at various stages which all seem to be on the money.

Thanks for your reply :slight_smile:

And just trying there to get the last square winning and yeah it seems square 9 is always a draw, does that mean the draw condition >=9 is the fault?

What value do you start your move count with? And when and where do you increment it. You may inadvertently be one move aheadā€¦ Or something similar.

Have you jumped ahead at any point?

There is one point where the entire EndTurn gets moved from ifā€™s to an if/else block.

ā€¦

Where are you in the tutorial?

Hey there Adam, I havenā€™t moved on any further than the second last section. hereā€™s my code :slight_smile:

(Please Ignore the comments as I put them there just to show myself why Iā€™ve done x y or z in that area and they show my complete n00bness :p)

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

[System.Serializable]
//creatingaclassissomethingtolookinto
//itseemsthatyoucanthenassignittoanobjectorwhateverandpullitinasyouneed?invoidsandstuff.
//Theybecomelikeadropdownmenu,intheinspector?notsurewhy.
public class Player
{
 public Image panel;
 public Text text;
}

[System.Serializable]
public class PlayerColor
{
 public Color panelColor;
 public Color textColor;
}

public class GameController : MonoBehaviour {
 //The[]meanscreateanarraythatispopulatedinthe editor
 public Text[] buttonList;
 //JusttheGameOvertextpaneland text
 public GameObject gameOverPanel;
 public Text gameOverText;
 //Torestartthe game
 public GameObject restartButton;
 //Playerisacustomclasswecallhere...
 public Player playerX;
 public Player playerO;
 //PlayerColourisalsoapublicclasswecallherethatwecreatedandpopulatedintheeditor.
 public PlayerColor activePlayerColor;
 public PlayerColor inactivePlayerColor;
 private string playerSide;
 //movecounterstored here
 private int moveCount;

 void Awake ()
 {
 SetGameControllerReferenceOnButtons();
 playerSide = "X";
 //stopsthegameoverwindowonfirst move
 gameOverPanel.SetActive(false);
 //setsmovecountto0soitcanstartcountingtowardsadraw.
 moveCount = 0;
 //turnsoffthereset button
 restartButton.SetActive(false);
 //setouractiveandinactiveplayercolours.
 SetPlayerColors(playerX, playerO);
 }

 void SetGameControllerReferenceOnButtons ()
 {
 for (int i = 0; i < buttonList.Length; i++)
 {
 buttonList .GetComponentInParent<GridSpace>().SetGameControllerReference(this);
 }
 }

 public string GetPlayerSide ()
 {
 return playerSide;
 }

 public void EndTurn ()
 {
 //addsanextramovetothemove counter
 moveCount ++;
 //checkshowmanymoveshavebeen made
 if (moveCount >= 9)
 {
 GameOver ("draw");
 }
 //checkstoseeiftheHorizontallinesare complete
 else if (buttonList [0].text == playerSide && buttonList [1].text == playerSide && buttonList [2].text == playerSide)
 {
 GameOver (playerSide);
 }
 else if (buttonList [3].text == playerSide && buttonList [4].text == playerSide && buttonList [5].text == playerSide)
 {
 GameOver (playerSide);
 }
 else if (buttonList [6].text == playerSide && buttonList [7].text == playerSide && buttonList [8].text == playerSide)
 {
 GameOver (playerSide);
 }
 //checkstoseeiftheverticallinesare complete
 else if (buttonList [0].text == playerSide && buttonList [3].text == playerSide && buttonList [6].text == playerSide)
 {
 GameOver (playerSide);
 }
 else if (buttonList [1].text == playerSide && buttonList [4].text == playerSide && buttonList [7].text == playerSide)
 {
 GameOver (playerSide);
 }
 else if (buttonList [2].text == playerSide && buttonList [5].text == playerSide && buttonList [8].text == playerSide)
 {
 GameOver (playerSide);
 }
 //checkstoseeifthediagonallinesare complete
 else if (buttonList [0].text == playerSide && buttonList [4].text == playerSide && buttonList [8].text == playerSide)
 {
 GameOver (playerSide);
 }
 else if (buttonList [2].text == playerSide && buttonList [4].text == playerSide && buttonList [6].text == playerSide)
 {
 GameOver (playerSide);
 }
 else ChangeSides ();
 }

 void ChangeSides ()
 {
 playerSide = (playerSide == "X") ? "O" : "X";
 if (playerSide == "X")
 {
 SetPlayerColors(playerX, playerO);
 }
 else
 {
 SetPlayerColors(playerO, playerX);
 }
 }

 void SetPlayerColors (Player newPlayer,Player oldPlayer)
 {
 newPlayer.panel.color = activePlayerColor.panelColor;
 newPlayer.text.color = activePlayerColor.textColor;
 oldPlayer.panel.color = inactivePlayerColor.panelColor;
 oldPlayer.text.color = inactivePlayerColor.textColor;
 }

 void GameOver(string winningPlayer)
 {
 if (winningPlayer == "draw")
 {
 SetGameOverText("It'saDraw!");
 }
 else
 {
 SetGameOverText(winningPlayer + "Wins!");
 }
 SetBoardInteractable (false);
 restartButton.SetActive(true);
 }

 void SetGameOverText(string value)
 {
 gameOverPanel.SetActive(true);
 gameOverText.text = value;
 }
 public void RestartGame()
 {
 playerSide = "X";
 moveCount = 0;
 gameOverPanel.SetActive(false);
 restartButton.SetActive(false);
 SetPlayerColors(playerX, playerO);
 for (int i = 0; i < buttonList.Length; i++)
 {
 buttonList .text = "";
 }
 SetBoardInteractable(true);
 }

 void SetBoardInteractable (bool toggle)
 {
 for (int i = 0; i < buttonList.Length; i++)
 {
 buttonList .GetComponentInParent<Button>().interactable = toggle;
 }
 }
}

Edited to fix code tagsā€¦ Tried to add some new lines to make it more readableā€¦ Moderator

FWIW our code tags take square brackets:

I would have added them, but your code has lost its formatting.

It looks like you have the move count test first, not last. As code is executed top top bottom in a function, it reaches the move count test before the win test, so if itā€™s turn 9, you will always get a draw.

I see that you have some questions in your comments. If you still have questions, feel free to ask them.

Hey there Adam, thanks for your help buddy :slight_smile:

And well, Iā€™m still confused as to WHY Iā€™m doing a lot of these things, I find it a little hard to understand WHY I use things like void, but I know void update is everytime the game refreshes or something similar, in a previous project I used fixed update which was like a regular pulse. But yeahā€¦ what is void? :stuck_out_tongue:

And Sorry about the <> I used to do HTML, guess I just fell into an old habit :stuck_out_tongue:

The ā€œvoidā€ is saying ā€œthis function returns nothing at all.ā€ For a ā€œvoidā€ you can use ā€œreturn;ā€ or not have a return at all. For other types of returns, you set up the proper return value.

For example:

void SomeFunction() {} // Returns nothing.

int SomeOtherFunction() 
{
    return 5;
} // Returns an int.

bool SomeTrueFunction()
{
    return true;
}  // Always return true.

The Update() function is called every single frame, every time Unity draws the screen. There is some official documentation here. The FixedUpdate() is probably what you mention using before and is explained here.

This short tutorial may help make it more clear.

Edited to make more sense than my first post. :slight_smile:

What @Socrates saidā€¦

void is a return type for a function. In C#, all functions must have a return type. In many cases, functions simply ā€œDo Somethingā€ but are not necessarily processing data and returning a result. These are void.

void SetBoardInteractable (bool toggle)
{
   for (int i = 0; i < buttonList.Length; i++)
   {
      buttonList .GetComponentInParent<Button>().interactable = toggle;
   }
}

In the above example, the function toggles all of the buttons in buttonList, but returns no value, so the return type is void.

int AddTwo (int a, int b)
{
   return a + b;
}

In the above example, the function processes data and returns a value, so the return type must be int.

TYPE is the type of thing this is, in that ā€œIs this a GameObject? Is this a Camera? Is this a float?ā€ The type is the name of the class. If you write your own class, eg: public class GameController, the type is GameController. Donā€™t forget that ultimately, even thoā€™ Components are ā€œbuilt-inā€, these Components - like Cameras and Rigidbodies - are defined somewhere and they are usually classes, just like the classes you write in your scripts. They are identified by type.

Now, C# can get a little pedantic with type.

int myNewInt = AddTwo (4, 5);

int AddTwo (int a, int b)
{
   int calculatedValue = a + b;
   return calculatedValue;
}

We define int 3 times! 5 is you count the parameters! Shouldnā€™t the compiler know this should be a fussing int??

Orā€¦

GameObject clone = Instantiate (prefab) as GameObject;

Well, yes the compiler can figure it out but in many cases C# is pedantic by design so you are required to be absolutely clear about what you are writing. Other languages like JavaScript (or even UnityScript) are more forgiving and can try to figure out what type things are by implication.

There are some situations now, in C#, where you can let the compiler figure out type.

var clone = Instantiate (prefab) as GameObject;

The var her means ā€œThis is a variable of the type being returned by the right hand side of the statementā€ - in this case GameObject.

In general, itā€™s safest to make sure you write these out by hand so there is no confusion.

[edit]

As a side note, functions that do not return a value still do return when the function is done. Control of the game is returned to wherever it came from. If you think of the flow of logic in a game - the logic is flowing in a big loop reading all of the code on all of the relevant and active scripts and then Unity renders a frame and then the loop goes all over again.

public Text displayText;

void Update ()
{
   // Some code goes here - checking for input and doing things.
   SetDisplay (Time.time);
   // More code goes here - checking for input and doing things.
}

void SetDisplay (float time)
{
   displayText.text = time.ToString();
}

If you have a function like Update() and in Update, you call a function like SetDisplay. They are both functions that return void. Unityā€™s main game loop (which we donā€™t have control of) calls Update. The control of the game is now in Update doing things. During the execution of Update, the function SetDisplay is called. The control of the game - or the code being executed - is now in SetDisplay. Unity has executed ā€œsome codeā€ in Update, but has not yet called ā€œmore codeā€. The control moved to SetDisplay first. Unity executes all the code in SetDisplay and then SetDisplay returns void - but it does return. It just returns empty, nothing, void - and control of the game returns back to where it came from - in this case back to Update right after the line calling SetDisplay. Unity now executes ā€œmore codeā€ in Update. When Update is done, the function Update then returns void and control of the game is handed back to the main Unity game loop, which will go on and look for any more Update functions on any other Components and then continue on with all the other things it does.

So - the flow of logic is handed off to different functions and then handed back to where it left off all in a linear fashion, like a mouse threading through a maze.

For more reading, this is the order of events that Unity calls:
http://docs.unity3d.com/Manual/ExecutionOrder.html

Unity will call all of these events on all relevant and enabled scripts on active GameObjects and then move on the next set of events, in a big loop, until the gameā€™s application is closed.

As a lst side note, in advanced coding, you can have what is called ā€œmultiple threadsā€ all executing code simultaneously. This goes beyond the definition above ā€œall code is executed in one long linear sequenceā€ but you have to choose to do that and itā€™s an advanced topic. Know that it exists, but ignore it for now.

1 Like

In tutorial#8 (Ending in a draw),
if(moveCount >= 9)
should be
else if(moveCount >= 9)
Because if a player wins in the 9th move, without that else it will display ā€œItā€™s a Draw!ā€.

At what stage of the tutorial are you? ā€œElse Ifā€ is the final state. If you follow the tutorial and do the save and test steps, this should work as expected. If youā€™re testing in the middle of a step, then you might have problems. Be aware that code executes from top to bottom and the order of the code is important. If you have that line above the win checks, youā€™ll have problems.

If you feel there is a different error, then please point out the exact step you are in when it fails, as our tests of this tutorial donā€™t find this issue.

Hey there Iā€™m at the end of part 5 but I keep getting an error telling me on the gamecontroller script.
The type arguments for method ā€˜Component.GetComponentInParent()ā€™ cannot be inferred from the usage. Try specifying the type arguments explicitly.

I also get an error on the grid space script that says ā€˜GameControllerā€™ does not contain a definition for ā€˜GetplayerSideā€™ and no extension method ā€˜GetplayerSideā€™ accepting a first argument of type ā€˜GameControllerā€™ could be found (are you missing a using directive or an assembly reference?)

Iā€™ve attached pictures of both scripts. Iā€™m thinking Iā€™ve just missed something small.


I fixed the Component.GetComponentInParent problem, I had missed out

but Iā€™m still lost on the GameController problem.

Haha I just forgot to use a capital P. Iā€™m an idiot but at least itā€™s fixed now. :slight_smile:

Hi,
Iā€™m having a problem where a ninth move win comes up as a draw. Iā€™m at the end of part 8, ā€œEnding in a drawā€. Here is my code:

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

public class GameController : MonoBehaviour {

    public Text[] buttonList;
    public GameObject gameOverPanel;
    public Text gameOverText;

    private string playerSide;
    private int moveCount;

    void Awake ()
    {
        SetGameControllerReferenceOnButtons ();
        playerSide = "X";
        gameOverPanel.SetActive (false);
        moveCount = 0;
    }

    void SetGameControllerReferenceOnButtons ()
    {
        for (int i = 0; i < buttonList.Length; i++)
        {
            buttonList [i].GetComponentInParent<GridSpace> ().SetGameControllerReference(this);
        }
    }

    public string GetPlayerSide ()
    {
        return playerSide;
    }

    public void EndTurn ()
    {
        moveCount++;
        if (buttonList [0].text == playerSide && buttonList [1].text == playerSide && buttonList [2].text == playerSide)
        {
            GameOver ();
        }
        if (buttonList [3].text == playerSide && buttonList [4].text == playerSide && buttonList [5].text == playerSide)
        {
            GameOver ();
        }
        if (buttonList [6].text == playerSide && buttonList [7].text == playerSide && buttonList [8].text == playerSide)
        {
            GameOver ();
        }
        if (buttonList [0].text == playerSide && buttonList [3].text == playerSide && buttonList [6].text == playerSide)
        {
            GameOver ();
        }
        if (buttonList [1].text == playerSide && buttonList [4].text == playerSide && buttonList [7].text == playerSide)
        {
            GameOver ();
        }
        if (buttonList [2].text == playerSide && buttonList [5].text == playerSide && buttonList [8].text == playerSide)
        {
            GameOver ();
        }
        if (buttonList [0].text == playerSide && buttonList [4].text == playerSide && buttonList [8].text == playerSide)
        {
            GameOver ();
        }
        if (buttonList [2].text == playerSide && buttonList [4].text == playerSide && buttonList [6].text == playerSide)
        {
            GameOver ();
        }

        if (moveCount >= 9)
        {
            SetGameOverText ("It's a draw!");
        }

        ChangeSides ();
    }

    void ChangeSides ()
    {
        playerSide = (playerSide == "X") ? "O" : "X";
    }

    void GameOver ()
    {
        for (int i = 0; i < buttonList.Length; i++)
        {
            buttonList [i].GetComponentInParent<Button> ().interactable = false;
        }
        SetGameOverText (playerSide + " Wins!");
    }

    void SetGameOverText (string value)
    {
        gameOverPanel.SetActive (true);
        gameOverText.text = value;
    }
}

The only issue Iā€™ve had in testing is if X wins on the last square. Can you spot anything wrong with my code? Many thanks in advance!