Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Cyber Neon Tic Tac Toe</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script> | |
<style> | |
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap'); | |
:root { | |
--neon-pink: #ff2a6d; | |
--neon-blue: #05d9e8; | |
--neon-purple: #d300c5; | |
--neon-green: #00ff9d; | |
--dark-bg: #0d0221; | |
} | |
body { | |
font-family: 'Orbitron', sans-serif; | |
background-color: var(--dark-bg); | |
color: white; | |
overflow-x: hidden; | |
} | |
.neon-text-pink { | |
color: var(--neon-pink); | |
text-shadow: 0 0 10px var(--neon-pink), 0 0 20px var(--neon-pink); | |
} | |
.neon-text-blue { | |
color: var(--neon-blue); | |
text-shadow: 0 0 10px var(--neon-blue), 0 0 20px var(--neon-blue); | |
} | |
.neon-border { | |
border: 2px solid var(--neon-blue); | |
box-shadow: 0 0 10px var(--neon-blue), inset 0 0 10px var(--neon-blue); | |
} | |
.neon-glow { | |
animation: glow 2s infinite alternate; | |
} | |
@keyframes glow { | |
from { | |
box-shadow: 0 0 5px var(--neon-blue), 0 0 10px var(--neon-blue); | |
} | |
to { | |
box-shadow: 0 0 15px var(--neon-blue), 0 0 30px var(--neon-blue); | |
} | |
} | |
.cell { | |
width: 100px; | |
height: 100px; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
font-size: 3rem; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
position: relative; | |
overflow: hidden; | |
} | |
.cell:hover { | |
background-color: rgba(5, 217, 232, 0.1); | |
} | |
.cell.x::before, .cell.x::after { | |
content: ''; | |
position: absolute; | |
width: 80%; | |
height: 8px; | |
background-color: var(--neon-pink); | |
transform: rotate(45deg); | |
box-shadow: 0 0 10px var(--neon-pink), 0 0 20px var(--neon-pink); | |
} | |
.cell.x::after { | |
transform: rotate(-45deg); | |
} | |
.cell.o::before { | |
content: ''; | |
position: absolute; | |
width: 60%; | |
height: 60%; | |
border-radius: 50%; | |
border: 8px solid var(--neon-blue); | |
box-shadow: 0 0 10px var(--neon-blue), 0 0 20px var(--neon-blue); | |
} | |
.explosion { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
pointer-events: none; | |
z-index: 10; | |
} | |
.particle { | |
position: absolute; | |
width: 8px; | |
height: 8px; | |
border-radius: 50%; | |
background-color: var(--neon-green); | |
box-shadow: 0 0 5px var(--neon-green), 0 0 10px var(--neon-green); | |
} | |
.cyber-btn { | |
background: linear-gradient(45deg, var(--neon-purple), var(--neon-blue)); | |
border: none; | |
color: white; | |
padding: 12px 24px; | |
font-family: 'Orbitron', sans-serif; | |
font-weight: bold; | |
letter-spacing: 2px; | |
text-transform: uppercase; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
position: relative; | |
overflow: hidden; | |
} | |
.cyber-btn:hover { | |
transform: translateY(-3px); | |
box-shadow: 0 5px 15px rgba(5, 217, 232, 0.4); | |
} | |
.cyber-btn:active { | |
transform: translateY(1px); | |
} | |
.cyber-btn::before { | |
content: ''; | |
position: absolute; | |
top: -10px; | |
left: -10px; | |
right: -10px; | |
bottom: -10px; | |
background: linear-gradient(45deg, var(--neon-purple), var(--neon-blue), var(--neon-green)); | |
z-index: -1; | |
filter: blur(10px); | |
opacity: 0; | |
transition: opacity 0.3s ease; | |
} | |
.cyber-btn:hover::before { | |
opacity: 0.7; | |
} | |
.grid-lines { | |
position: absolute; | |
background-color: rgba(5, 217, 232, 0.3); | |
} | |
.grid-line-h { | |
width: 100%; | |
height: 2px; | |
} | |
.grid-line-v { | |
width: 2px; | |
height: 100%; | |
} | |
.ai-selection { | |
display: flex; | |
gap: 20px; | |
margin-top: 20px; | |
} | |
.ai-option { | |
padding: 10px 20px; | |
border: 2px solid var(--neon-blue); | |
cursor: pointer; | |
transition: all 0.3s ease; | |
} | |
.ai-option:hover { | |
background-color: rgba(5, 217, 232, 0.2); | |
} | |
.ai-option.selected { | |
background-color: var(--neon-blue); | |
color: var(--dark-bg); | |
font-weight: bold; | |
} | |
.scanline { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background: linear-gradient( | |
to bottom, | |
transparent 0%, | |
rgba(13, 2, 33, 0.1) 50%, | |
transparent 100% | |
); | |
background-size: 100% 8px; | |
pointer-events: none; | |
animation: scanline 6s linear infinite; | |
z-index: 100; | |
opacity: 0.3; | |
} | |
@keyframes scanline { | |
0% { | |
background-position: 0 0; | |
} | |
100% { | |
background-position: 0 100vh; | |
} | |
} | |
</style> | |
</head> | |
<body class="min-h-screen flex flex-col items-center justify-center p-4 relative"> | |
<div class="scanline"></div> | |
<div class="absolute top-0 left-0 w-full h-full overflow-hidden z-0"> | |
<div class="grid-line-h top-1/3" style="top: 33.33%"></div> | |
<div class="grid-line-h top-2/3" style="top: 66.66%"></div> | |
<div class="grid-line-v left-1/3" style="left: 33.33%"></div> | |
<div class="grid-line-v left-2/3" style="left: 66.66%"></div> | |
</div> | |
<h1 class="text-5xl font-bold mb-8 neon-text-pink">CYBER TAC TOE</h1> | |
<div id="game-container" class="relative z-10"> | |
<div id="ai-selection" class="mb-8 text-center"> | |
<h2 class="text-xl neon-text-blue mb-4">SELECT AI OPPONENT</h2> | |
<div class="ai-selection justify-center"> | |
<div class="ai-option selected" data-difficulty="easy">NOVICE</div> | |
<div class="ai-option" data-difficulty="medium">ADEPT</div> | |
<div class="ai-option" data-difficulty="hard">MASTER</div> | |
</div> | |
</div> | |
<div id="status" class="text-xl neon-text-blue mb-6 text-center h-8"></div> | |
<div class="grid grid-cols-3 gap-2 neon-border p-2 mb-8 relative"> | |
<div class="cell" data-index="0"></div> | |
<div class="cell" data-index="1"></div> | |
<div class="cell" data-index="2"></div> | |
<div class="cell" data-index="3"></div> | |
<div class="cell" data-index="4"></div> | |
<div class="cell" data-index="5"></div> | |
<div class="cell" data-index="6"></div> | |
<div class="cell" data-index="7"></div> | |
<div class="cell" data-index="8"></div> | |
</div> | |
<div class="flex justify-center"> | |
<button id="reset-btn" class="cyber-btn neon-glow">NEW GAME</button> | |
</div> | |
</div> | |
<div class="mt-8 text-sm neon-text-blue"> | |
<p>SYSTEM STATUS: <span class="neon-text-green">OPERATIONAL</span></p> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', () => { | |
const cells = document.querySelectorAll('.cell'); | |
const statusDisplay = document.getElementById('status'); | |
const resetButton = document.getElementById('reset-btn'); | |
const aiOptions = document.querySelectorAll('.ai-option'); | |
let gameActive = true; | |
let currentPlayer = 'X'; | |
let gameState = ['', '', '', '', '', '', '', '', '']; | |
let aiDifficulty = 'easy'; | |
const winningConditions = [ | |
[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 | |
]; | |
// AI difficulty selection | |
aiOptions.forEach(option => { | |
option.addEventListener('click', () => { | |
aiOptions.forEach(opt => opt.classList.remove('selected')); | |
option.classList.add('selected'); | |
aiDifficulty = option.dataset.difficulty; | |
resetGame(); | |
}); | |
}); | |
// Handle cell click | |
function handleCellClick(e) { | |
const clickedCell = e.target; | |
const clickedCellIndex = parseInt(clickedCell.getAttribute('data-index')); | |
if (gameState[clickedCellIndex] !== '' || !gameActive) return; | |
handleCellPlayed(clickedCell, clickedCellIndex); | |
handleResultValidation(); | |
// AI move if game is still active and it's O's turn | |
if (gameActive && currentPlayer === 'O') { | |
setTimeout(() => { | |
makeAIMove(); | |
}, 800); | |
} | |
} | |
// Handle cell played | |
function handleCellPlayed(clickedCell, clickedCellIndex) { | |
gameState[clickedCellIndex] = currentPlayer; | |
clickedCell.classList.add(currentPlayer.toLowerCase()); | |
createExplosion(clickedCell); | |
} | |
// Create explosion effect | |
function createExplosion(element) { | |
const explosion = document.createElement('div'); | |
explosion.className = 'explosion'; | |
element.appendChild(explosion); | |
const color = currentPlayer === 'X' ? 'var(--neon-pink)' : 'var(--neon-blue)'; | |
// Create particles | |
for (let i = 0; i < 20; i++) { | |
const particle = document.createElement('div'); | |
particle.className = 'particle'; | |
particle.style.backgroundColor = color; | |
particle.style.boxShadow = `0 0 5px ${color}, 0 0 10px ${color}`; | |
// Random position | |
const angle = Math.random() * Math.PI * 2; | |
const distance = 10 + Math.random() * 50; | |
const x = 50 + Math.cos(angle) * distance; | |
const y = 50 + Math.sin(angle) * distance; | |
particle.style.left = `${x}%`; | |
particle.style.top = `${y}%`; | |
// Animation | |
gsap.to(particle, { | |
x: Math.cos(angle) * 100, | |
y: Math.sin(angle) * 100, | |
opacity: 0, | |
duration: 1, | |
ease: 'power2.out', | |
onComplete: () => { | |
particle.remove(); | |
} | |
}); | |
explosion.appendChild(particle); | |
} | |
// Remove explosion after animation | |
setTimeout(() => { | |
explosion.remove(); | |
}, 1000); | |
} | |
// Validate game result | |
function handleResultValidation() { | |
let roundWon = false; | |
for (let i = 0; i < winningConditions.length; i++) { | |
const [a, b, c] = winningConditions[i]; | |
if (gameState[a] === '' || gameState[b] === '' || gameState[c] === '') continue; | |
if (gameState[a] === gameState[b] && gameState[b] === gameState[c]) { | |
roundWon = true; | |
break; | |
} | |
} | |
if (roundWon) { | |
statusDisplay.textContent = `PLAYER ${currentPlayer} DOMINATES!`; | |
gameActive = false; | |
highlightWinningCells(); | |
return; | |
} | |
const roundDraw = !gameState.includes(''); | |
if (roundDraw) { | |
statusDisplay.textContent = 'SYSTEM STALEMATE!'; | |
gameActive = false; | |
return; | |
} | |
currentPlayer = currentPlayer === 'X' ? 'O' : 'X'; | |
statusDisplay.textContent = `PLAYER ${currentPlayer} TURN`; | |
} | |
// Highlight winning cells | |
function highlightWinningCells() { | |
for (let condition of winningConditions) { | |
const [a, b, c] = condition; | |
if (gameState[a] === '' || gameState[b] === '' || gameState[c] === '') continue; | |
if (gameState[a] === gameState[b] && gameState[b] === gameState[c]) { | |
cells[a].classList.add('neon-glow'); | |
cells[b].classList.add('neon-glow'); | |
cells[c].classList.add('neon-glow'); | |
const color = currentPlayer === 'X' ? 'var(--neon-pink)' : 'var(--neon-blue)'; | |
cells[a].style.boxShadow = `0 0 15px ${color}, 0 0 30px ${color}`; | |
cells[b].style.boxShadow = `0 0 15px ${color}, 0 0 30px ${color}`; | |
cells[c].style.boxShadow = `0 0 15px ${color}, 0 0 30px ${color}`; | |
break; | |
} | |
} | |
} | |
// AI move logic | |
function makeAIMove() { | |
if (!gameActive) return; | |
let move; | |
switch (aiDifficulty) { | |
case 'easy': | |
move = getRandomMove(); | |
break; | |
case 'medium': | |
// 50% chance to make a smart move, 50% random | |
move = Math.random() < 0.5 ? getSmartMove() : getRandomMove(); | |
break; | |
case 'hard': | |
move = getSmartMove(); | |
break; | |
default: | |
move = getRandomMove(); | |
} | |
if (move !== -1) { | |
const cell = cells[move]; | |
handleCellPlayed(cell, move); | |
handleResultValidation(); | |
} | |
} | |
// Get random available move | |
function getRandomMove() { | |
const availableMoves = gameState.map((cell, index) => cell === '' ? index : null).filter(val => val !== null); | |
if (availableMoves.length === 0) return -1; | |
return availableMoves[Math.floor(Math.random() * availableMoves.length)]; | |
} | |
// Get smart move (tries to win or block) | |
function getSmartMove() { | |
// Check if AI can win | |
for (let condition of winningConditions) { | |
const [a, b, c] = condition; | |
if (gameState[a] === 'O' && gameState[b] === 'O' && gameState[c] === '') return c; | |
if (gameState[a] === 'O' && gameState[c] === 'O' && gameState[b] === '') return b; | |
if (gameState[b] === 'O' && gameState[c] === 'O' && gameState[a] === '') return a; | |
} | |
// Check if player can win and block | |
for (let condition of winningConditions) { | |
const [a, b, c] = condition; | |
if (gameState[a] === 'X' && gameState[b] === 'X' && gameState[c] === '') return c; | |
if (gameState[a] === 'X' && gameState[c] === 'X' && gameState[b] === '') return b; | |
if (gameState[b] === 'X' && gameState[c] === 'X' && gameState[a] === '') return a; | |
} | |
// Try to take center | |
if (gameState[4] === '') return 4; | |
// Try to take a corner | |
const corners = [0, 2, 6, 8]; | |
const availableCorners = corners.filter(index => gameState[index] === ''); | |
if (availableCorners.length > 0) { | |
return availableCorners[Math.floor(Math.random() * availableCorners.length)]; | |
} | |
// Take any available move | |
return getRandomMove(); | |
} | |
// Reset game | |
function resetGame() { | |
gameActive = true; | |
currentPlayer = 'X'; | |
gameState = ['', '', '', '', '', '', '', '', '']; | |
statusDisplay.textContent = `PLAYER ${currentPlayer} TURN`; | |
cells.forEach(cell => { | |
cell.classList.remove('x', 'o', 'neon-glow'); | |
cell.style.boxShadow = ''; | |
}); | |
} | |
// Event listeners | |
cells.forEach(cell => cell.addEventListener('click', handleCellClick)); | |
resetButton.addEventListener('click', resetGame); | |
// Initialize game | |
resetGame(); | |
}); | |
</script> | |
</body> | |
</html> |