Ending in a draw

확인 완료한 버전: 5.3

-

난이도: 초급

We now have a great way of dealing with a win, but what if it’s a draw? Or a tie?

Now, being honest here, most Tic-Tac-Toe games end in a tie or draw. To account for this, another brute force solution may be best. We could check to see if every grid space has been "used" by checking to see if all of the Button components in the buttonList are non-interactable by polling every tile every turn. On the other hand, we can simply count the number of moves. If the move count is 9 and no one has won, then it’s a draw.

  • Open the GameController script for editing.
  • Declare a private int variable called "moveCount".
private int moveCount;
  • In Awake, set moveCount to 0.
moveCount = 0;
  • In EndTurn, at the top of the function, increment moveCount by one.
moveCount ++;
  • In EndTurn at the end of the function, before changing sides, check moveCount and if it’s 9 or greater, activate the gameOverPanel and set the gameOverText.
if (moveCount >= 9)
{
    gameOverPanel.SetActive(true);
    gameOverText.text = "It's a draw!";
}

There is one worry, however...

When looking at the code, we are setting the state of the Game Over Panel and its contents in two different places. Once in EndTurn if it’s a draw and once again in GameOver if there is a win. This worries me and it’s not best practice. If there is logic that Does Something it should be in one place.

There are several reasons for this, but primarily it’s for the ease of Code Maintenance and the ease of understanding the code when reading it. Code Maintenance is very important. When we are developing a software project, we need to be able to quickly find and fix bugs. More importantly, however, as we improve our project or upgrade it to new or different functionality, we want to easily find and change functionality in as simple a way as possible. When our logic is spread all around the codebase, not only is it hard to find but it's very hard to fix and make sure that we have found all instances of a piece of logic or functionality.

Take for example this piece of code that sets the Game Over Text. If we want to change the way Game Over Text is set, or the content of that text, we have to remember that this is set in two different places and fix them both. If not, we will have a bug that can only be discovered by our players when they are playing the game. That is not good.

Now, if a project is done, it works and it is shippable, that’s a success in and of itself. Adding a new feature to an existing project for a point release or reusing a project for a new version based on the original is an even bigger success. Clean and reusable code allows us to do all of this and more. Clean and reusable code saves us time and frustration. If our code is clean, understandable and reusable, then it’s good code.

With this in mind, let’s put all of this logic into one place.

  • Open the GameController script for editing.
  • Create a new function that returns void called "SetGameOverText" that takes a string value as a parameter.
void SetGameOverText(string value)
{
    
}
  • Copy the code from GameOver to SetGameOverText and adjust it to use the string value parameter from the function.
gameOverPanel.SetActive(true);
gameOverText.text = value;
  • Remove the code activating the gameOverPanel and setting the gameOverText in EndTurn and replace it with a call to SetGameOverText with "It's a draw!" as an argument.
SetGameOverText("It's a draw!");
  • Remove the same code in GameOver and replace it with a call to SetGameOverText.
SetGameOverText(playerSide + " Wins!");

The final script should look like this:

GameController

Code snippet

using UnityEngine;
using UnityEngine.UI;
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().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().interactable = false;
        }
        SetGameOverText (playerSide + " Wins!");
    }

    void SetGameOverText (string value)
    {
        gameOverPanel.SetActive(true);
        gameOverText.text = value;
    }
}
  • Save the script.
  • Return to Unity.
  • Enter Play Mode.
  • Test by clicking any of the spaces.

It’s surprisingly hard to get a draw when playing against oneself, isn’t it? If one side wins, the Game Over Text should indicate a win and a winner. If it’s a draw, the Game Over Text should say "It’s a draw!".

description

To test, we have to restart the game manually by exiting and entering Play Mode. When the game is done, we will want to be able to keep playing as long as we want to without restarting the application. In the next lesson we will handle resetting and restarting the game.