├── 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 | 20 | 21 | 22 | 23 |
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 | } --------------------------------------------------------------------------------