Wednesday, 12 February 2020

Web Tutorial: Tic-tac-toe, Valentine's Day Edition (Part 2/2)

The current level of intelligence used in this JavaScript game is zero. The computer takes its turn with no strategy whatsoever, only using available squares and a random number as criteria. We can leverage on the existing code to determine strategic actions for the computer to take, rather than random ones.

In the game object, at the getFinalSquare() method. It should accept a parameter string, mark.
getRandomBlankSquare: function()
{
    var blanks = this.board.filter(function (x) {return x.value == ""});

    if (blanks.length == 0)
    {
        return null;
    }
    else
    {
        var rand = this.getRandomNumber(0, blanks.length - 1);
        return this.board.findIndex(function (x) {return x.square == blanks[rand].square});   
    }
},
getFinalSquare: function(mark)
{

},
nextTurn: function()
{
    this.turn =  (this.turn == "x" ? "o" : "x");
    user.myTurn();
},


It will return null by default; meaning, no strategic path was found. As in the getRandomBlankSquare() method, you first check for blank squares. If there are no blank squares, return null. If there are, also possibly return null.
getFinalSquare: function(mark)
{
    var blanks = this.board.filter(function (x) {return x.value == ""});

    if (blanks.length == 0)
    {
        return null;
    }
    else
    {
        return null;
    }
},


If there are blank squares, that means there is a possibility that a strategic path can be found. For this, we need to iterate through the game object's winPatterns array.
getFinalSquare: function(mark)
{
    var blanks = this.board.filter(function (x) {return x.value == ""});

    if (blanks.length == 0)
    {
        return null;
    }
    else
    {
        for (var i = 0; i < this.winPatterns.length; i++)
        {

        }

        return null;
    }
},


Within the For loop, define three variables; marked, filled - both set to 0 - and finalSquare, which is null by default.
getFinalSquare: function(mark)
{
    var blanks = this.board.filter(function (x) {return x.value == ""});

    if (blanks.length == 0)
    {
        return null;
    }
    else
    {
        for (var i = 0; i < this.winPatterns.length; i++)
        {
            var marked = 0;
            var filled = 0;
            var finalSquare = null;
        }

        return null;
    }
},


Iterate through each element of the winPatterns array. Because each element is in turn an array of three elements, remember? If the value is occupied by mark, increment marked. If the value is occupied (whether by mark or anything else), increment filled. If the value is blank, set finalSquare to the value of that square.
getFinalSquare: function(mark)
{
    var blanks = this.board.filter(function (x) {return x.value == ""});

    if (blanks.length == 0)
    {
        return null;
    }
    else
    {
        for (var i = 0; i < this.winPatterns.length; i++)
        {
            var marked = 0;
            var filled = 0;
            var finalSquare = null;

            for (j = 0; j < 3; j++)
            {
                if (this.winPatterns[i][j].value == mark) marked++;
                if (this.winPatterns[i][j].value != "") filled++;
                if (this.winPatterns[i][j].value == "") finalSquare = this.winPatterns[i][j].square;
            }
        }

        return null;
    }
},


Now outside of the inner For loop and in the main For loop, check if marked is 2 and filled is less than 3. That basically means that for that winPattern, two slots have been filled by the user's mark and one remains for victory. Once that's established, there's no need to search further - just return the index value of that square.

If no strategic paths are found at all, the method will return null.
getFinalSquare: function(mark)
{
    var blanks = this.board.filter(function (x) {return x.value == ""});

    if (blanks.length == 0)
    {
        return null;
    }
    else
    {
        for (var i = 0; i < this.winPatterns.length; i++)
        {
            var marked = 0;
            var filled = 0;
            var finalSquare = null;

            for (j = 0; j < 3; j++)
            {
                if (this.winPatterns[i][j].value == mark) marked++;
                if (this.winPatterns[i][j].value != "") filled++;
                if (this.winPatterns[i][j].value == "") finalSquare = this.winPatterns[i][j].square;
            }

            if (marked == 2 && filled < 3)
            {
                return this.board.findIndex(function (x) {return x.square == finalSquare});
            }
        }

        return null;
    }
},


Let's get back to the myTurn() method of the user object. We need to expand on this. Basically, before attempting to get a random blank square, the program must first check for a strategic move that can be made.

First, run the getFinalSquare() method using "x" as the argument. This searches for a "win" scenario where "x" would win the game. That's the first priority. If no "win" scenario exists, the next thing to do is try to stop user "o" from winning. So again, we run the getFinalSquare() method, but this time using "o" as an argument. And if there is no "win: scenario for "o" either, just use a random blank square.
setTimeout
(
    function()
    {
        var finalSquare = game.getFinalSquare("x");

        if (finalSquare == null)
        {
            finalSquare = game.getFinalSquare("o");

            if (finalSquare == null)
            {
                var randomBlankSquare = game.getRandomBlankSquare();

                if (randomBlankSquare == null)
                {
                    document.getElementById("user_" + game.turn + "_face").innerHTML = "&#9786";
                    document.getElementById("user_" + game.turn + "_text").innerHTML = "Looks like it's a draw.";
                    game.started = false;
                }
                else
                {
                    user.takeTurn(randomBlankSquare, "x");
                }
            }
        }
    },
    1000
)


Here, we set the Else blocks to take the appropriate turn if there are strategic paths to take.
setTimeout
(
    function()
    {
        var finalSquare = game.getFinalSquare("x");

        if (finalSquare == null)
        {
            finalSquare = game.getFinalSquare("o");

            if (finalSquare == null)
            {
                var randomBlankSquare = game.getRandomBlankSquare();

                if (randomBlankSquare == null)
                {
                    document.getElementById("user_" + game.turn + "_face").innerHTML = "&#9786";
                    document.getElementById("user_" + game.turn + "_text").innerHTML = "Looks like it's a draw.";
                    game.started = false;
                }
                else
                {
                    user.takeTurn(randomBlankSquare, "x");
                }
            }
            else
            {
                user.takeTurn(finalSquare, "x");
            }
        }
        else
        {
            user.takeTurn(finalSquare, "x");
        }
    },
    1000
)


Now try this! If you place your token in the center of the board hoping to get a diagonal row...


...this should happen!


And if you try to get a row while the computer is already on its way there...


...this happens!


Happy Valentine's Day, you crazy couples!

Celebrate love, romance, and all that shit. Just seize the day and enjoy the memories!

X my ♥,
T___T

No comments:

Post a Comment