From 6fd3e127de0305a5cfe1f52bfc251937b912c5c9 Mon Sep 17 00:00:00 2001 From: mHaines331 Date: Sun, 1 Oct 2023 12:11:24 -0700 Subject: [PATCH 01/10] boilerplate and gameboard divs --- index.html | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 index.html diff --git a/index.html b/index.html new file mode 100644 index 0000000..609c26c --- /dev/null +++ b/index.html @@ -0,0 +1,43 @@ + + + + + + + Tic Tac Toe + + + + + + + + +

PURPLE'S TURN

+
+ +
+
+
+
+
+
+
+
+
+
+ +
+ +
+ + +
+ + + + + + + + \ No newline at end of file From f387c3848f24f77baee7dea011d44e43a8a2b5e2 Mon Sep 17 00:00:00 2001 From: mHaines331 Date: Sun, 1 Oct 2023 12:32:26 -0700 Subject: [PATCH 02/10] added grid css --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 609c26c..d52cf79 100644 --- a/index.html +++ b/index.html @@ -8,8 +8,8 @@ - - + + From af9fb101bcce13c37a34abb676f1231869f038b6 Mon Sep 17 00:00:00 2001 From: mHaines331 Date: Sun, 1 Oct 2023 12:44:21 -0700 Subject: [PATCH 03/10] add renderBoard func --- app.js | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 app.js diff --git a/app.js b/app.js new file mode 100644 index 0000000..9ff2c4f --- /dev/null +++ b/app.js @@ -0,0 +1,42 @@ +/*----- constants -----*/ +const PLAYERS = { + 0: "init", + 1: "player", + "-1": "CPU", +}; + +/*----- state variables -----*/ +let board; // array of 7 column arrays +let turn; // 1 or -1 +let winner; // null = no winner; 1 or -1 = winner; 'T' = Tie + +/*----- cached elements -----*/ +//DOM ELEMENTS + +/*----- event listeners -----*/ + +/*----- functions -----*/ +init(); + +function init() { + renderBoard(); + renderControls(); + render(); +} + +function renderBoard() { + //turn 90deg counterclock to visualize + (board = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0], + ]), + (turn = 0); + winner = null; +} + +function renderMessage() { + if (winner === "T") { + //update dom element to its a tie + } +} From 72bfc8143d0aac8fad69355ea55c317c7c350cdc Mon Sep 17 00:00:00 2001 From: mHaines331 Date: Sun, 1 Oct 2023 16:23:04 -0700 Subject: [PATCH 04/10] finish handeClick func --- app.js | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 93 insertions(+), 10 deletions(-) diff --git a/app.js b/app.js index 9ff2c4f..6c75ffc 100644 --- a/app.js +++ b/app.js @@ -1,42 +1,125 @@ /*----- constants -----*/ + +// 1) Define required constants + const PLAYERS = { 0: "init", - 1: "player", - "-1": "CPU", + 1: "red", + "-1": "green", }; /*----- state variables -----*/ +// 2) Define required variables used to track the state of the game + let board; // array of 7 column arrays -let turn; // 1 or -1 +let turn = 0; // 1 or -1 let winner; // null = no winner; 1 or -1 = winner; 'T' = Tie /*----- cached elements -----*/ //DOM ELEMENTS +const message = document.getElementById("message"); +const playerTurnDisplay = document.getElementById("player-id"); +const boardCells = document.querySelectorAll(".cells"); /*----- event listeners -----*/ /*----- functions -----*/ init(); -function init() { +function render() { renderBoard(); renderControls(); render(); } +// 3) Store elements on the page that will be accessed in code more than once in variables to make code more concise, readable and performant. -function renderBoard() { +// 4) Upon loading the app should: +// 4.1) Initialize the state variables +// 4.2) Render those values to the page +// 4.3) Wait for the user to click a square +function init() { //turn 90deg counterclock to visualize (board = [ [0, 0, 0], [0, 0, 0], [0, 0, 0], ]), - (turn = 0); + (turn = 1); winner = null; } -function renderMessage() { - if (winner === "T") { - //update dom element to its a tie - } +//handles click and changes colors +function handleClick(e) { + boardCells.forEach((cell) => { + cell.addEventListener("click", (evt) => { + if (!evt.target.style.borderColor) { + turn = turn * -1; + + console.log(evt.target.id); + let clickedSquare = evt.target.id; + console.log(clickedSquare); + evt.target.style.borderColor = turn === 1 ? "red" : "green"; + + console.log(turn); + } + }); + }); } +handleClick(); + +// 5) Handle a player clicking a square: +// 5.1) Obtain the index of the square that was clicked by either: +// 5.1.1) "Extracting" the index from an id assigned to the element in the HTML, or +// 5.1.2) Looping through the cached square elements using a for loop and breaking out when the current square element equals the event object's target. + +// 5.2) If the board has a value at the index, immediately return because that square is already taken. +// 5.3) If winner is not null, immediately return because the game is over. +// 5.4) Update the board array at the index with the value of turn. +// 5.5) Flip turns by multiplying turn by -1 (flips a 1 to -1, and vice-versa). +// 5.6) Set the winner variable if there's a winner: +// 5.6.1) Loop through the each of the winning combination arrays defined. +// 5.6.2) Total up the three board positions using the three indexes in the current combo. +// 5.6.3) Convert the total to an absolute value (convert any negative total to positive). +// 5.6.4) If the total equals 3, we have a winner! Set winner to the board's value at the index specified by the first index in the combo array. Exit the loop. +// 5.7) If there's no winner, check if there's a tie: +// 5.7.1) Set winner to 'T' if there are no more nulls in the board array. +// 5.8) All state has been updated, so render the state to the page (step 4.2). + +// 6) Handle a player clicking the replay button +// Since most web apps are event-driven by nature. Coding an event-driven program generally requires that we set up the application when it loads (steps 1 - 4) and wait for the user to interact with the app (steps 5 & 6). + +// Now we can start adding as many detailed steps as desired. + +// Typically, pseudocode does not have to be as detailed as the following - it is highly detailed here to help you as much as possible: + +// 1) Define required constants: +// 1.1) Define a colors object with keys of 'null' (when the square is empty), and players 1 & -1. The value assigned to each key represents the color to display for an empty square (null), player 1 and player -1. +// 1.2) Define the 8 possible winning combinations, each containing three indexes of the board that make a winner if they hold the same player value. + +// 2) Define required variables used to track the state of the game: +// 2.1) Use a board array to represent the squares. +// 2.2) Use a turn variable to remember whose turn it is. +// 2.3) Use a winner variable to represent three different possibilities - player that won, a tie, or game in play. + +// 3) Store elements on the page that will be accessed in code more than once in variables to make code more concise, readable and performant: +// 3.1) Store the 9 elements that represent the squares on the page. + +// 4) Upon loading the app should: +// 4.1) Initialize the state variables: +// 4.1.1) Initialize the board array to 9 nulls to represent empty squares. The 9 elements will "map" to each square, where index 0 maps to the top-left square and index 8 maps to the bottom-right square. +// 4.1.2) Initialize whose turn it is to 1 (player 'X'). Player 'O' will be represented by -1. +// 4.1.3) Initialize winner to null to represent that there is no winner or tie yet. Winner will hold the player value (1 or -1) if there's a winner. Winner will hold a 'T' if there's a tie. +// 4.2) Render those state variables to the page: +// 4.2.1) Render the board: +// 4.2.1.1) Loop over each of the 9 elements that represent the squares on the page, and for each iteration: +// 4.2.1.1.2) Use the index of the iteration to access the mapped value from the board array. +// 4.3.1.1.3) Set the background color of the current element by using the value as a key on the colors lookup object (constant). +// 4.2.2) Render a message: +// 4.2.2.1) If winner has a value other than null (game still in progress), render whose turn it is - use the color name for the player, converting it to upper case. +// 4.2.2.2) If winner is equal to 'T' (tie), render a tie message. +// 4.2.2.3) Otherwise, render a congratulatory message to which player has won - use the color name for the player, converting it to uppercase. +// 4.3) Wait for the user to click a square + +// 6) Handle a player clicking the replay button: +// 6.1) Do steps 4.1 (initialize the state variables) and 4.2 (render). +// Using a numbered outline is not required but helps organize the more complex steps into detailed steps. From f6342aa6b6d66e3b784b5211306207f32731b9c4 Mon Sep 17 00:00:00 2001 From: mHaines331 Date: Sun, 1 Oct 2023 17:40:24 -0700 Subject: [PATCH 05/10] implement checkRowWinner func --- app.js | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/app.js b/app.js index 6c75ffc..c68a383 100644 --- a/app.js +++ b/app.js @@ -53,20 +53,41 @@ function handleClick(e) { boardCells.forEach((cell) => { cell.addEventListener("click", (evt) => { if (!evt.target.style.borderColor) { - turn = turn * -1; + const row = parseInt(evt.target.id.charAt(3)); + const col = parseInt(evt.target.id.charAt(1)); + turn = turn * -1; + board[row][col] = turn; console.log(evt.target.id); let clickedSquare = evt.target.id; console.log(clickedSquare); evt.target.style.borderColor = turn === 1 ? "red" : "green"; - - console.log(turn); + console.log(board); + checkRowWinner(); } + + // const squareId = cell.id; + // console.log(`Square ${squareId} was clicked.`); }); }); } handleClick(); +function checkRowWinner() { + let total = 0; + for (let i = 0; i < board.length; i++) { + board[i].forEach((cell) => { + total += cell; + return total; + }); + if (total === 3 || total === -3) { + console.log("winner"); + } + } +} +function checkColWinner() { + +} // 5) Handle a player clicking a square: // 5.1) Obtain the index of the square that was clicked by either: // 5.1.1) "Extracting" the index from an id assigned to the element in the HTML, or From cd48705c8e7b22078c93273017fb91a90da9c198 Mon Sep 17 00:00:00 2001 From: mHaines331 Date: Sun, 1 Oct 2023 18:01:22 -0700 Subject: [PATCH 06/10] define all winning combos --- app.js | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/app.js b/app.js index c68a383..935d5d1 100644 --- a/app.js +++ b/app.js @@ -64,6 +64,8 @@ function handleClick(e) { evt.target.style.borderColor = turn === 1 ? "red" : "green"; console.log(board); checkRowWinner(); + checkColWinner(); + checkDiaganolWinner(); } // const squareId = cell.id; @@ -79,14 +81,36 @@ function checkRowWinner() { total += cell; return total; }); - if (total === 3 || total === -3) { - console.log("winner"); + if (total === 3) { + console.log("RED WINS"); + } else if (total === -3) { + console.log("GREEN WINS"); } } } - function checkColWinner() { - + for (let i = 0; i < board.length; i++) { + let total = board[0][i] + board[1][i] + board[2][i]; + if (total === 3) { + console.log("RED WINS"); + } else if (total === -3) { + console.log("GREEN WINS"); + } + } +} + +function checkDiaganolWinner() { + if ( + board[0][0] + board[1][1] + board[2][2] === 3 || + board[0][2] + board[1][1] + board[2][0] === 3 + ) { + console.log("RED WINS"); + } else if ( + board[0][0] + board[1][1] + board[2][2] === -3 || + board[0][2] + board[1][1] + board[2][0] === -3 + ) { + console.log("GREEN WINS"); + } } // 5) Handle a player clicking a square: // 5.1) Obtain the index of the square that was clicked by either: From 874ea7c25192811b084eeb5a1eedc50b8cae163c Mon Sep 17 00:00:00 2001 From: mHaines331 Date: Sun, 1 Oct 2023 18:44:27 -0700 Subject: [PATCH 07/10] winner message appears, reset button added --- app.js | 57 +++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 14 deletions(-) diff --git a/app.js b/app.js index 935d5d1..c772a42 100644 --- a/app.js +++ b/app.js @@ -13,24 +13,26 @@ const PLAYERS = { let board; // array of 7 column arrays let turn = 0; // 1 or -1 -let winner; // null = no winner; 1 or -1 = winner; 'T' = Tie +let winner = 0; // null = no winner; 1 or -1 = winner; 'T' = Tie +let redWins = 0; +let greenWins = 0; /*----- cached elements -----*/ //DOM ELEMENTS const message = document.getElementById("message"); const playerTurnDisplay = document.getElementById("player-id"); const boardCells = document.querySelectorAll(".cells"); - +const resetButton = document.getElementById("reset-button"); +const redCounter = document.getElementById("redWins"); +const greenCounter = document.getElementById("greenWins"); /*----- event listeners -----*/ - +resetButton.addEventListener("click", function () { + init(); + message.style.display = "none"; +}); /*----- functions -----*/ init(); -function render() { - renderBoard(); - renderControls(); - render(); -} // 3) Store elements on the page that will be accessed in code more than once in variables to make code more concise, readable and performant. // 4) Upon loading the app should: @@ -39,15 +41,19 @@ function render() { // 4.3) Wait for the user to click a square function init() { //turn 90deg counterclock to visualize - (board = [ + board = [ [0, 0, 0], [0, 0, 0], [0, 0, 0], - ]), - (turn = 1); + ]; + turn = 1; winner = null; -} + // Clear the border colors of the cells + boardCells.forEach((cell) => { + cell.style.borderColor = ""; + }); +} //handles click and changes colors function handleClick(e) { boardCells.forEach((cell) => { @@ -66,6 +72,8 @@ function handleClick(e) { checkRowWinner(); checkColWinner(); checkDiaganolWinner(); + isThereAWinner(); + winCounter(); } // const squareId = cell.id; @@ -74,27 +82,35 @@ function handleClick(e) { }); } handleClick(); + function checkRowWinner() { - let total = 0; for (let i = 0; i < board.length; i++) { + let total = 0; + board[i].forEach((cell) => { total += cell; - return total; }); + if (total === 3) { console.log("RED WINS"); + return (winner = 1); + console.log(winner); } else if (total === -3) { console.log("GREEN WINS"); + return (winner = -1); } } } + function checkColWinner() { for (let i = 0; i < board.length; i++) { let total = board[0][i] + board[1][i] + board[2][i]; if (total === 3) { console.log("RED WINS"); + return (winner = 1); } else if (total === -3) { console.log("GREEN WINS"); + return (winner = -1); } } } @@ -105,13 +121,26 @@ function checkDiaganolWinner() { board[0][2] + board[1][1] + board[2][0] === 3 ) { console.log("RED WINS"); + return (winner = 1); } else if ( board[0][0] + board[1][1] + board[2][2] === -3 || board[0][2] + board[1][1] + board[2][0] === -3 ) { console.log("GREEN WINS"); + return (winner = -1); } } + +function isThereAWinner() { + if (winner === 1) { + message.innerHTML = "RED WINS"; + message.style.fontSize = "15px"; + } else if (winner === -1) { + message.innerHTML = "GREEN WINS"; + message.style.fontSize = "15px"; + } +} + // 5) Handle a player clicking a square: // 5.1) Obtain the index of the square that was clicked by either: // 5.1.1) "Extracting" the index from an id assigned to the element in the HTML, or From 3039f190b40d573cd30c50d614e6030f9588f999 Mon Sep 17 00:00:00 2001 From: mHaines331 Date: Sun, 1 Oct 2023 19:11:48 -0700 Subject: [PATCH 08/10] turn name colors added --- app.js | 10 ++++++++++ styles.css | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 styles.css diff --git a/app.js b/app.js index c772a42..4c55445 100644 --- a/app.js +++ b/app.js @@ -63,6 +63,13 @@ function handleClick(e) { const col = parseInt(evt.target.id.charAt(1)); turn = turn * -1; + if (turn === -1) { + playerTurnDisplay.style.color = "red"; + playerTurnDisplay.innerHTML = "RED'S"; + } else if (turn === 1) { + playerTurnDisplay.style.color = "green"; + playerTurnDisplay.innerHTML = "GREEN'S"; + } board[row][col] = turn; console.log(evt.target.id); let clickedSquare = evt.target.id; @@ -74,6 +81,7 @@ function handleClick(e) { checkDiaganolWinner(); isThereAWinner(); winCounter(); + checkTie(); } // const squareId = cell.id; @@ -135,9 +143,11 @@ function isThereAWinner() { if (winner === 1) { message.innerHTML = "RED WINS"; message.style.fontSize = "15px"; + message.style.color = "red"; } else if (winner === -1) { message.innerHTML = "GREEN WINS"; message.style.fontSize = "15px"; + message.style.color = "green"; } } diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..b558e84 --- /dev/null +++ b/styles.css @@ -0,0 +1,55 @@ +#main-game-container { + display: flex; + align-items: center; + justify-content: center; + background-color: ; +} + +#board { + display: inline-grid; + grid-template-columns: repeat(3, 1fr); + column-gap: 2px; + row-gap: 2px; +} +#board > div { + text-align: center; + border: 80px solid black; + opacity: 0.2; +} + +#board > div:hover { + transform: scale(1.1); +} + +#btn-container { + display: flex; + align-items: center; + justify-content: center; + margin-top: 20px; +} + +button { + color: white; + font-size: 2rem; + border-radius: 10%; + background-color: black; + opacity: 0.2; +} +button:hover { + color: white; + font-size: 2rem; + border-radius: 10%; + background-color: black; + opacity: 0.9; +} + +h2 { + text-align: center; +} + +h1 { + text-align: center; +} +#player-id { + color: green; +} From 5a67cadc1ac0a98c70875630fd50ceb7dcb3f30c Mon Sep 17 00:00:00 2001 From: mHaines331 Date: Sun, 1 Oct 2023 19:33:28 -0700 Subject: [PATCH 09/10] add tie checker --- app.js | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 4c55445..299326a 100644 --- a/app.js +++ b/app.js @@ -58,6 +58,11 @@ function init() { function handleClick(e) { boardCells.forEach((cell) => { cell.addEventListener("click", (evt) => { + if (winner !== null) { + // If there's a winner, return early and do not allow further moves + return; + } + if (!evt.target.style.borderColor) { const row = parseInt(evt.target.id.charAt(3)); const col = parseInt(evt.target.id.charAt(1)); @@ -79,9 +84,8 @@ function handleClick(e) { checkRowWinner(); checkColWinner(); checkDiaganolWinner(); - isThereAWinner(); - winCounter(); checkTie(); + isThereAWinner(); } // const squareId = cell.id; @@ -138,6 +142,23 @@ function checkDiaganolWinner() { return (winner = -1); } } +function checkTie() { + if (winner === null) { + let isTie = true; + + board.forEach((cellArr) => { + cellArr.forEach((cell) => { + if (cell === 0) { + isTie = false; + } + }); + }); + + if (isTie) { + winner = "T"; + } + } +} function isThereAWinner() { if (winner === 1) { @@ -148,6 +169,10 @@ function isThereAWinner() { message.innerHTML = "GREEN WINS"; message.style.fontSize = "15px"; message.style.color = "green"; + } else if (winner === "T") { + message.innerHTML = "ITS A TIE!"; + message.style.fontSize = "15px"; + message.style.color = "blue"; } } From ff405e224257050e3c45a1ee5e27b74b6e762945 Mon Sep 17 00:00:00 2001 From: mHaines331 Date: Sun, 1 Oct 2023 19:35:48 -0700 Subject: [PATCH 10/10] fix msg display inconsistencies --- app.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app.js b/app.js index 299326a..598dee4 100644 --- a/app.js +++ b/app.js @@ -48,6 +48,8 @@ function init() { ]; turn = 1; winner = null; + playerTurnDisplay.innerHTML = "GREEN'S"; + playerTurnDisplay.style.color = "green"; // Clear the border colors of the cells boardCells.forEach((cell) => {