Assignment Solution – Tic Tac Toe

Please post your questions below :raised_hands:

function getBoardString(gameBoard){
    let BoardString = '';
    for(let i = 0; i < gameBoard.length; i++){
        BoardString = BoardString + `${gameBoard[i] ?? i+1}`;
        if(i % 3 === 2){
            BoardString = BoardString + '\n';  
        }
    }

    return BoardString;
}

function getUserInput(nextPlayerSymbol, gameBoard) {
    return +prompt(`Board state: \n${getBoardString(gameBoard)} Enter ${nextPlayerSymbol}'s next move (1-9)`);   
}

function isMoveValid(move, gameBoard) {
    let boardIndex = move-1;
    if(gameBoard[boardIndex] === null && move>=1 && move<=9 && typeof move === 'number') {
        return true;
    }
    else 
    return false;
    
}

function makeAMove(gameBoard, nextPlayerSymbol) {
    // clone the game board before placing moves in it
    let move = getUserInput(nextPlayerSymbol);
    let newGameBoard = [...gameBoard];
    do {
        move = getUserInput(nextPlayerSymbol, gameBoard);
    } while ( !isMoveValid(move, gameBoard) );
    let boardIndex = move-1;
    newGameBoard[boardIndex] = nextPlayerSymbol;
     return newGameBoard;
}

function hasLastMoverWon(lastMove, gameBoard) {
    let winnerCombos = [
        [0, 1, 2], 
        [3, 4, 5], 
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7], 
        [2, 5, 8],
        [0, 4, 8], 
        [2, 4, 6]
    ];
    for (let [i1, i2, i3] of winnerCombos) {
        if (gameBoard[i1] === lastMove &&
            gameBoard[i1] === gameBoard[i2] && 
            gameBoard[i1] === gameBoard[i3] 
           ) {
            return true;
        }
    }
    return false;
}

function isGameOver(gameBoard, currentPlayerSymbol) {
    // 1. check if there is a winner 
    const lastMove = currentPlayerSymbol;
    if (hasLastMoverWon(lastMove, gameBoard) ) {
        // Write a message that last mover has won the game
        alert(`Congratulations, ${currentPlayerSymbol} has won the game`);
        return true;
    }
    // 2. check if the board is full
    if(!gameBoard.includes(null)){
        alert(`The game has ended in a draw`);
        return true;
    }
    else {
        return false;
    }
    // Return: winner/draw OR game is still in progress
}

function ticTacToe() {
    // State space 
    let gameBoard = new Array(9).fill(null);
    let currentPlayerSymbol = null;

    // Computations 
   do {
        currentPlayerSymbol = currentPlayerSymbol === 'X' ? 'O' : 'X'; 
        gameBoard = makeAMove(gameBoard, currentPlayerSymbol);
    } while ( !isGameOver(gameBoard, currentPlayerSymbol) );
    
} 

This code is copied exactly from the solution videos, but I kept getting these errors in the console. Why is it unable to read the length of the gameBoard array? It is defined in the ticTacToe function.
I think all of the other errors are caused by this as well.

You are passing only one parameter for getUserInput function, that function takes two parameters, if you look at the getUserInput function it receives two parameters


function getUserInput(nextPlayerSymbol, gameBoard) {
    return +prompt(`Board state: \n${getBoardString(gameBoard)} Enter ${nextPlayerSymbol}'s next move (1-9)`);   
}

}
And pass the second parameter to getBoardString function So when this function is called you are not passing any thing, if you see that function it loops,

function getBoardString(gameBoard){
    let BoardString = '';
    for(let i = 0; i < gameBoard.length; i++){
        BoardString = BoardString + `${gameBoard[i] ?? i+1}`;
        if(i % 3 === 2){
            BoardString = BoardString + '\n';  
        }
    }

    return BoardString;
}

if you don pass anything to that function, gameBoard is undefined and that’s why it is throwing cannot read properties of undefined (reading) length
Here is the code from the solution,

function renderBoard(board) {
  let innerHtml = `<pre>${getBoardString(board)}</pre>`;
  document.querySelector(".js-container").innerHTML = innerHtml;
}

function getBoardString(gameBoard) {
  let boardString = "";

  for (let i = 0; i < gameBoard.length; i += 1) {
    boardString += `${gameBoard[i] ?? i + 1}`;
    if (i % 3 === 2) {
      boardString += "\n";
    }
  }
  return boardString;
}

function getUserInput(nextPlayerSymbol, gameBoard) {
  return +prompt(
    `Board:\n${getBoardString(
      gameBoard
    )} Enter ${nextPlayerSymbol}'s next move (1-9):`
  );
}

function isMoveValid(move, gameBoard) {
  const boardIndex = move - 1;
  // move is a number
  // move is between 1 and 9 (inclusive)
  // gameBoard does not contain a symbol at the place of the move

  return (
    typeof move === "number" &&
    move >= 1 &&
    move <= 9 &&
    gameBoard[boardIndex] === null
  );
}

function makeAMove(gameBoard, nextPlayerSymbol) {
  const newGameBoard = [...gameBoard];
  let move = null;
  do {
    move = getUserInput(nextPlayerSymbol, gameBoard);
  } while (!isMoveValid(move, gameBoard));
  const boardIndex = move - 1;
  newGameBoard[boardIndex] = nextPlayerSymbol;
  renderBoard(newGameBoard);
  return newGameBoard;
}

function hasLastMoverWon(lastMove, gameBoard) {
  let winnerCombos = [
    [0, 1, 2],
    [3, 4, 5],
    [6, 7, 8],
    [0, 3, 6],
    [1, 4, 7],
    [2, 5, 8],
    [0, 4, 8],
    [2, 4, 6]
  ];
  for (let [i1, i2, i3] of winnerCombos) {
    if (
      gameBoard[i1] === lastMove &&
      gameBoard[i1] === gameBoard[i2] &&
      gameBoard[i1] === gameBoard[i3]
    ) {
      return true;
    }
  }
  return false;
}

function isGameOver(gameBoard, currentPlayerSymbol) {
  // 1. check if there is a winner
  if (hasLastMoverWon(currentPlayerSymbol, gameBoard)) {
    // Write a message that last mover has won the game
    alert(`Congratulations, ${currentPlayerSymbol} has won the game`);
    return true;
  }
  // 2. check if the board is full
  // if (draw) { alert(...); return true; }
  if (gameBoard.every((element) => element !== null)) {
    alert(`Draw. Game over.`);
    return true;
  }

  return false;
}

function ticTacToe() {
  // State space
  let gameBoard = new Array(9).fill(null);
  let currentPlayerSymbol = null;

  // Computations
  renderBoard(gameBoard);
  do {
    currentPlayerSymbol = currentPlayerSymbol === "X" ? "O" : "X";
    gameBoard = makeAMove(gameBoard, currentPlayerSymbol);
  } while (!isGameOver(gameBoard, currentPlayerSymbol));
}

ticTacToe();

I deviated from the way it was handled through the videos and made a different way using the DOM.
Here are my files.

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Static Template</title>
    <link rel="stylesheet" href="style.css" />
  </head>
  <body>
    <h1>Prompt Tic-Tac-Toe</h1>
    <div class="js-container">
      <div class="next-player">
        Current Turn: X
      </div>
      <div class="js-board board">
        <div class="js-row row">
          <div class="js-cell cell" data-cell="0">-</div>
          <div class="js-cell cell" data-cell="1">-</div>
          <div class="js-cell cell" data-cell="2">-</div>
        </div>
        <div class="js-row row">
          <div class="js-cell cell" data-cell="3">-</div>
          <div class="js-cell cell" data-cell="4">-</div>
          <div class="js-cell cell" data-cell="5">-</div>
        </div>
        <div class="js-row row">
          <div class="js-cell cell" data-cell="6">-</div>
          <div class="js-cell cell" data-cell="7">-</div>
          <div class="js-cell cell" data-cell="8">-</div>
        </div>
      </div>
      <div class="js-winner"></div>
      <button class="reset-game">Reset Game</button>
    </div>
    <script src="tictactoe.js"></script>
  </body>
</html>

style.css

body {
  box-sizing: border-box;
}

.board {
  width: 150px;
  border: 2px solid black;
}

.row {
  display: flex;
}

.cell {
  width: 50px;
  height: 50px;
  border: 1px solid black;
  text-align: center;
  line-height: 50px;
}

.reset-game {
  margin-top: 12px;
  display: none;
}

tictactoe.js - the good stuff!

let currentTurnSymbol = "X";
let gameBoard = [null, null, null, null, null, null, null, null, null];
let winner = null;
let winnerCombos = [
  [0, 1, 2],
  [3, 4, 5],
  [6, 7, 8],
  [0, 3, 6],
  [1, 4, 7],
  [2, 5, 8],
  [0, 4, 8],
  [2, 4, 6]
];

const $cell = document
  .querySelectorAll(".cell")
  .forEach((elem) => document.addEventListener("click", handleClickMove));

const $resetGame = document
  .querySelector(".reset-game")
  .addEventListener("click", newGame);

function handleClickMove(e) {
  if (winner) {
    return false;
  }

  if (gameBoard[e.target.dataset.cell]) {
    return false;
  }

  gameBoard[e.target.dataset.cell] = currentTurnSymbol;
  e.target.innerHTML = currentTurnSymbol;

  isGameOver(currentTurnSymbol, gameBoard);

  currentTurnSymbol = currentTurnSymbol === "X" ? "O" : "X";
  document.querySelector(
    ".next-player"
  ).innerHTML = `Current Turn: ${currentTurnSymbol}`;
}

function isGameOver(player, gameBoard) {
  // Check for Winner
  for (let [i1, i2, i3] of winnerCombos) {
    if (
      gameBoard[i1] === player &&
      gameBoard[i1] === gameBoard[i2] &&
      gameBoard[i1] === gameBoard[i3]
    ) {
      winner = player;
      document.querySelector(
        ".js-winner"
      ).innerHTML = `Congrats! ${winner} has won the game.`;
      document.querySelector(".reset-game").style.display = "block";
    }
  }

  // Check for draw
  if (!gameBoard.includes(null)) {
    document.querySelector(".js-winner").innerHTML =
      "It's a draw! Click reset to play again!";
    document.querySelector(".reset-game").style.display = "block";
  }

  return false;
}

function newGame() {
  winner = null;
  gameBoard = [null, null, null, null, null, null, null, null, null];
  currentTurnSymbol = "X";
  document.querySelector(".reset-game").style.display = "none";
  document.querySelector(".js-winner").innerHTML = "";
  document.querySelectorAll(".cell").forEach((elem) => (elem.innerHTML = "-"));
}

When I’m learning this Section, I’m trying so hard to display my work in the browser in Sandbox as below:

This image below should be displayed in the Sandbox browser:

How can I do it??? I would appreciate your help.

1 Like

Have you try to run it on a local server? like the live server of the course using vs code?

Also, can you share your code in the following way so i can try to replicate the issue?

Carlos Z

1 Like

I have a question about the use of β€œx” in the tictactoe game (for Javascript). In the video, [β€œx”, null, null] is used. But why use β€œx”? Why not use [ null, null, null]?

1 Like

I just found out, it’s in the video, and it should be [ null, null, null] :grinning:

1 Like

I copied the tictactoe solution given in the course, then pasted it into my browser console (inspect function). It did not work. What is the actual tictactoe solution that works?

1 Like

Could you please share the link where you get the solution of the exercise? :nerd_face:

Carlos Z

@thecil
I copied the tictactoe solution given in the course, then pasted it into my browser console (inspect function). It did not work. What is the actual tictactoe solution that works? (The code which I copied is in the next message, properly formatted.)

function printBoard(gameBoard) {
    let gameString = '';
    for (let i = 0; i <= 6; i += 3) {
        gameString += `${gameBoard[i] ?? i+1}${gameBoard[i+1] ?? i+2}${gameBoard[i+2] ?? i+3}\n`;
    }
    return gameString;
}

function getUserInput(nextPlayerSymbol, gameBoard) {
    return +prompt(`Board:\n${printBoard(gameBoard)} Enter your next move (1-9) :`);
}

function isMoveValid(coordinates, gameBoard) {
    return (
        typeof coordinates === 'number' && 
        coordinates >= 1 &&
        coordinates <= 9 && 
        gameBoard[coordinates - 1] === null
    ); 
}

function makeAMove(gameBoard, nextPlayerSymbol) {
    const newGameBoard = JSON.parse(JSON.stringify(gameBoard));
    let coordinates;
    do {
        coordinates = getUserInput(nextPlayerSymbol, gameBoard);
    } while ( !isMoveValid(coordinates, gameBoard) );
    newGameBoard[coordinates - 1] = nextPlayerSymbol;
    return newGameBoard;
}

function hasLastMoverWon(lastMove, gameBoard) {
    let winnerCombos = [
        [0, 1, 2], 
        [3, 4, 5], 
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7], 
        [2, 5, 8],
        [0, 4, 8], 
        [2, 4, 6]
    ];
    for (let [i1, i2, i3] of winnerCombos) {
        if (gameBoard[i1] === lastMove &&
            gameBoard[i1] === gameBoard[i2] && 
            gameBoard[i1] === gameBoard[i3] 
           ) {
            return true;
        }
    }
    return false;
}

function isGameOver(gameBoard, currentPlayerSymbol) {
    // 1. check if there is a winner
    if (hasLastMoverWon(currentPlayerSymbol, gameBoard) ) {
        // Write a message that last mover has won the game
        alert(`Congratulations, ${currentPlayerSymbol} has won the game`);
        return true;
    }
    // 2. check if the board is full
    const isBoardFull = gameBoard.every(element => element !== null);
    if (isBoardFull) {
        alert(`${printBoard(gameBoard)}\nDraw. Game Over.`);
        return true;
    }
    return false;
}

function ticTacToe() {
    // State space
    let gameBoard = new Array(9).fill(null);
    let players = ['X', 'O'];
    let nextPlayerIndex = 1;

    // Computations
   do {
        nextPlayerIndex = (nextPlayerIndex + 1) % 2;
        currentPlayerSymbol = players[nextPlayerIndex];
        gameBoard = makeAMove(gameBoard, currentPlayerSymbol);
    } while ( !isGameOver(gameBoard, currentPlayerSymbol) );
}

@thecil

Hi Carlos,

The solution to the tictactoe game (which did not work for me) can be found with this link.

https://academy.moralis.io/lessons/writing-good-javascript-conclusion

It is under the video.

Sincerely,
Aaron

1 Like

The exercise works as expected on the browser console, once you have paste the code on it, you just need to execute the initialize function which is ticTacToe(), just type that one into the console and the game should start properly.

Carlos Z

2 Likes

Thank you Carlos! Now it works! I am new to programming!

2 Likes