├── .DS_Store ├── assets ├── .DS_Store ├── images │ ├── SVG │ │ ├── icon-o-default.svg │ │ ├── icon-o-outline.svg │ │ ├── icon-o-win.svg │ │ ├── icon-o.svg │ │ ├── icon-restart.svg │ │ ├── icon-x-default.svg │ │ ├── icon-x-outline.svg │ │ ├── icon-x-win.svg │ │ ├── icon-x.svg │ │ ├── logo.svg │ │ └── test.txt │ ├── favicon-32x32.png │ ├── loading.gif │ └── test.txt ├── scripts │ ├── .DS_Store │ └── app.js └── styles │ ├── style.css │ └── test.txt ├── designs ├── accessability-score.PNG ├── css-validation.png ├── desktop-game-multiplayer-player1-win.png ├── desktop-game-multiplayer-player2-win.png ├── desktop-game-multiplayer.png ├── desktop-game-solo-player-loss.png ├── desktop-game-solo-player-win.png ├── desktop-game-solo.png ├── desktop-game-start.png ├── desktop-new-game-menu.png ├── desktop-restart-game.png ├── desktop-round-tied.png ├── html-validation.png ├── js-validation.png ├── main-screenshot.png ├── page-speed-desktop.png ├── page-speed-mobile.png └── test.txt ├── index.html ├── package-lock.json ├── package.json └── sass ├── _base.scss ├── _components.scss ├── main.scss └── test.txt+ /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/.DS_Store -------------------------------------------------------------------------------- /assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/assets/.DS_Store -------------------------------------------------------------------------------- /assets/images/SVG/icon-o-default.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/SVG/icon-o-outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/SVG/icon-o-win.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/SVG/icon-o.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/SVG/icon-restart.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/SVG/icon-x-default.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/SVG/icon-x-outline.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/SVG/icon-x-win.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/SVG/icon-x.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/SVG/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /assets/images/SVG/test.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/images/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/assets/images/favicon-32x32.png -------------------------------------------------------------------------------- /assets/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/assets/images/loading.gif -------------------------------------------------------------------------------- /assets/images/test.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /assets/scripts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/assets/scripts/.DS_Store -------------------------------------------------------------------------------- /assets/scripts/app.js: -------------------------------------------------------------------------------- 1 | const X_CLASS = 'x'; 2 | const O_CLASS = 'o'; 3 | const WINNING_COMBINATIONS = [ 4 | [0, 1, 2], 5 | [3, 4, 5], 6 | [6, 7, 8], 7 | [0, 3, 6], 8 | [1, 4, 7], 9 | [2, 5, 8], 10 | [0, 4, 8], 11 | [2, 4, 6], 12 | ]; 13 | 14 | let currentPlayerMark = O_CLASS; 15 | let isVsPlayer = false; 16 | let oTurn = false; 17 | 18 | let xWin = 0; 19 | let oWin = 0; 20 | let tie = 0; 21 | 22 | let winningArry; 23 | let currentClass; 24 | 25 | // CASHING DOM ELEMENTS 26 | const vsCpuBtn = document.getElementById('vs-cpu'); 27 | const vsPlayerBtn = document.getElementById('vs-player'); 28 | const restartBtn = document.getElementById('restart-btn'); 29 | 30 | const gameStartEl = document.getElementById('game-start'); 31 | const gamePlayEl = document.getElementById('gameplay'); 32 | const gameMarksEl = document.querySelectorAll('#game-start-marks div'); 33 | const gameBoardEl = document.getElementById('gameplay-board'); 34 | 35 | const modalEl = document.getElementById('modal'); 36 | const backdropEl = document.getElementById('backdrop'); 37 | 38 | const cells = document.querySelectorAll('.gameplay__card'); 39 | const opponentMessage = document.getElementById('opponent-message'); 40 | 41 | function setGameModeHandler() { 42 | const btnClickedId = this.id; 43 | 44 | if (btnClickedId === 'vs-player') isVsPlayer = true; 45 | 46 | changeDomLayout(gameStartEl, 'd-block', 'd-none'); 47 | changeDomLayout(gamePlayEl, 'd-none', 'd-grid'); 48 | startGame(); 49 | } 50 | 51 | function changeDomLayout(domEl, display1, display2) { 52 | domEl.classList.remove(display1); 53 | domEl.classList.add(display2); 54 | } 55 | 56 | function startGame() { 57 | setBoardHoverClass(); 58 | setScoreBoard(); 59 | setTurn(); 60 | 61 | if (!isVsPlayer) playVsCpu(); 62 | else playVsPlayer(); 63 | } 64 | 65 | function setBoardHoverClass() { 66 | if (oTurn) { 67 | gameBoardEl.classList.remove(X_CLASS); 68 | gameBoardEl.classList.add(O_CLASS); 69 | } else { 70 | gameBoardEl.classList.remove(O_CLASS); 71 | gameBoardEl.classList.add(X_CLASS); 72 | } 73 | } 74 | 75 | function setScoreBoard() { 76 | const xWinEl = document.getElementById('x-win'); 77 | const tieEl = document.getElementById('tie'); 78 | const oWinEl = document.getElementById('o-win'); 79 | 80 | xWinEl.innerHTML = `${ 81 | isVsPlayer 82 | ? 'X (P1)' 83 | : currentPlayerMark === O_CLASS 84 | ? 'X (CPU)' 85 | : 'X (You)' 86 | } ${xWin}`; 87 | tieEl.innerHTML = `Ties ${tie}`; 88 | oWinEl.innerHTML = `${ 89 | isVsPlayer 90 | ? 'O (P2)' 91 | : currentPlayerMark === O_CLASS 92 | ? 'O (You)' 93 | : 'O (CPU)' 94 | } ${oWin}`; 95 | } 96 | 97 | function setTurn() { 98 | const turnEl = document.getElementById('gameplay-turn'); 99 | 100 | turnEl.innerHTML = ` 101 | 106 |   Turn`; 107 | } 108 | 109 | function playVsCpu() { 110 | if (currentPlayerMark === O_CLASS) getCpuChoice(); 111 | // CPU starts first 112 | else getPlayerChoice(); // Player starts first 113 | } 114 | 115 | function playVsPlayer() { 116 | getPlayerChoice(); 117 | } 118 | 119 | function getEmptyCells() { 120 | const cellsArray = Array.from(cells); 121 | 122 | return cellsArray.filter( 123 | cell => !cell.classList.contains('x') && !cell.classList.contains('o') 124 | ); 125 | } 126 | 127 | function setCpuBestMove() { 128 | const emptyCells = getEmptyCells(); 129 | return emptyCells[Math.floor(Math.random() * emptyCells.length)]; 130 | } 131 | 132 | async function getCpuChoice() { 133 | currentClass = oTurn ? O_CLASS : X_CLASS; 134 | 135 | gameBoardEl.classList.remove(O_CLASS); 136 | gameBoardEl.classList.remove(X_CLASS); 137 | 138 | cells.forEach(cell => cell.removeEventListener('click', playHandler)); 139 | 140 | changeDomLayout(opponentMessage, 'd-none', 'd-block'); 141 | 142 | await new Promise(resolve => { 143 | setTimeout(() => { 144 | placeMark(setCpuBestMove(), currentClass); 145 | changeDomLayout(opponentMessage, 'd-block', 'd-none'); 146 | setGameLogic(); 147 | resolve('resolved'); 148 | }, 2000); 149 | }); 150 | 151 | getPlayerChoice(); 152 | } 153 | 154 | function getPlayerChoice() { 155 | cells.forEach(cell => { 156 | if (!cell.classList.contains('x') && !cell.classList.contains('o')) { 157 | cell.addEventListener('click', playHandler, { once: true }); 158 | } 159 | }); 160 | } 161 | 162 | function placeMark(cell, mark) { 163 | cell.classList.add(mark); 164 | } 165 | 166 | function setGameLogic() { 167 | if (checkWin(currentClass)) { 168 | endGame(false); 169 | } else if (isDraw()) { 170 | endGame(true); 171 | } else { 172 | swapTurns(); 173 | setBoardHoverClass(); 174 | } 175 | } 176 | 177 | function playHandler(event) { 178 | const cell = event.target; 179 | currentClass = oTurn ? O_CLASS : X_CLASS; 180 | 181 | placeMark(cell, currentClass); 182 | setGameLogic(); 183 | 184 | if (!isVsPlayer && !checkWin(currentClass) && !isDraw()) getCpuChoice(); 185 | } 186 | 187 | function checkWin(currentClass) { 188 | return WINNING_COMBINATIONS.some(combination => { 189 | return combination.every((element, index, array) => { 190 | let condition = cells[element].classList.contains(currentClass); 191 | if (condition) winningArry = array; 192 | return condition; 193 | }); 194 | }); 195 | } 196 | 197 | function isDraw() { 198 | return [...cells].every(cell => { 199 | return cell.classList.contains(X_CLASS) || cell.classList.contains(O_CLASS); 200 | }); 201 | } 202 | 203 | function configureModalButtons() { 204 | const nextRoundBtn = document.getElementById('next-round'); 205 | const quitBtn = document.getElementById('quit'); 206 | 207 | nextRoundBtn.addEventListener('click', setNextRound); 208 | quitBtn.addEventListener('click', () => { 209 | location.reload(); 210 | }); 211 | } 212 | 213 | function endGame(draw) { 214 | const tieInnerEl = document.getElementById('tie-inner'); 215 | 216 | if (draw) { 217 | tie++; 218 | tieInnerEl.innerText = tie; 219 | 220 | changeDomLayout(backdropEl, 'd-none', 'd-block'); 221 | changeDomLayout(modalEl, 'd-none', 'd-block'); 222 | 223 | modalEl.innerHTML = ` 224 |

Round Tied

225 | 226 | 230 | `; 231 | } else { 232 | setWinner(oTurn); 233 | } 234 | 235 | configureModalButtons(); 236 | } 237 | 238 | function swapTurns() { 239 | oTurn = !oTurn; 240 | setTurn(); 241 | } 242 | 243 | function setWinner() { 244 | const xWinInnerEl = document.getElementById('x-win-inner'); 245 | const oWinInnerEl = document.getElementById('o-win-inner'); 246 | 247 | if (oTurn) oWin++; 248 | else xWin++; 249 | 250 | xWinInnerEl.innerText = xWin; 251 | oWinInnerEl.innerText = oWin; 252 | 253 | winningArry.forEach(index => { 254 | cells[index].classList.add('win'); 255 | }); 256 | 257 | setTimeout(() => { 258 | changeDomLayout(backdropEl, 'd-none', 'd-block'); 259 | changeDomLayout(modalEl, 'd-none', 'd-block'); 260 | }, 500); 261 | 262 | modalEl.innerHTML = `

${ 263 | isVsPlayer 264 | ? oTurn 265 | ? 'Player 2 Win' 266 | : 'Player 1 win' 267 | : oTurn && currentPlayerMark === 'o' 268 | ? 'You won' 269 | : !oTurn && currentPlayerMark === 'x' 270 | ? 'You won' 271 | : 'oh No, you lost...' 272 | }

273 | 283 | 284 | `; 288 | } 289 | 290 | function setNextRound() { 291 | oTurn = false; 292 | 293 | changeDomLayout(modalEl, 'd-block', 'd-none'); 294 | changeDomLayout(backdropEl, 'd-block', 'd-none'); 295 | 296 | cells.forEach(cell => { 297 | cell.classList.remove(X_CLASS); 298 | cell.classList.remove(O_CLASS); 299 | cell.classList.remove('win'); 300 | cell.removeEventListener('click', playHandler); 301 | }); 302 | 303 | startGame(); 304 | } 305 | 306 | function restartHandler() { 307 | modalEl.innerHTML = `

Restart Game

308 | 309 | `; 317 | 318 | const restartBtn = document.getElementById('btn-restart'); 319 | const cancelBtn = document.getElementById('btn-cancel'); 320 | 321 | changeDomLayout(modalEl, 'd-none', 'd-block'); 322 | 323 | restartBtn.addEventListener('click', setNextRound); 324 | cancelBtn.addEventListener('click', () => { 325 | changeDomLayout(modalEl, 'd-block', 'd-none'); 326 | }); 327 | } 328 | 329 | function getUserChoiceHandler() { 330 | currentPlayerMark = this.id; 331 | 332 | this.classList.add('selected'); 333 | 334 | if (this.nextElementSibling) 335 | this.nextElementSibling.classList.remove('selected'); 336 | else this.previousElementSibling.classList.remove('selected'); 337 | } 338 | 339 | gameMarksEl.forEach(mark => { 340 | mark.addEventListener('click', getUserChoiceHandler); 341 | }); 342 | 343 | restartBtn.addEventListener('click', restartHandler); 344 | vsCpuBtn.addEventListener('click', setGameModeHandler); 345 | vsPlayerBtn.addEventListener('click', setGameModeHandler); 346 | -------------------------------------------------------------------------------- /assets/styles/style.css: -------------------------------------------------------------------------------- 1 | *, 2 | *::before, 3 | *::after { 4 | margin: 0; 5 | padding: 0; 6 | box-sizing: inherit; } 7 | 8 | html { 9 | font-size: 62.5%; 10 | box-sizing: border-box; } 11 | @media only screen and (max-width: 37.5em) { 12 | html { 13 | font-size: 56.25%; } } 14 | @media only screen and (max-width: 18.75em) { 15 | html { 16 | font-size: 35%; } } 17 | 18 | body { 19 | font-family: 'Outfit', sans-serif; 20 | font-size: 1.4rem; 21 | font-weight: 500; 22 | line-height: 1.8rem; 23 | color: #a8bfc9; 24 | background-color: #1a2a33; 25 | min-height: 100vh; 26 | display: flex; 27 | align-items: center; 28 | justify-content: center; 29 | overflow-x: hidden; 30 | position: relative; } 31 | 32 | .container { 33 | max-width: 90%; } 34 | 35 | .heading-lg { 36 | font-weight: 700; 37 | text-transform: uppercase; 38 | font-size: 4rem; 39 | line-height: 5rem; } 40 | .heading-lg--blue { 41 | color: #31c3bd; } 42 | .heading-lg--yellow { 43 | color: #f2b137; } 44 | @media only screen and (max-width: 37.5em) { 45 | .heading-lg { 46 | font-size: 2.4rem; } } 47 | 48 | .heading-xs { 49 | font-weight: 700; 50 | text-transform: uppercase; 51 | font-size: 1.6rem; 52 | line-height: 2rem; } 53 | 54 | .d-none { 55 | display: none !important; } 56 | 57 | .d-grid { 58 | display: grid !important; } 59 | 60 | .d-block { 61 | display: block !important; } 62 | 63 | @keyframes fadeIn { 64 | 0% { 65 | opacity: 0; } 66 | 100% { 67 | opacity: 1; } } 68 | 69 | .logo__icon { 70 | width: 7.2rem; 71 | height: 3.2rem; } 72 | 73 | .game-start { 74 | text-align: center; 75 | max-width: 90%; 76 | animation: fadeIn 1s ease-in-out; } 77 | .game-start__choices { 78 | margin: 4rem 0; 79 | background-color: #1f3641; 80 | padding: 2.4rem; 81 | border-radius: 1.5rem; 82 | box-shadow: inset 0 -0.8rem 0 #111c22; } 83 | .game-start__marks { 84 | margin: 2.4rem 0 1.7rem 0; 85 | padding: 0.9rem 0.8rem; 86 | background-color: #1a2a33; 87 | border-radius: 1rem; 88 | display: flex; 89 | height: 7rem; 90 | align-items: center; 91 | justify-content: space-between; } 92 | .game-start__x-mark, .game-start__o-mark { 93 | position: relative; 94 | width: 100%; 95 | height: 100%; 96 | border-radius: inherit; 97 | transition: all 0.4s ease; 98 | display: flex; 99 | align-items: center; 100 | justify-content: center; 101 | flex-basis: 50%; } 102 | .game-start__x-mark:hover, .game-start__o-mark:hover { 103 | background-color: #1d303a; 104 | cursor: pointer; } 105 | .game-start__x-mark.selected, .game-start__o-mark.selected { 106 | background-color: #a8bfc9; } 107 | .game-start__x-mark.selected > svg, .game-start__o-mark.selected > svg { 108 | fill: #1a2a33; } 109 | .game-start__x-icon, .game-start__o-icon { 110 | width: 3.2rem; 111 | height: 3.2rem; 112 | fill: #a8bfc9; } 113 | .game-start__footer { 114 | text-transform: uppercase; 115 | opacity: 0.5; } 116 | 117 | .btn { 118 | display: block; 119 | width: 100%; 120 | font-size: 2rem; 121 | font-weight: 700; 122 | color: #1a2a33; 123 | text-transform: uppercase; 124 | line-height: 2.5rem; 125 | text-align: center; 126 | padding: 2.5rem 12rem; 127 | border: none; 128 | border-radius: 1.5rem; 129 | transition: all 0.4s ease; } 130 | .btn:hover { 131 | cursor: pointer; } 132 | .btn--small { 133 | width: auto; 134 | display: inline-block; 135 | padding: 1.5rem 1.8rem; } 136 | .btn--small:last-child { 137 | margin-left: 1rem; } 138 | .btn:not(:last-child) { 139 | margin-bottom: 1.5rem; } 140 | .btn--yellow { 141 | background-color: #f2b137; 142 | box-shadow: inset 0 -0.8rem 0 #b77c0c; } 143 | .btn--yellow-small { 144 | background-color: #f2b137; 145 | box-shadow: inset 0 -0.4rem 0 #b77c0c; } 146 | .btn--yellow-small:hover { 147 | background-color: #ffc860; } 148 | .btn--yellow:hover { 149 | background-color: #ffc860; } 150 | .btn--blue { 151 | background-color: #31c3bd; 152 | box-shadow: inset 0 -0.8rem 0 #1d716e; } 153 | .btn--blue:hover { 154 | background-color: #65e9e4; } 155 | .btn--silver { 156 | background-color: #a8bfc9; 157 | box-shadow: inset 0 -0.8rem 0 #6991a2; } 158 | .btn--silver-small { 159 | background-color: #a8bfc9; 160 | box-shadow: inset 0 -0.4rem 0 #6991a2; } 161 | .btn--silver-small:hover { 162 | background-color: #dbe8ed; } 163 | .btn--silver:hover { 164 | background-color: #dbe8ed; } 165 | 166 | .gameplay { 167 | display: grid; 168 | margin: 5rem 0; 169 | grid-template-columns: repeat(3, 1fr); 170 | grid-gap: 2rem; 171 | align-items: center; 172 | text-align: center; 173 | animation: fadeIn 0.4s ease-in-out; 174 | display: none; } 175 | .gameplay__turn { 176 | width: 100%; 177 | background-color: #1f3641; 178 | padding: 2rem 3rem; 179 | border-radius: 1rem; 180 | box-shadow: inset 0 -0.4rem 0 #0f191e; 181 | display: flex; 182 | align-items: center; } 183 | @media only screen and (max-width: 37.5em) { 184 | .gameplay__turn { 185 | font-size: 1.4rem; 186 | padding: 2rem 1.5rem; } } 187 | .gameplay__turn-icon { 188 | width: 2rem; 189 | height: 2rem; 190 | fill: #a8bfc9; } 191 | .gameplay__restart { 192 | width: 50%; 193 | background-color: #a8bfc9; 194 | padding: 1.6rem; 195 | border-radius: 1rem; 196 | box-shadow: inset 0 -0.4rem 0 #6991a2; 197 | transition: all 0.4s ease; 198 | justify-self: flex-end; } 199 | .gameplay__restart:hover { 200 | cursor: pointer; 201 | background-color: #dbe8ed; } 202 | .gameplay__restart-icon { 203 | width: 2rem; 204 | height: 2rem; } 205 | .gameplay__board { 206 | grid-column: 1 / -1; 207 | display: grid; 208 | grid-template-columns: repeat(3, 14rem); 209 | grid-template-rows: repeat(3, 14rem); 210 | grid-gap: 2rem; } 211 | @media only screen and (max-width: 37.5em) { 212 | .gameplay__board { 213 | grid-template-columns: repeat(3, minmax(9.6rem, 1fr)); 214 | grid-template-rows: repeat(3, minmax(9.6rem, 1fr)); } } 215 | .gameplay__card { 216 | padding: 3.8rem; 217 | background-color: #1f3641; 218 | box-shadow: inset 0 -0.8rem 0 #0f191e; 219 | border-radius: 1.5rem; } 220 | @media only screen and (max-width: 37.5em) { 221 | .gameplay__card { 222 | padding: 2.8rem; } } 223 | .gameplay__card:hover { 224 | cursor: pointer; } 225 | .gameplay__card.x, .gameplay__card.o { 226 | cursor: not-allowed; 227 | box-shadow: inset 0 -0.5rem 0 #0f191e; } 228 | .gameplay__card.x::before { 229 | content: url("../images/SVG/icon-x.svg"); } 230 | .gameplay__card.x.win { 231 | background-color: #31c3bd; } 232 | .gameplay__card.x.win::before { 233 | content: url("../images/SVG/icon-x-win.svg"); } 234 | .gameplay__card.o.win { 235 | background-color: #f2b137; } 236 | .gameplay__card.o.win::before { 237 | content: url("../images/SVG/icon-o-win.svg"); } 238 | .gameplay__card.o::before { 239 | content: url("../images/SVG/icon-o.svg"); } 240 | .gameplay__board.x .gameplay__card:not(.x):not(.o):hover::before { 241 | content: url("../images/SVG/icon-x-outline.svg"); } 242 | .gameplay__board.o .gameplay__card:not(.x):not(.o):hover::before { 243 | content: url("../images/SVG/icon-o-outline.svg"); } 244 | .gameplay__win, .gameplay__tie, .gameplay__loss { 245 | color: #1a2a33; 246 | text-transform: uppercase; 247 | padding: 1.3rem 4rem; 248 | border-radius: 1.5rem; } 249 | @media only screen and (max-width: 37.5em) { 250 | .gameplay__win, .gameplay__tie, .gameplay__loss { 251 | padding: 1rem 2.3rem; } } 252 | .gameplay__win { 253 | background-color: #31c3bd; } 254 | .gameplay__tie { 255 | background-color: #a8bfc9; } 256 | .gameplay__loss { 257 | background-color: #f2b137; } 258 | .gameplay__highlight { 259 | display: block; 260 | font-size: 2.4rem; 261 | font-weight: 700; 262 | line-height: 3rem; } 263 | @media only screen and (max-width: 37.5em) { 264 | .gameplay__highlight { 265 | font-size: 2rem; 266 | line-height: 2.5rem; } } 267 | .gameplay__opponent-message { 268 | grid-column: span 3; } 269 | .gameplay__opponent-message > p { 270 | display: inline; } 271 | 272 | .backdrop { 273 | position: absolute; 274 | top: 0; 275 | left: 0; 276 | width: 100%; 277 | height: 100%; 278 | background-color: rgba(0, 0, 0, 0.5); 279 | display: none; } 280 | 281 | .modal { 282 | position: absolute; 283 | top: 50%; 284 | left: 0; 285 | width: 100%; 286 | padding: 6.7rem 0; 287 | background-color: #1f3641; 288 | text-align: center; 289 | transform: translateY(-50%); 290 | animation: fadeIn 0.4s ease-in-out; 291 | display: none; } 292 | .modal__result { 293 | display: flex; 294 | align-items: center; 295 | justify-content: center; 296 | margin-top: 3rem; } 297 | @media only screen and (max-width: 37.5em) { 298 | .modal__result { 299 | margin-top: 1.5rem; } } 300 | .modal__result > *:last-child { 301 | margin-left: 2.5rem; } 302 | @media only screen and (max-width: 37.5em) { 303 | .modal__result > *:last-child { 304 | margin-left: 1.5rem; } } 305 | .modal__icon { 306 | width: 6.4rem; 307 | height: 6.4rem; } 308 | @media only screen and (max-width: 37.5em) { 309 | .modal__icon { 310 | width: 2.8rem; 311 | height: 2.8rem; } } 312 | .modal__buttons { 313 | margin-top: 3rem; } 314 | -------------------------------------------------------------------------------- /assets/styles/test.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /designs/accessability-score.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/accessability-score.PNG -------------------------------------------------------------------------------- /designs/css-validation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/css-validation.png -------------------------------------------------------------------------------- /designs/desktop-game-multiplayer-player1-win.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/desktop-game-multiplayer-player1-win.png -------------------------------------------------------------------------------- /designs/desktop-game-multiplayer-player2-win.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/desktop-game-multiplayer-player2-win.png -------------------------------------------------------------------------------- /designs/desktop-game-multiplayer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/desktop-game-multiplayer.png -------------------------------------------------------------------------------- /designs/desktop-game-solo-player-loss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/desktop-game-solo-player-loss.png -------------------------------------------------------------------------------- /designs/desktop-game-solo-player-win.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/desktop-game-solo-player-win.png -------------------------------------------------------------------------------- /designs/desktop-game-solo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/desktop-game-solo.png -------------------------------------------------------------------------------- /designs/desktop-game-start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/desktop-game-start.png -------------------------------------------------------------------------------- /designs/desktop-new-game-menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/desktop-new-game-menu.png -------------------------------------------------------------------------------- /designs/desktop-restart-game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/desktop-restart-game.png -------------------------------------------------------------------------------- /designs/desktop-round-tied.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/desktop-round-tied.png -------------------------------------------------------------------------------- /designs/html-validation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/html-validation.png -------------------------------------------------------------------------------- /designs/js-validation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/js-validation.png -------------------------------------------------------------------------------- /designs/main-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/main-screenshot.png -------------------------------------------------------------------------------- /designs/page-speed-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/page-speed-desktop.png -------------------------------------------------------------------------------- /designs/page-speed-mobile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OracleBrain/Tic-Tac-Toe-Game/5e647f0af15f0f2ebb0a33b7b4e5e367c560e960/designs/page-speed-mobile.png -------------------------------------------------------------------------------- /designs/test.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 14 | 15 | 16 | 20 | 21 | 22 | 23 | 24 | Tic Tac Toe 25 | 26 | 27 |
 
28 | 29 | 30 | 31 | 32 | 33 |
34 | 41 | 42 |
43 |
44 |

Pick player 1's mark

45 |
46 |
47 |
48 | 49 | 52 | 53 |
54 |
55 | 56 | 59 | 60 |
61 |
62 |
Remember: x goes first
63 |
64 | 65 |
66 | 67 | 70 |
71 |
72 | 73 | 74 |
75 |
76 | 81 |
82 |
83 |
84 | 85 | 88 | 89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |

Your opponent is thinking

106 | Your opponent message 107 |
108 |
109 | 110 | 111 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tic-tac-toe-new", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tic-tac-toe-new", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "watch:sass": "node-sass sass/main.scss assets/styles/style.css -w" 8 | }, 9 | "keywords": [], 10 | "author": "", 11 | "license": "ISC", 12 | "devDependencies": {} 13 | } 14 | -------------------------------------------------------------------------------- /sass/_base.scss: -------------------------------------------------------------------------------- 1 | // COLOR VARIABLES 2 | $dark-navy: #1a2a33; 3 | $semi-dark-navy: #1f3641; 4 | $silver: #a8bfc9; 5 | $silver-hover: #dbe8ed; 6 | $light-blue: #31c3bd; 7 | $light-blue-hover: #65e9e4; 8 | $light-yellow: #f2b137; 9 | $light-yellow-hover: #ffc860; 10 | 11 | // MIXINS 12 | @mixin headingsCommon { 13 | font-weight: 700; 14 | text-transform: uppercase; 15 | } 16 | 17 | // MEDIA QUERY MANAGER 18 | @mixin respond($breakpoint) { 19 | @if $breakpoint == small { 20 | @media only screen and (max-width: 37.5em) { 21 | @content; 22 | } 23 | } 24 | 25 | @if $breakpoint == ex-small { 26 | @media only screen and (max-width: 18.75em) { 27 | @content; 28 | } 29 | } 30 | } 31 | 32 | // RESET 33 | *, 34 | *::before, 35 | *::after { 36 | margin: 0; 37 | padding: 0; 38 | box-sizing: inherit; 39 | } 40 | 41 | // MAIN STYLE 42 | html { 43 | font-size: 62.5%; 44 | box-sizing: border-box; 45 | 46 | @include respond(small) { 47 | font-size: 56.25%; 48 | } 49 | 50 | @include respond(ex-small) { 51 | font-size: 35%; 52 | } 53 | } 54 | 55 | body { 56 | font-family: 'Outfit', sans-serif; 57 | font-size: 1.4rem; 58 | font-weight: 500; 59 | line-height: 1.8rem; 60 | color: $silver; 61 | background-color: $dark-navy; 62 | min-height: 100vh; 63 | display: flex; 64 | align-items: center; 65 | justify-content: center; 66 | overflow-x: hidden; 67 | position: relative; 68 | } 69 | 70 | // UTILITIES 71 | .container { 72 | max-width: 90%; 73 | } 74 | 75 | .heading-lg { 76 | @include headingsCommon; 77 | font-size: 4rem; 78 | line-height: 5rem; 79 | 80 | &--blue { 81 | color: $light-blue; 82 | } 83 | 84 | &--yellow { 85 | color: $light-yellow; 86 | } 87 | 88 | @include respond(small) { 89 | font-size: 2.4rem; 90 | } 91 | } 92 | 93 | .heading-xs { 94 | @include headingsCommon; 95 | font-size: 1.6rem; 96 | line-height: 2rem; 97 | } 98 | 99 | // .text-yellow { 100 | // color: $light-yellow; 101 | // } 102 | 103 | // .text-blue { 104 | // color: $light-blue; 105 | // } 106 | 107 | .d-none { 108 | display: none !important; 109 | } 110 | 111 | .d-grid { 112 | display: grid !important; 113 | } 114 | 115 | .d-block { 116 | display: block !important; 117 | } 118 | 119 | // ANIMATION 120 | @keyframes fadeIn { 121 | 0% { 122 | opacity: 0; 123 | } 124 | 125 | 100% { 126 | opacity: 1; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /sass/_components.scss: -------------------------------------------------------------------------------- 1 | // Logo 2 | .logo { 3 | &__icon { 4 | width: 7.2rem; 5 | height: 3.2rem; 6 | } 7 | } 8 | 9 | // GAME START SECTION 10 | .game-start { 11 | text-align: center; 12 | // min-width: 40.6rem; 13 | max-width: 90%; 14 | animation: fadeIn 1s ease-in-out; 15 | 16 | &__choices { 17 | margin: 4rem 0; 18 | background-color: $semi-dark-navy; 19 | padding: 2.4rem; 20 | border-radius: 1.5rem; 21 | box-shadow: inset 0 -0.8rem 0 darken($color: $dark-navy, $amount: 5); 22 | } 23 | 24 | &__marks { 25 | margin: 2.4rem 0 1.7rem 0; 26 | padding: 0.9rem 0.8rem; 27 | background-color: $dark-navy; 28 | border-radius: 1rem; 29 | display: flex; 30 | height: 7rem; 31 | align-items: center; 32 | justify-content: space-between; 33 | } 34 | 35 | &__x-mark, 36 | &__o-mark { 37 | position: relative; 38 | width: 100%; 39 | height: 100%; 40 | border-radius: inherit; 41 | transition: all 0.4s ease; 42 | display: flex; 43 | align-items: center; 44 | justify-content: center; 45 | flex-basis: 50%; 46 | 47 | &:hover { 48 | background-color: lighten($color: $dark-navy, $amount: 2); 49 | cursor: pointer; 50 | } 51 | 52 | &.selected { 53 | background-color: $silver; 54 | 55 | & > svg { 56 | fill: $dark-navy; 57 | } 58 | } 59 | } 60 | 61 | &__x-icon, 62 | &__o-icon { 63 | width: 3.2rem; 64 | height: 3.2rem; 65 | fill: $silver; 66 | } 67 | 68 | &__footer { 69 | text-transform: uppercase; 70 | opacity: 0.5; 71 | } 72 | } 73 | 74 | // BUTTONS - BUTTON STYLE 75 | .btn { 76 | display: block; 77 | width: 100%; 78 | font-size: 2rem; 79 | font-weight: 700; 80 | color: $dark-navy; 81 | text-transform: uppercase; 82 | line-height: 2.5rem; 83 | text-align: center; 84 | padding: 2.5rem 12rem; 85 | border: none; 86 | border-radius: 1.5rem; 87 | transition: all 0.4s ease; 88 | 89 | @include respond(small) { 90 | font-size: 1.6rem; 91 | } 92 | 93 | &:hover { 94 | cursor: pointer; 95 | } 96 | 97 | &--small { 98 | width: auto; 99 | display: inline-block; 100 | padding: 1.5rem 1.8rem; 101 | 102 | &:last-child { 103 | margin-left: 1rem; 104 | } 105 | } 106 | 107 | &:not(:last-child) { 108 | margin-bottom: 1.5rem; 109 | } 110 | 111 | &--yellow { 112 | background-color: $light-yellow; 113 | box-shadow: inset 0 -0.8rem 0 darken($color: $light-yellow, $amount: 20); 114 | 115 | &-small { 116 | background-color: $light-yellow; 117 | box-shadow: inset 0 -0.4rem 0 darken($color: $light-yellow, $amount: 20); 118 | 119 | &:hover { 120 | background-color: $light-yellow-hover; 121 | } 122 | } 123 | 124 | &:hover { 125 | background-color: $light-yellow-hover; 126 | } 127 | } 128 | 129 | &--blue { 130 | background-color: $light-blue; 131 | box-shadow: inset 0 -0.8rem 0 darken($color: $light-blue, $amount: 20); 132 | 133 | &:hover { 134 | background-color: $light-blue-hover; 135 | } 136 | } 137 | 138 | &--silver { 139 | background-color: $silver; 140 | box-shadow: inset 0 -0.8rem 0 darken($color: $silver, $amount: 20); 141 | 142 | &-small { 143 | background-color: $silver; 144 | box-shadow: inset 0 -0.4rem 0 darken($color: $silver, $amount: 20); 145 | 146 | &:hover { 147 | background-color: $silver-hover; 148 | } 149 | } 150 | 151 | &:hover { 152 | background-color: $silver-hover; 153 | } 154 | } 155 | } 156 | 157 | // GAMEPLAY SECTION 158 | .gameplay { 159 | display: grid; 160 | margin: 5rem 0; 161 | grid-template-columns: repeat(3, 1fr); 162 | grid-gap: 2rem; 163 | align-items: center; 164 | text-align: center; 165 | animation: fadeIn 0.4s ease-in-out; 166 | display: none; 167 | 168 | &__turn { 169 | width: 100%; 170 | background-color: $semi-dark-navy; 171 | padding: 2rem 3rem; 172 | border-radius: 1rem; 173 | box-shadow: inset 0 -0.4rem 0 darken($color: $semi-dark-navy, $amount: 10); 174 | display: flex; 175 | align-items: center; 176 | 177 | @include respond(small) { 178 | font-size: 1.4rem; 179 | padding: 2rem 1.5rem; 180 | } 181 | 182 | &-icon { 183 | width: 2rem; 184 | height: 2rem; 185 | fill: $silver; 186 | } 187 | } 188 | 189 | &__restart { 190 | width: 50%; 191 | background-color: $silver; 192 | padding: 1.6rem; 193 | border-radius: 1rem; 194 | box-shadow: inset 0 -0.4rem 0 darken($color: $silver, $amount: 20); 195 | transition: all 0.4s ease; 196 | justify-self: flex-end; 197 | 198 | &:hover { 199 | cursor: pointer; 200 | background-color: $silver-hover; 201 | } 202 | 203 | &-icon { 204 | width: 2rem; 205 | height: 2rem; 206 | } 207 | } 208 | 209 | &__board { 210 | grid-column: 1 / -1; 211 | display: grid; 212 | grid-template-columns: repeat(3, 14rem); 213 | grid-template-rows: repeat(3, 14rem); 214 | grid-gap: 2rem; 215 | 216 | @include respond(small) { 217 | grid-template-columns: repeat(3, minmax(9.6rem, 1fr)); 218 | grid-template-rows: repeat(3, minmax(9.6rem, 1fr)); 219 | } 220 | } 221 | 222 | &__card { 223 | padding: 3.8rem; 224 | background-color: $semi-dark-navy; 225 | box-shadow: inset 0 -0.8rem 0 darken($color: $semi-dark-navy, $amount: 10); 226 | border-radius: 1.5rem; 227 | 228 | @include respond(small) { 229 | padding: 2.8rem; 230 | } 231 | 232 | &:hover { 233 | cursor: pointer; 234 | } 235 | 236 | &.x, 237 | &.o { 238 | cursor: not-allowed; 239 | box-shadow: inset 0 -0.5rem 0 darken($color: $semi-dark-navy, $amount: 10); 240 | } 241 | 242 | &.x::before { 243 | content: url('../images/SVG/icon-x.svg'); 244 | } 245 | 246 | &.x.win { 247 | background-color: $light-blue; 248 | 249 | &::before { 250 | content: url('../images/SVG/icon-x-win.svg'); 251 | } 252 | } 253 | 254 | &.o.win { 255 | background-color: $light-yellow; 256 | 257 | &::before { 258 | content: url('../images/SVG/icon-o-win.svg'); 259 | } 260 | } 261 | 262 | &.o::before { 263 | content: url('../images/SVG/icon-o.svg'); 264 | } 265 | } 266 | 267 | &__board.x &__card:not(.x):not(.o):hover::before { 268 | content: url('../images/SVG/icon-x-outline.svg'); 269 | } 270 | 271 | &__board.o &__card:not(.x):not(.o):hover::before { 272 | content: url('../images/SVG/icon-o-outline.svg'); 273 | } 274 | 275 | &__win, 276 | &__tie, 277 | &__loss { 278 | color: $dark-navy; 279 | text-transform: uppercase; 280 | padding: 1.3rem 4rem; 281 | border-radius: 1.5rem; 282 | 283 | @include respond(small) { 284 | padding: 1rem 2.3rem; 285 | } 286 | } 287 | 288 | &__win { 289 | background-color: $light-blue; 290 | } 291 | 292 | &__tie { 293 | background-color: $silver; 294 | } 295 | 296 | &__loss { 297 | background-color: $light-yellow; 298 | } 299 | 300 | &__highlight { 301 | display: block; 302 | font-size: 2.4rem; 303 | font-weight: 700; 304 | line-height: 3rem; 305 | 306 | @include respond(small) { 307 | font-size: 2rem; 308 | line-height: 2.5rem; 309 | } 310 | } 311 | 312 | &__opponent-message { 313 | grid-column: span 3; 314 | 315 | & > p { 316 | display: inline; 317 | } 318 | } 319 | } 320 | 321 | // BACKDROP & MODEL 322 | .backdrop { 323 | position: absolute; 324 | top: 0; 325 | left: 0; 326 | width: 100%; 327 | height: 100%; 328 | background-color: rgba(#000, 0.5); 329 | display: none; 330 | } 331 | 332 | .modal { 333 | position: absolute; 334 | top: 50%; 335 | left: 0; 336 | width: 100%; 337 | padding: 6.7rem 0; 338 | background-color: $semi-dark-navy; 339 | text-align: center; 340 | transform: translateY(-50%); 341 | animation: fadeIn 0.4s ease-in-out; 342 | display: none; 343 | 344 | &__result { 345 | display: flex; 346 | align-items: center; 347 | justify-content: center; 348 | margin-top: 3rem; 349 | 350 | @include respond(small) { 351 | margin-top: 1.5rem; 352 | } 353 | 354 | & > *:last-child { 355 | margin-left: 2.5rem; 356 | 357 | @include respond(small) { 358 | margin-left: 1.5rem; 359 | } 360 | } 361 | } 362 | 363 | &__icon { 364 | width: 6.4rem; 365 | height: 6.4rem; 366 | 367 | @include respond(small) { 368 | width: 2.8rem; 369 | height: 2.8rem; 370 | } 371 | } 372 | 373 | &__buttons { 374 | margin-top: 3rem; 375 | } 376 | } 377 | -------------------------------------------------------------------------------- /sass/main.scss: -------------------------------------------------------------------------------- 1 | @import 'base'; 2 | @import 'components'; 3 | -------------------------------------------------------------------------------- /sass/test.txt+: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------