Skip to content

Commit

Permalink
Add AI player
Browse files Browse the repository at this point in the history
  • Loading branch information
pedropcamellon committed Oct 7, 2024
1 parent 774073a commit 664e786
Show file tree
Hide file tree
Showing 2 changed files with 222 additions and 45 deletions.
4 changes: 1 addition & 3 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ <h1>
<img
src="dragon.png"
alt="dragon"
id="currentBeastImg"
style="width: var(--current-beast-img-height)"
/>

Expand All @@ -25,12 +24,11 @@ <h1>
<img
src="unicorn.png"
alt="unicorn"
id="currentBeastImg"
style="width: var(--current-beast-img-height)"
/>
</h1>

<!-- -->
<!-- Status -->
<div class="current-status" id="currentStatus">
<img src="unicorn.png" alt="unicorn" id="currentBeastImg" />
<p>'s turn</p>
Expand Down
263 changes: 221 additions & 42 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Twitter @
// AI plays as the dragon, and the human plays as the unicorn

// -- HTML elements --
const board = document.getElementById("board");
Expand All @@ -13,8 +13,21 @@ const winningMessageImg = document.createElement("img");

// -- Game Variables --
let gameIsLive = true;

let unicornTurn = true;
let winner = null;

const winner = null;

const AI_PLAYER = "dragon";

const AI_PLAYER_IMG = "dragon.png";

const HUMAN_PLAYER = "unicorn";

const HUMAN_PLAYER_IMG = "unicorn.png";

let currentPlayer = "";

const winningCombinations = [
[0, 1, 2],
[3, 4, 5],
Expand All @@ -27,31 +40,17 @@ const winningCombinations = [
];

// -- Functions --
const setBoardHoverClass = () => {
if (unicornTurn) {
board.classList.remove("dragon");
board.classList.add("unicorn");
} else {
board.classList.remove("unicorn");
board.classList.add("dragon");
}
};

const placeBeastImg = (cell, currentBeast) => {
cell.classList.add(currentBeast);
};

const swapTurns = () => {
unicornTurn = !unicornTurn;
};

const updateCurrentStatus = () => {
if (unicornTurn) {
currentBeastStatusImg.src = "unicorn.png";
currentBeastStatusImg.alt = "unicorn";
if (currentPlayer === HUMAN_PLAYER) {
currentBeastStatusImg.src = HUMAN_PLAYER_IMG;
currentBeastStatusImg.alt = HUMAN_PLAYER;
} else {
currentBeastStatusImg.src = "dragon.png";
currentBeastStatusImg.alt = "dragon";
currentBeastStatusImg.src = AI_PLAYER_IMG;
currentBeastStatusImg.alt = AI_PLAYER;
}
};

Expand All @@ -66,56 +65,236 @@ const checkWin = (currentBeast) => {
const isDraw = () => {
return [...cells].every((cell) => {
return (
cell.classList.contains("unicorn") || cell.classList.contains("dragon")
cell.classList.contains(HUMAN_PLAYER) ||
cell.classList.contains(AI_PLAYER)
);
});
};

const startGame = () => {
cells.forEach((cell) => {
winningMessageImg.remove();
cell.classList.remove("unicorn");
cell.classList.remove("dragon");

cell.classList.remove(HUMAN_PLAYER);

cell.classList.remove(AI_PLAYER);

cell.removeEventListener("click", handleCellClick);

cell.addEventListener("click", handleCellClick, { once: true });
});

setBoardHoverClass();
gameIsLive = true;

currentPlayer = HUMAN_PLAYER;

updateCurrentStatus();

board.classList.add(HUMAN_PLAYER);

gameEndOverlay.classList.remove("show");
};

const endGame = (draw) => {
const endGame = (draw, player) => {
if (draw) {
winningMessageText.innerText = `draw!`;
} else {
winningMessageImg.src = unicornTurn ? "unicorn.png" : "dragon.png";
winningMessageImg.alt = unicornTurn ? "unicorn" : "dragon";
winningMessage.insertBefore(winningMessageImg, winningMessageText);
winningMessageText.innerText = `wins!!!`;

// Img
winningMessageImg.src = "";
winningMessageImg.alt = "";
}
// AI won
else if (player === AI_PLAYER) {
winningMessageText.innerText = `you lose!`;

// Img
winningMessageImg.src = AI_PLAYER_IMG;
winningMessageImg.alt = AI_PLAYER;
}
// Human won
else {
winningMessageText.innerText = `you win!`;

// Img
winningMessageImg.src = HUMAN_PLAYER_IMG;
winningMessageImg.alt = HUMAN_PLAYER;
}

winningMessage.insertBefore(winningMessageImg, winningMessageText);

gameEndOverlay.classList.add("show");
};

// -- Event Handler --
const handleCellClick = (e) => {
const cell = e.target;
const currentBeast = unicornTurn ? "unicorn" : "dragon";

placeBeastImg(cell, currentBeast);
if (checkWin(currentBeast)) {
endGame(false);
} else if (isDraw()) {
endGame(true);
} else {
swapTurns();
updateCurrentStatus();
setBoardHoverClass();
if (
cell.classList.contains(HUMAN_PLAYER) ||
cell.classList.contains(AI_PLAYER) ||
!gameIsLive
) {
return;
}

placeBeastImg(cell, HUMAN_PLAYER);

if (checkWin(HUMAN_PLAYER)) {
// Little wait for the animation to finish
setTimeout(() => {
endGame(false);
}, 500);

return;
}

if (isDraw()) {
// Little wait for the animation to finish
setTimeout(() => {
endGame(true);
}, 500);

return;
}

// AI's turn
currentPlayer = AI_PLAYER;

updateCurrentStatus();

console.log("AI is thinking...");

// Add a delay of 1 second before AI's turn
setTimeout(() => {
handleAITurn();
}, 1000);
};

// -- Event Listener --
resetButton.addEventListener("click", startGame);

// -- AI --

function handleAITurn() {
// AI's turn
const bestMove = findBestMove();

placeBeastImg(cells[bestMove], AI_PLAYER);

// Check if AI won
if (checkWin(AI_PLAYER)) {
endGame(false, AI_PLAYER);

return;
}

// Check if draw
if (isDraw()) {
endGame(true, AI_PLAYER);

return;
}

currentPlayer = HUMAN_PLAYER;

updateCurrentStatus();
}

function findBestMove() {
let boardCopy = [...cells];

let bestScore = -Infinity;

let bestMove;

for (let i = 0; i < boardCopy.length; i++) {
if (
!boardCopy[i].classList.contains(HUMAN_PLAYER) &&
!boardCopy[i].classList.contains(AI_PLAYER)
) {
boardCopy[i].classList.add(AI_PLAYER);

let score = minimax(boardCopy, 0, false);

boardCopy[i].classList.remove(AI_PLAYER);

if (score > bestScore) {
bestScore = score;
bestMove = i;
}
}
}
return bestMove;
}

// Minimax algorithm
function minimax(board, depth, isMaximizing) {
let result = checkWinner();

if (result !== null) {
return result === AI_PLAYER ? 10 - depth : depth - 10;
}

if (isDraw()) {
return 0;
}

if (isMaximizing) {
let bestScore = -Infinity;

for (let i = 0; i < cells.length; i++) {
if (
!cells[i].classList.contains(HUMAN_PLAYER) &&
!cells[i].classList.contains(AI_PLAYER)
) {
cells[i].classList.add(AI_PLAYER);
let score = minimax(board, depth + 1, false);
cells[i].classList.remove(AI_PLAYER);
bestScore = Math.max(score, bestScore);
}
}

return bestScore;
} else {
let bestScore = Infinity;

for (let i = 0; i < cells.length; i++) {
if (
!cells[i].classList.contains(HUMAN_PLAYER) &&
!cells[i].classList.contains(AI_PLAYER)
) {
cells[i].classList.add(HUMAN_PLAYER);
let score = minimax(board, depth + 1, true);
cells[i].classList.remove(HUMAN_PLAYER);
bestScore = Math.min(score, bestScore);
}
}
return bestScore;
}
}

function checkWinner() {
for (let combo of winningCombinations) {
if (
cells[combo[0]].classList.contains(AI_PLAYER) &&
cells[combo[1]].classList.contains(AI_PLAYER) &&
cells[combo[2]].classList.contains(AI_PLAYER)
) {
return AI_PLAYER;
}
if (
cells[combo[0]].classList.contains(HUMAN_PLAYER) &&
cells[combo[1]].classList.contains(HUMAN_PLAYER) &&
cells[combo[2]].classList.contains(HUMAN_PLAYER)
) {
return HUMAN_PLAYER;
}
}

return null;
}

// -- END AI --

// -- Start Game --
startGame();
document.addEventListener("DOMContentLoaded", startGame);

0 comments on commit 664e786

Please sign in to comment.