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> |