363 lines
12 KiB
JavaScript
363 lines
12 KiB
JavaScript
document.addEventListener('DOMContentLoaded', function() {
|
|
const playNowButton = document.querySelector('.btn-primary');
|
|
if (playNowButton) {
|
|
playNowButton.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
document.querySelector('#game-section').scrollIntoView({ behavior: 'smooth' });
|
|
});
|
|
}
|
|
|
|
const gameBoard = document.getElementById('game-board');
|
|
const resetButton = document.getElementById('reset-button');
|
|
const gameStatus = document.getElementById('game-status');
|
|
const boardSizeSelect = document.getElementById('board-size-select');
|
|
const aiDifficultySelect = document.getElementById('ai-difficulty-select');
|
|
|
|
const WIN_CONDITION = 4;
|
|
const PLAYER_SYMBOL = '🐾';
|
|
const AI_SYMBOL = '🧶';
|
|
|
|
let BOARD_SIZE = 5;
|
|
let boardState;
|
|
let currentPlayer;
|
|
let gameActive;
|
|
let cells;
|
|
|
|
function createBoard(size) {
|
|
BOARD_SIZE = size;
|
|
gameBoard.innerHTML = '';
|
|
gameBoard.className = 'game-board';
|
|
gameBoard.classList.add(`size-${size}`);
|
|
|
|
gameBoard.style.gridTemplateColumns = `repeat(${size}, 1fr)`;
|
|
gameBoard.style.gridTemplateRows = `repeat(${size}, 1fr)`;
|
|
|
|
cells = [];
|
|
for (let i = 0; i < size * size; i++) {
|
|
const cell = document.createElement('div');
|
|
cell.classList.add('game-cell');
|
|
const row = Math.floor(i / BOARD_SIZE);
|
|
const col = i % BOARD_SIZE;
|
|
cell.addEventListener('click', () => handleCellClick(row, col));
|
|
gameBoard.appendChild(cell);
|
|
cells.push(cell);
|
|
}
|
|
}
|
|
|
|
function handleCellClick(row, col) {
|
|
if (!gameActive || boardState[row][col] !== null || currentPlayer !== 'PLAYER') {
|
|
return;
|
|
}
|
|
|
|
boardState[row][col] = 'PLAYER';
|
|
renderBoard();
|
|
|
|
if (checkWin('PLAYER')) {
|
|
endGame('Player');
|
|
return;
|
|
}
|
|
|
|
if (isDraw()) {
|
|
endGame('Draw');
|
|
return;
|
|
}
|
|
|
|
currentPlayer = 'AI';
|
|
updateStatus("AI's turn...");
|
|
setTimeout(makeAIMove, 500);
|
|
}
|
|
|
|
function makeAIMove() {
|
|
if (!gameActive) return;
|
|
|
|
const difficulty = aiDifficultySelect.value;
|
|
let move;
|
|
|
|
if (difficulty === 'easy') {
|
|
move = findRandomMove();
|
|
} else if (difficulty === 'medium') {
|
|
move = findBestMoveMedium();
|
|
} else { // Hard
|
|
move = findBestMoveHard();
|
|
}
|
|
|
|
if (move) {
|
|
boardState[move.row][move.col] = 'AI';
|
|
renderBoard();
|
|
|
|
if (checkWin('AI')) {
|
|
endGame('AI');
|
|
return;
|
|
}
|
|
|
|
if (isDraw()) {
|
|
endGame('Draw');
|
|
return;
|
|
}
|
|
}
|
|
|
|
currentPlayer = 'PLAYER';
|
|
updateStatus("Your turn!");
|
|
}
|
|
|
|
function findRandomMove() {
|
|
let availableCells = [];
|
|
for (let i = 0; i < BOARD_SIZE; i++) {
|
|
for (let j = 0; j < BOARD_SIZE; j++) {
|
|
if (boardState[i][j] === null) {
|
|
availableCells.push({ row: i, col: j });
|
|
}
|
|
}
|
|
}
|
|
if (availableCells.length > 0) {
|
|
return availableCells[Math.floor(Math.random() * availableCells.length)];
|
|
}
|
|
return null;
|
|
}
|
|
|
|
function findBestMoveMedium() {
|
|
// 1. Check if AI can win
|
|
for (let i = 0; i < BOARD_SIZE; i++) {
|
|
for (let j = 0; j < BOARD_SIZE; j++) {
|
|
if (boardState[i][j] === null) {
|
|
boardState[i][j] = 'AI';
|
|
if (checkWin('AI')) {
|
|
boardState[i][j] = null; // Reset
|
|
return { row: i, col: j };
|
|
}
|
|
boardState[i][j] = null; // Reset
|
|
}
|
|
}
|
|
}
|
|
|
|
// 2. Check if Player is about to win and block
|
|
for (let i = 0; i < BOARD_SIZE; i++) {
|
|
for (let j = 0; j < BOARD_SIZE; j++) {
|
|
if (boardState[i][j] === null) {
|
|
boardState[i][j] = 'PLAYER';
|
|
if (checkWin('PLAYER')) {
|
|
boardState[i][j] = null; // Reset
|
|
return { row: i, col: j };
|
|
}
|
|
boardState[i][j] = null; // Reset
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3. Otherwise, make a random move
|
|
return findRandomMove();
|
|
}
|
|
|
|
function findBestMoveHard() {
|
|
let bestScore = -Infinity;
|
|
let move = null;
|
|
|
|
for (let i = 0; i < BOARD_SIZE; i++) {
|
|
for (let j = 0; j < BOARD_SIZE; j++) {
|
|
if (boardState[i][j] === null) {
|
|
boardState[i][j] = 'AI';
|
|
let score = evaluateBoard();
|
|
boardState[i][j] = null;
|
|
if (score > bestScore) {
|
|
bestScore = score;
|
|
move = { row: i, col: j };
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return move || findRandomMove(); // Fallback to random if no good move
|
|
}
|
|
|
|
function evaluateBoard() {
|
|
let score = 0;
|
|
// Prioritize winning moves
|
|
if (checkWin('AI')) return 10000;
|
|
// Prioritize blocking player's winning moves
|
|
if (checkWin('PLAYER')) return -9000;
|
|
|
|
// Evaluate lines for potential
|
|
score += evaluateLinesForPlayer('AI');
|
|
score -= evaluateLinesForPlayer('PLAYER');
|
|
|
|
return score;
|
|
}
|
|
|
|
function evaluateLinesForPlayer(player) {
|
|
let score = 0;
|
|
const symbol = player;
|
|
|
|
for (let i = 0; i < BOARD_SIZE; i++) {
|
|
for (let j = 0; j < BOARD_SIZE; j++) {
|
|
// Horizontal
|
|
if (j + WIN_CONDITION <= BOARD_SIZE) {
|
|
score += evaluateLine(i, j, 0, 1, symbol);
|
|
}
|
|
// Vertical
|
|
if (i + WIN_CONDITION <= BOARD_SIZE) {
|
|
score += evaluateLine(i, j, 1, 0, symbol);
|
|
}
|
|
// Diagonal (down-right)
|
|
if (i + WIN_CONDITION <= BOARD_SIZE && j + WIN_CONDITION <= BOARD_SIZE) {
|
|
score += evaluateLine(i, j, 1, 1, symbol);
|
|
}
|
|
// Diagonal (down-left)
|
|
if (i + WIN_CONDITION <= BOARD_SIZE && j - WIN_CONDITION + 1 >= 0) {
|
|
score += evaluateLine(i, j, 1, -1, symbol);
|
|
}
|
|
}
|
|
}
|
|
return score;
|
|
}
|
|
|
|
function evaluateLine(row, col, dr, dc, player) {
|
|
let playerCount = 0;
|
|
let emptyCount = 0;
|
|
for (let k = 0; k < WIN_CONDITION; k++) {
|
|
const current = boardState[row + k * dr][col + k * dc];
|
|
if (current === player) {
|
|
playerCount++;
|
|
} else if (current === null) {
|
|
emptyCount++;
|
|
}
|
|
}
|
|
|
|
// If the line is blocked by the opponent, it has no potential.
|
|
if (playerCount + emptyCount < WIN_CONDITION) {
|
|
return 0;
|
|
}
|
|
|
|
// Assign scores based on the number of pieces in a line
|
|
if (playerCount === 3 && emptyCount === 1) return 100;
|
|
if (playerCount === 2 && emptyCount === 2) return 10;
|
|
if (playerCount === 1 && emptyCount === 3) return 1;
|
|
return 0;
|
|
}
|
|
|
|
|
|
function checkWin(player) {
|
|
const symbol = player;
|
|
|
|
for (let i = 0; i < BOARD_SIZE; i++) {
|
|
for (let j = 0; j < BOARD_SIZE; j++) {
|
|
// Horizontal
|
|
if (j + WIN_CONDITION <= BOARD_SIZE) {
|
|
let count = 0;
|
|
for (let k = 0; k < WIN_CONDITION; k++) {
|
|
if (boardState[i][j + k] === symbol) count++;
|
|
}
|
|
if (count === WIN_CONDITION) return true;
|
|
}
|
|
// Vertical
|
|
if (i + WIN_CONDITION <= BOARD_SIZE) {
|
|
let count = 0;
|
|
for (let k = 0; k < WIN_CONDITION; k++) {
|
|
if (boardState[i + k][j] === symbol) count++;
|
|
}
|
|
if (count === WIN_CONDITION) return true;
|
|
}
|
|
// Diagonal (down-right)
|
|
if (i + WIN_CONDITION <= BOARD_SIZE && j + WIN_CONDITION <= BOARD_SIZE) {
|
|
let count = 0;
|
|
for (let k = 0; k < WIN_CONDITION; k++) {
|
|
if (boardState[i + k][j + k] === symbol) count++;
|
|
}
|
|
if (count === WIN_CONDITION) return true;
|
|
}
|
|
// Diagonal (down-left)
|
|
if (i + WIN_CONDITION <= BOARD_SIZE && j - WIN_CONDITION + 1 >= 0) {
|
|
let count = 0;
|
|
for (let k = 0; k < WIN_CONDITION; k++) {
|
|
if (boardState[i + k][j - k] === symbol) count++;
|
|
}
|
|
if (count === WIN_CONDITION) return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isDraw() {
|
|
return boardState.every(row => row.every(cell => cell !== null));
|
|
}
|
|
|
|
function endGame(winner) {
|
|
gameActive = false;
|
|
if (winner === 'Draw') {
|
|
updateStatus("It's a draw!");
|
|
} else {
|
|
updateStatus(`${winner} wins!`);
|
|
}
|
|
}
|
|
|
|
function resetGame() {
|
|
const size = parseInt(boardSizeSelect.value);
|
|
createBoard(size);
|
|
boardState = Array(BOARD_SIZE).fill(null).map(() => Array(BOARD_SIZE).fill(null));
|
|
currentPlayer = 'PLAYER';
|
|
gameActive = true;
|
|
renderBoard();
|
|
updateStatus("Your turn!");
|
|
}
|
|
|
|
function renderBoard() {
|
|
cells.forEach((cell, index) => {
|
|
const row = Math.floor(index / BOARD_SIZE);
|
|
const col = index % BOARD_SIZE;
|
|
const state = boardState[row][col];
|
|
|
|
if (state === 'PLAYER') {
|
|
cell.innerHTML = `<div class="symbol">${PLAYER_SYMBOL}</div>`;
|
|
} else if (state === 'AI') {
|
|
cell.innerHTML = `<div class="symbol">${AI_SYMBOL}</div>`;
|
|
} else {
|
|
cell.innerHTML = '';
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateStatus(message) {
|
|
gameStatus.textContent = message;
|
|
}
|
|
|
|
if (resetButton) {
|
|
resetButton.addEventListener('click', resetGame);
|
|
}
|
|
|
|
if (boardSizeSelect) {
|
|
boardSizeSelect.addEventListener('change', resetGame);
|
|
}
|
|
|
|
if(aiDifficultySelect) {
|
|
aiDifficultySelect.addEventListener('change', resetGame);
|
|
}
|
|
|
|
resetGame();
|
|
|
|
const catGallery = document.getElementById('cat-gallery');
|
|
if (catGallery) {
|
|
fetch('api/pexels.php')
|
|
.then(response => response.json())
|
|
.then(images => {
|
|
images.forEach(image => {
|
|
const galleryItem = document.createElement('div');
|
|
galleryItem.className = 'cat-gallery-item';
|
|
|
|
const img = document.createElement('img');
|
|
img.src = image.src;
|
|
img.alt = 'A cute cat';
|
|
|
|
const credit = document.createElement('div');
|
|
credit.className = 'photographer-credit';
|
|
credit.innerHTML = `Photo by <a href="${image.photographer_url}" target="_blank">${image.photographer}</a>`;
|
|
|
|
galleryItem.appendChild(img);
|
|
galleryItem.appendChild(credit);
|
|
catGallery.appendChild(galleryItem);
|
|
});
|
|
})
|
|
.catch(error => {
|
|
console.error('Error fetching cat images:', error);
|
|
catGallery.innerHTML = '<p class="text-center">Could not load cat pictures. Meow-be later?</p>';
|
|
});
|
|
}
|
|
}); |