Tic Tac Toe in JavaScript

Tic Tac Toe in JavaScript

Best Tic Tac Toe against computer

·

6 min read

Hi!

In this post I will try to create Tic Tac Toe game. I remember my first months of learning programming I watched some tutorial online and followed every step on how to create such game. This time I wont use any tutorials, so it may not be the easiest solution but working one :)

HTML markup

To make it easier explaining JavaScript I will paste my ready HTML markup. In my game user will be able to choose if he wants to play as X or as O. And the rule will be that whoever has O will start the game.

Then the game will be played on a board. To display the board I used div elements.

If user will win/loose or would like to reset the game by any reason I will add button to reset the game.

<h1>Best TicTacToe ever!</h1>
  <div class="userpanel">
    <p>Please choose symbol to play (O starts)</p>
    <button class="choosebtn" onclick="saveUserChoice(0)">O</button>
    <button class="choosebtn" onclick="saveUserChoice(1)">X</button>
    <div id="winner"></div>
  </div>

  <div class="wrap">
    <div class="col">
      <div class="block" id="0"></div>
      <div class="block" id="1"></div>
      <div class="block" id="2"></div>
    </div>
    <div class="col">
      <div class="block" id="3"></div>
      <div class="block" id="4"></div>
      <div class="block" id="5"></div>
    </div>
    <div class="col">
      <div class="block" id="6"></div>
      <div class="block" id="7"></div>
      <div class="block" id="8"></div>
    </div>
  </div>

  <div class="restartwrap">
    <button class="restartbtn" onclick="restart()">Restart</button>
  </div>

JavaScript

User choose buttons

I would like to allow user to choose only once if he will play as X or O. For that I will disable the choose buttons after user clicks one.

buttons.forEach(btn => {
  btn.addEventListener('click', disableButton)
})

const disableButton = () => {
  buttons.forEach(btn => {
    btn.disabled = true
  })
}

Save user choice

Okay next we need to save our user choice, for this I already placed inside HTML onclick menthods to call saveUserChoice() function. Also I assumed 0 will be O, and 1 will be X. For the computer I will just assign opposite value. (Function superAI() will be used for computer to make first move if he is O, we will create it later.

Now when we have user choice we can display our board. For this I will create displayBoard() function next.

var userchoice = ''
var aichoice = ''

function saveUserChoice(choice){
  if(choice == 0) {userchoice = 'O'; aichoice = 'X'}
  if(choice == 1) {userchoice = 'X'; aichoice = 'O'; superAI()}
  displayBoard()
}

Display board

To store all data and changes in our board I will use simple array with 9 positions. For now I will populate it with empty strings.

To display this I selected all my divs by class and looped through them assigning innerHTML to according array element values.

var displayboard = document.querySelectorAll('.block')
var board = ['','','','','','','','','']

function displayBoard(){
  let i=0;  
  displayboard.forEach(block => {
    block.innerHTML = board[i]
    i++
  })
  i=0
}

I also added few css styles. Most important is our board, this must be in order to work as it should. This is css I used for displaying board.

.wrap{
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.col{
  display: flex;
}
.block{
  width: 100px;
  height: 100px;
  background-color: rgb(204, 204, 204);
  margin: 2px;
  font-size: 3rem;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-family: Arial, Helvetica, sans-serif;
  font-weight: bold;
}

My board looks like this.

ttt1.PNG

Computer moves

For computer moves I will find it useful to check which places in my board are still empty. This will allow me to skip occupied positions when choosing where my computer has to place move.

In this function I return new array that contains positions of empty places.

function checkEmptyPlaces(){
  let empty = []
  for (let i =0; i< board.length; i++){
    if(board[i] == '') empty.push(i)
  }
  return empty
}

Now I can use it to generate where my computer will move. I named my computer superAI just to add seriousness. (in reality it will just place items randomly :))

Here you can see why I needed new array with empty places. Every move this new array will be shorter allowing me to generate smaller numbers in my randomizer. Thanks to this I also do not need to check if space is occupied, I already know it is empty.

function superAI(){
  let empty = checkEmptyPlaces()
  let position = empty[Math.floor(Math.random() * empty.length)]
  board[position] = aichoice
}

User moves

Next we need to service user moves. For this I used addEventListener on my divs and read their ids. Also I call here new function updateBoard() which will be created in next step.

displayboard.forEach(block => {
  block.addEventListener('click', e => {updateBoard(e.target.id)});
})

Update board

Update board function will be our main place where we link everything together.

First I want to check if user already made a choice and picked up symbol O or X. Next I need to check if user did not click in any occupied field. If everything is ok I can assign user move to its place. Also next I call superAI() so computer can make next move. Finally calling displayBoard() to update user view.

function updateBoard(position){
  if( userchoice == '') return alert("please choose symbol")
  if(board[position] != '') return
  board[position] = userchoice   
  superAI()
  displayBoard() 
}

Check win

Okay so our user and computer are able to play now. Last thing that we need to do is to check who will win.

Here I was considering few options for a longer moment wondering how I can make it simpler, I could not think of anything while having one dimensional array. And changing it to two dimensional would require rebuilding whole app, so I just went with simple yet not most pretty solution.

So my function checkWin() will just check all possible combinations of winning, and we have 8 of them. So I check all 3 cells of the board in horizontally, vertically and across. I had to add one more condition to check if one cell is not empty not to trigger win when we have empty board.

function checkWin(){
  let winner ='';
  if ((board[0] == board[1]) && (board[1] == board[2]) && (board[0] != '')) winner = board[0]
  if ((board[3] == board[4]) && (board[4]== board[5]) && (board[3] != '')) winner = board[3]
  if ((board[6] == board[7]) && (board[7]== board[8]) && (board[6] != '')) winner = board[6]

  if ((board[0] == board[4]) && (board[4]== board[8]) && (board[0] != '')) winner = board[0]
  if ((board[6] == board[4]) && (board[4]== board[2]) && (board[6] != '')) winner = board[6]

  if ((board[0] == board[3]) && (board[3] == board[6]) && (board[0] != '')) winner = board[0]
  if ((board[1] == board[4]) && (board[4]== board[7]) && (board[1] != '')) winner = board[1]
  if ((board[2] == board[5]) && (board[5]== board[8]) && (board[2] != '')) winner = board[2]

  return winner
}

To make it easier I will post a photo below with numbers .

ttt2.PNG

I made one more function that will display who won the game.

Here I take winner variable from function checkWin(), check if it is not empty. If we have something I change hidden div display to visible. And according to the value of winner proper message is displayed to the user.

function displayWinner(winner){
  if (winner == '') return

  winneris.style.display = "block";

  if (winner == userchoice) winneris.innerHTML = "You won! Congrats!"
  if (winner == aichoice) winneris.innerHTML = "You lost to the computer :("
}

Restart

To finish it all, only thing left now is our restart function. Here I just clear the board, enable the buttons to choose sign, close the win div and refresh the board with displayBoard()

Game live

Feel free to play the game here: click

If you find any mistakes or you have ideas how to refactor my solutions to be simplier/ better please let me know :)

Did you find this article valuable?

Support knowledge blog by becoming a sponsor. Any amount is appreciated!