Monday 10 February 2020

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

Can you feel the romance in the air?! Valentine's Day is coming up, lovebirds, and boy do I have the web tutorial for you today.

Remember last year when we made this neat little Tic-tac-toe game in HTML, CSS and JavaScript? Today, we will revisit it and improve on it. We'll first put a little Valentine's Day theme to it, and then we'll even implement some artificial intelligence. All while leveraging on the existing code.

First, the colors!

It wouldn't be Valentine's Day if we didn't saturate this sucker with unhealthy doses of red and pink, so let's make some changes to the CSS. That's right... turn the buttons red, and pink on a mouseover.
.btn
{
    font-family: "Comic Sans MS";
    font-size: 2em;
    display: block;
    width: 5em;
    height: 1.5em;
    border-radius: 0.5em;
    border: 0px solid;
    background-color: #FF0000;
    color: #FFFFFF;
    margin: 1em auto 0 auto;
}

.btn:hover
{
    background-color: #FF8888;
    color: #FF0000;
}

A good beginning...

The board needs a little makeover as well. Lighter shades of pink should do it.
#board_container
{
    width: 330px;
    height: 330px;
    margin: 0 auto 0 auto;
    background-color: #FFCCCC;
    padding-top: 10px;
    padding-left: 10px;
}

.square
{
    width: 100px;
    height: 100px;
    margin-right: 10px;
    margin-bottom: 10px;
    float: left;
    outline: 1px solid #FFEEEE;
    background-color: #FFFFFF;
    cursor: pointer;
}


And for good measure, we'll even change the colors of the users.
#user_x_container
{
    color: #FF0000;
}

#user_o_container
{
    color: #FF8888;
}


Ooh, sappy, eh?


Now, for the tokens, instead of "X" and "O", we'll use hearts of a different color.
{
    content: "\2764";
    color: #FF0000;
}

.o:before
{
    content: "\2764";
    color: #FF8888;
}


And upon winning, this shade of pink should contrast nicely with both colors!
.win
{
    background-color: #FF4444;
}


Here you go.


Oh hey, here's a cheeky touch.
<h1>Let's play Tic-tac-t&hearts;e!</h1>


Damn, so cheesy.


Some randomly fun features

I think it's a wee bit boring to have the facial expressions unchanged, and the messages look repetitive. So let's introduce a random element into all this.

Add the getRandomRemark() method into the game object.
var game =
{
    turn: "x",
    started: true,
    board:
    [
        {square: "a", value: ""},
        {square: "b", value: ""},
        {square: "c", value: ""},
        {square: "d", value: ""},
        {square: "e", value: ""},
        {square: "f", value: ""},
        {square: "g", value: ""},
        {square: "h", value: ""},
        {square: "i", value: ""}
    ],
    winPatterns:
    [
        [
            {square: "a", value: ""},
            {square: "b", value: ""},
            {square: "c", value: ""}
        ],
        [
            {square: "a", value: ""},
            {square: "d", value: ""},
            {square: "g", value: ""}
        ],
        [
            {square: "a", value: ""},
            {square: "e", value: ""},
            {square: "i", value: ""}
        ],
        [
            {square: "b", value: ""},
            {square: "e", value: ""},
            {square: "h", value: ""}
        ],
        [
            {square: "c", value: ""},
            {square: "f", value: ""},
            {square: "i", value: ""}
        ],
        [
            {square: "c", value: ""},
            {square: "e", value: ""},
            {square: "g", value: ""}
        ],
        [
            {square: "d", value: ""},
            {square: "e", value: ""},
            {square: "f", value: ""}
        ],
        [
            {square: "g", value: ""},
            {square: "h", value: ""},
            {square: "i", value: ""}
        ]
    ],
    init: function()
    {
        this.started = true;
        this.turn = "x";

        for (var i = 0; i < this.board.length; i++)
        {
            this.board[i].value = "";
        }

        for (var i = 0; i < this.winPatterns.length; i++)
        {
            for (var j = 0; j < this.winPatterns[i].length; j++)
            {
                this.winPatterns[i][j].value = "";
            }
        }

        for (var i = 0; i < 9; i++)
        {
            document.getElementById("square" + i).className = "square";
        }

        var texts = document.getElementsByClassName("text");
        for (var i = 0; i < texts.length; i++)
        {
            texts.innerHTML = "";
        }
    },
    play: function()
    {
        document.getElementById("btnPlay").style.display = "none";
        document.getElementById("btnQuit").style.display = "block";
        document.getElementById("game_container").style.display = "block";
        this.nextTurn();
    },
    quit: function()
    {
        this.init();
        document.getElementById("btnQuit").style.display = "none";
        document.getElementById("btnPlay").style.display = "block";
        document.getElementById("game_container").style.display = "none";
    },
    getRandomNumber: function(min, max)
    {
        return Math.floor((Math.random() * (min - max + 1)) + max);
    },
    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});   
        }
    },
    nextTurn: function()
    {
        this.turn =  (this.turn == "x" ? "o" : "x");
        user.myTurn();
    },
    checkWin: function(mark)
    {
        var findSquare;

        for (var i = 0; i < this.winPatterns.length; i++)
        {
            if (this.winPatterns[i][0].value == mark && this.winPatterns[i][1].value == mark && this.winPatterns[i][2].value == mark)
            {
                for (var j = 0; j < 3; j++)
                {
                    var winPatterns = this.winPatterns;
                    findSquare = this.board.findIndex(function (x) {return x.square == winPatterns[i][j].square});
                    document.getElementById("square" + findSquare).className += " win";                           
                }

                return true;
            }   
        }

        return false;
    },
    getRandomRemark: function()
    {
   
    }               
}


Define a variable, remarks. It will be an array of objects.
getRandomRemark: function()
{
    var remarks =
    [

    ];
}   


Each object has the properties face and text. face is the HTML symbol used to accompany the remark, and text is the remark itself.
getRandomRemark: function()
{
    var remarks =
    [
        {face: "&#9786;", text: "Looks like it's my turn!"},
        {face: "&#9854;", text: "Brilliant move!"},
        {face: "&#9785;", text: "Ouch!"},
        {face: "&#9863;", text: "Um..."},
        {face: "&#9863;", text: "This is hard."},
        {face: "&#9786;", text: "Let me see..."},
        {face: "&#9786;", text: "I'm gonna cream you!"},
        {face: "&#9786;", text: "Ooh. Nice."}
    ];
}


Grab a random number using the existing getRandomNo() method, passing in 0 and the length of remarks minus 1, as arguments, and assign the value to the variable rand.
getRandomRemark: function()
{
    var remarks =
    [
        {face: "&#9786;", text: "Looks like it's my turn!"},
        {face: "&#9854;", text: "Brilliant move!"},
        {face: "&#9785;", text: "Ouch!"},
        {face: "&#9863;", text: "Um..."},
        {face: "&#9863;", text: "This is hard."},
        {face: "&#9786;", text: "Let me see..."},
        {face: "&#9786;", text: "I'm gonna cream you!"},
        {face: "&#9786;", text: "Ooh. Nice."}
    ];

    var rand = this.getRandomNumber(0, remarks.length - 1);
}


Finally, return the array element pointed to by rand.
getRandomRemark: function()
{
    var remarks =
    [
        {face: "&#9786;", text: "Looks like it's my turn!"},
        {face: "&#9854;", text: "Brilliant move!"},
        {face: "&#9785;", text: "Ouch!"},
        {face: "&#9863;", text: "Um..."},
        {face: "&#9863;", text: "This is hard."},
        {face: "&#9786;", text: "Let me see..."},
        {face: "&#9786;", text: "I'm gonna cream you!"},
        {face: "&#9786;", text: "Ooh. Nice."}
    ];

    var rand = this.getRandomNumber(0, remarks.length - 1);
    return remarks[rand];   
}


In the myTurn() method of the user object, remove this line.
myTurn: function()
{
    document.getElementById("user_x_text").innerHTML = "";
    document.getElementById("user_o_text").innerHTML = "";
    //document.getElementById("user_" + game.turn + "_text").innerHTML = "My turn!";


Instead, declare the variable remark and set it to the object returned by calling the getRandomRemark() method of the game object.
myTurn: function()
{
    document.getElementById("user_x_text").innerHTML = "";
    document.getElementById("user_o_text").innerHTML = "";
    //document.getElementById("user_" + game.turn + "_text").innerHTML = "My turn!";

    var remark = game.getRandomRemark();


Then use the face and text properties to populate these placeholders.
myTurn: function()
{
    document.getElementById("user_x_text").innerHTML = "";
    document.getElementById("user_o_text").innerHTML = "";
    //document.getElementById("user_" + game.turn + "_text").innerHTML = "My turn!";

    var remark = game.getRandomRemark();
    document.getElementById("user_" + game.turn + "_text").innerHTML = remark.text;
    document.getElementById("user_" + game.turn + "_face").innerHTML = remark.face;


Also, in your HTML, add ids for these placeholders.
<div id="game_container">
    <div id="user_x_container" class="player">
        <div class="token" id="user_x_face">&#9786;</div>
        <div id="user_x_text" class="text"></div>
    </div>

    <div id="board_container">
        <div id="square0" class="square" onclick="user.takeTurn(0, 'o');"></div>
        <div id="square1" class="square" onclick="user.takeTurn(1, 'o');"></div>
        <div id="square2" class="square" onclick="user.takeTurn(2, 'o');"></div>
        <div id="square3" class="square" onclick="user.takeTurn(3, 'o');"></div>
        <div id="square4" class="square" onclick="user.takeTurn(4, 'o');"></div>
        <div id="square5" class="square" onclick="user.takeTurn(5, 'o');"></div>
        <div id="square6" class="square" onclick="user.takeTurn(6, 'o');"></div>
        <div id="square7" class="square" onclick="user.takeTurn(7, 'o');"></div>
        <div id="square8" class="square" onclick="user.takeTurn(8, 'o');"></div>
    </div>

    <div id="user_o_container" class="player">
        <div class="token" id="user_o_face">&#9786;</div>
        <div id="user_o_text" class="text"></div>
    </div>
</div>


Before I forget, also populate the "face" placeholder when the game is won.
if (game.checkWin(mark))
{
    document.getElementById("user_" + mark + "_face").innerHTML = "&#9786";
    document.getElementById("user_" + mark + "_text").innerHTML = "I win!";
    game.started = false;
}
else
{
    game.nextTurn();
}


Oh, ho! Now things are getting interesting!



Next

Let's simulate some intelligence in this game!

No comments:

Post a Comment