├── .gitignore ├── README.md ├── index.html ├── css └── master.css └── js └── main.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tic Tac Toe 2 | ## Tic Tac Toe Game using JS 3 | 4 | ## Synopsis 5 | * Allow multi-player 6 | * When winner, cells highlight 7 | * Include button to reset board at any time during game 8 | 9 | ## Links 10 | 11 | * Live Example - https://robhitt.github.io/tic-tac-toe-js 12 | * Github - https://www.github.com/robhitt/tic-tac-toe-js 13 | 14 | ## Installation 15 | 16 | * Fire up the [index.html](https://robhitt.github.io/tic-tac-toe-js) file in any web browser and you'll be good to go! 17 | 18 | ## Contributors 19 | 20 | #### Rob Hitt 21 | * [E-mail](mailto:robhitt@gmail.com) 22 | * [Website](https://www.robhitt.com/) 23 | * [Resume](http://www.robhitt.com/resume) 24 | * [LinkedIn](http://www.linkedin.com/in/robhitt) 25 | * [Twitter](http://www.twitter.com/robhitt) 26 | * [Instagram](http://www.instagram.com/robhitt) 27 | * [Bodega Cats](http://www.instagram.com/bodegacatsofinstagram) -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 |

Tic Tac Toe

21 |
22 |
23 | 24 |
25 |
26 | 27 | 28 |
29 |
30 | 31 | 32 |
33 | 34 | 35 |
36 |
37 | 38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 | 52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | 62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | 74 |
75 |
76 |
77 | 80 |
81 | 82 | 83 | -------------------------------------------------------------------------------- /css/master.css: -------------------------------------------------------------------------------- 1 | * { 2 | margin: 0; 3 | padding: 0; 4 | font-family: "Helvetica", sans-serif; 5 | box-sizing: border-box; 6 | } 7 | 8 | .hide-container { 9 | display: none; 10 | } 11 | 12 | .header { 13 | text-align: center; 14 | margin: 15px 0; 15 | font-family: "Permanent Marker"; 16 | } 17 | 18 | .enter-players { 19 | margin: 0 auto; 20 | width: 80%; 21 | text-align: center; 22 | position: relative; 23 | } 24 | 25 | .input-field { 26 | border: 2px solid #000; 27 | outline: none; 28 | padding: 4px 8px; 29 | margin: 0 0 10px 4px; 30 | font-size: 18px; 31 | } 32 | 33 | .input-field:focus { 34 | border: 2px solid #E15E32; 35 | } 36 | 37 | .submit-btn { 38 | border: 2px solid #000; 39 | padding: 8px 8px; 40 | font-size: 18px; 41 | width: 250px; 42 | border-radius: 5px; 43 | margin-top: 10px; 44 | background-color: #fff; 45 | } 46 | 47 | .submit-btn:active, 48 | .submit-btn:focus { 49 | outline: none; 50 | border: 2px solid #E15E32; 51 | } 52 | 53 | .submit-btn:hover { 54 | background-color: #ece9e9; 55 | } 56 | 57 | .board___player-turn { 58 | text-align: center; 59 | margin: 10px 0 10px; 60 | height: 54px; 61 | } 62 | 63 | .name--style { 64 | font-size: 22px; 65 | } 66 | 67 | .board__container { 68 | width: 40%; 69 | background-color: pink; 70 | margin: 0 auto; 71 | font-size: 0; 72 | border: 2px solid #000; 73 | -webkit-box-shadow: 3px 3px 3px 0px rgba(0,0,0,0.75); 74 | -moz-box-shadow: 3px 3px 3px 0px rgba(0,0,0,0.75); 75 | box-shadow: 3px 3px 3px 0px rgba(0,0,0,0.75); 76 | } 77 | 78 | .board__cell { 79 | width: calc(100% / 3); 80 | display: inline-block; 81 | font-size: 40px; 82 | text-align: center; 83 | border: 2px solid #000; 84 | padding: 20px; 85 | vertical-align: top; 86 | font-family: "Permanent Marker"; 87 | } 88 | 89 | .board__cell--winner { 90 | background-color: #f9738a; 91 | } 92 | 93 | .letter { 94 | position: relative; 95 | top: 50%; 96 | transform: translateY(-50%); 97 | font-family: "Permanent Marker"; 98 | } 99 | 100 | /* WINNER CONTAINER */ 101 | .reset { 102 | text-align: center; 103 | margin: 20px auto 0; 104 | } 105 | 106 | .reset--hidden { 107 | display: none; 108 | } 109 | 110 | .replay-btn { 111 | width: 25%; 112 | padding: 10px 20px; 113 | border: 2px solid #000; 114 | border-radius: 5px; 115 | outline: none; 116 | letter-spacing: 0; 117 | text-transform: uppercase; 118 | font-size: 16px; 119 | margin-top: 12px; 120 | word-spacing: 3px; 121 | background-color: #fff; 122 | } 123 | 124 | .replay-btn:hover, 125 | .replay-btn:active { 126 | outline: none; 127 | color: #fff; 128 | background-color: #000; 129 | } 130 | 131 | .congratulations { 132 | font-size: 24px; 133 | } 134 | 135 | .u-r-winner { 136 | font-size: 18px; 137 | height: 18px; 138 | line-height: 18px; 139 | margin: 2px 0; 140 | } 141 | 142 | @media only screen and (max-width: 767px) { 143 | .board__cell { 144 | font-size: 16px; 145 | padding: 5px; 146 | } 147 | 148 | .replay-btn { 149 | width: 50%; 150 | } 151 | } -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | window.addEventListener('load', app); 4 | 5 | let gameBoard = ['', '', '', '', '', '', '', '', '']; 6 | let turn = 0; // Keeps track if X or O player's turn 7 | let winner = false; 8 | 9 | // CREATE PLAYER 10 | const player = (name) => { 11 | name = name; 12 | return {name}; 13 | }; 14 | 15 | let playerX = player(""); 16 | let playerY = player(""); 17 | 18 | // INITIALIZE APP 19 | function app() { 20 | let inputField = document.querySelector('.input-field').focus(); 21 | 22 | const addPlayerForm = document.getElementById('player-form'); 23 | addPlayerForm.addEventListener('submit', addPlayers); 24 | 25 | let replayButton = document.querySelector('.replay-btn'); 26 | replayButton.addEventListener('click', resetBoard); 27 | } 28 | 29 | // Add PLAYERS 30 | function addPlayers(event) { 31 | event.preventDefault(); 32 | 33 | if (this.player1.value === '' || this.player2.value === '') { 34 | alert('You Must Enter a Name for Each Field'); 35 | return; 36 | } 37 | 38 | const playerFormContainer = document.querySelector('.enter-players'); 39 | const boardMain = document.querySelector('.board__main'); 40 | playerFormContainer.classList.add('hide-container'); 41 | boardMain.classList.remove('hide-container'); 42 | 43 | playerX.name = this.player1.value; 44 | playerY.name = this.player2.value; 45 | buildBoard(); 46 | } 47 | 48 | // RETURN CURRENT PLAYER 49 | function currentPlayer() { 50 | return turn % 2 === 0 ? 'X' : 'O'; 51 | } 52 | 53 | // Resize squares in event browser is resized 54 | window.addEventListener("resize", onResize); 55 | function onResize() { 56 | let allCells = document.querySelectorAll('.board__cell'); 57 | let cellHeight = allCells[0].offsetWidth; 58 | 59 | allCells.forEach( cell => { 60 | cell.style.height = `${cellHeight}px`; 61 | }); 62 | } 63 | 64 | // Build Board 65 | function buildBoard() { 66 | let resetContainer = document.querySelector('.reset'); 67 | resetContainer.classList.remove('reset--hidden'); 68 | 69 | onResize(); 70 | addCellClickListener(); 71 | changeBoardHeaderNames(); 72 | } 73 | 74 | // CELL CLICK EVENT FOR PLAYER TO ATTEMPT TO MAKE MOVE 75 | function makeMove(event) { 76 | console.log(turn); 77 | 78 | let currentCell = parseInt(event.currentTarget.firstElementChild.dataset.id); 79 | let cellToAddToken = document.querySelector(`[data-id='${currentCell}']`); 80 | 81 | if (cellToAddToken.innerHTML !== '') { 82 | console.log('This cell is already taken.'); 83 | return; 84 | } else { 85 | if (currentPlayer() === 'X') { 86 | cellToAddToken.textContent = currentPlayer(); 87 | gameBoard[currentCell] = 'X'; 88 | } else { 89 | cellToAddToken.textContent = currentPlayer(); 90 | gameBoard[currentCell] = 'O'; 91 | } 92 | } 93 | 94 | // CHECK IF WE HAVE A WINNER 95 | isWinner(); 96 | 97 | // Update turn count so next player can choose 98 | turn ++; 99 | 100 | // CHANGE BOARD HEADER INFO 101 | changeBoardHeaderNames(); 102 | } 103 | 104 | function checkIfTie() { 105 | if (turn > 7) { 106 | alert('game over a tie') 107 | } 108 | } 109 | 110 | function isWinner() { 111 | const winningSequences = [ 112 | [0, 1, 2], 113 | [3, 4, 5], 114 | [6, 7, 8], 115 | [0, 3, 6], 116 | [1, 4, 7], 117 | [2, 5, 8], 118 | [0, 4, 8], 119 | [2, 4, 6] 120 | ]; 121 | 122 | winningSequences.forEach( winningCombos => { 123 | let cell1 = winningCombos[0]; 124 | let cell2 = winningCombos[1]; 125 | let cell3 = winningCombos[2]; 126 | if ( 127 | gameBoard[cell1] === currentPlayer() && 128 | gameBoard[cell2] === currentPlayer() && 129 | gameBoard[cell3] === currentPlayer() 130 | ) { 131 | 132 | 133 | const cells = document.querySelectorAll('.board__cell'); 134 | let letterId1 = document.querySelector(`[data-id='${cell1}']`); 135 | let letterId2 = document.querySelector(`[data-id='${cell2}']`); 136 | let letterId3 = document.querySelector(`[data-id='${cell3}']`); 137 | 138 | cells.forEach( cell => { 139 | let cellId = cell.firstElementChild.dataset.id; 140 | 141 | if (cellId == cell1 || cellId == cell2 || cellId == cell3 ) { 142 | cell.classList.add('board__cell--winner'); 143 | } 144 | }); 145 | 146 | let currentPlayerText = document.querySelector('.board___player-turn'); 147 | if (currentPlayer() === 'X') { 148 | currentPlayerText.innerHTML = ` 149 |
Congratulations ${playerX.name}
150 |
You are our winner!
151 | `; 152 | winner = true; 153 | removeCellClickListener(); 154 | return true; 155 | } else { 156 | currentPlayerText.innerHTML = ` 157 |
Congratulations ${playerY.name}
158 |
You are our winner!
159 | `; 160 | winner = true; 161 | removeCellClickListener(); 162 | return true; 163 | } 164 | } 165 | }); 166 | 167 | if (!winner) { 168 | checkIfTie(); 169 | } 170 | 171 | return false; 172 | } 173 | 174 | function changeBoardHeaderNames() { 175 | if (!winner) { 176 | let currentPlayerText = document.querySelector('.board___player-turn'); 177 | if (currentPlayer() === 'X') { 178 | currentPlayerText.innerHTML = ` 179 | ${playerX.name}, you are up! 180 |
181 | ` 182 | } else { 183 | currentPlayerText.innerHTML = ` 184 | ${playerY.name}, you are up. 185 |
186 | ` 187 | } 188 | } 189 | } 190 | 191 | function resetBoard() { 192 | console.log('resetting'); 193 | 194 | gameBoard = ['', '', '', '', '', '', '', '', '']; 195 | 196 | let cellToAddToken = document.querySelectorAll('.letter'); 197 | cellToAddToken.forEach( square => { 198 | square.textContent = ''; 199 | square.parentElement.classList.remove('board__cell--winner'); 200 | }); 201 | 202 | turn = 0; 203 | winner = false; 204 | 205 | let currentPlayerText = document.querySelector('.board___player-turn'); 206 | currentPlayerText.innerHTML = ` 207 | ${playerX.name}, you are up! 208 |
209 | ` 210 | 211 | addCellClickListener(); 212 | } 213 | 214 | function addCellClickListener() { 215 | const cells = document.querySelectorAll('.board__cell'); 216 | cells.forEach( cell => { 217 | cell.addEventListener('click', makeMove); 218 | }); 219 | } 220 | 221 | function removeCellClickListener() { 222 | let allCells = document.querySelectorAll('.board__cell'); 223 | allCells.forEach( cell => { 224 | cell.removeEventListener('click', makeMove); 225 | }); 226 | } 227 | 228 | --------------------------------------------------------------------------------