├── LICENSE ├── index.html ├── README.md ├── css └── style.css └── js └── index.js /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 SHREYA MALOGI 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | XOXO Game by 5hre9a 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 |
16 |
17 |

Player 1 choose your symbol

18 | 19 | 20 |
21 | 22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
0
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
0
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 | 67 |
68 | 69 | 70 |
71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XOXO Game 🎮 2 | 3 | Welcome to the XOXO Game - a classic tic-tac-toe experience (XOXO) game. 4 | 5 | [![GitHub stars](https://img.shields.io/github/stars/5hre9a/XOXO-Game.svg?style=social)](https://github.com/5hre9a/XOXO-Game/stargazers) 6 | 7 | ### Game Details: 🕹️🎲📅 8 | 9 | - **Functionality:** Play tic-tac-toe against AI (Artificial Intelligence) single-player mode. 10 | - **Tech Stack:** `HTML`, `CSS`, `JavaScript `, `Bootstrap`, `Font Awesome`, `jQuery`, `AI` `(minimax algorithm for computer opponent)` 11 | - **Author:** [@shreyamalogi](https://github.com/shreyamalogi/) 12 | - **Year of Creation:** 2020 13 | - **View the Project Here** https://shreyamalogi.github.io/XOXO-game/ 14 | --- 15 | 16 | ## Table of Contents 17 | - [Introduction](#introduction) 18 | - [Challenegs and Solution](#introduction) 19 | - [Features](#features) 20 | - [How to Play](#how-to-play-%EF%B8%8F) 21 | - [Game Rules](#game-rules-) 22 | - [Getting Started](#getting-started) 23 | - [Contributing](#contribution---cast-your-star-) 24 | - [License](#license-%EF%B8%8F) 25 | 26 | 27 | ## Introduction 28 | 29 | This project is a web-based Tic-Tac-Toe game developed by **Shreya Malogi**. It allows players 🎮 to compete against AI 🤖 and enjoy a classic game of XOXO. 🌐🎮 30 | 31 | 32 | ### Challenge 1: Game Logic 🎮 33 | 34 | **Challenge:** Implementing Tic-Tac-Toe logic, ensuring valid moves and determining wins or ties. 35 | 36 | **Solution:** Shreya's research and step-by-step approach effectively crafted the core game functionality. 37 | 38 | 39 | 40 | ### Challenge 2: AI Opponent 🤖 41 | 42 | **Challenge:** Creating a challenging AI opponent with strategic moves. 43 | 44 | **Solution:** Shreya implemented the minimax algorithm, elevating the game's excitement. 45 | 46 | 47 | 48 | ### Challenge 3: Testing 🧪 49 | 50 | **Challenge:** Rigorous testing for a bug-free and functional game. 51 | 52 | **Solution:** Shreya's thorough testing and user feedback refined the game for a smooth experience. 53 | 54 | ## Features 55 | 56 | - Choose Your Symbol 💡: Play as either 'X' or 'O'. 57 | - Interactive UI 🎨: Enjoy a user-friendly and visually appealing game interface. 58 | - Player vs. Computer 🤖: Test your skills against an AI opponent. 59 | - Scoreboard 🏆: Keep track of your wins and losses. 60 | - Responsive Design 📱: Play the game on various devices with ease. 61 | 62 | ## How to Play: 🎮🕹️ 63 | 64 | 1. Open the `index.html` file in your preferred web browser. 65 | 2. Player 1 selects their symbol by clicking on ❌ or ⭕. 66 | 3. The game begins, and players take turns making moves. 67 | 4. If playing against the computer, watch out for its strategic moves! 68 | 5. Continue until a player wins or the game ends in a tie. 69 | 6. Scores are displayed on the right, and game status is shown at the bottom. 70 | 7. To start a new game, click the ♻️ icon in the top-right corner. 71 | 72 | ## Game Rules: 📜🎲 73 | 74 | - Follows standard tic-tac-toe rules. 🔄🕹️ 75 | - Achieve three in a row horizontally, vertically, or diagonally to win. 🏆🔢 76 | - A tie occurs if all cells are filled with no winner. 🤝🚫 77 | 78 | ## Getting Started 79 | 80 | 81 | To run the XOXO Game locally on your machine, follow these detailed steps: 82 | 83 | 1. **Clone the Repository:** 84 | - Open your terminal. 85 | - Run the command: `git clone https://github.com/shreyamalogi/XOXO-game.git`. 86 | 87 | 2. **Open the Game:** 88 | - Navigate to the cloned directory using: `cd XOXO-game`. 89 | - Find and open the `index.html` file in your preferred web browser. 90 | 91 | Now you can enjoy the XOXO Game on your local machine! 92 | 93 | Alternatively, you can play the game online at [web app](https://8jzrw.csb.app/) or [GitHub Pages](https://shreyamalogi.github.io/XOXO-game/). 🌐🎮 94 | 95 | 96 | ## Contribution - Cast Your Star! ⭐🌟✨ 97 | 98 | Feel the enchantment? Contribute to this magical adventure and make it even more spellbinding. Don't forget to star the project! ⭐🌟 99 | 100 | ## License: 🕊️📘 101 | 102 | This magical creation is licensed under the spell of the MIT License. Share the magic responsibly! 103 | 104 | MIT License 105 | 106 | Copyright (c) 2020 Shreya Malogi 107 | 108 | Stay Enchanted! 🌍💙 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /css/style.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | font-family: "Play", sans-serif !important; 4 | margin: 0; 5 | background: white; 6 | background: -moz-linear-gradient( 7 | 131deg, 8 | rgba(255, 255, 255, 1) 0%, 9 | rgba(255, 255, 255, 1) 24%, 10 | rgba(102, 238, 206, 1) 53%, 11 | rgba(102, 161, 238, 1) 67%, 12 | rgba(102, 161, 238, 1) 69%, 13 | rgba(102, 161, 238, 1) 76%, 14 | rgba(102, 161, 238, 1) 83%, 15 | rgba(102, 161, 238, 1) 100% 16 | ); /* ff3.6+ */ 17 | background: -webkit-gradient( 18 | linear, 19 | left top, 20 | right bottom, 21 | color-stop(0%, rgba(102, 161, 238, 1)), 22 | color-stop(17%, rgba(102, 161, 238, 1)), 23 | color-stop(24%, rgba(102, 161, 238, 1)), 24 | color-stop(31%, rgba(102, 161, 238, 1)), 25 | color-stop(33%, rgba(102, 161, 238, 1)), 26 | color-stop(47%, rgba(102, 238, 206, 1)), 27 | color-stop(76%, rgba(255, 255, 255, 1)), 28 | color-stop(100%, rgba(255, 255, 255, 1)) 29 | ); /* safari4+,chrome */ 30 | background: -webkit-linear-gradient( 31 | 131deg, 32 | rgba(255, 255, 255, 1) 0%, 33 | rgba(255, 255, 255, 1) 24%, 34 | rgba(102, 238, 206, 1) 53%, 35 | rgba(102, 161, 238, 1) 67%, 36 | rgba(102, 161, 238, 1) 69%, 37 | rgba(102, 161, 238, 1) 76%, 38 | rgba(102, 161, 238, 1) 83%, 39 | rgba(102, 161, 238, 1) 100% 40 | ); /* safari5.1+,chrome10+ */ 41 | background: -o-linear-gradient( 42 | 131deg, 43 | rgba(255, 255, 255, 1) 0%, 44 | rgba(255, 255, 255, 1) 24%, 45 | rgba(102, 238, 206, 1) 53%, 46 | rgba(102, 161, 238, 1) 67%, 47 | rgba(102, 161, 238, 1) 69%, 48 | rgba(102, 161, 238, 1) 76%, 49 | rgba(102, 161, 238, 1) 83%, 50 | rgba(102, 161, 238, 1) 100% 51 | ); /* opera 11.10+ */ 52 | background: -ms-linear-gradient( 53 | 131deg, 54 | rgba(255, 255, 255, 1) 0%, 55 | rgba(255, 255, 255, 1) 24%, 56 | rgba(102, 238, 206, 1) 53%, 57 | rgba(102, 161, 238, 1) 67%, 58 | rgba(102, 161, 238, 1) 69%, 59 | rgba(102, 161, 238, 1) 76%, 60 | rgba(102, 161, 238, 1) 83%, 61 | rgba(102, 161, 238, 1) 100% 62 | ); /* ie10+ */ 63 | background: linear-gradient( 64 | 319deg, 65 | rgba(255, 255, 255, 1) 0%, 66 | rgba(255, 255, 255, 1) 24%, 67 | rgba(102, 238, 206, 1) 53%, 68 | rgba(102, 161, 238, 1) 67%, 69 | rgba(102, 161, 238, 1) 69%, 70 | rgba(102, 161, 238, 1) 76%, 71 | rgba(102, 161, 238, 1) 83%, 72 | rgba(102, 161, 238, 1) 100% 73 | ); /* w3c */ 74 | background-position: center; 75 | background-repeat: no-repeat; 76 | background-attachment: fixed; 77 | background-size: cover; 78 | } 79 | 80 | .container-fluid { 81 | width: 100%; 82 | height: 100%; 83 | } 84 | 85 | .game-well { 86 | position: relative; 87 | background-color: #fff; 88 | margin: auto; 89 | margin-top: 100px; 90 | margin-bottom: 100px; 91 | width: 450px; 92 | height: 450px; 93 | padding: 10px; 94 | border-style: solid; 95 | border-width: thin; 96 | border-color: #ccc; 97 | } 98 | 99 | .right-shadow { 100 | box-shadow: 10px 10px 10px 0px rgba(204, 204, 204, 0.75); 101 | } 102 | 103 | .symbolOptions { 104 | position: relative; 105 | text-align: center; 106 | width: 100%; 107 | height: 100%; 108 | } 109 | 110 | .mid-center { 111 | margin: 0; 112 | position: absolute; 113 | top: 35%; 114 | left: 50%; 115 | margin-right: -50%; 116 | transform: translate(-50%, -50%); 117 | } 118 | 119 | .symbolOptions > button { 120 | padding-left: 20px; 121 | padding-right: 20px; 122 | position: relative; 123 | top: 40%; 124 | } 125 | 126 | .symbolOptions > button:hover { 127 | font-size: 22px; 128 | } 129 | 130 | button { 131 | border: 0 !important; 132 | -webkit-appearance: none; 133 | background-color: transparent; 134 | } 135 | 136 | button:focus { 137 | outline: none !important; 138 | background-color: transparent !important; 139 | } 140 | 141 | button::-moz-focus-inner { 142 | border: 0; 143 | } 144 | 145 | .row.no-gutters { 146 | margin-right: 0; 147 | margin-left: 0; 148 | } 149 | 150 | .no-gutters > [class*="col-"] { 151 | padding-right: 0; 152 | padding-left: 0; 153 | } 154 | 155 | .score-board { 156 | position: relative; 157 | color: #fff; 158 | height: 100px; 159 | border-radius: 0 5px 5px 0; 160 | text-transform: uppercase; 161 | } 162 | 163 | .board-container { 164 | display: none; 165 | height: 100%; 166 | } 167 | 168 | .player-and-stats { 169 | margin-bottom: 5px; 170 | } 171 | 172 | .player { 173 | text-align: left; 174 | padding-left: 5px !important; 175 | margin-left: 5px; 176 | } 177 | 178 | .stats { 179 | text-align: center; 180 | border-radius: 5px; 181 | } 182 | 183 | .player, 184 | .stats { 185 | height: 35px; 186 | } 187 | 188 | .sponsor-name { 189 | position: absolute; 190 | bottom: 0; 191 | left: 10%; 192 | right: 10%; 193 | text-align: center; 194 | color: #4c4c4c; 195 | opacity: 0.8; 196 | font-size: 12px; 197 | } 198 | 199 | .x-gradient-color { 200 | background: #66eece; /* For browsers that do not support gradients */ 201 | background: -webkit-linear-gradient( 202 | #a0e8d6, 203 | #66eece 204 | ); /* For Safari 5.1 to 6.0 */ 205 | background: -o-linear-gradient(#a0e8d6, #66eece); /* For Opera 11.1 to 12.0 */ 206 | background: -moz-linear-gradient( 207 | #a0e8d6, 208 | #66eece 209 | ); /* For Firefox 3.6 to 15 */ 210 | background: linear-gradient( 211 | #a0e8d6, 212 | #66eece 213 | ); /* Standard syntax (must be last) */ 214 | } 215 | 216 | .fa-times { 217 | color: #66eece; 218 | transition: 0.3s ease-in; 219 | } 220 | 221 | .fa-circle-o { 222 | color: #66a1ee; 223 | transition: 0.3s ease-in; 224 | } 225 | 226 | .o-gradient-color { 227 | background: #66a1ee; /* For browsers that do not support gradients */ 228 | background: -webkit-linear-gradient( 229 | #b2ccea, 230 | #66a1ee 231 | ); /* For Safari 5.1 to 6.0 */ 232 | background: -o-linear-gradient(#b2ccea, #66a1ee); /* For Opera 11.1 to 12.0 */ 233 | background: -moz-linear-gradient( 234 | #b2ccea, 235 | #66a1ee 236 | ); /* For Firefox 3.6 to 15 */ 237 | background: linear-gradient( 238 | #b2ccea, 239 | #66a1ee 240 | ); /* Standard syntax (must be last) */ 241 | } 242 | 243 | .black-gradient { 244 | background: #4c4c4c; /* Old browsers */ 245 | background: -moz-linear-gradient( 246 | top, 247 | #4c4c4c 0%, 248 | #595959 12%, 249 | #666666 25%, 250 | #474747 39%, 251 | #2c2c2c 50%, 252 | #000000 51%, 253 | #111111 60%, 254 | #2b2b2b 76%, 255 | #1c1c1c 91%, 256 | #131313 100% 257 | ); /* FF3.6-15 */ 258 | background: -webkit-linear-gradient( 259 | top, 260 | #4c4c4c 0%, 261 | #595959 12%, 262 | #666666 25%, 263 | #474747 39%, 264 | #2c2c2c 50%, 265 | #000000 51%, 266 | #111111 60%, 267 | #2b2b2b 76%, 268 | #1c1c1c 91%, 269 | #131313 100% 270 | ); /* Chrome10-25,Safari5.1-6 */ 271 | background: linear-gradient( 272 | to bottom, 273 | #4c4c4c 0%, 274 | #595959 12%, 275 | #666666 25%, 276 | #474747 39%, 277 | #2c2c2c 50%, 278 | #000000 51%, 279 | #111111 60%, 280 | #2b2b2b 76%, 281 | #1c1c1c 91%, 282 | #131313 100% 283 | ); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 284 | /*filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#4c4c4c', endColorstr='#131313',GradientType=0 ); /* IE6-9 */ 285 | } 286 | 287 | .fa-gradient { 288 | -webkit-background-clip: text; 289 | -webkit-text-fill-color: transparent; 290 | } 291 | 292 | .text-shadow { 293 | text-shadow: 12px 5px 5px #ccc !important; 294 | -webkit-text-shadow: 12px 5px 5px #ccc !important; 295 | } 296 | 297 | .statuses { 298 | height: 20px; 299 | font-size: 18px; 300 | text-transform: uppercase; 301 | text-align: center; 302 | margin: 30px; 303 | } 304 | 305 | .boxList { 306 | width: 100%; 307 | height: 240px; 308 | padding-left: 0 !important; 309 | list-style: none; 310 | position: relative; 311 | } 312 | 313 | li { 314 | position: relative; 315 | 316 | width: 33.33%; 317 | height: 33.33%; 318 | float: left; 319 | text-align: center; 320 | border-style: solid; 321 | border-top: none; 322 | border-left: none; 323 | border-width: thin; 324 | border-color: #ccc; 325 | box-sizing: border-box; 326 | padding: 10px; 327 | } 328 | 329 | li > i { 330 | pointer-events: none; 331 | } 332 | 333 | li:nth-child(3n) { 334 | border-right: none; 335 | } 336 | 337 | li:nth-last-child(-n + 3) { 338 | border-bottom: none; 339 | } 340 | 341 | footer { 342 | margin-top: 10px; 343 | text-align: center; 344 | color: #66a1ee; 345 | } 346 | 347 | a:link { 348 | text-decoration: none !important; 349 | outline: none; 350 | border: 0; 351 | color: #66eece !important; 352 | } 353 | a:visited { 354 | color: #66eece !important; 355 | } 356 | 357 | /*any devices smaller than a tablet*/ 358 | @media only screen and (max-width: 550px) { 359 | .game-well { 360 | width: 100%; 361 | margin-top: 70px; 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /js/index.js: -------------------------------------------------------------------------------- 1 | //http://www.geeksforgeeks.org/minimax-algorithm-in-game-theory-set-3-tic-tac-toe-ai-finding-optimal-move/ 2 | //https://stackoverflow.com/questions/8207897/jquery-waiting-for-the-fadeout-to-complete-before-running-fadein 3 | const MAX_PLAYERS = 2; 4 | 5 | function TicTacToe() { 6 | this.player1 = {}; 7 | this.player2 = {}; 8 | this.currentPlayer = null; 9 | this.winner = null; 10 | this.winningMoves = null; 11 | this.finished = false; 12 | this.board = [ 13 | [0, 0, 0], 14 | [0, 0, 0], 15 | [0, 0, 0], 16 | ]; 17 | } 18 | 19 | function player(name, symbol) { 20 | this.name = name; 21 | this.symbol = symbol; 22 | this.score = 0; 23 | } 24 | 25 | TicTacToe.prototype.setPlayers = function (name, symbol) { 26 | this.player1 = new player(name, symbol.toUpperCase()); 27 | this.player2 = new player( 28 | "computer", 29 | this.player1.symbol.toUpperCase() == "X" ? "O" : "X" 30 | ); 31 | //When a new game is started, the first turn is picked randomly. 32 | this.setCurrentPlayer("random"); 33 | }; 34 | 35 | TicTacToe.prototype.setCurrentPlayer = function (player) { 36 | if (player === "random") { 37 | var getTurn = flipCoin(0, MAX_PLAYERS); 38 | this.currentPlayer = getTurn == 0 ? this.player1 : this.player2; 39 | } else { 40 | this.currentPlayer = player; 41 | } 42 | }; 43 | 44 | //returns a random number between 0 and max-1 45 | function flipCoin(min, max) { 46 | return Math.floor(Math.random() * (max - min)) + min; 47 | } 48 | 49 | //returns a succesful move or empty object; 50 | TicTacToe.prototype.play = function (player, row, col) { 51 | var move = {}; 52 | //place symbol 53 | if (player.toLowerCase() == "computer") { 54 | move = this.getBestMove(); 55 | this.board[move.row][move.col] = this.player2.symbol; 56 | } else { 57 | //if move hasn't already been taken then set that move to the player's symbol 58 | if (!this.moveFilled(row, col)) { 59 | this.board[row][col] = this.player1.symbol; 60 | move.row = row; 61 | move.col = col; 62 | } else { 63 | //return an empty move to represent that the move was not available 64 | return move; 65 | } 66 | } 67 | //check if there is a winner 68 | var isWinner = this.checkWinner(); 69 | if (isWinner) { 70 | this.currentPlayer.score += 1; 71 | this.winner = this.currentPlayer; 72 | this.setCurrentPlayer(this.winner); 73 | this.finished = true; 74 | return move; 75 | } 76 | //check if the game has finished; 77 | this.finished = this.isGameFinished(); 78 | 79 | //Switch players 80 | this.currentPlayer = 81 | this.currentPlayer == this.player1 ? this.player2 : this.player1; 82 | return move; 83 | }; 84 | 85 | TicTacToe.prototype.checkWinner = function () { 86 | var row = 0, 87 | col = 0; 88 | //checks if there is a horizontal line of 3 of the same symbol 89 | for (row = 0; row <= 2; row++) { 90 | if ( 91 | this.board[row][col] === this.board[row][col + 1] && 92 | this.board[row][col] != 0 93 | ) { 94 | if (this.board[row][col + 1] === this.board[row][col + 2]) { 95 | this.winningMoves = [ 96 | [row, col], 97 | [row, col + 1], 98 | [row, col + 2], 99 | ]; 100 | if (this.board[row][col] == this.currentPlayer.symbol) { 101 | return 10; 102 | } 103 | return -10; 104 | } 105 | } 106 | } 107 | //checks if there is a vertical line of 3 of the same symbol 108 | for (col = 0, row = 0; col <= 2; col++) { 109 | if ( 110 | this.board[row][col] === this.board[row + 1][col] && 111 | this.board[row][col] != 0 112 | ) { 113 | if (this.board[row + 1][col] === this.board[row + 2][col]) { 114 | this.winningMoves = [ 115 | [row, col], 116 | [row + 1, col], 117 | [row + 2, col], 118 | ]; 119 | if (this.board[row][col] == this.currentPlayer.symbol) { 120 | return 10; 121 | } 122 | return -10; 123 | } 124 | } 125 | } 126 | 127 | (col = 0), (row = 0); 128 | //checks if there is a diagonal line of 3 of one's own symbol 129 | if ( 130 | this.board[row][col] === this.board[row + 1][col + 1] && 131 | this.board[row][col] != 0 132 | ) { 133 | if (this.board[row + 1][col + 1] === this.board[row + 2][col + 2]) { 134 | this.winningMoves = [ 135 | [row, col], 136 | [row + 1, col + 1], 137 | [row + 2, col + 2], 138 | ]; 139 | if (this.board[row][col] == this.currentPlayer.symbol) { 140 | return 10; 141 | } 142 | return -10; 143 | } 144 | } 145 | 146 | (col = 0), (row = 0); 147 | //checks if there is a diagonal line of 3 of one's own symbol 148 | if ( 149 | this.board[row + 2][col] === this.board[row + 1][col + 1] && 150 | this.board[row + 2][col] != 0 151 | ) { 152 | if (this.board[row + 1][col + 1] == this.board[row][col + 2]) { 153 | this.winningMoves = [ 154 | [row + 2, col], 155 | [row + 1, col + 1], 156 | [row, col + 2], 157 | ]; 158 | if (this.board[row + 2][col] == this.currentPlayer.symbol) { 159 | return 10; 160 | } 161 | return -10; 162 | } 163 | } 164 | return 0; 165 | }; 166 | 167 | //returns false if a value of 0 is found in the board 168 | TicTacToe.prototype.isGameFinished = function () { 169 | for (var i = 0; i < this.board.length; i++) { 170 | var row = this.board[i]; 171 | if (row.indexOf(0) != -1) { 172 | return false; 173 | } 174 | } 175 | return true; 176 | }; 177 | 178 | //returns an array that represents a cell in the board 179 | TicTacToe.prototype.getBestMove = function () { 180 | var bestMove = { 181 | row: -1, 182 | col: -1, 183 | val: -1000, 184 | }; 185 | for (var row = 0; row < 3; row++) { 186 | for (var col = 0; col < 3; col++) { 187 | //check that cell is empty 188 | if (this.board[row][col] === 0) { 189 | //make the move and call minimax 190 | this.board[row][col] = this.currentPlayer.symbol; 191 | 192 | var currentMoveVal = this.minimax(this.board, 0, false); 193 | //undo the move 194 | this.board[row][col] = 0; 195 | this.winningMoves = null; 196 | if (currentMoveVal > bestMove.val) { 197 | bestMove.row = row; 198 | bestMove.col = col; 199 | bestMove.val = currentMoveVal; 200 | } 201 | } 202 | } 203 | } 204 | return bestMove; 205 | }; 206 | 207 | TicTacToe.prototype.minimax = function (board, depth, isMaximizingPlayer) { 208 | //if board is in terminal state, then return value of board 209 | var score = this.checkWinner(); 210 | this.winningMoves = null; 211 | 212 | //If Maximizer or Minimizer has won the game return player's score 213 | if (score == 10) { 214 | return score; 215 | } else if (score == -10) { 216 | return score; 217 | } 218 | 219 | //If there are no more moves and no winner then it is a tie 220 | if (this.isGameFinished()) { 221 | return 0; 222 | } 223 | 224 | //Maximizer's move 225 | if (isMaximizingPlayer) { 226 | var bestVal = -Infinity; 227 | for (var row = 0; row < 3; row++) { 228 | for (var col = 0; col < 3; col++) { 229 | if (this.board[row][col] === 0) { 230 | this.board[row][col] = this.currentPlayer.symbol; 231 | bestVal = Math.max( 232 | bestVal, 233 | this.minimax(board, depth + 1, !isMaximizingPlayer) 234 | ); 235 | this.board[row][col] = 0; 236 | this.winningMoves = null; 237 | } 238 | } 239 | } 240 | return bestVal; 241 | //Minimizer's move 242 | } else { 243 | var bestVal = Infinity; 244 | for (var row = 0; row < 3; row++) { 245 | for (var col = 0; col < 3; col++) { 246 | if (this.board[row][col] === 0) { 247 | this.board[row][col] = 248 | this.currentPlayer == this.player1 249 | ? this.player2.symbol 250 | : this.player1.symbol; 251 | bestVal = Math.min( 252 | bestVal, 253 | this.minimax(board, depth + 1, !isMaximizingPlayer) 254 | ); 255 | this.board[row][col] = 0; 256 | this.winningMoves = null; 257 | } 258 | } 259 | } 260 | return bestVal; 261 | } 262 | }; 263 | 264 | //returns boolean reperesenting whether the cell in the board has been filled 265 | TicTacToe.prototype.moveFilled = function (row, col) { 266 | //if the cell is empty return false 267 | if (this.board[row][col] == 0) { 268 | return false; 269 | } 270 | return true; 271 | }; 272 | 273 | TicTacToe.prototype.reset = function () { 274 | //clear all properties 275 | this.player1 = {}; 276 | this.player2 = {}; 277 | this.currentPlayer = null; 278 | this.clearBoard(); 279 | }; 280 | 281 | TicTacToe.prototype.clearBoard = function () { 282 | this.finished = false; 283 | this.winner = null; 284 | this.winnerMove = null; 285 | this.board = [ 286 | [0, 0, 0], 287 | [0, 0, 0], 288 | [0, 0, 0], 289 | ]; 290 | }; 291 | 292 | //model layer 293 | var model = { 294 | game: {}, 295 | setupGame: function () { 296 | this.game = new TicTacToe(); 297 | }, 298 | }; 299 | 300 | //controller layer 301 | var controller = { 302 | initializeGame: function () { 303 | model.setupGame(); 304 | }, 305 | setPlayers: function (name, symbol) { 306 | model.game.setPlayers(name, symbol); 307 | //update showScores to two different functions. One to be setStats and the other to be updateStats. 308 | view.setScoreBoard(model.game.player1, model.game.player2); 309 | view.startGame(); 310 | var timeoutID = setTimeout( 311 | function () { 312 | this.playGame(); 313 | }.bind(this), 314 | 1000 315 | ); 316 | }, 317 | playGame: function (move) { 318 | if (!model.game.finished) { 319 | //if the curent player is the computer, then disable the board and let computer take a turn 320 | if (model.game.currentPlayer.name.toLowerCase() == "computer") { 321 | view.disableBoard(); 322 | view.showTurn(model.game.currentPlayer); 323 | var move = model.game.play(model.game.currentPlayer.name); 324 | var timeoutID = setTimeout( 325 | function () { 326 | //show the computer's move on the screen 327 | view.showMove(model.game.player2.symbol, move.row, move.col); 328 | this.playGame(); 329 | }.bind(this), 330 | 1000 331 | ); 332 | } else { 333 | view.enableBoard(); 334 | view.showTurn(model.game.currentPlayer); 335 | } 336 | } else { 337 | //Once the game is finished, disable board and display who won or if it was a tie. 338 | view.disableBoard(); 339 | this.showWinnerAndReplay(); 340 | } 341 | }, 342 | //shows winner and start new game round 343 | showWinnerAndReplay: function () { 344 | var timeoutID = setTimeout(function () { 345 | view.updateScores(model.game.player1.score, model.game.player2.score); 346 | view.showWinningMove(model.game.winningMoves); 347 | view.showWinner(model.game.winner); 348 | }, 1000); 349 | 350 | this.replayGame(); 351 | }, 352 | replayGame: function () { 353 | var timeoutID = setTimeout( 354 | function () { 355 | model.game.clearBoard(); 356 | view.clearGameBoard(); 357 | this.playGame(); 358 | }.bind(this), 359 | 3000 360 | ); 361 | }, 362 | playerTurn: function (row, col) { 363 | var move = model.game.play(model.game.currentPlayer.name, row, col); 364 | //if the play method doesn't return an empty move object, show the move 365 | if (Object.keys(move).length !== 0) { 366 | //show the computer's move on the screen 367 | view.showMove(model.game.player1.symbol, move.row, move.col); 368 | } 369 | this.playGame(); 370 | }, 371 | resetAll: function () { 372 | model.game.reset(); 373 | view.disableBoard(); 374 | view.resetAll(); 375 | }, 376 | }; 377 | 378 | var view = { 379 | boardEnabled: false, 380 | btnEntry: { 381 | box0: [0, 0], 382 | box1: [0, 1], 383 | box2: [0, 2], 384 | box3: [1, 0], 385 | box4: [1, 1], 386 | box5: [1, 2], 387 | box6: [2, 0], 388 | box7: [2, 1], 389 | box8: [2, 2], 390 | }, 391 | setUpEventListeners: function () { 392 | var boxList = document.querySelector("ul"); 393 | 394 | boxList.addEventListener("click", function (event) { 395 | if (view.boardEnabled) { 396 | var boxClicked = event.target; 397 | controller.playerTurn( 398 | view.btnEntry[boxClicked.id][0], 399 | view.btnEntry[boxClicked.id][1] 400 | ); 401 | } 402 | }); 403 | 404 | document.getElementById("X").addEventListener("click", function () { 405 | controller.setPlayers("player1", "X"); 406 | }); 407 | document.getElementById("O").addEventListener("click", function () { 408 | controller.setPlayers("player1", "O"); 409 | }); 410 | document.getElementById("reset").addEventListener("click", function () { 411 | controller.resetAll(); 412 | }); 413 | }, 414 | enableBoard: function () { 415 | this.boardEnabled = true; 416 | }, 417 | disableBoard: function () { 418 | this.boardEnabled = false; 419 | }, 420 | showMove: function (playerSymbol, row, col) { 421 | var boxID = ""; 422 | 423 | for (prop in this.btnEntry) { 424 | if (this.btnEntry[prop][0] == row && this.btnEntry[prop][1] == col) { 425 | boxID = prop; 426 | break; 427 | } 428 | } 429 | var box = document.getElementById(boxID); 430 | var circleIcon = "fa-circle-o"; 431 | var timesIcon = "fa-times"; 432 | 433 | var fontIcon = playerSymbol.toUpperCase() == "X" ? timesIcon : circleIcon; 434 | box.getElementsByTagName("i")[0].classList.add(fontIcon); 435 | }, 436 | showBoardContainer: function () { 437 | $(".board-container").fadeIn(1200); 438 | }, 439 | clearGameBoard: function () { 440 | $("li>i").removeClass("fa-circle-o fa-times text-shadow"); 441 | }, 442 | hideBoardContainer: function () { 443 | return $(".board-container").fadeOut(300); 444 | }, 445 | setScoreBoard: function (player1, player2) { 446 | var player1Color = 447 | player1.symbol.toUpperCase() == "X" 448 | ? "x-gradient-color" 449 | : "o-gradient-color"; 450 | var player2Color = 451 | player2.symbol.toUpperCase() == "X" 452 | ? "x-gradient-color" 453 | : "o-gradient-color"; 454 | 455 | //document.getElementsByClassName("player1")[0].classList.add(player1Color); 456 | $(".player1").addClass(player1Color); 457 | $(".player2").addClass(player2Color); 458 | 459 | //document.getElementsByClassName("player1-name")[0].innerHTML = model.game.player1.name; 460 | $(".player1-name").html(model.game.player1.name); 461 | $(".player2-name").html(model.game.player2.name); 462 | 463 | $(".player1-score").html(model.game.player1.score); 464 | $(".player2-score").html(model.game.player2.score); 465 | }, 466 | clearScoreBoard: function () { 467 | $(".player1").removeClass("x-gradient-color o-gradient-color"); 468 | $(".player2").removeClass("x-gradient-color o-gradient-color"); 469 | 470 | $(".player1-name").html(""); 471 | $(".player2-name").html(""); 472 | 473 | $(".player1-score").html(""); 474 | $(".player2-score").html(""); 475 | }, 476 | updateScores: function (player1Score, player2Score) { 477 | $(".player1-score").html(player1Score); 478 | $(".player2-score").html(player2Score); 479 | }, 480 | hideSymbolOptions: function () { 481 | return $(".symbolOptions").fadeOut(200); 482 | }, 483 | showSymbolOptions: function () { 484 | return $(".symbolOptions").fadeIn(1200); 485 | }, 486 | startGame: function () { 487 | this.hideSymbolOptions() 488 | .promise() 489 | .done( 490 | function () { 491 | this.showBoardContainer(); 492 | }.bind(this) 493 | ); 494 | }, 495 | showTurn: function (player) { 496 | $("#whosTurn").html(player.name + "'s " + "turn"); 497 | document.getElementById("whosTurn").style.display = "block"; 498 | document.getElementById("whoWon").style.display = "none"; 499 | }, 500 | hideTurn: function () { 501 | return $("#whosTurn").fadeOut(200); 502 | }, 503 | clearStatuses: function (player) { 504 | $("#whosTurn").html(""); 505 | $("#whosWon").html(""); 506 | }, 507 | showWinningMove: function (winningMove) { 508 | if (winningMove == null) { 509 | $("li>i").addClass("text-shadow"); 510 | } else { 511 | for (var i = 0; i < winningMove.length; i++) { 512 | for (prop in this.btnEntry) { 513 | if ( 514 | this.btnEntry[prop][0] == winningMove[i][0] && 515 | this.btnEntry[prop][1] == winningMove[i][1] 516 | ) { 517 | var box = document.getElementById(prop); 518 | box.getElementsByTagName("i")[0].classList.add("text-shadow"); 519 | break; 520 | } 521 | } 522 | } 523 | } 524 | }, 525 | showWinner: function (winner) { 526 | var message = ""; 527 | if (winner == null) { 528 | message = "It was a tie"; 529 | } else if (winner.name == "player1") { 530 | message = "You Won :)"; 531 | } else if (winner.name == "computer") { 532 | message = "You lost this time -_-"; 533 | } 534 | document.getElementById("whoWon").style.display = "block"; 535 | document.getElementById("whoWon").innerHTML = message; 536 | document.getElementById("whosTurn").style.display = "none"; 537 | }, 538 | resetAll: function () { 539 | this.clearGameBoard(); 540 | this.clearStatuses(); 541 | this.clearScoreBoard(); 542 | this.hideBoardContainer() 543 | .promise() 544 | .done( 545 | function () { 546 | this.showSymbolOptions(); 547 | }.bind(this) 548 | ); 549 | }, 550 | }; 551 | 552 | //initializes the game when this script is loaded 553 | controller.initializeGame(); 554 | view.setUpEventListeners(); 555 | --------------------------------------------------------------------------------