1// Initial game state setup
2let currentPlayer = Math.random() < 0.5 ? "X" : "O";
3let isGameOver = false;
4let winningIndexes = [];
5let winningStrike = "";
6
7let cheatcodes = [];
8const cheatkeys = ["h", "l", "k", "1", "8"];
9let cheatTimeout;
10let isCheatActivated = false;
11
12const tooltip = document.getElementById("tooltip");
13const GameState = {
14 X: -10,
15 O: 10,
16 Tie: 0
17};
18
19let board = [
20 ["", "", ""],
21 ["", "", ""],
22 ["", "", ""]
23];
24
25// Check if the game is a tie
26function checkTie() {
27 return board.flat().every(cell => cell !== "");
28}
29
30// Check if a player has won
31function checkWinner(player) {
32 const diag1_check = board.every((row, i) => row[i] === player);
33 const diag2_check = board.every((row, i) => row[2 - i] === player);
34
35 // Check diagonals
36 if (diag1_check || diag2_check) {
37 if (diag1_check) {
38 winningIndexes = [0, 4, 8];
39 winningStrike = "backward";
40 } else {
41 winningIndexes = [2, 4, 6];
42 winningStrike = "forward";
43 }
44 return true;
45 }
46
47 // Check rows and columns
48 for (let i = 0; i < 3; i++) {
49 const rows_check = board[i].every(cell => cell === player);
50 const cols_check = board.every(row => row[i] === player);
51 if (rows_check || cols_check) {
52 if (rows_check) {
53 winningIndexes = [i * 3, i * 3 + 1, i * 3 + 2];
54 winningStrike = `vertical.row${i}`
55 } else {
56 winningIndexes = [i, i + 3, i + 6];
57 winningStrike = `horizontal.col${i}`
58 }
59 return true;
60 }
61 }
62 return false;
63}
64
65// Check if the game has a winner or is a tie
66function checkGameState() {
67 if (checkWinner("X")) {
68 return GameState.X;
69 }
70 if (checkWinner("O")) {
71 return GameState.O;
72 }
73 if (checkTie()) {
74 return GameState.Tie;
75 }
76 return null;
77}
78
79// Minimax algorithm implementation
80function minimax(board, depth, isMaximizing) {
81 let result = checkGameState();
82 if (result !== null) {
83 return result - depth;
84 }
85
86 if (isMaximizing) {
87 let bestScore = -Infinity;
88 for (let i = 0; i < 3; i++) {
89 for (let j = 0; j < 3; j++) {
90 if (board[i][j] === "") {
91 board[i][j] = "O";
92 let score = minimax(board, depth + 1, false);
93 board[i][j] = "";
94 bestScore = Math.max(score, bestScore);
95 }
96 }
97 }
98 return bestScore;
99 } else {
100 let bestScore = Infinity;
101 for (let i = 0; i < 3; i++) {
102 for (let j = 0; j < 3; j++) {
103 if (board[i][j] === "") {
104 board[i][j] = "X";
105 let score = minimax(board, depth + 1, true);
106 board[i][j] = "";
107 bestScore = Math.min(score, bestScore);
108 }
109 }
110 }
111 return bestScore;
112 }
113}
114
115function getRandomMove() {
116 // get a random position from the board which is empty
117 let emptyCells = [];
118 board.forEach((row, i) => {
119 row.forEach((cell, j) => {
120 if (cell === "") {
121 emptyCells.push({ i, j });
122 }
123 });
124 });
125 return emptyCells[Math.floor(Math.random() * emptyCells.length)];
126}
127
128// Algorithm makes a move using the Minimax algorithm
129function machinePlays() {
130 let bestScore = currentPlayer === "O" ? -Infinity : Infinity;
131 let bestMoves = [];
132 let move;
133
134 for (let i = 0; i < 3; i++) {
135 for (let j = 0; j < 3; j++) {
136 if (board[i][j] !== "") continue;
137 board[i][j] = currentPlayer;
138 let score = minimax(board, 0, currentPlayer == "O" ? false : true);
139 board[i][j] = "";
140
141 if ((currentPlayer === "O" && score > bestScore) || (currentPlayer === "X" && score < bestScore)) {
142 bestScore = score;
143 bestMoves = [{ i, j }];
144 } else if (score === bestScore) {
145 bestMoves.push({ i, j });
146 }
147 }
148 }
149
150 if (bestMoves.length > 0) {
151 move = bestMoves[Math.floor(Math.random() * bestMoves.length)];
152 }
153
154 if (isCheatActivated) {
155 move = getRandomMove();
156 }
157
158 if (move) {
159 board[move.i][move.j] = currentPlayer;
160 document.querySelector(`.cell[data-value="${move.i * 3 + move.j + 1}"]`).textContent = currentPlayer;
161 if (checkGameOver()) return;
162 currentPlayer = currentPlayer === "O" ? "X" : "O";
163 }
164}
165
166function changeColorForWinnerIndexes(indexes) {
167 indexes.forEach(index => {
168 document.querySelector(`.cell[data-value="${index + 1}"]`).style.color = "var(--gold)";
169 });
170 winningStrike.split(".").forEach(cls => {
171 document.getElementById("strikeline").classList.add(cls);
172 });
173}
174
175function checkGameOver() {
176 if (checkWinner(currentPlayer)) {
177 changeColorForWinnerIndexes(winningIndexes);
178 tooltip.textContent = `Press F5 to play again.`;
179 isGameOver = true;
180 return true;
181 }
182
183 if (checkTie()) {
184 tooltip.textContent = "It's a tie!.\nPress F5 to play again.";
185 isGameOver = true;
186 return true;
187 }
188 return false;
189}
190
191// User makes a move by clicking a cell
192document.querySelectorAll(".cell").forEach(cell => {
193 cell.addEventListener("click", () => {
194 if (cell.textContent === "" && !isGameOver) {
195 let cellIndex = cell.dataset.value - 1;
196 board[Math.floor(cellIndex / 3)][cellIndex % 3] = currentPlayer;
197 cell.textContent = currentPlayer;
198 if (checkGameOver()) return;
199 currentPlayer = currentPlayer === "X" ? "O" : "X";
200 machinePlays();
201 }
202 });
203});
204
205if (currentPlayer === "O") {
206 machinePlays();
207}
208
209document.addEventListener("keydown", event => {
210 if (event.key === "F5") {
211 window.location.reload();
212 }
213
214 if (cheatkeys.includes(event.key.toLowerCase())) {
215 cheatcodes.push(event.key.toLowerCase());
216 clearTimeout(cheatTimeout);
217 cheatTimeout = setTimeout(() => {
218 cheatcodes = [];
219 }, 3000);
220
221 if (cheatcodes.length == 5) {
222 let cheatcode = cheatcodes.join("");
223 if (cheatcode === "hlk18") {
224 isCheatActivated = true;
225 tooltip.textContent = `Cheat code activated!`;
226 }
227 cheatcodes = [];
228 }
229 } else {
230 cheatcodes = [];
231 }
232});
233
234