Skip to main content

Command Palette

Search for a command to run...

Crafting a Simple Tic Tac Toe Game with HTML, CSS & Javascript

Published
7 min read
Crafting a Simple Tic Tac Toe Game with HTML, CSS & Javascript
J

Hey! I’m a second-year BTech student in Information Technology at a Tier-3 college under AKTU. Right now, I’m working on building myself to secure an irreplaceable place in the tech industry. I’m someone who loves diving deep into details, pondering over problems I can solve, and exploring different realms of reality.This space is where I will document my journey!

It's now time to strengthen up those javascript skills with another mini-project. This time along, I am making a Tic-Tac-Toe game. This game consists of simple layout and style, equipped with adequate functionality, following the basic rules of the game. Three files one for each html, css and javascript have been made which I'll discuss in detail as the article unravels, so come along!

The Layout:

The HTML file consists of the foundational structure which includes linking the css file. But, more importantly the body includes an outer div which encloses the heading, all the nine boxes for play and a reset button. Somewhat like this:

<div class="outer">
    <h1> Tic-Tac-Toe Game</h1>
    <div class="boxes"> 
    <div class="main" data-index="0"></div>
    <div class="main" data-index="1"></div>
    <div class="main" data-index="2" ></div>     
    <div class="main" data-index="3"></div>     
    <div class="main" data-index="4"></div>
    <div class="main" data-index="5"></div>        
    <div class="main" data-index="6"></div>
    <div class="main" data-index="7"></div>
    <div class="main" data-index="8"></div>     
    </div>
    <button class="Reset">RESET</button>
    </div>

The rest of the section covers one div for displaying the outcome of the match, one as a New Game button and one audio tag inside the winning section to boost the interactivity of the game. Also, just before closing the body tag, I have linked javascript file via script tag. Like so,

    <div class="WON"> </div>
    <div class="NewGame"></div>
    <audio id="audio" src="audio.aac"></audio>
    <script src="TTT.js"></script>

The Styling:

I started styling the page by keeping a clean black background and a single font-family for the entire body section. Secondly, to put together those shiny edges of the nine boxes I had to use the border-image property with the linear gradient function and 1 as the border-image-slice value. This value basically determines how much image-pixels have to be cut from each side of the main image to make them as the border. Attainable by:-

border-image: linear-gradient(to bottom right,rgb(85, 84, 84),rgb(218, 216, 216))1;

Followed by a grid arrangement of the nine boxes. This includes distribution of the area uniformly and equally both row and column-wise. Like so,

.boxes {
    display: grid;
    justify-content: center; 
    grid-template-columns: 180px 180px 180px;
    grid-template-rows: 180px 180px 180px; }

The rest of the major things include a Reset button which is positioned relative to the document. The button is styled with a likewise background, text color and border.

Closing out with the winning section which features a dynamically updating overlay. This overly includes the filter property with a blur function which is applicable

over the entire body, enhancing focus on the winning box.

.overlay {
    width:100%;
    height:100%;
    filter:blur(15px); }

The winning box features a dynamically updating message about the outcome of the match and a New Game button. Both of these are positioned absolute and placed in the center horizontally using ‘margin: 0 auto’ .

.showBox,.showButton {
    margin:0 auto;
    left:0;
    right:0;
    border-radius:10px;
    position: absolute;
    text-align:center; }

This duo is styled with a visually contrasting background with the linear gradient function similar to the above sections, so that, everything is in harmony with each other.

Inside the JavaScript File:

Hear me out! The following code may not be the least complicated approach to get the desired result. It is more on the intuitive, hit and trial based side. But I’ll try to make it seem like a cakewalk, so bear with me.

I started with selecting all the nine boxes as a NodeList and the Reset button using querySelector. Then all the declarations and initialisations at the top itself as a good practise.

These boxes are attached with an event listener one by one. Also, every event listener I have added has an optional parameter which is a boolean value that determines whether the event handler function will be called only once or not. The function has a parameter, thereby I have wrapped this function inside another one. Like so,

for(let i=0;i<m.length;i++)
{   
     m[i].addEventListener("click",wrapping,{once:true});
}
function wrapping(event)
{
     Game(event.target);
}

Game Function

Within the Game function, Firstly, I have extracted the index value from the dataset attribute. Also, to record the order in which that particular box has been clicked, I have declared another dataset. Secondly, Calculated the two index values of the two dimensional box array. Then, Created an image element and appended it to the current node of the NodeList. Now, Why did I save the order in which boxes are clicked?, To determine whether the cross or zero should be displayed. This is done by the simple even-odd logic. I have also assigned another dataset attribute value to help in determining the wining combination. The assignment of X’s & O’s :-

 if(m[i].dataset.order%2==0)
  {
   box[k][l].src="circle.jpeg";
   box[k][l].dataset.value="O";
  }
  else{
   box[k][l].src="cross.jpeg";
   box[k][l].dataset.value='X';
  }

To decide the winning combinations, I have put necessary if-else conditions (wherever required), inside which two functions (updatex & updatez) one for keeping the account of 0’s and the other for X’s is placed. These functions modify the value of the respective count and update it to the count variable inside the event listener, for their consideration in the next click.

Diagonal Win→ This happens when the row index is equal to the column index of the box array with a single piece.

Reverse Diagonal Win → This happens when the row index is equal to ‘3-column_index-1’ with respect to zero-based indexing.

Straight Column Win & Straight Row Win→ No if-else condition is required here. I have taken four arrays instead, two for each columns and rows, among which one for each piece. This involves indexing the array with the column and row index respectively. Somewhat like this:

// Straight Column Win
sccountz[l]=updatez(l,k,sccountz[l]); 
sccountx[l]=updatex(l,k,sccountx[l]);
// Straight Row Win 
srcountz[k]=updatez(l,k,srcountz[k]);
srcountx[k]=updatex(l,k,srcountx[k]);

All this is followed by an if condition to determine whether either of the count’s value is equal to three, if yes then the win function is called with a timeout and the piece which won, along with a zero function to reinitialize all variables used. If all the dataset order values are used and the value of all count variables are still not equal to three then ‘Tied’ is passed instead of a particular piece in the win function. Like so,

else if((c==0)&&(m[i].dataset.order==8))
{
setTimeout(win,450,"Tied");
zero();}
}

Restart Function

Next up is an event listener attached to the reset button. This event handler function includes a for loop, inside which I have calculated the indices as above, followed by checking if the values corresponding to those indices in the box array are defined or not. If defined, the removeChild function is operated, to remove the respective image from the NodeList. Moreover, the order attribute of dataset is reinitialized, along with the box array, also called the zero function for the others to do the same. I have then readded the event listener on all the nodes. The for loop looks like -

for(i=0;i<m.length;i++)
   {
     k=Math.trunc(i/3);
     l=i%3;
    if(box[k][l]!=undefined)
   {
    m[i].removeChild(box[k][l]);
   }
    m[i].dataset.order="";
    m[i].addEventListener("click",wrapping,{once:true});
  }

In the previous sections, I referred to two different functions updatex and updatez. Let’s look inside each now! Basically, these functions are supported by if conditions to check their dataset value, whether they equal to X or O. Then the respective count variables are updated by one in each, then returned.

Wining Box

This section is commenced by selecting WON, outer, NewGame classes along with the audio element. Inside the win function which is being called from the Game function, there exists functionalities for displaying the outcome of the match.

Begining with ‘audio.play()’ method to add a sound whenever the outcome is going to be displayed. This livens up the game a bit. This is followed by removing event listener from every box, so that, no piece is displayed, even if the user accidentally clicks one of them, as the game has technically ended. There is no logical output of this step, it just adds to the aesthetics.

To put emphasis on the wining box, I have added the class ‘overlay’ on the ‘outer’ classed div. The blur function in it will give me a stage to display the output. Furthermore, I had called the win function with an argument above. This value determines which player has won. Thus, the text content of the WON classes div is updated accordingly. Implemented as below:

outer.classList.add("overlay");
if(player=='O'||player=='X')
won.textContent=`The Player '${player}' Won!`;
else
won.textContent="The Match Tied.";

Now Adding the box and the button in it dynamically. This is followed by adding an event listener of this New game button. Inside which, I have tried to achieve the conditions which are same as they were in the beginning of the game. This involves removing the classes overlay, showBox and showButton. At last, the Restart function is called to undo the actions of the previous game.

Final Thoughts

If I say that I was able to make this perfect then that would be a very wrong statement. Thereby, I would like to invite you, the reader to take this attempt a step further. This could include, either adding things like glow effects on the winning combination of boxes prior from displaying the result or the visually appealing classic straight line. Secondly, adding Redo & Undo buttons to boost interactivity. Anything for that matter, the stage is all yours!

Phew! This marks the end of my article. Thank you for reading till here to witness the making of my Javascript mini-project. I have discussed almost the whole idea here but, for accessing the entire code, feel free to visit my GitHub account.

Hoping to see you in the next one!

~JasDoIt