You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
251 lines
7.3 KiB
251 lines
7.3 KiB
class Matrix {
|
|
constructor() {
|
|
this.cases = Array(3).fill(null).map(() => Array(3).fill(0));
|
|
this.marked_case = 0;
|
|
}
|
|
|
|
is_full() {
|
|
return this.marked_case === 9;
|
|
}
|
|
|
|
mark(row, col, player) {
|
|
if (this.cases[row][col] === 0) {
|
|
this.cases[row][col] = player;
|
|
this.marked_case++;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
check_win(player) {
|
|
const win_combinations = [
|
|
[[0, 0], [0, 1], [0, 2]],
|
|
[[1, 0], [1, 1], [1, 2]],
|
|
[[2, 0], [2, 1], [2, 2]],
|
|
[[0, 0], [1, 0], [2, 0]],
|
|
[[0, 1], [1, 1], [2, 1]],
|
|
[[0, 2], [1, 2], [2, 2]],
|
|
[[0, 0], [1, 1], [2, 2]],
|
|
[[0, 2], [1, 1], [2, 0]],
|
|
];
|
|
|
|
return win_combinations.some(combination =>
|
|
combination.every(([row, col]) => this.cases[row][col] === player)
|
|
);
|
|
}
|
|
}
|
|
|
|
class AI {
|
|
constructor(level = 5, player = 2) {
|
|
this.level = level;
|
|
this.player = player;
|
|
}
|
|
|
|
random_move(matrix) {
|
|
const available_moves = [];
|
|
|
|
matrix.cases.forEach((row, i) => {
|
|
row.forEach((cell, j) => {
|
|
if (cell === 0) {
|
|
available_moves.push([i, j]);
|
|
}
|
|
});
|
|
});
|
|
|
|
if (available_moves.length === 0) {
|
|
return null;
|
|
}
|
|
|
|
const random_index = Math.floor(Math.random() * available_moves.length);
|
|
return available_moves[random_index];
|
|
}
|
|
|
|
minimax(matrix, depth, maximizingPlayer) {
|
|
const winner = this.check_winner(matrix);
|
|
|
|
if (winner !== 0) {
|
|
return winner === this.player ? 10 - depth : depth - 10;
|
|
}
|
|
|
|
if (matrix.is_full()) {
|
|
return 0;
|
|
}
|
|
|
|
if (depth >= this.level) {
|
|
return 0;
|
|
}
|
|
|
|
const opponent = 3 - this.player;
|
|
const currentPlayer = maximizingPlayer ? this.player : opponent;
|
|
|
|
let bestScore = maximizingPlayer ? -Infinity : Infinity;
|
|
|
|
for (let row = 0; row < 3; row++) {
|
|
for (let col = 0; col < 3; col++) {
|
|
if (matrix.cases[row][col] === 0) {
|
|
matrix.mark(row, col, currentPlayer);
|
|
const score = this.minimax(matrix, depth + 1, !maximizingPlayer);
|
|
matrix.cases[row][col] = 0;
|
|
matrix.marked_case--;
|
|
|
|
bestScore = maximizingPlayer ? Math.max(bestScore, score) : Math.min(bestScore, score);
|
|
}
|
|
}
|
|
}
|
|
|
|
return bestScore;
|
|
}
|
|
|
|
check_winner(matrix) {
|
|
for (let player = 1; player <= 2; player++) {
|
|
if (matrix.check_win(player)) {
|
|
return player;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
move(matrix) {
|
|
let bestScore = -Infinity;
|
|
let bestMove = null;
|
|
|
|
for (let row = 0; row < 3; row++) {
|
|
for (let col = 0; col < 3; col++) {
|
|
if (matrix.cases[row][col] === 0) {
|
|
matrix.mark(row, col, this.player);
|
|
const score = this.minimax(matrix, 1, false);
|
|
matrix.cases[row][col] = 0;
|
|
matrix.marked_case--;
|
|
|
|
if (score > bestScore) {
|
|
bestScore = score;
|
|
bestMove = [row, col];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bestMove;
|
|
}
|
|
|
|
}
|
|
|
|
class Game {
|
|
constructor() {
|
|
this.matrix = new Matrix();
|
|
this.ai = new AI();
|
|
this.player = 1;
|
|
this.gamemode = 'ai';
|
|
this.running = true;
|
|
this.ai_starts = false;
|
|
}
|
|
|
|
updateBoard() {
|
|
const cells = document.querySelectorAll('.cell');
|
|
|
|
this.matrix.cases.forEach((row, rowIndex) => {
|
|
row.forEach((cell, cellIndex) => {
|
|
const cellElement = cells[rowIndex * 3 + cellIndex];
|
|
cellElement.classList.remove('x', 'o');
|
|
if (cell === 1) {
|
|
cellElement.classList.add('x');
|
|
} else if (cell === 2) {
|
|
cellElement.classList.add('o');
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
displayMessage(messageId) {
|
|
const messages = ['p1win', 'p2win', 'aiwin', 'draw'];
|
|
messages.forEach((id) => {
|
|
const element = document.getElementById(id);
|
|
if (id === messageId) {
|
|
element.style.display = 'block';
|
|
} else {
|
|
element.style.display = 'none';
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
|
|
handleCellClick(row, col) {
|
|
if (this.running && this.matrix.mark(row, col, this.player)) {
|
|
if (this.matrix.check_win(this.player)) {
|
|
this.displayMessage(this.player === 1 ? 'p1win' : 'p2win');
|
|
this.running = false;
|
|
} else if (this.matrix.is_full()) {
|
|
this.displayMessage('draw');
|
|
this.running = false;
|
|
} else {
|
|
this.player = 3 - this.player;
|
|
if (this.gamemode === 'ai' && this.player === this.ai.player) {
|
|
const [ai_row, ai_col] = this.ai.move(this.matrix);
|
|
if (ai_row !== null && ai_col !== null) {
|
|
this.matrix.mark(ai_row, ai_col, this.ai.player);
|
|
if (this.matrix.check_win(this.ai.player)) {
|
|
this.displayMessage('aiwin');
|
|
this.running = false;
|
|
} else if (this.matrix.is_full()) {
|
|
this.displayMessage('draw');
|
|
this.running = false;
|
|
}
|
|
}
|
|
this.player = 3 - this.player;
|
|
}
|
|
}
|
|
this.updateBoard();
|
|
}
|
|
}
|
|
|
|
|
|
setupEventListeners() {
|
|
const cells = document.querySelectorAll('[data-cell]');
|
|
cells.forEach((cell, index) => {
|
|
const row = Math.floor(index / 3);
|
|
const col = index % 3;
|
|
cell.addEventListener('click', () => this.handleCellClick(row, col));
|
|
});
|
|
}
|
|
|
|
|
|
start() {
|
|
this.running = true;
|
|
this.matrix = new Matrix();
|
|
if (this.gamemode === 'ai' && this.ai_starts) {
|
|
const [ai_row, ai_col] = this.ai.move(this.matrix);
|
|
this.matrix.mark(ai_row, ai_col, this.ai.player);
|
|
this.player = 3 - this.player;
|
|
}
|
|
this.updateBoard();
|
|
const messages = ['p1win', 'p2win', 'aiwin', 'draw'];
|
|
messages.forEach((id) => {
|
|
document.getElementById(id).style.display = 'none';
|
|
});
|
|
}
|
|
|
|
}
|
|
|
|
const cellElements = document.querySelectorAll('[data-cell]');
|
|
cellElements.forEach((cell, index) => {
|
|
cell.addEventListener('click', () => {
|
|
const row = Math.floor(index / 3);
|
|
const col = index % 3;
|
|
game.handleCellClick(row, col);
|
|
});
|
|
});
|
|
|
|
|
|
const game = new Game();
|
|
game.start();
|
|
|
|
const resetButton = document.getElementById('reset');
|
|
resetButton.addEventListener('click', () => game.start());
|
|
|
|
const aiButton = document.getElementById('ai');
|
|
aiButton.addEventListener('click', () => {
|
|
game.gamemode = game.gamemode === 'ai' ? 'player' : 'ai';
|
|
game.start();
|
|
});
|