main
90e35a3 ยท 1 year ago 22 commits
  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