Week 10 – Continuing Tic Tac Toe in JavaScript

Share on facebook
Share on google
Share on twitter
Share on linkedin

This week’s focus was to complete the remaining core goals set for the Tic Tac Toe challenge:

  • The player can start a 3×3 game in the browser
  • Make it look pretty

Core Goal: The player can start a 3×3 game in the browser

Getting a player’s mark to show on the board when clicked

Event listeners were set up for each position / div. When clicked, the player’s mark would be displayed:

one.addEventListener("click", function(){
  gameController.takeTurn(1);
  one.innerHTML = board.grid[0];
});
- note, this was repeated for the remaining 8 divs / positions

Switching to the next player when a move had been made

The above would only allow ‘x’ to be played in all positions on the board and didn’t switch to the second player. To resolve this, the takeTurn function in the Game Controller class needed to be tweaked to contain the togglePlayer function:

takeTurn(position) {
  if (this.game.board.isValidPosition(position)) {
    this.game.makeMove(this.game.currentPlayer, position);
this.togglePlayer();
  };
}

Stop allowing moves to be made if player has won

The above would not prevent players from continuing to make moves on the grid if a player had won. In addition, regardless of whether a player had won, the player would be switched.

To rectify this, the takeTurn method was amended to only allow the current player to make a move if the position they have selected is valid and the game isn’t a tie or a player hasn’t won. After the move has been made, only if the game wasn’t a tie or a player hasn’t won, then the player would switch.

takeTurn(position) {
  if (this.game.board.isValidPosition(position) && this.canContinuePlaying()) {
    this.game.makeMove(this.game.currentPlayer, position);
    if (this.canContinuePlaying()) {
      this.togglePlayer();
    }
  }
}

Duplicated code and using event.target

Although the above worked, having an event listener for each position resulted in a mountain of repeated code. To reduce this, our mentor, Dan, recommended adding an event listener to the whole grid (the player-grid div) that would be able to handle clicks anywhere on the grid. Then using event.target to see which cell was clicked.

This worked great, however if the user was to click outside of the player-grid div, undefined displayed in the browser and parts of the grid would disappear!

undefined displayed when clicking outside of the player-grid div

To overcome this, the action that would occur when a player clicked outside of the grid needed to be defined. The divs were single numbers such as 1, 2, 3 etc. As a result, an event should only occur if the div was a number. If it wasn’t a number, nothing would occur:

function singleTurn(event) {
  const cellIdString = event.target.id;
  const cellIdNumber = parseInt(cellIdString);
  let selectedCell = document.getElementById(cellIdString);
  if(parseInt(event.target.id) === cellIdNumber) {
    ...
  } else {
return;
  }

Core Goal: Make it look pretty

An aim was to change the colour of the selected div in the grid when a player made a valid move. In style.css, the following css classes were created:

.player-one-move {
  background-color: #F9CF3C;
}
.player-two-move {
  background-color: #FD70BC;
}

In index.js, a function was created that would apply the class to the div using classList.add. If the player is ‘x’, then .player-one-move is applied to the div. If the player is ‘o’, player-two-move is applied instead. This function was then included within the singleTurn function.

function highlight(cellId) {
  if (gameController.game.currentPlayer === 'x') {
document.getElementById(cellId).classList.add('player-one-move');
  } else if (gameController.game.currentPlayer === 'o') {
document.getElementById(cellId).classList.add('player-two-move');
  }
}
the highlight function in action

DRY — Don’t Repeat Yourself!

…don’t repeat yourself (DRY) is a principle of software development aimed at reducing repetition of software patterns…
Source — Wikipedia

Along with condensing the numerous event listeners, here are other examples where repeated code was refactored:

Clearing the content in the divs when a new game is started:

Before:

  document.getElementById("1").innerHTML = '';
  document.getElementById("2").innerHTML = '';
  document.getElementById("3").innerHTML = '';
  document.getElementById("4").innerHTML = '';
  document.getElementById("5").innerHTML = '';
  document.getElementById("6").innerHTML = '';
  document.getElementById("7").innerHTML = '';
  document.getElementById("8").innerHTML = '';
  document.getElementById("9").innerHTML = '';

After:

const divIds = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
...
divIds.forEach(function(div) {
    document.getElementById(div).innerHTML = '';
  });

Removing the background colours from the divs when a new game is started:

Before:

document.getElementById('1').classList.remove('player-one-move');  document.getElementById('1').classList.remove('player-two-move');
document.getElementById('2').classList.remove('player-one-move');  document.getElementById('2').classList.remove('player-two-move');
document.getElementById('3').classList.remove('player-one-move');  document.getElementById('3').classList.remove('player-two-move');
document.getElementById('4').classList.remove('player-one-move');  document.getElementById('4').classList.remove('player-two-move');
document.getElementById('5').classList.remove('player-one-move');  document.getElementById('5').classList.remove('player-two-move');
document.getElementById('6').classList.remove('player-one-move');  document.getElementById('6').classList.remove('player-two-move');
document.getElementById('7').classList.remove('player-one-move');  document.getElementById('7').classList.remove('player-two-move');
document.getElementById('8').classList.remove('player-one-move');  document.getElementById('8').classList.remove('player-two-move');
document.getElementById('9').classList.remove('player-one-move');  document.getElementById('9').classList.remove('player-two-move');

After:

const divIds = ['1', '2', '3', '4', '5', '6', '7', '8', '9'];
...
divIds.forEach(function(div) {
    document.getElementById(div).classList.remove('player-one-move');
    document.getElementById(div).classList.remove('player-two-move');
  });

What I’ve learned:

  • Using event.target
  • Spotting where to refactor
  • Taking a break away from the screen when I’m stuck helps with processing the problem

I’ve struggled with:

  • Getting my head around integrating the classes created with the front end to allow the game to be played in the browser

What I want to focus on: