Create a simple tic tac toe game using HTML, CSS and JavaScript

Keywords: Javascript Front-end html css

🌊 Author home page: Hai Yong
🌊 About the author: πŸ† CSDN high-quality creators in the whole stack field πŸ₯‡ HDZ core group members
🌊 Fan benefits: Fan group Six books a week and various small gifts a month

Creating games with JavaScript is the most interesting way to learn. It keeps you motivated, which is crucial to learning complex skills such as Web development. In addition, you can play with your friends or just show them the little things you make. They will also feel very interesting. In today's blog post, we will create a tic tac toe game using HTML, CSS and JavaScript.

Demo address:

Implement HTML

First, in the head section, I will include the css and javascript files we will create later. I also added a Google font called Itim.

<link rel="stylesheet" href="style.css">
<link rel="preconnect" href="">
<link href="" rel="stylesheet">
<script src="index.js"></script>

The body of HTML will be fairly simple. To wrap everything, I'll use a main tag and apply a class background to it. Inside the main wrapper, we will have five parts.

The first part will contain only our title h1.

The second part will show who is the current turn. In the display, we have a span containing X or O, depending on the current user. We apply classes to this span to color the text.

The third part is the part with the game board. It has a container class, so we can place tiles correctly. In this section, we have 9 div s that will act as tiles in the board.

The fourth part will be responsible for publishing the final competition results. By default, it is empty, and we will modify its contents from javascript.

The last section will save our controls, including a restart button.

<main class="background">
        <section class="title">
            <h1>Tic tac toe</h1>
        <section class="display">
            game player <span class="display-player playerX">X</span> Round of
        <section class="container">
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
            <div class="tile"></div>
        <section class="display announcer hide"></section>
        <section class="controls">
            <button id="reset">restart</button>


I won't go into every line of CSS in detail, but you can see the complete code in the source code.

First, I'll create the style.css file and remove any browser defined margins and padding, and set the Google fonts I include in HTML for the entire document.

* {
    padding: 0;
    margin: 0;
    font-family: 'Itim', cursive;

The next important thing we have to add is the style of our board. We will use the CSS grid to create the board. We can split the container in two by providing three times 33% of the space for columns and rows. We will set the maximum width and center the container. margin: 0 auto;.

.container {
    margin: 0 auto;
    display: grid;
    grid-template-columns: 33% 33% 33%;
    grid-template-rows: 33% 33% 33%;
    max-width: 300px;

Next, we will add a style for the tiles in the board. We will apply a small white border and set the minimum width and height to 100 pixels. We will use the central content of Flexbox to justify content and align items to the center. We will give it a large font size and apply cursor: pointer so that users will know that this field is clickable.

.tile {
    border: 1px solid white;
    min-width: 100px;
    min-height: 100px;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 50px;
    cursor: pointer;

I used two different colors to better distinguish the two players. To do this, I create two utility classes. Player X is green and player O is blue.

.playerX {
    color: #09C372;

.playerO {
    color: #498AFB;

Implement Javascript part
Since we include javascript files in < head >, this is necessary because our script will be loaded before the browser parses the HTML body. If you don't want to include everything in this function, feel free to add defer to the script tag or move the script tag to body

window.addEventListener('DOMContentLoaded', () => {


First, we will save the reference to the DOM node. We will use document.querySelectorAll(). We want an array, but this function returns a NodeList, so we must use Array.from(). We will also get references to the player display, reset button and announcer.

const tiles = Array.from(document.querySelectorAll('.tile'));
const playerDisplay = document.querySelector('.display-player');
const resetButton = document.querySelector('#reset');
const announcer = document.querySelector('.announcer');

Next, we will add the global variables needed to control the game. We will initialize a board with an array of nine empty strings. This saves the X abd O value for each block on the board. We will have a currentPlayer that holds the flag of active players in the current turn. The isGameActive variable will remain true until someone wins or the game ends in a draw. In these cases, we set it to false so that the remaining tiles are inactive before reset. We have three constants representing the end state of the game. We use these constants to avoid spelling mistakes.

let board = ['', '', '', '', '', '', '', '', ''];
let currentPlayer = 'X';
let isGameActive = true;

const TIE = 'TIE';

In the next step, we will store all winning positions on the chessboard. In each subarray, we will store the indexes of the three positions that can win the game. So this [0, 1, 2] will represent the situation where the first horizontal line is occupied by the player. We will use this array to determine whether we have a winner.

   Indexes within the board
   [0] [1] [2]
   [3] [4] [5]
   [6] [7] [8]

const winningConditions = [
   [0, 1, 2],
   [3, 4, 5],
   [6, 7, 8],
   [0, 3, 6],
   [1, 4, 7],
   [2, 5, 8],
   [0, 4, 8],
   [2, 4, 6]

Now we will write some practical functions. In the isValidAction function, we will decide whether the user wants to perform a valid action. If the inner text of the tile is XorO, we return false as the operation is invalid, otherwise the tile is empty, so the operation is valid.

const isValidAction = (tile) => {
    if (tile.innerText === 'X' || tile.innerText === 'O'){
        return false;

    return true;

The next utility function will be very simple. In this function, we will receive an index as a parameter and set the corresponding element in the chessboard array as the symbol of our current player.

const updateBoard =  (index) => {
   board[index] = currentPlayer;

We will write a small function to handle player changes. In this function, we will first from the playerDisplay. String template text, player${currentPlayer} will become playerX or playerO, depending on the current player. Next, we will use a ternary expression to change the value of the current player. If it is x, it will be O, otherwise it will be X. Now that we have changed the value of our users, we need to update innerText's playerDisplay and apply the new player class.

const changePlayer = () => {
    currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
    playerDisplay.innerText = currentPlayer;

Now we will write the announcer function to announce the final game result. It will receive the end game type and innerText will update the announcer DOM node based on the result. In the last line, we must remove the hidden class, because the announcer is hidden by default until the end of the game.

const announce = (type) => {
       case PLAYERO_WON:
            announcer.innerHTML = 'Player <span class="playerO">O</span> Won';
       case PLAYERX_WON:
            announcer.innerHTML = 'Player <span class="playerX">X</span> Won';
       case TIE:
            announcer.innerText = 'Tie';

Next, we'll write one of the most interesting parts of the project - outcome evaluation. First, we will create a roundWon variable and initialize it to false. Then we will traverse the winConditions array and check each winning condition on the chessboard. For example, in the second iteration, we will check these values: board, board, board.

We will also do some optimization. If any field is empty, we will call continue and jump to the next iteration, because if there are empty tiles in the winning condition, you will not win. If all fields are equal, we have a winner, so we set roundWon to true and break the for loop, because any further iteration will waste calculation.

After the loop, we will check the value of the roundWon variable. If it is true, we will announce the winner and set the game inactive. If we have no winner, we will check whether there are empty cards on the chessboard. If we have no winner and no empty cards, we will declare a draw.

function handleResultValidation() {
  let roundWon = false;
  for (let i = 0; i <= 7; i++) {
    const winCondition = winningConditions[i];
    const a = board[winCondition[0]];
    const b = board[winCondition[1]];
    const c = board[winCondition[2]];
    if (a === "" || b === "" || c === "") {
    if (a === b && b === c) {
      roundWon = true;

  if (roundWon) {
    announce(currentPlayer === "X" ? PLAYERX_WON : PLAYERO_WON);
    isGameActive = false;

  if (!board.includes("")) announce(TIE);

Next, we will deal with the user's actions. This function takes a tile and an index as arguments. This function is called when the user clicks a block. First, we need to check whether it is a valid action. We will also check whether the game is currently active. If both are true, we innerText update the tile with the symbol of the current player, add the corresponding class and update the board array. Now that everything is updated, we must check whether the game is over, so we call handleResultValidation(). Finally, we must call the changePlayer method to pass the round to another player.

const userAction = (tile, index) => {
  if (isValidAction(tile) && isGameActive) {
    tile.innerText = currentPlayer;

In order for the game to work properly, we must add an event listener to the tile. We can do this by looping through the block array and adding an event listener for each block. (for better performance, we can only add an event listener to the container and use event bubbling to capture tile clicks on the parent, but I think it's easier for beginners to understand.)

tiles.forEach( (tile, index) => {
    tile.addEventListener('click', () => userAction(tile, index));

We only missed one feature: reset the game. To do this, we will write a resetBoard function. In this function, we set the board X to consist of nine empty strings, set the game to active, remove the announcer and change the player back (X always starts according to the definition).

The last thing we have to do is traverse the block and set innerText back to an empty string, and remove any player specific classes from the block.

const resetBoard = () => {
    board = ['', '', '', '', '', '', '', '', ''];
    isGameActive = true;

    if (currentPlayer === 'O') {

    tiles.forEach(tile => {
        tile.innerText = '';

Now we just need to register this function as a click event handler for the reset button.

resetButton.addEventListener('click', resetBoard);

That's it. We have a fully functional tic tac toe game. You can play with your friends and have a good time.

πŸ₯‡ Draw fans and send books

Vue.js framework and Web front end development from getting started to mastering

The following link can participate in the lucky draw:

Written at the end

The author is determined to create a game with 100 games Fishing website , update progress: 41 / 100

I have been writing a technology blog for a long time and mainly published through nuggets. This is my article about creating a simple tic tac toe game using HTML, CSS and JavaScript. I like to share technology and happiness through articles. You can visit my blog: For more information. I hope you will like it! 😊

πŸ’Œ You are welcome to put forward your opinions and suggestions in the comment area! πŸ’Œ

Posted by wispas on Sun, 21 Nov 2021 19:53:10 -0800