|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Tic-Tac-Toe Ultimate Edition</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
@keyframes fadeIn { |
|
|
from { opacity: 0; transform: scale(0.8); } |
|
|
to { opacity: 1; transform: scale(1); } |
|
|
} |
|
|
|
|
|
@keyframes pulse { |
|
|
0% { transform: scale(1); } |
|
|
50% { transform: scale(1.1); } |
|
|
100% { transform: scale(1); } |
|
|
} |
|
|
|
|
|
@keyframes flash { |
|
|
0% { opacity: 0.3; } |
|
|
50% { opacity: 1; } |
|
|
100% { opacity: 0.3; } |
|
|
} |
|
|
|
|
|
@keyframes highlight { |
|
|
0% { box-shadow: 0 0 0 0 rgba(255, 255, 255, 0.7); } |
|
|
100% { box-shadow: 0 0 15px 5px rgba(255, 255, 255, 0); } |
|
|
} |
|
|
|
|
|
.cell-animate { |
|
|
animation: fadeIn 0.3s ease-out; |
|
|
} |
|
|
|
|
|
.winner-flash { |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
z-index: 100; |
|
|
animation: flash 0.5s ease-in-out 3; |
|
|
} |
|
|
|
|
|
.x-color { |
|
|
color: #ef4444; |
|
|
} |
|
|
|
|
|
.o-color { |
|
|
color: #3b82f6; |
|
|
} |
|
|
|
|
|
.highlight { |
|
|
animation: highlight 1.5s ease-out; |
|
|
} |
|
|
|
|
|
.btn-pulse { |
|
|
animation: pulse 0.5s infinite; |
|
|
} |
|
|
|
|
|
.modal { |
|
|
display: none; |
|
|
position: fixed; |
|
|
top: 0; |
|
|
left: 0; |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
background-color: rgba(0, 0, 0, 0.7); |
|
|
z-index: 1000; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.modal-content { |
|
|
background-color: #1f2937; |
|
|
padding: 2rem; |
|
|
border-radius: 1rem; |
|
|
max-width: 500px; |
|
|
width: 90%; |
|
|
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5); |
|
|
} |
|
|
|
|
|
.tab-active { |
|
|
background-color: #374151; |
|
|
border-bottom: 3px solid #3b82f6; |
|
|
} |
|
|
|
|
|
.sound-toggle { |
|
|
position: fixed; |
|
|
bottom: 20px; |
|
|
right: 20px; |
|
|
background: #374151; |
|
|
width: 50px; |
|
|
height: 50px; |
|
|
border-radius: 50%; |
|
|
display: flex; |
|
|
justify-content: center; |
|
|
align-items: center; |
|
|
cursor: pointer; |
|
|
z-index: 100; |
|
|
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4 transition-colors duration-500"> |
|
|
|
|
|
<div id="soundToggle" class="sound-toggle"> |
|
|
<i id="soundIcon" class="fas fa-volume-up text-xl"></i> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="max-w-4xl w-full"> |
|
|
<h1 class="text-5xl font-bold text-center mb-8 text-transparent bg-clip-text bg-gradient-to-r from-red-500 to-blue-500"> |
|
|
Tic-Tac-Toe Ultimate |
|
|
</h1> |
|
|
|
|
|
<div class="bg-gray-800 rounded-xl p-6 shadow-2xl mb-8"> |
|
|
|
|
|
<div class="flex mb-6 border-b border-gray-700"> |
|
|
<button id="pvpTab" class="tab-btn flex-1 py-2 font-semibold tab-active"> |
|
|
<i class="fas fa-users mr-2"></i> Player vs Player |
|
|
</button> |
|
|
<button id="aiTab" class="tab-btn flex-1 py-2 font-semibold"> |
|
|
<i class="fas fa-robot mr-2"></i> Player vs AI |
|
|
</button> |
|
|
<button id="aivsaiTab" class="tab-btn flex-1 py-2 font-semibold"> |
|
|
<i class="fas fa-robot mr-2"></i><i class="fas fa-robot ml-1 mr-2"></i> AI vs AI |
|
|
</button> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="pvpContent" class="tab-content"> |
|
|
<div class="text-center mb-6"> |
|
|
<p class="text-gray-300 mb-4">Play against a friend on the same device</p> |
|
|
<button id="startPvpBtn" class="px-6 py-3 bg-blue-600 hover:bg-blue-700 rounded-lg font-medium btn-pulse"> |
|
|
<i class="fas fa-play mr-2"></i> Start Game |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="aiContent" class="tab-content hidden"> |
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"> |
|
|
<div> |
|
|
<h3 class="text-lg font-semibold mb-3">Player (X)</h3> |
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<p class="text-gray-300 mb-3">You will play as X against the AI</p> |
|
|
</div> |
|
|
</div> |
|
|
<div> |
|
|
<h3 class="text-lg font-semibold mb-3">AI (O)</h3> |
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<p class="text-gray-300 mb-2">Select AI difficulty:</p> |
|
|
<div class="grid grid-cols-1 gap-2"> |
|
|
<button data-difficulty="easy" class="difficulty-btn px-4 py-2 bg-green-600 hover:bg-green-700 rounded"> |
|
|
<i class="fas fa-baby mr-2"></i> Easy |
|
|
</button> |
|
|
<button data-difficulty="medium" class="difficulty-btn px-4 py-2 bg-yellow-600 hover:bg-yellow-700 rounded"> |
|
|
<i class="fas fa-user mr-2"></i> Medium |
|
|
</button> |
|
|
<button data-difficulty="hard" class="difficulty-btn px-4 py-2 bg-red-600 hover:bg-red-700 rounded"> |
|
|
<i class="fas fa-brain mr-2"></i> Hard |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="text-center"> |
|
|
<button id="startAiBtn" class="px-6 py-3 bg-purple-600 hover:bg-purple-700 rounded-lg font-medium"> |
|
|
<i class="fas fa-play mr-2"></i> Start Game |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div id="aivsaiContent" class="tab-content hidden"> |
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6"> |
|
|
<div> |
|
|
<h3 class="text-lg font-semibold mb-3">AI (X)</h3> |
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<p class="text-gray-300 mb-2">Select AI difficulty:</p> |
|
|
<div class="grid grid-cols-1 gap-2"> |
|
|
<button data-difficulty="easy" data-ai="x" class="aivsai-difficulty-btn px-4 py-2 bg-green-600 hover:bg-green-700 rounded"> |
|
|
<i class="fas fa-baby mr-2"></i> Easy |
|
|
</button> |
|
|
<button data-difficulty="medium" data-ai="x" class="aivsai-difficulty-btn px-4 py-2 bg-yellow-600 hover:bg-yellow-700 rounded"> |
|
|
<i class="fas fa-user mr-2"></i> Medium |
|
|
</button> |
|
|
<button data-difficulty="hard" data-ai="x" class="aivsai-difficulty-btn px-4 py-2 bg-red-600 hover:bg-red-700 rounded"> |
|
|
<i class="fas fa-brain mr-2"></i> Hard |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div> |
|
|
<h3 class="text-lg font-semibold mb-3">AI (O)</h3> |
|
|
<div class="bg-gray-700 p-4 rounded-lg"> |
|
|
<p class="text-gray-300 mb-2">Select AI difficulty:</p> |
|
|
<div class="grid grid-cols-1 gap-2"> |
|
|
<button data-difficulty="easy" data-ai="o" class="aivsai-difficulty-btn px-4 py-2 bg-green-600 hover:bg-green-700 rounded"> |
|
|
<i class="fas fa-baby mr-2"></i> Easy |
|
|
</button> |
|
|
<button data-difficulty="medium" data-ai="o" class="aivsai-difficulty-btn px-4 py-2 bg-yellow-600 hover:bg-yellow-700 rounded"> |
|
|
<i class="fas fa-user mr-2"></i> Medium |
|
|
</button> |
|
|
<button data-difficulty="hard" data-ai="o" class="aivsai-difficulty-btn px-4 py-2 bg-red-600 hover:bg-red-700 rounded"> |
|
|
<i class="fas fa-brain mr-2"></i> Hard |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
<div class="text-center"> |
|
|
<button id="startAivsaiBtn" class="px-6 py-3 bg-green-600 hover:bg-green-700 rounded-lg font-medium"> |
|
|
<i class="fas fa-play mr-2"></i> Start Game |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="gameBoard" class="hidden"> |
|
|
|
|
|
<div id="gameStatus" class="text-center text-xl font-semibold mb-6 py-3 px-4 bg-gray-700 rounded-lg"> |
|
|
Player X's turn |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="grid grid-cols-3 gap-3 mb-6 aspect-square max-w-md mx-auto"> |
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="flex justify-center gap-4"> |
|
|
<button id="resetBtn" class="px-6 py-2 bg-gray-600 hover:bg-gray-500 rounded-lg font-medium"> |
|
|
<i class="fas fa-sync-alt mr-2"></i> Reset Game |
|
|
</button> |
|
|
<button id="autoPlayBtn" class="px-6 py-2 bg-indigo-600 hover:bg-indigo-500 rounded-lg font-medium hidden"> |
|
|
<i class="fas fa-play mr-2"></i> Auto Play |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div id="winnerFlash" class="winner-flash hidden"></div> |
|
|
|
|
|
|
|
|
<audio id="clickSound" src="https://assets.mixkit.co/sfx/preview/mixkit-select-click-1109.mp3" preload="auto"></audio> |
|
|
<audio id="winSound" src="https://assets.mixkit.co/sfx/preview/mixkit-winning-chimes-2015.mp3" preload="auto"></audio> |
|
|
<audio id="drawSound" src="https://assets.mixkit.co/sfx/preview/mixkit-neutral-game-notification-951.mp3" preload="auto"></audio> |
|
|
<audio id="bgMusic" loop src="https://assets.mixkit.co/music/preview/mixkit-game-show-suspense-waiting-668.mp3" preload="auto"></audio> |
|
|
|
|
|
<script> |
|
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
|
|
|
let board = ['', '', '', '', '', '', '', '', '']; |
|
|
let currentPlayer = 'X'; |
|
|
let gameActive = false; |
|
|
let gameMode = null; |
|
|
let aiDifficulty = { x: 'medium', o: 'medium' }; |
|
|
let playerAiDifficulty = 'medium'; |
|
|
let aiPlaying = false; |
|
|
let aiInterval = null; |
|
|
let soundsEnabled = true; |
|
|
|
|
|
|
|
|
const gameStatusEl = document.getElementById('gameStatus'); |
|
|
const resetBtn = document.getElementById('resetBtn'); |
|
|
const autoPlayBtn = document.getElementById('autoPlayBtn'); |
|
|
const winnerFlash = document.getElementById('winnerFlash'); |
|
|
const gameBoardEl = document.getElementById('gameBoard'); |
|
|
const boardEl = document.querySelector('#gameBoard .grid'); |
|
|
|
|
|
|
|
|
const pvpTab = document.getElementById('pvpTab'); |
|
|
const aiTab = document.getElementById('aiTab'); |
|
|
const aivsaiTab = document.getElementById('aivsaiTab'); |
|
|
const pvpContent = document.getElementById('pvpContent'); |
|
|
const aiContent = document.getElementById('aiContent'); |
|
|
const aivsaiContent = document.getElementById('aivsaiContent'); |
|
|
|
|
|
|
|
|
const startPvpBtn = document.getElementById('startPvpBtn'); |
|
|
const startAiBtn = document.getElementById('startAiBtn'); |
|
|
const startAivsaiBtn = document.getElementById('startAivsaiBtn'); |
|
|
|
|
|
|
|
|
const clickSound = document.getElementById('clickSound'); |
|
|
const winSound = document.getElementById('winSound'); |
|
|
const drawSound = document.getElementById('drawSound'); |
|
|
const bgMusic = document.getElementById('bgMusic'); |
|
|
const soundToggle = document.getElementById('soundToggle'); |
|
|
const soundIcon = document.getElementById('soundIcon'); |
|
|
|
|
|
|
|
|
function createBoard() { |
|
|
boardEl.innerHTML = ''; |
|
|
for (let i = 0; i < 9; i++) { |
|
|
const cell = document.createElement('div'); |
|
|
cell.className = 'cell bg-gray-700 rounded-lg flex items-center justify-center text-6xl font-bold cursor-pointer transition-all duration-200 hover:bg-gray-600'; |
|
|
cell.dataset.index = i; |
|
|
cell.addEventListener('click', () => handleCellClick(i)); |
|
|
boardEl.appendChild(cell); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function switchTab(tab) { |
|
|
|
|
|
pvpTab.classList.remove('tab-active'); |
|
|
aiTab.classList.remove('tab-active'); |
|
|
aivsaiTab.classList.remove('tab-active'); |
|
|
pvpContent.classList.add('hidden'); |
|
|
aiContent.classList.add('hidden'); |
|
|
aivsaiContent.classList.add('hidden'); |
|
|
|
|
|
|
|
|
if (tab === 'pvp') { |
|
|
pvpTab.classList.add('tab-active'); |
|
|
pvpContent.classList.remove('hidden'); |
|
|
} else if (tab === 'ai') { |
|
|
aiTab.classList.add('tab-active'); |
|
|
aiContent.classList.remove('hidden'); |
|
|
} else if (tab === 'aivsai') { |
|
|
aivsaiTab.classList.add('tab-active'); |
|
|
aivsaiContent.classList.remove('hidden'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
pvpTab.addEventListener('click', () => switchTab('pvp')); |
|
|
aiTab.addEventListener('click', () => switchTab('ai')); |
|
|
aivsaiTab.addEventListener('click', () => switchTab('aivsai')); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.difficulty-btn').forEach(btn => { |
|
|
btn.addEventListener('click', function() { |
|
|
|
|
|
document.querySelectorAll('.difficulty-btn').forEach(b => { |
|
|
b.classList.remove('bg-green-700', 'bg-yellow-700', 'bg-red-700', 'ring-2', 'ring-green-400', 'ring-yellow-400', 'ring-red-400'); |
|
|
}); |
|
|
|
|
|
|
|
|
const difficulty = this.dataset.difficulty; |
|
|
playerAiDifficulty = difficulty; |
|
|
|
|
|
if (difficulty === 'easy') { |
|
|
this.classList.add('bg-green-700', 'ring-2', 'ring-green-400'); |
|
|
} else if (difficulty === 'medium') { |
|
|
this.classList.add('bg-yellow-700', 'ring-2', 'ring-yellow-400'); |
|
|
} else if (difficulty === 'hard') { |
|
|
this.classList.add('bg-red-700', 'ring-2', 'ring-red-400'); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
document.querySelectorAll('.aivsai-difficulty-btn').forEach(btn => { |
|
|
btn.addEventListener('click', function() { |
|
|
|
|
|
const ai = this.dataset.ai; |
|
|
document.querySelectorAll(`.aivsai-difficulty-btn[data-ai="${ai}"]`).forEach(b => { |
|
|
b.classList.remove('bg-green-700', 'bg-yellow-700', 'bg-red-700', 'ring-2', 'ring-green-400', 'ring-yellow-400', 'ring-red-400'); |
|
|
}); |
|
|
|
|
|
|
|
|
const difficulty = this.dataset.difficulty; |
|
|
aiDifficulty[ai] = difficulty; |
|
|
|
|
|
if (difficulty === 'easy') { |
|
|
this.classList.add('bg-green-700', 'ring-2', 'ring-green-400'); |
|
|
} else if (difficulty === 'medium') { |
|
|
this.classList.add('bg-yellow-700', 'ring-2', 'ring-yellow-400'); |
|
|
} else if (difficulty === 'hard') { |
|
|
this.classList.add('bg-red-700', 'ring-2', 'ring-red-400'); |
|
|
} |
|
|
}); |
|
|
}); |
|
|
|
|
|
|
|
|
startPvpBtn.addEventListener('click', () => { |
|
|
gameMode = 'pvp'; |
|
|
startGame(); |
|
|
}); |
|
|
|
|
|
startAiBtn.addEventListener('click', () => { |
|
|
gameMode = 'ai'; |
|
|
startGame(); |
|
|
}); |
|
|
|
|
|
startAivsaiBtn.addEventListener('click', () => { |
|
|
gameMode = 'aivsai'; |
|
|
startGame(); |
|
|
}); |
|
|
|
|
|
|
|
|
resetBtn.addEventListener('click', resetGame); |
|
|
|
|
|
|
|
|
autoPlayBtn.addEventListener('click', toggleAutoPlay); |
|
|
|
|
|
|
|
|
soundToggle.addEventListener('click', () => { |
|
|
soundsEnabled = !soundsEnabled; |
|
|
soundIcon.className = soundsEnabled ? 'fas fa-volume-up text-xl' : 'fas fa-volume-mute text-xl'; |
|
|
|
|
|
if (soundsEnabled) { |
|
|
bgMusic.play().catch(e => console.log("Autoplay prevented")); |
|
|
} else { |
|
|
bgMusic.pause(); |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
function playSound(sound) { |
|
|
if (!soundsEnabled) return; |
|
|
|
|
|
sound.currentTime = 0; |
|
|
sound.play().catch(e => console.log("Sound play prevented")); |
|
|
} |
|
|
|
|
|
|
|
|
function startGame() { |
|
|
|
|
|
pvpContent.classList.add('hidden'); |
|
|
aiContent.classList.add('hidden'); |
|
|
aivsaiContent.classList.add('hidden'); |
|
|
gameBoardEl.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
createBoard(); |
|
|
const cells = document.querySelectorAll('.cell'); |
|
|
|
|
|
resetGame(); |
|
|
gameActive = true; |
|
|
|
|
|
if (gameMode === 'aivsai') { |
|
|
autoPlayBtn.classList.remove('hidden'); |
|
|
toggleAutoPlay(); |
|
|
} else { |
|
|
autoPlayBtn.classList.add('hidden'); |
|
|
} |
|
|
|
|
|
|
|
|
if (soundsEnabled) { |
|
|
bgMusic.play().catch(e => console.log("Autoplay prevented")); |
|
|
} |
|
|
|
|
|
updateGameStatus(); |
|
|
} |
|
|
|
|
|
|
|
|
function resetGame() { |
|
|
board = ['', '', '', '', '', '', '', '', '']; |
|
|
currentPlayer = 'X'; |
|
|
gameActive = true; |
|
|
aiPlaying = false; |
|
|
|
|
|
if (aiInterval) { |
|
|
clearInterval(aiInterval); |
|
|
aiInterval = null; |
|
|
autoPlayBtn.innerHTML = '<i class="fas fa-play mr-2"></i> Auto Play'; |
|
|
} |
|
|
|
|
|
|
|
|
const cells = document.querySelectorAll('.cell'); |
|
|
cells.forEach(cell => { |
|
|
cell.textContent = ''; |
|
|
cell.classList.remove('x-color', 'o-color', 'highlight', 'cell-animate'); |
|
|
}); |
|
|
|
|
|
|
|
|
winnerFlash.classList.add('hidden'); |
|
|
winnerFlash.classList.remove('bg-red-500', 'bg-blue-500'); |
|
|
|
|
|
|
|
|
document.body.classList.remove('bg-red-900', 'bg-blue-900'); |
|
|
|
|
|
updateGameStatus(); |
|
|
} |
|
|
|
|
|
|
|
|
function toggleAutoPlay() { |
|
|
if (aiInterval) { |
|
|
clearInterval(aiInterval); |
|
|
aiInterval = null; |
|
|
autoPlayBtn.innerHTML = '<i class="fas fa-play mr-2"></i> Auto Play'; |
|
|
} else { |
|
|
aiInterval = setInterval(() => { |
|
|
if (gameActive && gameMode === 'aivsai' && !aiPlaying) { |
|
|
makeAIMove(); |
|
|
} |
|
|
}, 800); |
|
|
autoPlayBtn.innerHTML = '<i class="fas fa-pause mr-2"></i> Pause'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function handleCellClick(index) { |
|
|
if (!gameActive || board[index] !== '' || aiPlaying) return; |
|
|
|
|
|
|
|
|
if (gameMode === 'ai' && currentPlayer === 'O') return; |
|
|
|
|
|
|
|
|
playSound(clickSound); |
|
|
|
|
|
|
|
|
makeMove(index); |
|
|
|
|
|
|
|
|
if (gameActive && gameMode === 'ai' && currentPlayer === 'O') { |
|
|
setTimeout(() => { |
|
|
makeAIMove(); |
|
|
}, 500); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function makeMove(index) { |
|
|
if (!gameActive || board[index] !== '') return; |
|
|
|
|
|
|
|
|
board[index] = currentPlayer; |
|
|
|
|
|
|
|
|
const cells = document.querySelectorAll('.cell'); |
|
|
const cell = cells[index]; |
|
|
cell.textContent = currentPlayer; |
|
|
cell.classList.add(currentPlayer === 'X' ? 'x-color' : 'o-color', 'cell-animate'); |
|
|
|
|
|
|
|
|
const winner = checkWinner(); |
|
|
if (winner) { |
|
|
handleGameEnd(winner); |
|
|
} else if (!board.includes('')) { |
|
|
handleGameEnd(null); |
|
|
} else { |
|
|
|
|
|
currentPlayer = currentPlayer === 'X' ? 'O' : 'X'; |
|
|
updateGameStatus(); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function makeAIMove() { |
|
|
if (!gameActive || aiPlaying) return; |
|
|
|
|
|
aiPlaying = true; |
|
|
|
|
|
setTimeout(() => { |
|
|
let moveIndex; |
|
|
const difficulty = gameMode === 'aivsai' ? |
|
|
aiDifficulty[currentPlayer.toLowerCase()] : |
|
|
playerAiDifficulty; |
|
|
|
|
|
switch (difficulty) { |
|
|
case 'easy': |
|
|
moveIndex = getRandomMove(); |
|
|
break; |
|
|
case 'medium': |
|
|
moveIndex = getMediumAIMove(); |
|
|
break; |
|
|
case 'hard': |
|
|
moveIndex = getHardAIMove(); |
|
|
break; |
|
|
default: |
|
|
moveIndex = getRandomMove(); |
|
|
} |
|
|
|
|
|
|
|
|
playSound(clickSound); |
|
|
|
|
|
makeMove(moveIndex); |
|
|
aiPlaying = false; |
|
|
}, 500); |
|
|
} |
|
|
|
|
|
|
|
|
function getRandomMove() { |
|
|
const emptyCells = board.map((cell, index) => cell === '' ? index : null).filter(val => val !== null); |
|
|
return emptyCells[Math.floor(Math.random() * emptyCells.length)]; |
|
|
} |
|
|
|
|
|
|
|
|
function getMediumAIMove() { |
|
|
|
|
|
for (let i = 0; i < 9; i++) { |
|
|
if (board[i] === '') { |
|
|
board[i] = currentPlayer; |
|
|
if (checkWinner() === currentPlayer) { |
|
|
board[i] = ''; |
|
|
return i; |
|
|
} |
|
|
board[i] = ''; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const opponent = currentPlayer === 'X' ? 'O' : 'X'; |
|
|
for (let i = 0; i < 9; i++) { |
|
|
if (board[i] === '') { |
|
|
board[i] = opponent; |
|
|
if (checkWinner() === opponent) { |
|
|
board[i] = ''; |
|
|
return i; |
|
|
} |
|
|
board[i] = ''; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (board[4] === '') return 4; |
|
|
|
|
|
|
|
|
return getRandomMove(); |
|
|
} |
|
|
|
|
|
|
|
|
function getHardAIMove() { |
|
|
|
|
|
for (let i = 0; i < 9; i++) { |
|
|
if (board[i] === '') { |
|
|
board[i] = currentPlayer; |
|
|
if (checkWinner() === currentPlayer) { |
|
|
board[i] = ''; |
|
|
return i; |
|
|
} |
|
|
board[i] = ''; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const opponent = currentPlayer === 'X' ? 'O' : 'X'; |
|
|
for (let i = 0; i < 9; i++) { |
|
|
if (board[i] === '') { |
|
|
board[i] = opponent; |
|
|
if (checkWinner() === opponent) { |
|
|
board[i] = ''; |
|
|
return i; |
|
|
} |
|
|
board[i] = ''; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
if (board[4] === '') return 4; |
|
|
|
|
|
|
|
|
const corners = [0, 2, 6, 8]; |
|
|
const availableCorners = corners.filter(i => board[i] === ''); |
|
|
if (availableCorners.length > 0) { |
|
|
return availableCorners[Math.floor(Math.random() * availableCorners.length)]; |
|
|
} |
|
|
|
|
|
|
|
|
const edges = [1, 3, 5, 7]; |
|
|
const availableEdges = edges.filter(i => board[i] === ''); |
|
|
if (availableEdges.length > 0) { |
|
|
return availableEdges[Math.floor(Math.random() * availableEdges.length)]; |
|
|
} |
|
|
|
|
|
|
|
|
return getRandomMove(); |
|
|
} |
|
|
|
|
|
|
|
|
function checkWinner() { |
|
|
|
|
|
const winPatterns = [ |
|
|
[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 pattern of winPatterns) { |
|
|
const [a, b, c] = pattern; |
|
|
if (board[a] && board[a] === board[b] && board[a] === board[c]) { |
|
|
|
|
|
const cells = document.querySelectorAll('.cell'); |
|
|
cells[a].classList.add('highlight'); |
|
|
cells[b].classList.add('highlight'); |
|
|
cells[c].classList.add('highlight'); |
|
|
|
|
|
return board[a]; |
|
|
} |
|
|
} |
|
|
|
|
|
return null; |
|
|
} |
|
|
|
|
|
|
|
|
function handleGameEnd(winner) { |
|
|
gameActive = false; |
|
|
|
|
|
if (winner) { |
|
|
|
|
|
if (gameMode === 'aivsai') { |
|
|
winner = Math.random() < 0.5 ? 'X' : 'O'; |
|
|
|
|
|
|
|
|
const winPatterns = [ |
|
|
[0, 1, 2], [3, 4, 5], [6, 7, 8], |
|
|
[0, 3, 6], [1, 4, 7], [2, 5, 8], |
|
|
[0, 4, 8], [2, 4, 6] |
|
|
]; |
|
|
|
|
|
const randomPattern = winPatterns[Math.floor(Math.random() * winPatterns.length)]; |
|
|
const cells = document.querySelectorAll('.cell'); |
|
|
|
|
|
randomPattern.forEach(i => { |
|
|
board[i] = winner; |
|
|
cells[i].textContent = winner; |
|
|
cells[i].classList.add(winner === 'X' ? 'x-color' : 'o-color', 'highlight'); |
|
|
}); |
|
|
} |
|
|
|
|
|
gameStatusEl.textContent = `Player ${winner} wins!`; |
|
|
|
|
|
|
|
|
playSound(winSound); |
|
|
|
|
|
|
|
|
winnerFlash.classList.remove('hidden'); |
|
|
winnerFlash.classList.add(winner === 'X' ? 'bg-red-500' : 'bg-blue-500'); |
|
|
document.body.classList.add(winner === 'X' ? 'bg-red-900' : 'bg-blue-900'); |
|
|
|
|
|
|
|
|
setTimeout(resetGame, 3000); |
|
|
} else { |
|
|
gameStatusEl.textContent = "Game ended in a draw!"; |
|
|
|
|
|
|
|
|
playSound(drawSound); |
|
|
|
|
|
|
|
|
setTimeout(resetGame, 3000); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function updateGameStatus() { |
|
|
if (gameMode === 'pvp') { |
|
|
gameStatusEl.textContent = `Player ${currentPlayer}'s turn`; |
|
|
} else if (gameMode === 'ai') { |
|
|
if (currentPlayer === 'X') { |
|
|
gameStatusEl.textContent = "Your turn (X)"; |
|
|
} else { |
|
|
gameStatusEl.textContent = "AI is thinking..."; |
|
|
} |
|
|
} else if (gameMode === 'aivsai') { |
|
|
gameStatusEl.textContent = `AI ${currentPlayer}'s turn`; |
|
|
} |
|
|
|
|
|
|
|
|
gameStatusEl.classList.remove('x-color', 'o-color'); |
|
|
if (gameActive) { |
|
|
gameStatusEl.classList.add(currentPlayer === 'X' ? 'x-color' : 'o-color'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
createBoard(); |
|
|
switchTab('pvp'); |
|
|
|
|
|
|
|
|
document.querySelector('.difficulty-btn[data-difficulty="medium"]').classList.add('bg-yellow-700', 'ring-2', 'ring-yellow-400'); |
|
|
document.querySelector('.aivsai-difficulty-btn[data-ai="x"][data-difficulty="medium"]').classList.add('bg-yellow-700', 'ring-2', 'ring-yellow-400'); |
|
|
document.querySelector('.aivsai-difficulty-btn[data-ai="o"][data-difficulty="medium"]').classList.add('bg-yellow-700', 'ring-2', 'ring-yellow-400'); |
|
|
}); |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Joshua353/joshua" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |