Saturday 10 August 2019

Web Tutorial: Tic-tac-toe (Part 1/2)

Aloha!

Today, we will be doing something mind-numbingly simple... apparently. It's a Noughts And Crosses game on the web, also known as "Tic-tac-toe" in local parlance. 

Rules of the game, for everyone who hasn't played this as a kid (which is absolutely nobody, but let's do it anyway, just to spell out the requirements):

1. There is a 3 by 3 square board, totalling nine squares forming a larger square.



2. Each of the opponents take turns placing their mark (either "x" or "o") on the board.

X
O



3. Once either opponent manages to get three squares in a row with "x" or "o" (vertically, horizontally or diagonally), the game is won.

X O
X O
X



4. You can't place an "x" or "o" in any occupied square.
5. If there are no more vacant squares and no one has won the game, it ends in a draw.

Clear? Let's begin.

Set up some basic HTML structure. We'll just do the easy part first.

<!DOCTYPE html>
<html>
    <head>
        <title>Tic-tac-toe</title>

        <style>

        </style>

        <script>

        </script>
    </head>

    <body>

    </body>
</html>



Add a header tag and two buttons. Their ids are btnPlay and btnQuit respectively, and they're styled with the CSS class btn. One says "PLAY" and the other says "QUIT".

<!DOCTYPE html>
<html>
    <head>
        <title>Tic-tac-toe</title>

        <style>

        </style>

        <script>

        </script>
    </head>

    <body>
        <h1>Let's play Tic-tac-toe!</h1>
        <button id="btnPlay" class="btn">PLAY</button>
        <button id="btnQuit" class="btn">QUIT</button>
    </body>
</html>



So far so good. This is just the beginning.




Now for some CSS! It's a kiddy game, so let's use a kiddy font for the game... the hideously cheerful Comic Sans MS. I'm going to include some styling for the header and buttons, but honestly, they don't matter much in the grand scheme of things.

<style>
            body
            {
                font-family: "Comic Sans MS";
            }

            h1
            {
                width: 30em;
                text-align: center;
                margin: 0 auto 0 auto;
            }

            .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: #FF4400;
                color: #FFFFFF;
                margin: 1em auto 0 auto;
            }

            .btn:hover
            {
                background-color: #FFFF00;
                color: #FF4400;
            }
</style>



Bueno!




Add a div. The id is game_container. Within it, add three other divs. They are user_x_container, board_container and user_o_container. Style user_x_container and user_o_container using the player CSS class.

    <body>
        <h1>Let's play Tic-tac-toe!</h1>
        <button id="btnPlay" class="btn">PLAY</button>
        <button id="btnQuit" class="btn">QUIT</button>
    
        <div id="game_container">
            <div id="user_x_container" class="player">

            </div>

            <div id="board_container">

            </div>

            <div id="user_o_container" class="player">

            </div>
        </div>
    </body>


Within user_x_container and user_o_container, add divs styled using token and text. The divs styled using text should have ids of user_x_text and user_o_text, respectively. Also add smiley face symbols in the divs styled with token.

        <div id="game_container">
            <div id="user_x_container" class="player">
                <div class="token">&#9786;</div>
                <div id="user_x_text" class="text"></div>
            </div>

            <div id="board_container">

            </div>

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



That's all you will see right now - two tiny smiley faces on the left.




Use different colors for user_x_container and user_o_container. I'm going with orange and green.

<style>
            .btn:hover
            {
                background-color: #FFFF00;
                color: #FF4400;
            }
           
            #user_x_container
            {
                color: #FF4400;
            }

            #user_o_container
            {
                color: #44FF00;
            }
</style>



For player, set the width and height, and use the margin property to align the divs to the middle of the screen. For token, this is mostly cosmetic. Set the font size to something large, limit the width, and float left. Since we want the smiley faces to align nicely, set the text-align property to center. text aligns left as well, and takes up 70% width. I've made some margin adjustments so that any text we put in there won't be hideously misaligned.

<style>
            #user_o_container
            {
                color: #44FF00;
            }

            .player
            {
                width: 600px;
                height: 150px;
                margin: 0 auto 0 auto;
            }

            .token
            {
                width: 20%;
                font-size: 5em;
                float: left;
                text-align: center;
                padding: 10px;
            }

            .text
            {
                width: 70%;
                font-size: 2em;
                float: left;
                text-align: left;
                padding: 10px;
                margin-top: 1.5em;
            }
</style>



And it's taking shape...




Now let's set the board. Add nine divs within board_container, all with a class of square. Set the ids from 0 to 8.

            <div id="board_container">
                <div id="square0" class="square"></div>
                <div id="square1" class="square"></div>
                <div id="square2" class="square"></div>
                <div id="square3" class="square"></div>
                <div id="square4" class="square"></div>
                <div id="square5" class="square"></div>
                <div id="square6" class="square"></div>
                <div id="square7" class="square"></div>
                <div id="square8" class="square"></div>
            </div>



board_container will be 330 by 330 pixels, and set in the middle of the screen. We give it a light grey background, and some padding to the left and top. square is a 100 by 100 pixel square, with a right and bottom margin of 10 pixels to separate it from the other divs, so we get some nice spacing between squares. Give it a deep grey outline and a white background. And finally, set the cursor property to pointer.

And... this really should go without saying, but, adjust colors to personal taste.

            .btn:hover
            {
                background-color: #FFFF00;
                color: #FF4400;
            }            body

            #board_container
            {
                width: 330px;
                height: 330px;
                margin: 0 auto 0 auto;
                background-color: #AAAAAA;
                padding-top: 10px;
                padding-left: 10px;
            }

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

            #user_x_container
            {
                color: #FF4400;
            }



OK! You have a board.




Time to JavaScript the hell out of this guy...

We still start with two objects - game and user.

<script>
var game =
{

};

var user =
{

};
</script>



game has four properties (a string, a boolean and two arrays) and seven methods (use these names for now; I'll explain as we go through them. user has two methods - takeTurn() and myTurn().

<script>
var game =
{
                turn: "x",
                started: true,
                board:
                [

                ],
                winPatterns:
                [

                ],
                init: function()
                {

                },
                play: function()
                {

                },
                quit: function()
                {

                },
                getRandomNumber: function(min, max)
                {

                },
                getRandomBlankSquare: function()
                {

                },
                nextTurn: function()
                {

                },
                checkWin: function(mark)
                {

                }               
};

var user =
{
                takeTurn: function(square, mark)
                {

                },
                myTurn: function()
                {

                }
};
</script>



For the game object, turn is a string used to determine whether player "x" or "o" gets to make their turn. Kind of like a traffic controller, if you will, and set to "x" by default. started is a flag that determines if the game has begun. By default, it's true.

board is a series of objects - each with square and value properties. The square property corresponds to the layout of the board. value is meant to determine if the square is currently occupied by "x" or "o".

a b c
d e f
g h i




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:
                [

                ],
                init: function()
                {

                },
                play: function()
                {

                },
                quit: function()
                {

                },
                getRandomNumber: function(min, max)
                {

                },
                getRandomBlankSquare: function()
                {

                },
                nextTurn: function()
                {

                },
                checkWin: function(mark)
                {

                }               
};



winPatterns is an array of an array of objects that help you determine if victory has been achieved. Take, for example, the first array that has been filled in for you. "abc" is a straight line right at the top of the board.

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: ""}
                    ]
                ],
                init: function()
                {

                },
                play: function()
                {

                },
                quit: function()
                {

                },
                getRandomNumber: function(min, max)
                {

                },
                getRandomBlankSquare: function()
                {

                },
                nextTurn: function()
                {

                },
                checkWin: function(mark)
                {

                }               
};



And the rest, you can figure out yourself. There are eight possible winning combinations.

                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: ""}
                    ]
                ]



Now let's set create the init() method for the game object. This initializes the properties of the game object. We start with setting turn to "x" and started to true.

                init: function()
                {
                    this.started = true;
                    this.turn = "x";
                },



And then we iterate through the board and winPatterns arrays, and reset all values to an empty string.

                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 = "";
                        }
                    }
                },



After that, go through all the squares in the board and reset the class to square.

                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";
                    }
                },



Then clear the innerHTML property of all elements styled using the text CSS class.

                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 = "";
                    }
                },



For the play() method, hide the "PLAY" button and show the "QUIT" button. Then show game_container and run the nextTurn() method.

For quit(), you do almost the exact opposite - show the "PLAY" button and hide the "QUIT" button. Then hide game_container. But before all that, run the init() method.

                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";
                },



Now make sure the buttons run those.

        <button id="btnPlay" class="btn" onclick="game.play()">PLAY</button>
        <button id="btnQuit" class="btn" onclick="game.quit()">QUIT</button>



For there to be anything noticeable when you click the buttons, you first need to set this CSS to hide the "QUIT" button and game_container. Now, refresh. Click the "PLAY" button. It should disappear, only to be replaced by the "QUIT" button. And then the board should appear as well.

            .btn:hover
            {
                background-color: #FFFF00;
                color: #FF4400;
            }

            #btnQuit, #game_container
            {
                display: none;
            }

            #board_container
            {
                width: 330px;
                height: 330px;
                margin: 0 auto 0 auto;
                background-color: #AAAAAA;
                padding-top: 10px;
                padding-left: 10px;
            }



The nextTurn() method will set the turn property to "x" if it's currently "o", and vice versa. Then it will run the myTurn() method of the user object.

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



Let's create myTurn(), then. This will manipulate the DOM to display whose turn it is on the board. First we clear the user_x_text and user_o_text divs, then fill the appropriate div with the string "My turn!".

                myTurn: function()
                {
                    document.getElementById("user_x_text").innerHTML = "";
                    document.getElementById("user_o_text").innerHTML = "";
                    document.getElementById("user_" + game.turn + "_text").innerHTML = "My turn!";
                }



If is the computer's turn, i.e. "x", then we do more stuff. Let's just leave the If block here for now.

                myTurn: function()
                {
                    document.getElementById("user_x_text").innerHTML = "";
                    document.getElementById("user_o_text").innerHTML = "";
                    document.getElementById("user_" + game.turn + "_text").innerHTML = "My turn!";

                    if (game.turn == "x")
                    {

                    }
                }



Now test this. When you refresh, you should now only see the "PLAY" button.




When you click the "PLAY" button, does "My turn!" come on for player "o"?




Next

The groundwork has been laid. The next codes we write will handle the meat of the game.

No comments:

Post a Comment