├── 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 | [](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 |
--------------------------------------------------------------------------------