Give the ability to change the look so have multiple themes and have the ability to change between them
d10df16 verified | document.addEventListener('DOMContentLoaded', () => { | |
| // Game state | |
| let board = ['', '', '', '', '', '', '', '', '']; | |
| let currentPlayer = 'X'; | |
| let gameActive = true; | |
| let gameMode = 'pvp'; // pvp, pvc, cvc | |
| let scores = { x: 0, o: 0, d: 0 }; | |
| let theme = 'default'; | |
| // DOM elements | |
| const cells = document.querySelectorAll('#game-board .cell'); | |
| const gameOverlay = document.getElementById('game-overlay'); | |
| const gameResult = document.getElementById('game-result'); | |
| const playAgainBtn = document.getElementById('play-again-btn'); | |
| const xWinsEl = document.getElementById('x-wins'); | |
| const oWinsEl = document.getElementById('o-wins'); | |
| const drawsEl = document.getElementById('draws'); | |
| const gameBoard = document.getElementById('game-board'); | |
| // Initialize the game board | |
| function initializeBoard() { | |
| gameBoard.innerHTML = ''; | |
| for (let i = 0; i < 9; i++) { | |
| const cell = document.createElement('div'); | |
| cell.classList.add('cell', 'flex', 'items-center', 'justify-center', 'text-6xl', 'font-bold', 'bg-white', 'dark:bg-gray-800', 'rounded-lg', 'shadow-md'); | |
| cell.dataset.index = i; | |
| cell.addEventListener('click', () => handleCellClick(i)); | |
| gameBoard.appendChild(cell); | |
| } | |
| } | |
| // Handle cell click | |
| function handleCellClick(index) { | |
| if (!gameActive || board[index] !== '') return; | |
| makeMove(index, currentPlayer); | |
| if (gameMode === 'pvc' && currentPlayer === 'O' && gameActive) { | |
| setTimeout(() => { | |
| const bestMove = findBestMove(); | |
| makeMove(bestMove, 'O'); | |
| }, 500); | |
| } else if (gameMode === 'cvc' && gameActive) { | |
| setTimeout(() => { | |
| const bestMove = findBestMove(); | |
| makeMove(bestMove, currentPlayer); | |
| }, 500); | |
| } | |
| } | |
| // Make a move | |
| function makeMove(index, player) { | |
| board[index] = player; | |
| const cell = gameBoard.children[index]; | |
| cell.textContent = player; | |
| cell.classList.add(player.toLowerCase()); | |
| if (checkWin(player)) { | |
| endGame(`${player} Wins!`); | |
| scores[player.toLowerCase()]++; | |
| updateScoreboard(); | |
| highlightWinningCells(); | |
| return; | |
| } | |
| if (checkDraw()) { | |
| endGame("It's a Draw!"); | |
| scores.d++; | |
| updateScoreboard(); | |
| return; | |
| } | |
| currentPlayer = currentPlayer === 'X' ? 'O' : 'X'; | |
| // If it's computer's turn in pvc mode and player is O | |
| if (gameMode === 'pvc' && currentPlayer === 'O' && gameActive) { | |
| setTimeout(() => { | |
| const bestMove = findBestMove(); | |
| makeMove(bestMove, 'O'); | |
| }, 500); | |
| } | |
| } | |
| // Check for win | |
| function checkWin(player) { | |
| const winPatterns = [ | |
| [0, 1, 2], [3, 4, 5], [6, 7, 8], // rows | |
| [0, 3, 6], [1, 4, 7], [2, 5, 8], // columns | |
| [0, 4, 8], [2, 4, 6] // diagonals | |
| ]; | |
| return winPatterns.some(pattern => { | |
| return pattern.every(index => board[index] === player); | |
| }); | |
| } | |
| // Check for draw | |
| function checkDraw() { | |
| return board.every(cell => cell !== ''); | |
| } | |
| // End the game | |
| function endGame(message) { | |
| gameActive = false; | |
| gameResult.textContent = message; | |
| gameOverlay.classList.remove('hidden'); | |
| } | |
| // Reset the game | |
| function resetGame() { | |
| board = ['', '', '', '', '', '', '', '', '']; | |
| currentPlayer = 'X'; | |
| gameActive = true; | |
| gameOverlay.classList.add('hidden'); | |
| Array.from(gameBoard.children).forEach(cell => { | |
| cell.textContent = ''; | |
| cell.classList.remove('x', 'o', 'winning-cell'); | |
| }); | |
| if (gameMode === 'cvc') { | |
| setTimeout(() => { | |
| const bestMove = findBestMove(); | |
| makeMove(bestMove, 'X'); | |
| }, 500); | |
| } else if (gameMode === 'pvc' && currentPlayer === 'O') { | |
| setTimeout(() => { | |
| const bestMove = findBestMove(); | |
| makeMove(bestMove, 'O'); | |
| }, 500); | |
| } | |
| } | |
| // Update scoreboard | |
| function updateScoreboard() { | |
| xWinsEl.textContent = scores.x; | |
| oWinsEl.textContent = scores.o; | |
| drawsEl.textContent = scores.d; | |
| } | |
| // Highlight winning cells | |
| function highlightWinningCells() { | |
| const winPatterns = [ | |
| [0, 1, 2], [3, 4, 5], [6, 7, 8], // rows | |
| [0, 3, 6], [1, 4, 7], [2, 5, 8], // columns | |
| [0, 4, 8], [2, 4, 6] // diagonals | |
| ]; | |
| for (const pattern of winPatterns) { | |
| const [a, b, c] = pattern; | |
| if (board[a] !== '' && board[a] === board[b] && board[a] === board[c]) { | |
| gameBoard.children[a].classList.add('winning-cell'); | |
| gameBoard.children[b].classList.add('winning-cell'); | |
| gameBoard.children[c].classList.add('winning-cell'); | |
| break; | |
| } | |
| } | |
| } | |
| // Computer AI - Minimax algorithm | |
| function findBestMove() { | |
| if (gameMode === 'cvc') { | |
| // For computer vs computer, just pick a random empty spot for demo | |
| const emptyCells = board.map((cell, index) => cell === '' ? index : null).filter(val => val !== null); | |
| return emptyCells[Math.floor(Math.random() * emptyCells.length)]; | |
| } | |
| // Minimax algorithm for optimal play | |
| let bestScore = -Infinity; | |
| let bestMove = null; | |
| for (let i = 0; i < 9; i++) { | |
| if (board[i] === '') { | |
| board[i] = 'O'; | |
| const score = minimax(board, 0, false); | |
| board[i] = ''; | |
| if (score > bestScore) { | |
| bestScore = score; | |
| bestMove = i; | |
| } | |
| } | |
| } | |
| return bestMove; | |
| } | |
| function minimax(board, depth, isMaximizing) { | |
| const scores = { | |
| 'X': -10, | |
| 'O': 10, | |
| 'draw': 0 | |
| }; | |
| if (checkWin('O')) return scores['O'] - depth; | |
| if (checkWin('X')) return scores['X'] + depth; | |
| if (checkDraw()) return scores['draw']; | |
| if (isMaximizing) { | |
| let bestScore = -Infinity; | |
| for (let i = 0; i < 9; i++) { | |
| if (board[i] === '') { | |
| board[i] = 'O'; | |
| const score = minimax(board, depth + 1, false); | |
| board[i] = ''; | |
| bestScore = Math.max(score, bestScore); | |
| } | |
| } | |
| return bestScore; | |
| } else { | |
| let bestScore = Infinity; | |
| for (let i = 0; i < 9; i++) { | |
| if (board[i] === '') { | |
| board[i] = 'X'; | |
| const score = minimax(board, depth + 1, true); | |
| board[i] = ''; | |
| bestScore = Math.min(score, bestScore); | |
| } | |
| } | |
| return bestScore; | |
| } | |
| } | |
| // Event listeners | |
| playAgainBtn.addEventListener('click', resetGame); | |
| // Theme change handler | |
| document.addEventListener('theme-change', (e) => { | |
| theme = e.detail.theme; | |
| applyTheme(); | |
| }); | |
| // Game mode change handler | |
| document.addEventListener('game-mode-change', (e) => { | |
| gameMode = e.detail.mode; | |
| resetGame(); | |
| }); | |
| // Apply theme | |
| function applyTheme() { | |
| // Remove all theme classes first | |
| const themeClasses = Array.from(document.body.classList).filter(cls => cls.startsWith('theme-')); | |
| themeClasses.forEach(cls => document.body.classList.remove(cls)); | |
| // Add the current theme class | |
| document.body.classList.add(`theme-${theme}`); | |
| // Update CSS variables | |
| document.documentElement.style.setProperty('--primary-color', getComputedStyle(document.body).getPropertyValue('--primary-color')); | |
| document.documentElement.style.setProperty('--secondary-color', getComputedStyle(document.body).getPropertyValue('--secondary-color')); | |
| // Update cell colors | |
| document.querySelectorAll('.cell.x').forEach(cell => { | |
| cell.style.color = `var(--primary-color)`; | |
| }); | |
| document.querySelectorAll('.cell.o').forEach(cell => { | |
| cell.style.color = `var(--secondary-color)`; | |
| }); | |
| } | |
| // Initialize | |
| initializeBoard(); | |
| applyTheme(); | |
| }); |