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

Keywords: Javascript Front-end

🌊 Author home page: Hai Yong
🌊 About the author: 🏆 CSDN high-quality creators in the whole stack field 🥇 HDZ core group members 🥈 Ranked among the top ten in the weekly list of station C
🌊 Fan benefits: 👉 Fan group 👈 Four books per week and various small gifts per month (enamel cup, pillow, mouse pad, mug, etc.)

Jump straight to the end Go to the review area to get the book

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.

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 3,board4,board5.

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.

🥇 Fans in the review area are pumping books

💌 You are welcome to put forward your opinions and suggestions in the comment area! (select two lucky ones to send books, and the physical picture is as follows) 💌

Scan the code to participate in the lucky draw:

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

[content introduction]

Starting from the basic concept of Vue.js framework technology, this book gradually goes deep into the advanced practice of Vue.js, and finally cooperates with a website project and a background system development practice case, focusing on the front-end development using Vue.js+axios+ElementUI+wangEditor and the reuse of Vue single page web pages using components, Readers can not only systematically learn the relevant knowledge of Vue.js front-end development framework, but also have a deeper understanding of the analysis ideas of business logic and practical application development.
With plain language, witty words, rich cases and strong practicability, this book is especially suitable for newcomers to the workplace, primary readers and advanced readers of Vue.js framework, as well as other programming enthusiasts who want to transform from background development to front-end programmers. In addition, this book is also suitable for use as teaching materials for relevant training institutions.

Students who don't want to smoke and want to buy by themselves can refer to the following link

JD self purchase link:

Vue.js framework and Web front end development from introduction to mastery - JD books

Dangdang self operated purchase link:

Vue.js framework and Web front end development from introduction to mastery - Dangdang book

🌊 Interview question bank: Java, Python, front-end core knowledge points and interview real question materials
🌊 E-books: 300 Turing program books and 6000 free genuine books from the machinery industry press
🌊 Office supplies: thousands of high-quality PPT templates and more than 1000 resume templates
🌊 Learning materials: 2300 sets of PHP website source code, wechat applet introduction materials

If you fail to win the prize, you will be regarded as giving up. You can find the author's contact from the official account below. You will send four books every week. The book will be increased later. Sending hundreds of thousands of books a year is not a problem. Reply to [resources] to obtain the above information 👇🏻👇🏻👇🏻

Posted by AV1611 on Thu, 18 Nov 2021 23:54:44 -0800