├── README.md
├── index.html
├── style.css
└── script.js
/README.md:
--------------------------------------------------------------------------------
1 | # TicTacToeGameAILab
2 | This is a puzzle game developed for AI lab project -- minimax unbeatable AI
3 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Tic Tac Toe Game - Minimax
11 |
12 |
13 |
14 | Tic Tac Toe Puzzle Game
15 |
16 |
17 |
18 |
19 |
24 |
25 |
26 |
27 |
30 |
31 |
--------------------------------------------------------------------------------
/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
6 | header {
7 | background-color: #a4b835;
8 | color: rgb(7, 7, 7);
9 | text-align: center;
10 | height: 60px;
11 | padding-top: 5px;
12 | display: block;
13 | margin-top: 0px;
14 | margin-bottom: 60px;
15 | box-sizing: border-box;
16 | position: relative;
17 | width: 100%;
18 | }
19 |
20 | p {
21 | font-size: 16pt;
22 | font-weight: bold;
23 | font-family: sans-serif;
24 | text-align: center;
25 | position: relative;
26 | text-align: center;
27 | margin-left: auto;
28 | margin-right: auto;
29 | display: block;
30 | }
31 |
32 | a {
33 | color: #4c9677;
34 | text-decoration: none;
35 | }
36 | a:hover {
37 | color: #4c9677;
38 | }
39 | a:visited {
40 | color: #4c9677;
41 | }
42 |
43 | #tab-tic-tac-toe {
44 | margin-left: auto;
45 | margin-right: auto;
46 | padding: 5px;
47 | font-size: 4em;
48 | font-family: Arial, Helvetica, sans-serif;
49 | color: rgb(100, 92, 92);
50 | background: #88622fa8;
51 | width: 310px;
52 | height: 300px;
53 | text-align: center;
54 | vertical-align: center;
55 | border: 2px solid #CECECE;
56 | border-radius: 5px;
57 | box-shadow: 1px 1px 1px #CCC;
58 | }
59 |
60 | /*Column style*/
61 | #tab-tic-tac-toe td {
62 | border-collapse:collapse;
63 | border-left: 5px solid #CCC;
64 | border-bottom: 5px solid #CCC;
65 | }
66 |
67 | #tab-tic-tac-toe td:first-child {
68 | border-left: none;
69 | }
70 |
71 | #tab-tic-tac-toe tr:last-child td {
72 | border-bottom: none;
73 | }
74 |
75 | /*Cells*/
76 | #tab-tic-tac-toe td {
77 | cursor: pointer;
78 | height: 95px;
79 | width: 95px;
80 | }
81 |
82 | #tab-tic-tac-toe td:hover {
83 | background: #ECECEC;
84 | }
85 |
86 | #bnt-restart {
87 | display: block;
88 | padding: 10px;
89 | margin-left: auto;
90 | margin-right: auto;
91 | width: 200px;
92 | background: #4c9677;
93 | font-size: 1.5em;
94 | color: #FFF;
95 | border: none;
96 | border-radius: 6px;
97 | cursor: pointer;
98 | }
99 |
100 | #bnt-restart:hover {
101 | background: #4c9677;
102 | }
103 |
104 | #bnt-restart:active {
105 | background: #4c9677;
106 | }
107 |
108 | #bnt-restart:disabled {
109 | color: #444;
110 | background: #CECECE;
111 | }
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 |
2 | var board = [
3 | [0, 0, 0],
4 | [0, 0, 0],
5 | [0, 0, 0],
6 | ];
7 |
8 | var HUMAN = -1;
9 | var COMP = +1;
10 |
11 | function evalute(state) {
12 | var score = 0;
13 |
14 | if (gameOver(state, COMP)) {
15 | score = +1;
16 | }
17 | else if (gameOver(state, HUMAN)) {
18 | score = -1;
19 | } else {
20 | score = 0;
21 | }
22 |
23 | return score;
24 | }
25 |
26 | function gameOver(state, player) {
27 | var win_state = [
28 | [state[0][0], state[0][1], state[0][2]],
29 | [state[1][0], state[1][1], state[1][2]],
30 | [state[2][0], state[2][1], state[2][2]],
31 | [state[0][0], state[1][0], state[2][0]],
32 | [state[0][1], state[1][1], state[2][1]],
33 | [state[0][2], state[1][2], state[2][2]],
34 | [state[0][0], state[1][1], state[2][2]],
35 | [state[2][0], state[1][1], state[0][2]],
36 | ];
37 |
38 | for (var i = 0; i < 8; i++) {
39 | var line = win_state[i];
40 | var filled = 0;
41 | for (var j = 0; j < 3; j++) {
42 | if (line[j] == player)
43 | filled++;
44 | }
45 | if (filled == 3)
46 | return true;
47 | }
48 | return false;
49 | }
50 |
51 | function gameOverAll(state) {
52 | return gameOver(state, HUMAN) || gameOver(state, COMP);
53 | }
54 |
55 | function emptyCells(state) {
56 | var cells = [];
57 | for (var x = 0; x < 3; x++) {
58 | for (var y = 0; y < 3; y++) {
59 | if (state[x][y] == 0)
60 | cells.push([x, y]);
61 | }
62 | }
63 |
64 | return cells;
65 | }
66 |
67 | function validMove(x, y) {
68 | var empties = emptyCells(board);
69 | try {
70 | if (board[x][y] == 0) {
71 | return true;
72 | }
73 | else {
74 | return false;
75 | }
76 | } catch (e) {
77 | return false;
78 | }
79 | }
80 |
81 | function setMove(x, y, player) {
82 | if (validMove(x, y)) {
83 | board[x][y] = player;
84 | return true;
85 | }
86 | else {
87 | return false;
88 | }
89 | }
90 |
91 | function minimax(state, depth, player) {
92 | var best;
93 |
94 | if (player == COMP) {
95 | best = [-1, -1, -1000];
96 | }
97 | else {
98 | best = [-1, -1, +1000];
99 | }
100 |
101 | if (depth == 0 || gameOverAll(state)) {
102 | var score = evalute(state);
103 | return [-1, -1, score];
104 | }
105 |
106 | emptyCells(state).forEach(function (cell) {
107 | var x = cell[0];
108 | var y = cell[1];
109 | state[x][y] = player;
110 | var score = minimax(state, depth - 1, -player);
111 | state[x][y] = 0;
112 | score[0] = x;
113 | score[1] = y;
114 |
115 | if (player == COMP) {
116 | if (score[2] > best[2])
117 | best = score;
118 | }
119 | else {
120 | if (score[2] < best[2])
121 | best = score;
122 | }
123 | });
124 |
125 | return best;
126 | }
127 |
128 | function aiTurn() {
129 | var x, y;
130 | var move;
131 | var cell;
132 |
133 | if (emptyCells(board).length == 9) {
134 | x = parseInt(Math.random() * 3);
135 | y = parseInt(Math.random() * 3);
136 | }
137 | else {
138 | move = minimax(board, emptyCells(board).length, COMP);
139 | x = move[0];
140 | y = move[1];
141 | }
142 |
143 | if (setMove(x, y, COMP)) {
144 | cell = document.getElementById(String(x) + String(y));
145 | cell.innerHTML = "O";
146 | }
147 | }
148 |
149 | function clickedCell(cell) {
150 | var button = document.getElementById("bnt-restart");
151 | button.disabled = true;
152 | var conditionToContinue = gameOverAll(board) == false && emptyCells(board).length > 0;
153 |
154 | if (conditionToContinue == true) {
155 | var x = cell.id.split("")[0];
156 | var y = cell.id.split("")[1];
157 | var move = setMove(x, y, HUMAN);
158 | if (move == true) {
159 | cell.innerHTML = "X";
160 | if (conditionToContinue)
161 | aiTurn();
162 | }
163 | }
164 | if (gameOver(board, COMP)) {
165 | var lines;
166 | var cell;
167 | var msg;
168 |
169 | if (board[0][0] == 1 && board[0][1] == 1 && board[0][2] == 1)
170 | lines = [[0, 0], [0, 1], [0, 2]];
171 | else if (board[1][0] == 1 && board[1][1] == 1 && board[1][2] == 1)
172 | lines = [[1, 0], [1, 1], [1, 2]];
173 | else if (board[2][0] == 1 && board[2][1] == 1 && board[2][2] == 1)
174 | lines = [[2, 0], [2, 1], [2, 2]];
175 | else if (board[0][0] == 1 && board[1][0] == 1 && board[2][0] == 1)
176 | lines = [[0, 0], [1, 0], [2, 0]];
177 | else if (board[0][1] == 1 && board[1][1] == 1 && board[2][1] == 1)
178 | lines = [[0, 1], [1, 1], [2, 1]];
179 | else if (board[0][2] == 1 && board[1][2] == 1 && board[2][2] == 1)
180 | lines = [[0, 2], [1, 2], [2, 2]];
181 | else if (board[0][0] == 1 && board[1][1] == 1 && board[2][2] == 1)
182 | lines = [[0, 0], [1, 1], [2, 2]];
183 | else if (board[2][0] == 1 && board[1][1] == 1 && board[0][2] == 1)
184 | lines = [[2, 0], [1, 1], [0, 2]];
185 |
186 | for (var i = 0; i < lines.length; i++) {
187 | cell = document.getElementById(String(lines[i][0]) + String(lines[i][1]));
188 | cell.style.color = "red";
189 | }
190 |
191 | msg = document.getElementById("message");
192 | msg.innerHTML = "You lose!";
193 | }
194 | if (emptyCells(board).length == 0 && !gameOverAll(board)) {
195 | var msg = document.getElementById("message");
196 | msg.innerHTML = "Draw!";
197 | }
198 | if (gameOverAll(board) == true || emptyCells(board).length == 0) {
199 | button.value = "Restart";
200 | button.disabled = false;
201 | }
202 | }
203 |
204 | /* Restart the game*/
205 | function restartBnt(button) {
206 | if (button.value == "Start AI") {
207 | aiTurn();
208 | button.disabled = true;
209 | }
210 | else if (button.value == "Restart") {
211 | var htmlBoard;
212 | var msg;
213 |
214 | for (var x = 0; x < 3; x++) {
215 | for (var y = 0; y < 3; y++) {
216 | board[x][y] = 0;
217 | htmlBoard = document.getElementById(String(x) + String(y));
218 | htmlBoard.style.color = "#444";
219 | htmlBoard.innerHTML = "";
220 | }
221 | }
222 | button.value = "Start AI";
223 | msg = document.getElementById("message");
224 | msg.innerHTML = "";
225 | }
226 | }
--------------------------------------------------------------------------------