Tictactoe

Circular icon with two abstract cats

Click here to play Tictactoe || Click here to view the Github Repo

Tictactoe board with sheep in play

 

To become better at JavaScript, I took on a challenge to create Tictactoe in the browser with HTML5, CSS3 and vanilla JS (ES6).

I first started with creating a board – a 3×3 grid. I did this by iterating through a for loop and each time creating an HTML element and append it to the body. With CSS I styled it so that it became a 350 x 350 pixels grid and each tile is 100 x 100 pixels.

This was the first function for creating the tiles:

 

  function createTiles()
  {
    for (var i = 0; i < amountOfTiles; i++)
    {
      var tileContainer = document.getElementById("tile-container");
      var tile = document.createElement("div");
      tile.setAttribute("class", "tile");
      tile.setAttribute("id", "tile-" + i);
      tileContainer.appendChild(tile);
      tileArray.push(tile);
    }
  }

 

Sheep rotating when clicked

 

It worked! But it wasn't as good as I had hoped. For instance, I fetched the tileContainer with each iteration. That wasn't needed! Plus I also used vars instead of lets. I had recently learned about lets so I decided to incorporate that into this project.

 

This was the code for checking whose turn it is:

 

  var turnCounter = 0;

  function checkWhoIsNext(clickedElement)
  {
    if(turnCounter % 2 == 0)
    {
      console.log("It is X turn");
      clickedElement.innerHTML = "X";
    }
    else
    {
      console.log("It is O turn");
      clickedElement.innerHTML = "O";
    }

    turnCounter++;

 

…well, I mean it did the job, but talk about ugly code. When I first created the tiles, I thought that when the user clicked on a tile, a way to register that is to write an X or an O to the innerHTML property of the clicked object. I thought it was pretty clever!
But yes, don't do this as it is pretty bad.

How about checking whether the first row has all the same tiles?

 

 for (var i = 0; i < tileArray.length; i++)
    {
      if(tileArray[0].innerHTML && tileArray[1].innerHTML && tileArray[2].innerHTML === "X") // anders een switch state maken
      {
        console.log("X has won");
      }
      else
      {
        console.log("No match")
      }
    }
    console.log(tileArray.indexOf("X"));
    return(turnCounter);
  }

 

 

This worked! Hurray! But it was hardcoded; not dynamic at all. You mean..do I have to make like 20 if/else statements to check each row and for both X and O? And what about vertical? Diagnonal?!
This was going to be an issue. How am I going to create a dynamic way of checking the grid for all possibilities?

 

I need to check every tile for horizontal, vertical and diagonal winners.  Now I could loop from tile 0 to tile 1 to tile 2 etc. and save each input to an array. But I want to only check tile 0, 1 and 2 and stop there. But I want it to continue afterwards with tile 3, 4 and 5 and of course 6, 7 and 8. And don't forget vertical! This sounds like a complicated for loop…but I figured it out:

 

 

For the vertical for loop, I start at tile 0 or i = 0. I then check i + 3 (tile 3) and i + 6 (tile 6)  and I can perform a check with these. Are they equal? No? Increment i by one and do it again.

The same I can do for horizontal, but instead of incrementing i by 1, I need to do i + 3 to proceed to the next row and check i + 1 and i +2.

For vertical with i++ I proceed to the next column!

 

function isVerticalTilesWinner(arrayOfTilesToCheck)
{
  // check tiles vertical per column
  for (let i = 0; i < 3; i++)
  {
    let firstTile = arrayOfTilesToCheck[i];
    let secondTile = arrayOfTilesToCheck[i + 3];
    let thirdTile = arrayOfTilesToCheck[i + 6];

    let firstTileInput = firstTile.innerHTML;
    let secondTileInput = secondTile.innerHTML;
    let thirdTileInput = thirdTile.innerHTML;

    if(firstTileInput != "" || secondTileInput != "" || thirdTileInput != "")
    {
      if(firstTileInput === secondTileInput && secondTileInput === thirdTileInput)
      {
        console.log((firstTileInput) + " has won vertically!");
      }
    }
  }
}

 

A pretty dynamic for loop! And I didn't even need to hardcode it! And I did this for horizontal and diagonal as well. Better than 20 if/else for sure.
But still, something felt off…

You see, I still checked for innerHTML of the tile objects. And while that was great and all, I wanted to create a different design for the board. Not plain X and O, but something else that could represent it. Plus I wanted something different than visual X and O:

 

Tictactoe board with sheep

 

I already had the best tictactoe design in the world sitting right here near my desk. A checkered meadow with black and white sheep. Brilliant!
So how I am going to implement a way for each sheep to have a property that I can check whether it's a black or white sheep?

I needed to do a few things:

 

  1. Instead of X and O, place a sheep
  2. I should not check the innerHTML anymore, but something else

 

Placing a sheep isn't that hard. I created 2 classes; one for a black sheep and one for a white sheep. Internally I had to keep track of whose turn it was. But isn't there a better way of doing that instead of the function I showed earlier?

Enter enums.

  var GameState =
  {
    IN_PROGRESS : 0,
    X_WINS : 1,
    O_WINS : 2,
    DRAW : 3,
  };

  var PlayerTurn =
  {
    X_TURN : 0,
    O_TURN : 1,
  }

  var gameState = GameState.IN_PROGRESS;
  var playerTurn = PlayerTurn.X_TURN;

 

Wow. My mind was blown when learning about enums. You mean I can create a variable and assign it different values and still keep it readable? Okay!
So when I placed a black sheep, I could also change the PlayerTurn state!

  function playerXTurn(clickedElement, randomTransformDegrees)
  {
    clickedElement.innerHTML = "X";
    clickedElement.className += " tile-x";
    clickedElement.style.transform = "rotate(" + randomTransformDegrees +"deg)";
    gameMessage.innerHTML = "is now on the play.";
    gameMessageSheep.className = " tile-o";
    playerTurn = PlayerTurn.O_TURN;
  }

 

I felt like I was pretty close to finishing this project. I had cool sheep falling onto the board, the grid would be thoroughly checked for every winning combo, the winning sheep rotated…But I wasn't completely satisfied yet. I still thought the code could improve. I mean my code still used innerHTML to check the user input. I knew that could be done better, but how?

 

var UserInput =
  {
    EMPTY : 0,
    BLACK_SHEEP : 1,
    WHITE_SHEEP : 2,
  };

 

More enums! No more writing to innerHTML! Just assign a value to the element abstractly. Why didn't I think of this earlier?
But I wasn't going to stop here.
I took a more closer look at my tile spawner.

  function createTiles()
  {
    let tileContainer = document.getElementById("tile-container");
    tileContainer.innerHTML = ""; // for reset game
    tileContainer.addEventListener('click', objectFinder, false);

    for (let i = 0; i < amountOfTiles; i++)
    {
      let tile = document.createElement("div");
      tile.setAttribute("class", "tile");
      tile.setAttribute("id", "tile-parent-" + i);

      let tileInnerChild = document.createElement("div");
      tileInnerChild.setAttribute("id", "tile-child-" + i);

      let spanInnerChild = document.createElement("span");
      spanInnerChild.setAttribute("class", "tile-userinput");

      tile.appendChild(tileInnerChild);
      tileInnerChild.appendChild(spanInnerChild);

      tileContainer.appendChild(tile);
      tileArray.push(spanInnerChild);
    }
  }

 

Okay, not bad. I create a div element and I create some more div and span elements and I append these to one another. I'm making a tile object, no? So why am I not creating an object then in JavaScript for tiles?

 

Like this:

 

function create2DGrid()
  {
    let grid = [];
    let gridColumn = 3;
    let gridRow = 3;
    let tileContainer = document.getElementById("tile-container");
    tileContainer.innerHTML = ""; // for reset game
    tileContainer.addEventListener('click', objectFinder, false);

    for (let i = 0; i < gridRow; i++)
    {
      let row = [];

      for (let j = 0; j < gridColumn; j++)
      {
        let tile =
        {
          outerDiv : document.createElement("div"),
          outerDivId: "tile-parent-" + i + "-" + j,
          outerDivClass : "tile",
          innerDiv : document.createElement("div"),
          innerDivId : "tile-child-" + i + "-" + j,
          userInput : UserInput.EMPTY
        }

        tile.outerDiv.className = tile.outerDivClass;
        tile.outerDiv.id = tile.outerDivId;

        tile.innerDiv.id = tile.innerDivId;

        tile.userInput = UserInput.EMPTY;

        tile.outerDiv.appendChild(tile.innerDiv);
        tileContainer.appendChild(tile.outerDiv);

        row.push(tile);
      }
      grid.push(row);
    }
    return grid;
  }

 

Nice! I create a tile and set each property before it gets pushed into an array that I later use for accessing the tiles. But it was kinda messy. I mean, the function create2DGrid() should return just the grid. But I am creating the tiles in this function, too. Can I split them? Well, of course…why don't I create a class of Tile?

 

class Tile
  {
    constructor(i, j, tileContainer)
    {
      this.i = i;
      this.j = j;

      this.outerDiv = document.createElement("div");
      this.outerDivId = "tile-parent-" + i + "-" + j;
      this.outerDivClass = "tile";

      this.innerDiv = document.createElement("div");
      this.innerDivId = "tile-child-" + i + "-" + j;

      this.userInput = UserInput.EMPTY;

      // Set attributes

      this.outerDiv.className = this.outerDivClass;
      this.outerDiv.id = this.outerDivId;

      this.innerDiv.id = this.innerDivId;

      this.outerDiv.appendChild(this.innerDiv);
      tileContainer.appendChild(this.outerDiv);
    }
  }

  function create2DGrid()
  {
    let grid = [];
    let tileContainer = document.getElementById("tile-container");
    tileContainer.innerHTML = ""; // for reset game
    tileContainer.addEventListener('click', objectFinder, false);

    for (let i = 0; i < numberOfRows; i++)
    {
      let row = [];

      for (let j = 0; j < numberOfColumns; j++)
      {
        let newTile = new Tile(i, j, tileContainer);

        row.push(newTile);
      }
      grid.push(row);
    }
    return grid;
  }

 

 

Much better. The function is way less cluttered now. It still created a new tile object, but the code that handles the tile object is now a separate entity.
Now only one thing remained.

I wanted to create a dynamic grid. My code was dynamic or adaptable, so I wanted to show that to the user. I want it to be possible to switch from a 3×3 grid to a 4×4 grid. My code would support that! But how?

 

Well, the winner functions had to be rewritten a little bit like this:

function isVerticalWinner()
  {
    let winningTiles = [];

    for (let i = 0; i < numberOfColumns; i++)
    {
      winningTiles = [];
      let tileToCompareTo = gridOfTiles[0][i];

      if(tileToCompareTo.userInput != UserInput.EMPTY)
      {
        for (let j = 0; j < numberOfRows; j++)
        {
          let tileToBeCompared = gridOfTiles[j][i];

          if(tileToCompareTo.userInput == tileToBeCompared.userInput)
          {
            winningTiles.push(tileToBeCompared);

            if(winningTiles.length == numberOfColumns)
            {
              return winningTiles;
            }
          }
        }
      }
    }
    return [];
  }

 

By using the variable i to access the tiles, it didn't matter if the grid was 3 or 4 tiles wide (for the vertical function). So if numberOfColumns was 4 (4×4 grid), it would iterate until i = 4 and still everything would work.

And now for the 4×4 grid.

  var GridType =
  {
    GRID_3X3 : 0,
    GRID_4X4 : 1,
  }

  var gridType = GridType.GRID_3X3;

  function createGrid()
  {
    let grid = [];
    let tileClassName;

    tileContainer.innerHTML = ""; // for reset game

    if(gridType == GridType.GRID_3X3)
    {
      numberOfRows = 3;
      numberOfColumns = 3;
      tileClassName = "tile-3x3";

      tileContainer.className = "grid-3x3";
    }
    else
    {
      numberOfRows = 4;
      numberOfColumns = 4;
      tileClassName = "tile-4x4";

      tileContainer.className = "grid-4x4";
    }

    for (let i = 0; i < numberOfRows; i++)
    {
      let row = [];

      for (let j = 0; j < numberOfColumns; j++)
      {
        let newTile = new Tile(i, j, tileContainer);
        newTile.outerDiv.className = tileClassName;

        row.push(newTile);
      }
      grid.push(row);
    }
    return grid;
  }

  function switchGrid()
  {
    if(gridType == GridType.GRID_3X3)
    {
      gridType = GridType.GRID_4X4;
      changeGridButton.innerHTML = "Switch to 3x3 grid";
      tryAgain();
    }
    else
    {
      gridType = GridType.GRID_3X3;
      changeGridButton.innerHTML = "Switch to 4x4 grid";
      tryAgain();
    }
  }

 

And done. By adding a button to switch between grids, I can set an enum to a grid state and based on that, switch it up.

This project took me about a month, but I'm glad with how it turned out. I learned a lot about structuring code, enums, classes and more out-of-the-box ways for loops.