parent
f29f1280a2
commit
b0c8148005
@ -0,0 +1,200 @@
|
|||||||
|
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, maximizing) {
|
||||||
|
const caseStatus = matrix.winner();
|
||||||
|
|
||||||
|
if (caseStatus === 1) return { value: 1, move: null };
|
||||||
|
if (caseStatus === 2) return { value: -1, move: null };
|
||||||
|
if (matrix.isFull()) return { value: 0, move: null };
|
||||||
|
|
||||||
|
let bestValue;
|
||||||
|
let bestMove = null;
|
||||||
|
const emptyCases = matrix.getEmptyCases();
|
||||||
|
|
||||||
|
if (maximizing) {
|
||||||
|
bestValue = -Infinity;
|
||||||
|
|
||||||
|
for (const [row, col] of emptyCases) {
|
||||||
|
const tempMatrix = matrix.clone();
|
||||||
|
tempMatrix.markCase(row, col, 1);
|
||||||
|
|
||||||
|
const { value } = this.minimax(tempMatrix, false);
|
||||||
|
|
||||||
|
if (value > bestValue) {
|
||||||
|
bestValue = value;
|
||||||
|
bestMove = [row, col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bestValue = Infinity;
|
||||||
|
|
||||||
|
for (const [row, col] of emptyCases) {
|
||||||
|
const tempMatrix = matrix.clone();
|
||||||
|
tempMatrix.markCase(row, col, this.player);
|
||||||
|
|
||||||
|
const { value } = this.minimax(tempMatrix, true);
|
||||||
|
|
||||||
|
if (value < bestValue) {
|
||||||
|
bestValue = value;
|
||||||
|
bestMove = [row, col];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { value: bestValue, move: bestMove };
|
||||||
|
}
|
||||||
|
|
||||||
|
evaluate(mainMatrix) {
|
||||||
|
if (this.level === 0) {
|
||||||
|
const emptyCases = mainMatrix.getEmptyCases();
|
||||||
|
const randomIndex = Math.floor(Math.random() * emptyCases.length);
|
||||||
|
return emptyCases[randomIndex];
|
||||||
|
} else {
|
||||||
|
const { move } = this.minimax(mainMatrix, false);
|
||||||
|
return move;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
move(matrix) {
|
||||||
|
if (this.level === 0) {
|
||||||
|
return this.random_move(matrix);
|
||||||
|
} else {
|
||||||
|
return this.evaluate(matrix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 gameBoard = document.getElementById('game-board');
|
||||||
|
gameBoard.innerHTML = '';
|
||||||
|
|
||||||
|
this.matrix.cases.forEach((row, rowIndex) => {
|
||||||
|
const rowElement = document.createElement('div');
|
||||||
|
rowElement.classList.add('row');
|
||||||
|
|
||||||
|
row.forEach((cell, cellIndex) => {
|
||||||
|
const cellElement = document.createElement('div');
|
||||||
|
cellElement.classList.add('cell');
|
||||||
|
cellElement.dataset.mark = cell;
|
||||||
|
cellElement.addEventListener('click', () => this.handleCellClick(rowIndex, cellIndex));
|
||||||
|
rowElement.appendChild(cellElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
gameBoard.appendChild(rowElement);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleCellClick(row, col) {
|
||||||
|
if (this.running && this.matrix.mark(row, col, this.player)) {
|
||||||
|
if (this.matrix.check_win(this.player)) {
|
||||||
|
alert(`Le joueur ${this.player} a gagné !`);
|
||||||
|
this.running = false;
|
||||||
|
} else if (this.matrix.is_full()) {
|
||||||
|
alert("Match nul !");
|
||||||
|
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)) {
|
||||||
|
alert(`L'IA a gagné !`);
|
||||||
|
this.running = false;
|
||||||
|
} else if (this.matrix.is_full()) {
|
||||||
|
alert("Match nul !");
|
||||||
|
this.running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.player = 3 - this.player;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.updateBoard();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 game = new Game();
|
||||||
|
game.start();
|
||||||
|
|
@ -0,0 +1,136 @@
|
|||||||
|
const cells = document.querySelectorAll('.cell');
|
||||||
|
const resetButton = document.querySelector('#reset');
|
||||||
|
const aiButton = document.querySelector('#toggle-ai');
|
||||||
|
|
||||||
|
let board = Array.from({ length: 9 }, () => 0);
|
||||||
|
let currentPlayer = 1;
|
||||||
|
let aiEnabled = true;
|
||||||
|
let gameMode = 'ai';
|
||||||
|
let running = true;
|
||||||
|
|
||||||
|
function handleClick(e) {
|
||||||
|
const cell = e.target;
|
||||||
|
const index = cells.findIndex(c => c === cell);
|
||||||
|
|
||||||
|
if (board[index] !== 0 || !running) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
makeMove(index);
|
||||||
|
|
||||||
|
if (checkWinner(board) !== 0 || board.every(cell => cell !== 0)) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (running && gameMode === 'ai' && currentPlayer === 2) {
|
||||||
|
const { move } = minimax(board, false);
|
||||||
|
makeMove(move);
|
||||||
|
|
||||||
|
if (checkWinner(board) !== 0 || board.every(cell => cell !== 0)) {
|
||||||
|
running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeMove(index) {
|
||||||
|
board[index] = currentPlayer;
|
||||||
|
cells[index].textContent = currentPlayer === 1 ? 'X' : 'O';
|
||||||
|
currentPlayer = 3 - currentPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRunning() {
|
||||||
|
return !checkWinner(board) && board.some(cell => cell === 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkWinner(tempBoard) {
|
||||||
|
const winningCombinations = [
|
||||||
|
[0, 1, 2],
|
||||||
|
[3, 4, 5],
|
||||||
|
[6, 7, 8],
|
||||||
|
[0, 3, 6],
|
||||||
|
[1, 4, 7],
|
||||||
|
[2, 5, 8],
|
||||||
|
[0, 4, 8],
|
||||||
|
[2, 4, 6],
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const combination of winningCombinations) {
|
||||||
|
const [a, b, c] = combination;
|
||||||
|
if (tempBoard[a] !== 0 && tempBoard[a] === tempBoard[b] && tempBoard[a] === tempBoard[c]) {
|
||||||
|
return tempBoard[a];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkGameOver() {
|
||||||
|
const winner = checkWinner(board);
|
||||||
|
if (winner !== 0) {
|
||||||
|
alert(`Le joueur ${winner} a gagné !`);
|
||||||
|
} else if (!isRunning()) {
|
||||||
|
alert('Match nul !');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resetBoard() {
|
||||||
|
board = Array.from({ length: 9 }, () => 0);
|
||||||
|
currentPlayer = 1;
|
||||||
|
running = true;
|
||||||
|
cells.forEach(cell => (cell.textContent = ''));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleAi() {
|
||||||
|
aiEnabled = !aiEnabled;
|
||||||
|
gameMode = aiEnabled ? 'ai' : '1v1';
|
||||||
|
}
|
||||||
|
|
||||||
|
function minimax(tempBoard, maximizing) {
|
||||||
|
const winner = checkWinner(tempBoard);
|
||||||
|
|
||||||
|
if (winner !== 0) {
|
||||||
|
return { eval: winner === 1 ? 1 : -1, move: null };
|
||||||
|
} else if (!tempBoard.some(cell => cell === 0)) {
|
||||||
|
return { eval: 0, move: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
let bestEval, bestMove;
|
||||||
|
|
||||||
|
if (maximizing) {
|
||||||
|
bestEval = -Infinity;
|
||||||
|
|
||||||
|
for (let i = 0; i < tempBoard.length; i++) {
|
||||||
|
if (tempBoard[i] === 0) {
|
||||||
|
const newBoard = [...tempBoard];
|
||||||
|
newBoard[i] = 1;
|
||||||
|
|
||||||
|
const eval = minimax(newBoard, false).eval;
|
||||||
|
if (eval > bestEval) {
|
||||||
|
bestEval = eval;
|
||||||
|
bestMove = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bestEval = Infinity;
|
||||||
|
|
||||||
|
for (let i = 0; i < tempBoard.length; i++) {
|
||||||
|
if (tempBoard[i] === 0) {
|
||||||
|
const newBoard = [...tempBoard];
|
||||||
|
newBoard[i] = 2;
|
||||||
|
|
||||||
|
const eval = minimax(newBoard, true).eval;
|
||||||
|
if (eval < bestEval) {
|
||||||
|
bestEval = eval;
|
||||||
|
bestMove = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { eval: bestEval, move: bestMove };
|
||||||
|
}
|
||||||
|
|
||||||
|
cells.forEach(cell => cell.addEventListener('click', handleClick));
|
||||||
|
resetButton.addEventListener('click', resetBoard);
|
||||||
|
aiButton.addEventListener('click', toggleAi);
|
@ -0,0 +1,48 @@
|
|||||||
|
body {
|
||||||
|
font-family: Arial, sans-serif;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
#game-board {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 300px;
|
||||||
|
height: 300px;
|
||||||
|
background-color: white;
|
||||||
|
border: 1px solid black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
height: 33.33%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
width: 33.33%;
|
||||||
|
height: 100%;
|
||||||
|
border: 1px solid black;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell[data-mark="1"]::before {
|
||||||
|
content: "X";
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #2196F3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell[data-mark="2"]::before {
|
||||||
|
content: "O";
|
||||||
|
font-size: 2rem;
|
||||||
|
color: #F44336;
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
body {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
height: 100vh;
|
||||||
|
margin: 0;
|
||||||
|
background-color: #1E2125;
|
||||||
|
font-family: "Arial", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 100px);
|
||||||
|
grid-template-rows: repeat(3, 100px);
|
||||||
|
gap: 15px;
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cell {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #2C3036;
|
||||||
|
color: #FFFFFF;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 10px 20px;
|
||||||
|
background-color: #1E2125;
|
||||||
|
color: #FFFFFF;
|
||||||
|
border: 2px solid #FFFFFF;
|
||||||
|
font-size: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
background-color: #2C3036;
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Tic Tac Toe - IA</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<div class="grid">
|
||||||
|
<div class="cell" data-cell></div>
|
||||||
|
<div class="cell" data-cell></div>
|
||||||
|
<div class="cell" data-cell></div>
|
||||||
|
<div class="cell" data-cell></div>
|
||||||
|
<div class="cell" data-cell></div>
|
||||||
|
<div class="cell" data-cell></div>
|
||||||
|
<div class="cell" data-cell></div>
|
||||||
|
<div class="cell" data-cell></div>
|
||||||
|
<div class="cell" data-cell></div>
|
||||||
|
</div>
|
||||||
|
<button id="reset">Recommencer</button>
|
||||||
|
<button id="ai">Activer/désactiver IA</button>
|
||||||
|
</div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,14 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Jeu de Morpion</title>
|
||||||
|
<link rel="stylesheet" href="style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Jeu de Morpion</h1>
|
||||||
|
<div id="game-board"></div>
|
||||||
|
<script src="script.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in new issue