Assignment – Bottom-up Planning

Share your exercise results here, and don’t be afraid to ask questions if you need help!

It’s a little sloppy in that I had to set the current player to the opposite of the desired choice when defining the variable in the ticTacToe() function because it needed to be reversed at the beginning of the do/while loop.

https://codesandbox.io/s/tender-colden-3ke87?file=/game.js

/**
 * Prompts user for move coordinates.
 */
function getUserInput(nextPlayerSymbol) {
  return prompt(`Place ${nextPlayerSymbol} at coordinate (0 - 9):`);
}

/**
 * Checks if square is available and coordinates are within the game board.
 */
function isMoveValid(coordinates, gameBoard) {
  return (
    gameBoard[coordinates] === null && Number(coordinates) < gameBoard.length
  );
}

/**
 * If move is valid, applies current move to game board.
 */
function makeAMove(gameBoard, nextPlayerSymbol) {
  let coordinates = getUserInput(nextPlayerSymbol);
  let newGameBoard = [...gameBoard];

  // Continue prompting user until valid coordinates provided
  while (!isMoveValid(coordinates, gameBoard)) {
    coordinates = getUserInput(nextPlayerSymbol);
  }

  // Apply move to copy of game board
  newGameBoard[coordinates] = nextPlayerSymbol;

  return newGameBoard;
}

/**
 * Checks if last move satisfies 'win' criteria.
 */
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;
}

/**
 * Checks if game has ended with a win or draw.
 */
function isGameOver(gameBoard, currentPlayerSymbol) {
  const lastMove = currentPlayerSymbol;

  // Check if there is a winner
  if (hasLastMoverWon(lastMove, gameBoard)) {
    // Write a message that last mover has won the game
    alert(`Congratulations, ${currentPlayerSymbol} has won the game`);
    return true;
  }
  // Check if the board is full
  if (!gameBoard.includes(null)) {
    alert("Game has ended in a draw!");
    return true;
  }

  // Continue game
  return false;
}

/**
 * Initializes game and handles player turns.
 */
function ticTacToe() {
  let gameBoard = new Array(9).fill(null);
  let players = ["X", "O"];
  let nextPlayerIndex = 1;
  let currentPlayerSymbol = players[nextPlayerIndex];

  // Process turns
  do {
    // Set current player symbol
    if (nextPlayerIndex === 0) {
      nextPlayerIndex = 1;
    } else {
      nextPlayerIndex = 0;
    }
    currentPlayerSymbol = players[nextPlayerIndex];

    // Make a move
    gameBoard = makeAMove(gameBoard, currentPlayerSymbol);

    // Show updated game board to user
    alert(`
      ${gameBoard[0]}|${gameBoard[1]}|${gameBoard[2]}
      ${gameBoard[3]}|${gameBoard[4]}|${gameBoard[5]}
      ${gameBoard[6]}|${gameBoard[7]}|${gameBoard[8]}
    `);
  } while (!isGameOver(gameBoard, currentPlayerSymbol));
}

// Initialize game
ticTacToe();
2 Likes

Im lost… i copied and past the code into sandbox in the index.html under the script tag so i can edit and run the code. i think we need to learn how to use sandbox first

var lastMove = null;

function getUserInput(nextPlayerSymbol) {
    let coordinates = prompt('Enter coordinates (xy) between 0-2: ');
    [x,y] = [...coordinates];
    return Number.parseInt(x),Number.parseInt(y);
}

function isMoveValid(coordinates, gameBoard) {
    if ((gameBoard[(Number.parseInt(y)*Number.parseInt(3)) + Number.parseInt(x)] == null) && (x<3) && (y<3)){
        return true
    }
        return false;
}

function makeAMove(gameBoard, nextPlayerSymbol) {
    // clone the game board before placing moves in it
    let gb = gameBoard.slice();
    let coord = null;
    do {
       coord = getUserInput(nextPlayerSymbol);
    } while ( !isMoveValid(coord, gb) );
    lastMove = (Number.parseInt(y)*Number.parseInt(3)) + Number.parseInt(x);
    gb[lastMove] = nextPlayerSymbol;
    return gb
}

function hasLastMoverWon(_lastMover, 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]
    ];
    console.log("lastmove" + lastMove)
    for (let [i1, i2, i3] of winnerCombos) {
        console.log("combos:  " + i1 + ", " + i2 + ', ' + i3)
        if (gameBoard[i1] == _lastMover &&
            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 (gameBoard.every(e => e != null)){
        return true
    }

    // Return: winner/draw OR game is still in progress
}

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

    // Computations 
   do {
        currentPlayerSymbol = players[nextPlayerIndex];
        gameBoard = makeAMove(gameBoard, currentPlayerSymbol);
        console.log(gameBoard.slice(0,3));
        console.log(gameBoard.slice(3,6));
        console.log(gameBoard.slice(6,9));

        // console.log("lastmove" + lastMove)

        if (nextPlayerIndex == 0){
            nextPlayerIndex = 1;
        } else{
            nextPlayerIndex = 0;
        }

    } while ( !isGameOver(gameBoard, currentPlayerSymbol) );
    
    // Return value
    // return undefined;
} 
1 Like

I could’nt use it either… but if you program in visual studio code and copy the entire code (ctrl + a ) to the Chrome developer console and press enter… and then type in ticTacToe() it should work. It did for me.

let lastMove;

function getUserInputAsBoardIndex(nextPlayerSymbol, gameBoard) {
    const promptMsg = `${getBoardPrintStr(gameBoard)}\nEnter move for ${nextPlayerSymbol} by specifying row and column separated by comma, e.g. 1,1. Board is [3,3]`;
    const result = prompt(promptMsg);
    return convertUserInputToBoardIndex(result);
}

function convertUserInputToBoardIndex(userInput) {
    if (!userInput)
        return null;

    const inputArr = userInput.trim().split(',');
    if (inputArr.length !== 2) 
      return null;

    const x = inputArr[0].trim();
    const y = inputArr[1].trim();

    if (!Number.isInteger(Number(x)) || !Number.isInteger(Number(y)))
        return null;

    if (x > 3 || y > 3)
        return null;

    // End users count from 1, thus the subtraction
    const boardIndex = ((x - 1) * 3) + (y - 1);
    return boardIndex;
}

function isMoveValid(boardIndex, gameBoard) {
    if (Number.isInteger(boardIndex)) {
        if (boardIndex >= 0 && boardIndex <= 8) {
            if (!gameBoard[boardIndex]) {
                return true; // valid index of empty slot board
            }
        }
    }

    return false;
}

function makeAMove(gameBoard, nextPlayerSymbol) {
    let userInputBoardIndex;
    do {
      userInputBoardIndex = getUserInputAsBoardIndex(nextPlayerSymbol, gameBoard);
    } while (!isMoveValid(userInputBoardIndex, gameBoard));

    lastMove = nextPlayerSymbol;
    // clone the game board before placing moves in it
    const newGameBoard = [...gameBoard];
    newGameBoard[userInputBoardIndex] = 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(lastMove, gameBoard)) {
        // Write a message that last mover has won the game
        alert(`${getBoardPrintStr(gameBoard)}\nCongratulations, ${currentPlayerSymbol} has won the game`);
        return true;
    }

    const hasBoardEmptySlots = gameBoard.some(x => !x);
    if (hasBoardEmptySlots) {
        console.log('Game in progress...');
        return false;
    } else {
        alert(`${getBoardPrintStr(gameBoard)}\nDraw!`);
        return true;
    }
}

function getBoardPrintStr(gameBoard) {
    let str = '';
    let newLineCounter = 0;

    gameBoard.forEach(element => {
        str += `${element || '__'}  `;
        if (newLineCounter >= 2) {
            str += '\n';
            newLineCounter = 0;
            // new line and reset
        } else {
            newLineCounter++;
        }
    });

    return str;
}

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

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

    // Return value: undefined
} 

Can someone explain me how he made the destructuring with the [i1, i2, i3] in this function :

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;
}

I already read the chapter “Destructuring” of the PDF “ES6”.

Thanks for the help!

With a little help from the Internet :slight_smile:

@AdamFortuna @zsolt-nagy Would you please review the TODOs in the comments of my code?

  1. Not sure why we have to clone the board in makeMove(). The game seems to work fine without cloning
  2. I tried to break out changePlayer() into a separate function. The function worked fine, but I could not get the new player value to return to the calling function. Can you explain why?

Thanks and onward!

const PLAYERX_WON = 'Player X won!! Game over.';
const PLAYERO_WON = 'Player 0 won!! Game over.';
const DRAW = 'It\'s a draw. Game over.';


function getUserInput(board, currentPlayer) {
    displayBoard(board);

    let tic = prompt(`Player: ${currentPlayer}. It's your move.
    A valid move is a number from 1 - 9, which is not already taken.`);
    return tic - 1;
}

function displayBoard(board) {
    
    alert(`${getBoardString(board)}`);
}


function getBoardString(board) { 
    let boardString = '';
    let tile = 0;

    for (let i = 0; i < board.length; i += 1) {
        boardString += board[i] === '' ? ` ${i+1} ` : ` ${board[i]} `;
        if (i === 2) {boardString += '\n'};
      	if (i === 5) {boardString += '\n'};
        if (i === 8) {boardString += '\n'};
        }
    return boardString;
}

function ticTacToe() {
    
    let board = ['','','','','','','','',''];
    //let board = new Array(9).fill('');
    let currentPlayer = 'O';

    do {
        // changePlayer(currentPlayer); <--TODO: The new changePlayer value is not passed back to this call. Why??
        currentPlayer = currentPlayer === 'X' ? 'O' : 'X';
        board = makeMove(board, currentPlayer);
    } while ( !isGameOver(board, currentPlayer) );

    console.log('END')
}

function changePlayer(currentPlayer) {
    currentPlayer = currentPlayer === 'X' ? 'O' : 'X'; 
    //console.log(`changePlayer() returns: ${currentPlayer}`)  
    return currentPlayer; // TODO: I could not get the value to pass back to the calling function
}

function makeMove(board, currentPlayer) {
    //TODO: why do we need to clone the board? The game seems to work fine without cloning
    let newBoard = [...board];
    do {
        //have to use var to make 'move' visible outside the 'do' block
        var move = getUserInput(board, currentPlayer);
    } while ( !isMoveValid(move, board) );
    newBoard[move] = currentPlayer;
    return newBoard;
}

function isMoveValid (move, board) {
 let result = board[move] === '' ? true : false;
 return result;
}

function isGameOver(board, currentPlayer) {
    let gameWon = false;

    let winningCombos = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8],
        [0, 3, 6],
        [1, 4, 7],
        [2, 5, 8],
        [0, 4, 8],
        [2, 4, 6]
    ];
    
    //check if there is a winner
    for (let i = 0; i <= 7; i += 1) {
        const winCondition = winningCombos[i];
        const a = board[winCondition[0]];
        const b = board[winCondition[1]];
        const c = board[winCondition[2]];
        if (a === '' || b === '' || c === '') {
            continue;
        }
        if (a === b && b === c) {
            gameWon = true;
            break;
        }
    }
    
    if (gameWon) {
        announce(currentPlayer === 'X' ? PLAYERX_WON : PLAYERO_WON);
        displayBoard(board);
        return true;
    }

    if ( !board.includes('') ) { 
        announce(DRAW);
        displayBoard(board);
        return true;
    }
  return false;
}

function announce(message) {
    switch(message){
        case PLAYERO_WON:
            alert(`${PLAYERO_WON}`);
            console.log(`${PLAYERO_WON}`);
            break;
        case PLAYERX_WON:
            alert(`${PLAYERX_WON}`);
            console.log(`${PLAYERX_WON}`);
            break;
        case DRAW:
            alert(`${DRAW}`);
            console.log(`${DRAW}`);
            break;
    }
}

//console.log(ticTacToe())
1 Like

Hey @pivot, hope you are ok.

I think you have just complete the exercise in a different way, you might not need to clone the board since your getUserInput function does return the index position of the array.

Try with this, instead of changing the value of the currentPlayer on that function, try to just calculate and return the next value.

function changePlayer(currentPlayer) {
    let res = currentPlayer === 'X' ? 'O' : 'X'; 
    //console.log(`changePlayer() returns: ${currentPlayer}`)  
    return res; // TODO: I could not get the value to pass back to the calling function
}

Then in your ticTacToe function change to:

function ticTacToe() {
    
    let board = ['','','','','','','','',''];
    //let board = new Array(9).fill('');
    let currentPlayer = 'O';

    do {
        // changePlayer(currentPlayer); <--TODO: The new changePlayer value is not passed back to this call. Why??
        currentPlayer = changePlayer(currentPlayer)
        board = makeMove(board, currentPlayer);
    } while ( !isGameOver(board, currentPlayer) );

    console.log('END')
}

Hope it helps :nerd_face:

Carlos Z

Hi @thecil

Great. Thanks for the response.

  1. When is it necessary to clone an array when passing as an argument? How do you decide?

  2. I made the change you suggested and changePlayer() works now. However, i don’t understand why passing back a reference to new variable (e.g., ‘return res;’) works, while modifying currentPlayer and passing that back doesn’t. Can you elucidate?

Thanks and best

1 Like

Can’t tell for sure, depends on the logic you need for it, although, is good practice to make a reference of a variable value, instead directly working on it, depend on the case, might be necessary or not.

There is some JS fundamental about this, the first time currentPlayer variable is declared, its on the tictactoe() function, but it’s a local variable (let), is just accessible only by the function itself.

Keeping that in mind, each of the other declaration of currentPlayer is not directly related to the same variable, although the name is the same. For example:

function makeMove(board, currentPlayer){...} and function changePlayer(currentPlayer){...} share the same argument name, inside their body logic, you will use the same argument name to work within it, but their not related at all in value’s terms.

The reference is just a calculation I made before returning the result, just to be sure my calculation work. :nerd_face:

Carlos Z

I kind of took this exercise a step further and completed the tic tac toe game my own way.
Please see code for html, css, and js below

Index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>JS Intro Playground</title>
    <link rel="stylesheet" href="./indexCSS.css">
    <script src="./TicTacToe.js"></script>
</head>

<body>

    <h1>Tic-Tac-Toe</h1>
    <h4>Instructions (2 Players)</h4>
    <p style="width: 500px;">Take turns claiming a spot on the game board. 
        Player 1 will be denoted by the letter X while Player 2 will be denoted by the letter O.</p> 
    <p style="width: 500px;">A player wins if they can claim all three spots either horizontally, vertically, or diagonally. 
        If the objective is not met before all spots are filled then the game results in a tie</p> 
    
    <!--Play Tic Tac Toe Button-->
    <div style="padding-top: 25px; padding-bottom: 25px;">
        <button id="playButton" style="background-color:lightgreen;" onclick="clearBoard()">Play Tic-Tac-Toe</button>
        <p id="playerMsg"></p>
    </div>

    <!--HTML Gameboard-->
    <p></p>
    <table id="gameBoard" style="width:250px;">
        <tr>
            <td><button id="Tic1" class="ticButton" onclick="checkBoard('Tic1')" disabled></button></td>
            <td><button id="Tic2" class="ticButton" onclick="checkBoard('Tic2')" disabled></button></td>
            <td><button id="Tic3" class="ticButton" onclick="checkBoard('Tic3')" disabled></button></td>
        </tr>
        <tr>
            <td><button id="Tic4" class="ticButton" onclick="checkBoard('Tic4')" disabled></button></td>
            <td><button id="Tic5" class="ticButton" onclick="checkBoard('Tic5')" disabled></button></td>
            <td><button id="Tic6" class="ticButton" onclick="checkBoard('Tic6')" disabled></button></td>
        </tr>
        <tr>
            <td><button id="Tic7" class="ticButton" onclick="checkBoard('Tic7')" disabled></button></td>
            <td><button id="Tic8" class="ticButton" onclick="checkBoard('Tic8')" disabled></button></td>
            <td><button id="Tic9" class="ticButton" onclick="checkBoard('Tic9')" disabled></button></td>
        </tr>
      </table> 
    
</body>
</html>

TicTacToe.js

//Global Var
let currPlayer;
let gameOver;
let moveCount = 0;

function playGame() {
  
  let gameOver = false;
  clearBoard();
  document.getElementById("playButton").innerHTML = "Reset Game";

  document.getElementById("playerMsg").innerHTML = "Player 1 make your move!";
  
}

function clearBoard() {

  // Reset currPlayer and game status
  currPlayer = 'X';
  gameOver = false;
  moveCount = 0;

  // Setup some user messages
  document.getElementById("playButton").innerHTML = "Reset Game";
  document.getElementById("playerMsg").innerHTML = "Player 1 make your move!";

  // Enabled all buttons
  document.getElementById("Tic1").disabled = false;
  document.getElementById("Tic2").disabled = false;
  document.getElementById("Tic3").disabled = false;
  document.getElementById("Tic4").disabled = false;
  document.getElementById("Tic5").disabled = false;
  document.getElementById("Tic6").disabled = false;
  document.getElementById("Tic7").disabled = false;
  document.getElementById("Tic8").disabled = false;
  document.getElementById("Tic9").disabled = false;

  // Clear moves
  document.getElementById("Tic1").innerHTML = '';
  document.getElementById("Tic2").innerHTML = '';
  document.getElementById("Tic3").innerHTML = '';
  document.getElementById("Tic4").innerHTML = '';
  document.getElementById("Tic5").innerHTML = '';
  document.getElementById("Tic6").innerHTML = '';
  document.getElementById("Tic7").innerHTML = '';
  document.getElementById("Tic8").innerHTML = '';
  document.getElementById("Tic9").innerHTML = '';

  // Clear background
  document.getElementById("Tic1").style.backgroundColor = "white";
  document.getElementById("Tic2").style.backgroundColor = "white";
  document.getElementById("Tic3").style.backgroundColor = "white";
  document.getElementById("Tic4").style.backgroundColor = "white";
  document.getElementById("Tic5").style.backgroundColor = "white";
  document.getElementById("Tic6").style.backgroundColor = "white";
  document.getElementById("Tic7").style.backgroundColor = "white";
  document.getElementById("Tic8").style.backgroundColor = "white";
  document.getElementById("Tic9").style.backgroundColor = "white";

}

function checkBoard(ticPressed) {

  document.getElementById(ticPressed).innerHTML = currPlayer;
  document.getElementById(ticPressed).disabled = true;
  moveCount ++;

  if (currPlayer == 'X') {
    currPlayer = 'O';
    document.getElementById("playerMsg").innerHTML = "Player 2 make your move!";
  } else {
    currPlayer = 'X';
    document.getElementById("playerMsg").innerHTML = "Player 1 make your move!";
  }

  checkGameOver();
  
}

function checkGameOver() {
  
  //Declare win conditions as array
  const winCond1 = [document.getElementById("Tic1").innerHTML, document.getElementById("Tic2").innerHTML, document.getElementById("Tic3").innerHTML];  // Top-left horizontal win
  const winCond2 = [document.getElementById("Tic1").innerHTML, document.getElementById("Tic4").innerHTML, document.getElementById("Tic7").innerHTML];  // Top-left vertical win
  const winCond3 = [document.getElementById("Tic1").innerHTML, document.getElementById("Tic5").innerHTML, document.getElementById("Tic9").innerHTML];  // Top-left diagonal win
  const winCond4 = [document.getElementById("Tic2").innerHTML, document.getElementById("Tic5").innerHTML, document.getElementById("Tic8").innerHTML];  // Top-middle vertical win
  const winCond5 = [document.getElementById("Tic3").innerHTML, document.getElementById("Tic5").innerHTML, document.getElementById("Tic7").innerHTML];  // Top-right diagonal win
  const winCond6 = [document.getElementById("Tic3").innerHTML, document.getElementById("Tic6").innerHTML, document.getElementById("Tic9").innerHTML];  // Top-right vertical win
  const winCond7 = [document.getElementById("Tic4").innerHTML, document.getElementById("Tic5").innerHTML, document.getElementById("Tic6").innerHTML];  // Middle horizontal win
  const winCond8 = [document.getElementById("Tic7").innerHTML, document.getElementById("Tic8").innerHTML, document.getElementById("Tic9").innerHTML];  // Bottom horizontal win

  if ((winCond1[0] != "" && winCond1[0] == winCond1[1]) && (winCond1[1] == winCond1[2])) {
    document.getElementById("playerMsg").innerHTML = "You Win!";
    document.getElementById("Tic1").style.backgroundColor = "blue";
    document.getElementById("Tic2").style.backgroundColor = "blue";
    document.getElementById("Tic3").style.backgroundColor = "blue";
    disableBoard();
      
  } else if ((winCond2[0] != "" && winCond2[0] == winCond2[1]) && (winCond2[1] == winCond2[2])) {
    document.getElementById("playerMsg").innerHTML = "You Win!";
    document.getElementById("Tic1").style.backgroundColor = "blue";
    document.getElementById("Tic4").style.backgroundColor = "blue";
    document.getElementById("Tic7").style.backgroundColor = "blue";
    disableBoard();

} else if ((winCond3[0] != "" && winCond3[0] == winCond3[1]) && (winCond3[1] == winCond3[2])) {
    document.getElementById("playerMsg").innerHTML = "You Win!";
    document.getElementById("Tic1").style.backgroundColor = "blue";
    document.getElementById("Tic5").style.backgroundColor = "blue";
    document.getElementById("Tic9").style.backgroundColor = "blue";
    disableBoard();
      

} else if ((winCond4[0] != "" && winCond4[0] == winCond4[1]) && (winCond4[1] == winCond4[2])) {
    document.getElementById("playerMsg").innerHTML = "You Win!";
    document.getElementById("Tic2").style.backgroundColor = "blue";
    document.getElementById("Tic5").style.backgroundColor = "blue";
    document.getElementById("Tic8").style.backgroundColor = "blue";
    disableBoard();
      

} else if ((winCond5[0] != "" && winCond5[0] == winCond5[1]) && (winCond5[1] == winCond5[2])) {
    document.getElementById("playerMsg").innerHTML = "You Win!";
    document.getElementById("Tic3").style.backgroundColor = "blue";
    document.getElementById("Tic5").style.backgroundColor = "blue";
    document.getElementById("Tic7").style.backgroundColor = "blue";
    disableBoard();
    

} else if ((winCond6[0] != "" && winCond6[0] == winCond6[1]) && (winCond6[1] == winCond6[2])) {
    document.getElementById("playerMsg").innerHTML = "You Win!";
    document.getElementById("Tic3").style.backgroundColor = "blue";
    document.getElementById("Tic6").style.backgroundColor = "blue";
    document.getElementById("Tic9").style.backgroundColor = "blue";
    disableBoard();
      

} else if ((winCond7[0] != "" && winCond7[0] == winCond7[1]) && (winCond7[1] == winCond7[2])) {
    document.getElementById("playerMsg").innerHTML = "You Win!";
    document.getElementById("Tic4").style.backgroundColor = "blue";
    document.getElementById("Tic5").style.backgroundColor = "blue";
    document.getElementById("Tic6").style.backgroundColor = "blue";
    disableBoard();
      

} else if ((winCond8[0] != "" && winCond8[0] == winCond8[1]) && (winCond8[1] == winCond8[2])) {
    document.getElementById("playerMsg").innerHTML = "You Win!";
    document.getElementById("Tic7").style.backgroundColor = "blue";
    document.getElementById("Tic8").style.backgroundColor = "blue";
    document.getElementById("Tic9").style.backgroundColor = "blue";
    disableBoard();
      

} else if (moveCount == 9) {
    document.getElementById("playerMsg").innerHTML = "The game is a tie!";
}

}


function disableBoard() {

// Disable all buttons
document.getElementById("Tic1").disabled = true;
document.getElementById("Tic2").disabled = true;
document.getElementById("Tic3").disabled = true;
document.getElementById("Tic4").disabled = true;
document.getElementById("Tic5").disabled = true;
document.getElementById("Tic6").disabled = true;
document.getElementById("Tic7").disabled = true;
document.getElementById("Tic8").disabled = true;
document.getElementById("Tic9").disabled = true;


}

indexCSS.css

table, th, td {
    border: 1px solid black;
  }

th, td {
    text-align: center;
    width: 20px;
    height: 20px;
}

.ticButton {
    background-color: white;
    padding: 10px 10px;
  }
6 Likes

Why do we need “const boardIndex = move - 1” to have a board Index. The instructor said it helps with simplifying things. But I don’t understand how.

Hi everyone,
I am sharing my code (I am not a developer and I am fully aware it is not the most optimised and the most elegant, still a lot to learn for sure):

function visualizeBoard(gameBoard){
    let boardString=''
    let gameBoardClone=[...gameBoard]
    for (let index in gameBoardClone){
        if (gameBoardClone[index]===null){
            gameBoardClone[index]=parseInt(index)+1;
        }

    }
    for (let i=0;i<=2;i+=1){
        boardString=boardString+`${gameBoardClone[3*i]} ${gameBoardClone[3*i+1]} ${gameBoardClone[3*i+2]}` + '\n';
    }
    return boardString;
}


function getUserInput(gameBoard, nextPlayerSymbol){
    let boardString=visualizeBoard(gameBoard);
    let coordinates=prompt(`User ${nextPlayerSymbol}, make your pick\n ${boardString}`);
    return coordinates;
}

function isMoveValid(coordinates, gameBoard){
    if (gameBoard[coordinates-1] ===null)
    {
        return true
    }
    else 
    {
        return false
    }
}


function makeAMove(gameBoard, nextPlayerSymbol) {
    newgameBoard=[...gameBoard];
    let coordinates='';
    do {
        coordinates = getUserInput(gameBoard,nextPlayerSymbol);
    } while ( isMoveValid(coordinates, gameBoard)===false );
    newgameBoard[coordinates-1]=nextPlayerSymbol;
    return newgameBoard;
}


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;
    }
    else
    {
        // Return: winner/draw OR game is still in progress
        let indic=0;
        for (let index in gameBoard)
        {
            if (gameBoard[index]===null)
            {
            //console.log(gameBoard[index]);
            indic=indic+1;
            }
        }
        if (indic===0)
        {
            alert('That is a draw');
            return true;
        }
        else
        {
            return false;
        }
    }
}

function ticTacToe() {

    let gameBoard = new Array(9).fill(null);
    let players = ['X', 'O'];
    let currentPlayerSymbol=null

   do {
        if (currentPlayerSymbol==='X'){
            currentPlayerSymbol='O'}
            else
            {currentPlayerSymbol='X'}
        gameBoard = makeAMove(gameBoard, currentPlayerSymbol);
    } while ( !isGameOver(gameBoard, currentPlayerSymbol) );
    
} 
1 Like
function renderBoard(gameBoard){
    let s = '\n'
    let b = 1
     for(let i=0; i<gameBoard.length; i+=1){
         if(gameBoard[i]===null){
             s += `${i+1}`
         }else{
             s += `${gameBoard[i]}`
         }
         if(b === 3) {
             s += '\n'
             b = 0
         }
         b+=1
     }
    return s
}

function getUserInput(nextPlayerSymbol, gameBoard) {
    return +prompt(`${renderBoard(gameBoard)} place your move 1-9 : `)
}

function isMoveValid(coordinates, gameBoard) {
    if(gameBoard[coordinates-1]===null && 
       coordinates>0 && 10>coordinates)return true
    else return false
}

function makeAMove(gameBoard, nextPlayerSymbol) {
    // clone the game board before placing moves in it
    let newGameBoard = JSON.parse(JSON.stringify(gameBoard))
    let coordinates = 0
    do {
        coordinates = getUserInput(nextPlayerSymbol, gameBoard);
    } while ( !isMoveValid(coordinates, gameBoard) );
    // return newGameBoard;
    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 
    let 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)){
        return false
    }else{
        alert('Game ended a Draw!')
        return true
    }
    // Return: winner/draw OR game is still in progress
}

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

    // Computations 
   do {
        if(nextPlayerIndex === 0) nextPlayerIndex = 1
        else nextPlayerIndex = 0
        currentPlayerSymbol = players[nextPlayerIndex]
        gameBoard = makeAMove(gameBoard, currentPlayerSymbol);
    } while ( !isGameOver(gameBoard, currentPlayerSymbol) );
    
    // Return value 
    // return undefined;
} 
1 Like
function getBoardString(gameBoard) {                                              
     let boardString = '';                                                                   
 for (let i= 0; i < gameBoard.lenght; 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, coordinates, gameBoard) {
      let boardIndex = move - 1;                                                                                                                                    
    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;                                            
 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(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.every(element => element !== null)) {                                
          alert(`Dra. Game over.`);                                                       
          return true;                                                                           }                                                                                                                           
       return false;
}

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

    // Computations 
   do {
         nextPlayerIndex = nextPlayerIndex +1;                                                                                 
         gameBoard = makeAMove(gameBoard, currentPlayerSymbol);
    } while ( !isGameOver(gameBoard, currentPlayerSymbol) );
    
    // Return value 
    // return undefined;
} type or paste code here
function makeAMove(gameBoard, nextPlayerSymbol) {
    // clone the game board before placing moves in it
    let newGameBoard = JSON.parse(JSON.stringify(gameBoard))

function isGameOver(gameBoard, currentPlayerSymbol) {
    // 1. check if there is a winner 
    if (hasLastMoverWon(lastMove, gameBoard) ) {
        // Write a message that last mover has won the game
        alert(`Congrats, ${currentPlayerSymbol} has won the game`);
        return true;

I was able to do these 2 parts so far. Very beginner and I will have to watch some more videos to continue :frowning:

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

  // Computations
  do {
    gameBoard = makeAMove(gameBoard, players[nextPlayerIndex]);
    nextPlayerIndex === 0 ? (nextPlayerIndex = 1) : (nextPlayerIndex = 0);
    alert(`
    ${gameBoard[0]}|${gameBoard[1]}|${gameBoard[2]}
    ${gameBoard[3]}|${gameBoard[4]}|${gameBoard[5]}
    ${gameBoard[6]}|${gameBoard[7]}|${gameBoard[8]}
  `);
  } while (!isGameOver(gameBoard, players[nextPlayerIndex]));
}

function makeAMove(gameBoard, nextPlayerSymbol) {
  // clone the game board before placing moves in it
  let newGameBoard = [...gameBoard];
  let coordinates = "";

  do {
    coordinates = getUserInput(nextPlayerSymbol);
  } while (!isMoveValid(coordinates, gameBoard));

  newGameBoard[coordinates] = nextPlayerSymbol;

  return newGameBoard;
}

function getUserInput(nextPlayerSymbol) {
  return prompt("Which coordinate would you like to move");
}

function isMoveValid(coordinates, gameBoard) {
  if (
    coordinates >= 0 &&
    coordinates <= 8 &&
    !isSpaceTaken(coordinates, gameBoard)
  ) {
    return true;
  } else {
    return false;
  }
}

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 isSpaceTaken(coordinates, gameBoard) {
  if (gameBoard[coordinates] === null) {
    return false;
  } else {
    return true;
  }
}

function isGameOver(gameBoard, currentPlayerSymbol) {
  let isBoardFull = false;
  let lastMove = currentPlayerSymbol === "X" ? "O" : "X";
  // 1. check if there is a winner
  if (hasLastMoverWon(lastMove, gameBoard)) {
    // Write a message that last mover has won the game
    alert(`Congratulations, ${lastMove} has won the game`);
    return true;
  }
  // 2. check if the board is full
  if (!gameBoard.includes(null)) {
    isBoardFull = true;
  }

  if (isBoardFull) {
    alert("It's a Tie!");
    return true;
  }

  return false;
}

// Initialize game

ticTacToe();

2 Likes

About the TICTACTOE program: The small sections seemed fine for me, but the whole thing, with all the sections together does not work well! What is wrong?

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;
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;
return newGameBoard;
}

function hasLastMoverWon(lastMove, gameBoard) {
let winnerCombinations = [ [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 winnerCombinations) { if (gameBoard[i1] === lastMove &&
gameBoard[i1] === gameBoard[i2] &&
gameBoard[i1] === gameBoard[i3])
{ return true; } } return false; }

function isGameOver(gameBoard, currentPlayerSymbol)
{ if (hasLastMoverWon(currentPlayerSymbol, gameBoard) ) {
alert(Congradtulations ${currentPlayerSymbol} has won the game);
return true;
} if (gameBoard.every(element => element !== null) ) {
alert(Tie. Game over.);
return true; } return false;
}

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 ticTacToe() {
let gameBoard = new array(9).fill(null);
let currentPlayerSymbol = null;
do {
currentPlayerSymbol = currentPlayerSymbol === “x” ? “o” : “x”;
gameBoard = makeAMove(gameBoard, currentPlayerSymbol);
} while (!isGameOver(gameBoard, currentPlayerSymbol) );
}

2 Likes

I had a little bit of trouble trying to figure everything out without watching the videos and seeing the full code.

I think if we had a flow chart for this project that showed how everything is tied together and able to see it visually, it would have clicked better. Still being fairly new to Javascript, it can be difficult to visualize how everything is supposed to flow together from scratch without a chart. Things didn’t start clicking until I took the full code and categorized it more into sections with additional comments.

Don’t mind me, I still have a lot to learn lol , but for example:


//Element 1: The Game TicTacToe
//Element 1a: Gameboard
function ticTacToe() {
    //...code...

//Element 1b: Players
    let currentPlayerSymbol //...code...

//Element 1c: computations
do {
    currentPlayerSymbol //...code...
    gameBoard = makeAMove //...code...
} while !isGameOver //...code...
}

//==============================

// Element 2: Rules
// Element 2a: Ways to Win

function hasLastMoverWon //...code...

//Element 2: Rules
//Element 2b: Validating Moves

function isMoveValid //...code...

//==============================

//Element 3: User Actions
//Element 3a: Viewing The Current Board

function getBoardString //...code...

//Element 3: User Actions
//Element 3b: Input Prompts

function getUserInput //...code...

//==============================

//Element 4: Computing
//Element 4a: User Moves

function makeAMove //...code...

//Element 4: Computing
//Element 4b: Winner

function isGameOver //...code...


One thing that I didn’t understand was why the Cancel button wasn’t working with the prompt messages. I tried searching on stack overflow and tried some things, but still couldn’t get the cancel button to work.

While messing around with the full code to breakdown and understand some functions, there were several times that I had to close the browser window to go back and adjust the code lol.

Does anyone know how to adjust the code to where the cancel button on the prompt will work?

1 Like