Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Neon Tic Tac Toe</title> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Poppins:wght@300;400;600&display=swap" rel="stylesheet"> | |
<style> | |
:root { | |
--neon-blue: #00f2ff; | |
--neon-pink: #ff00ff; | |
--dark-bg: #0f0f1a; | |
--light-bg: #1a1a2e; | |
--text-light: #e6e6ff; | |
} | |
body { | |
font-family: 'Poppins', sans-serif; | |
background-color: var(--dark-bg); | |
color: var(--text-light); | |
min-height: 100vh; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
overflow-x: hidden; | |
} | |
h1, h2, h3 { | |
font-family: 'Orbitron', sans-serif; | |
} | |
.neon-text-blue { | |
text-shadow: 0 0 5px var(--neon-blue), 0 0 10px var(--neon-blue); | |
color: var(--neon-blue); | |
} | |
.neon-text-pink { | |
text-shadow: 0 0 5px var(--neon-pink), 0 0 10px var(--neon-pink); | |
color: var(--neon-pink); | |
} | |
.neon-border-blue { | |
box-shadow: 0 0 10px var(--neon-blue), 0 0 20px var(--neon-blue), inset 0 0 10px var(--neon-blue); | |
border: 1px solid var(--neon-blue); | |
} | |
.neon-border-pink { | |
box-shadow: 0 0 10px var(--neon-pink), 0 0 20px var(--neon-pink), inset 0 0 10px var(--neon-pink); | |
border: 1px solid var(--neon-pink); | |
} | |
.cell { | |
width: 100px; | |
height: 100px; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
font-size: 4rem; | |
cursor: pointer; | |
transition: all 0.3s ease; | |
position: relative; | |
overflow: hidden; | |
} | |
.cell:hover { | |
background-color: rgba(255, 255, 255, 0.05); | |
} | |
.cell.x::before, .cell.x::after { | |
content: ''; | |
position: absolute; | |
width: 15px; | |
height: 80px; | |
background-color: var(--neon-blue); | |
transform: rotate(45deg); | |
} | |
.cell.x::after { | |
transform: rotate(-45deg); | |
} | |
.cell.o::before { | |
content: ''; | |
position: absolute; | |
width: 60px; | |
height: 60px; | |
border-radius: 50%; | |
border: 15px solid var(--neon-pink); | |
} | |
.winning-cell { | |
animation: pulse 1s infinite alternate; | |
} | |
@keyframes pulse { | |
from { | |
transform: scale(1); | |
opacity: 1; | |
} | |
to { | |
transform: scale(1.1); | |
opacity: 0.8; | |
} | |
} | |
.board { | |
display: grid; | |
grid-template-columns: repeat(3, 1fr); | |
gap: 10px; | |
background-color: var(--light-bg); | |
padding: 20px; | |
border-radius: 10px; | |
position: relative; | |
} | |
.board::before { | |
content: ''; | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background: linear-gradient(45deg, transparent 48%, var(--neon-blue) 49%, var(--neon-blue) 51%, transparent 52%), | |
linear-gradient(-45deg, transparent 48%, var(--neon-blue) 49%, var(--neon-blue) 51%, transparent 52%); | |
background-size: 10px 10px; | |
opacity: 0.05; | |
pointer-events: none; | |
} | |
.btn { | |
transition: all 0.3s ease; | |
font-family: 'Orbitron', sans-serif; | |
letter-spacing: 1px; | |
} | |
.btn:hover { | |
transform: translateY(-2px); | |
} | |
.btn-blue { | |
background-color: var(--neon-blue); | |
color: var(--dark-bg); | |
} | |
.btn-blue:hover { | |
box-shadow: 0 0 10px var(--neon-blue), 0 0 20px var(--neon-blue); | |
} | |
.btn-pink { | |
background-color: var(--neon-pink); | |
color: var(--dark-bg); | |
} | |
.btn-pink:hover { | |
box-shadow: 0 0 10px var(--neon-pink), 0 0 20px var(--neon-pink); | |
} | |
.modal { | |
display: none; | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0, 0, 0, 0.8); | |
z-index: 100; | |
align-items: center; | |
justify-content: center; | |
animation: fadeIn 0.3s ease; | |
} | |
@keyframes fadeIn { | |
from { opacity: 0; } | |
to { opacity: 1; } | |
} | |
.modal-content { | |
background-color: var(--light-bg); | |
padding: 2rem; | |
border-radius: 10px; | |
max-width: 500px; | |
width: 90%; | |
text-align: center; | |
position: relative; | |
animation: slideDown 0.3s ease; | |
} | |
@keyframes slideDown { | |
from { transform: translateY(-50px); opacity: 0; } | |
to { transform: translateY(0); opacity: 1; } | |
} | |
.close-modal { | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
cursor: pointer; | |
font-size: 1.5rem; | |
} | |
.radio-tile-group { | |
display: flex; | |
flex-wrap: wrap; | |
justify-content: center; | |
gap: 1rem; | |
margin: 1rem 0; | |
} | |
.radio-tile { | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
padding: 1rem; | |
border-radius: 8px; | |
background-color: rgba(255, 255, 255, 0.05); | |
cursor: pointer; | |
transition: all 0.3s ease; | |
min-width: 100px; | |
} | |
.radio-tile:hover { | |
background-color: rgba(255, 255, 255, 0.1); | |
} | |
.radio-tile.selected { | |
background-color: var(--neon-blue); | |
color: var(--dark-bg); | |
} | |
.radio-tile.selected-pink { | |
background-color: var(--neon-pink); | |
color: var(--dark-bg); | |
} | |
input[type="radio"] { | |
display: none; | |
} | |
@media (max-width: 768px) { | |
.cell { | |
width: 80px; | |
height: 80px; | |
font-size: 3rem; | |
} | |
.cell.x::before, .cell.x::after { | |
height: 60px; | |
} | |
.cell.o::before { | |
width: 50px; | |
height: 50px; | |
border-width: 10px; | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<div class="container mx-auto px-4 py-8 flex flex-col items-center"> | |
<h1 class="text-4xl md:text-5xl font-bold mb-2 neon-text-blue">NEON</h1> | |
<h1 class="text-4xl md:text-5xl font-bold mb-8 neon-text-pink">TIC TAC TOE</h1> | |
<div class="flex flex-col md:flex-row gap-8 items-center justify-center w-full"> | |
<!-- Game Controls --> | |
<div class="w-full md:w-1/3 max-w-md bg-gray-900 bg-opacity-50 rounded-xl p-6"> | |
<h2 class="text-2xl font-bold mb-4 text-center">Game Settings</h2> | |
<div class="mb-6"> | |
<h3 class="text-lg font-semibold mb-2">Game Mode</h3> | |
<div class="radio-tile-group"> | |
<label class="radio-tile" id="pvp-label"> | |
<input type="radio" name="gameMode" value="pvp" checked> | |
<span class="font-medium">Player vs Player</span> | |
</label> | |
<label class="radio-tile" id="pvc-label"> | |
<input type="radio" name="gameMode" value="pvc"> | |
<span class="font-medium">Player vs AI</span> | |
</label> | |
</div> | |
</div> | |
<div class="mb-6" id="ai-settings"> | |
<h3 class="text-lg font-semibold mb-2">AI Difficulty</h3> | |
<div class="radio-tile-group"> | |
<label class="radio-tile" id="easy-label"> | |
<input type="radio" name="aiDifficulty" value="easy" checked> | |
<span class="font-medium">Easy</span> | |
</label> | |
<label class="radio-tile" id="medium-label"> | |
<input type="radio" name="aiDifficulty" value="medium"> | |
<span class="font-medium">Medium</span> | |
</label> | |
<label class="radio-tile" id="hard-label"> | |
<input type="radio" name="aiDifficulty" value="hard"> | |
<span class="font-medium">Hard</span> | |
</label> | |
</div> | |
</div> | |
<div class="mb-6" id="player-symbol"> | |
<h3 class="text-lg font-semibold mb-2">Your Symbol</h3> | |
<div class="radio-tile-group"> | |
<label class="radio-tile selected" id="x-label"> | |
<input type="radio" name="playerSymbol" value="x" checked> | |
<span class="font-medium">X</span> | |
</label> | |
<label class="radio-tile" id="o-label"> | |
<input type="radio" name="playerSymbol" value="o"> | |
<span class="font-medium">O</span> | |
</label> | |
</div> | |
</div> | |
<div class="flex justify-center gap-4"> | |
<button id="new-game-btn" class="btn btn-blue px-6 py-2 rounded-lg font-bold">New Game</button> | |
<button id="reset-score-btn" class="btn btn-pink px-6 py-2 rounded-lg font-bold">Reset Score</button> | |
</div> | |
</div> | |
<!-- Game Board --> | |
<div class="w-full md:w-2/3 flex flex-col items-center"> | |
<div class="flex justify-between w-full max-w-sm mb-4"> | |
<div id="player-x-score" class="text-center px-4 py-2 rounded-lg neon-border-blue"> | |
<div class="text-sm">Player X</div> | |
<div class="text-2xl font-bold">0</div> | |
</div> | |
<div id="player-o-score" class="text-center px-4 py-2 rounded-lg neon-border-pink"> | |
<div class="text-sm">Player O</div> | |
<div class="text-2xl font-bold">0</div> | |
</div> | |
</div> | |
<div class="board mb-6"> | |
<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 id="game-status" class="text-xl font-bold mb-4 neon-text-blue">Player X's Turn</div> | |
</div> | |
</div> | |
</div> | |
<!-- Winner Modal --> | |
<div id="winner-modal" class="modal"> | |
<div class="modal-content"> | |
<span class="close-modal">×</span> | |
<h2 id="winner-text" class="text-3xl font-bold mb-4"></h2> | |
<p id="winner-subtext" class="mb-6"></p> | |
<button id="play-again-btn" class="btn btn-blue px-6 py-2 rounded-lg font-bold">Play Again</button> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', () => { | |
// Game state | |
let board = ['', '', '', '', '', '', '', '', '']; | |
let currentPlayer = 'x'; | |
let gameActive = true; | |
let scores = { x: 0, o: 0 }; | |
let gameMode = 'pvp'; | |
let aiDifficulty = 'easy'; | |
let playerSymbol = 'x'; | |
// DOM elements | |
const cells = document.querySelectorAll('.cell'); | |
const gameStatus = document.getElementById('game-status'); | |
const playerXScore = document.querySelector('#player-x-score div:last-child'); | |
const playerOScore = document.querySelector('#player-o-score div:last-child'); | |
const winnerModal = document.getElementById('winner-modal'); | |
const winnerText = document.getElementById('winner-text'); | |
const winnerSubtext = document.getElementById('winner-subtext'); | |
const playAgainBtn = document.getElementById('play-again-btn'); | |
const newGameBtn = document.getElementById('new-game-btn'); | |
const resetScoreBtn = document.getElementById('reset-score-btn'); | |
const closeModal = document.querySelector('.close-modal'); | |
const aiSettings = document.getElementById('ai-settings'); | |
const playerSymbolSection = document.getElementById('player-symbol'); | |
// Radio button labels | |
const pvpLabel = document.getElementById('pvp-label'); | |
const pvcLabel = document.getElementById('pvc-label'); | |
const easyLabel = document.getElementById('easy-label'); | |
const mediumLabel = document.getElementById('medium-label'); | |
const hardLabel = document.getElementById('hard-label'); | |
const xLabel = document.getElementById('x-label'); | |
const oLabel = document.getElementById('o-label'); | |
// Event listeners | |
cells.forEach(cell => cell.addEventListener('click', handleCellClick)); | |
newGameBtn.addEventListener('click', startNewGame); | |
resetScoreBtn.addEventListener('click', resetScores); | |
playAgainBtn.addEventListener('click', startNewGame); | |
closeModal.addEventListener('click', () => winnerModal.style.display = 'none'); | |
// Game mode radio buttons | |
document.querySelectorAll('input[name="gameMode"]').forEach(radio => { | |
radio.addEventListener('change', (e) => { | |
gameMode = e.target.value; | |
aiSettings.style.display = gameMode === 'pvc' ? 'block' : 'none'; | |
playerSymbolSection.style.display = gameMode === 'pvc' ? 'block' : 'none'; | |
// Update UI | |
if (gameMode === 'pvp') { | |
pvpLabel.classList.add('selected'); | |
pvcLabel.classList.remove('selected'); | |
} else { | |
pvpLabel.classList.remove('selected'); | |
pvcLabel.classList.add('selected'); | |
} | |
startNewGame(); | |
}); | |
}); | |
// AI difficulty radio buttons | |
document.querySelectorAll('input[name="aiDifficulty"]').forEach(radio => { | |
radio.addEventListener('change', (e) => { | |
aiDifficulty = e.target.value; | |
// Update UI | |
easyLabel.classList.remove('selected'); | |
mediumLabel.classList.remove('selected'); | |
hardLabel.classList.remove('selected'); | |
if (aiDifficulty === 'easy') { | |
easyLabel.classList.add('selected'); | |
} else if (aiDifficulty === 'medium') { | |
mediumLabel.classList.add('selected'); | |
} else { | |
hardLabel.classList.add('selected'); | |
} | |
}); | |
}); | |
// Player symbol radio buttons | |
document.querySelectorAll('input[name="playerSymbol"]').forEach(radio => { | |
radio.addEventListener('change', (e) => { | |
playerSymbol = e.target.value; | |
// Update UI | |
xLabel.classList.remove('selected'); | |
oLabel.classList.remove('selected'); | |
if (playerSymbol === 'x') { | |
xLabel.classList.add('selected'); | |
} else { | |
oLabel.classList.add('selected'); | |
} | |
startNewGame(); | |
}); | |
}); | |
// Initialize game | |
startNewGame(); | |
function handleCellClick(e) { | |
const clickedCell = e.target; | |
const clickedCellIndex = parseInt(clickedCell.getAttribute('data-index')); | |
// If cell is already filled or game is not active, ignore click | |
if (board[clickedCellIndex] !== '' || !gameActive) return; | |
// If it's AI's turn in PVC mode, ignore click | |
if (gameMode === 'pvc' && currentPlayer !== playerSymbol) return; | |
// Update board and UI | |
board[clickedCellIndex] = currentPlayer; | |
updateCell(clickedCell, currentPlayer); | |
// Check for win or draw | |
const gameWon = checkWin(); | |
const gameDraw = checkDraw(); | |
if (gameWon) { | |
handleWin(gameWon); | |
return; | |
} | |
if (gameDraw) { | |
handleDraw(); | |
return; | |
} | |
// Switch player | |
switchPlayer(); | |
// If it's AI's turn in PVC mode, make AI move | |
if (gameMode === 'pvc' && currentPlayer !== playerSymbol && gameActive) { | |
setTimeout(makeAIMove, 500); // Small delay for better UX | |
} | |
} | |
function updateCell(cell, player) { | |
cell.classList.add(player); | |
} | |
function switchPlayer() { | |
currentPlayer = currentPlayer === 'x' ? 'o' : 'x'; | |
updateGameStatus(); | |
} | |
function updateGameStatus() { | |
if (gameMode === 'pvp') { | |
gameStatus.textContent = `Player ${currentPlayer.toUpperCase()}'s Turn`; | |
gameStatus.className = `text-xl font-bold mb-4 ${currentPlayer === 'x' ? 'neon-text-blue' : 'neon-text-pink'}`; | |
} else { | |
if (currentPlayer === playerSymbol) { | |
gameStatus.textContent = "Your Turn"; | |
gameStatus.className = `text-xl font-bold mb-4 ${playerSymbol === 'x' ? 'neon-text-blue' : 'neon-text-pink'}`; | |
} else { | |
gameStatus.textContent = "AI's Turn"; | |
gameStatus.className = "text-xl font-bold mb-4 text-gray-400"; | |
} | |
} | |
} | |
function checkWin() { | |
// All possible winning combinations | |
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 (let pattern of winPatterns) { | |
const [a, b, c] = pattern; | |
// Check if all cells in the pattern are filled by the same player | |
if (board[a] && board[a] === board[b] && board[a] === board[c]) { | |
return { | |
winner: board[a], | |
winningCells: pattern | |
}; | |
} | |
} | |
return null; | |
} | |
function checkDraw() { | |
return !board.includes('') && !checkWin(); | |
} | |
function handleWin(winInfo) { | |
gameActive = false; | |
scores[winInfo.winner]++; | |
updateScores(); | |
// Highlight winning cells | |
winInfo.winningCells.forEach(index => { | |
cells[index].classList.add('winning-cell'); | |
}); | |
// Show winner modal | |
if (gameMode === 'pvp') { | |
winnerText.textContent = `Player ${winInfo.winner.toUpperCase()} Wins!`; | |
winnerText.className = `text-3xl font-bold mb-4 ${winInfo.winner === 'x' ? 'neon-text-blue' : 'neon-text-pink'}`; | |
winnerSubtext.textContent = `Player ${winInfo.winner.toUpperCase()} has won the game!`; | |
} else { | |
if (winInfo.winner === playerSymbol) { | |
winnerText.textContent = "You Win!"; | |
winnerText.className = `text-3xl font-bold mb-4 ${playerSymbol === 'x' ? 'neon-text-blue' : 'neon-text-pink'}`; | |
winnerSubtext.textContent = "Congratulations! You defeated the AI."; | |
} else { | |
winnerText.textContent = "AI Wins!"; | |
winnerText.className = "text-3xl font-bold mb-4 text-gray-400"; | |
winnerSubtext.textContent = "The AI has won this round. Try again!"; | |
} | |
} | |
winnerModal.style.display = 'flex'; | |
} | |
function handleDraw() { | |
gameActive = false; | |
// Show draw modal | |
winnerText.textContent = "It's a Draw!"; | |
winnerText.className = "text-3xl font-bold mb-4 text-gray-400"; | |
winnerSubtext.textContent = "No one wins this time. Play again!"; | |
winnerModal.style.display = 'flex'; | |
} | |
function updateScores() { | |
playerXScore.textContent = scores.x; | |
playerOScore.textContent = scores.o; | |
} | |
function resetScores() { | |
scores = { x: 0, o: 0 }; | |
updateScores(); | |
startNewGame(); | |
} | |
function startNewGame() { | |
// Reset board | |
board = ['', '', '', '', '', '', '', '', '']; | |
currentPlayer = 'x'; | |
gameActive = true; | |
// Reset UI | |
cells.forEach(cell => { | |
cell.className = 'cell'; | |
}); | |
updateGameStatus(); | |
winnerModal.style.display = 'none'; | |
// If it's AI's turn in PVC mode and player is O, make AI move | |
if (gameMode === 'pvc' && playerSymbol === 'o') { | |
setTimeout(makeAIMove, 500); // Small delay for better UX | |
} | |
} | |
function makeAIMove() { | |
if (!gameActive) return; | |
let move; | |
switch (aiDifficulty) { | |
case 'easy': | |
move = getRandomMove(); | |
break; | |
case 'medium': | |
move = getMediumAIMove(); | |
break; | |
case 'hard': | |
move = getBestMove(); | |
break; | |
default: | |
move = getRandomMove(); | |
} | |
// Make the move | |
board[move] = currentPlayer; | |
updateCell(cells[move], currentPlayer); | |
// Check for win or draw | |
const gameWon = checkWin(); | |
const gameDraw = checkDraw(); | |
if (gameWon) { | |
handleWin(gameWon); | |
return; | |
} | |
if (gameDraw) { | |
handleDraw(); | |
return; | |
} | |
// Switch player | |
switchPlayer(); | |
} | |
function getRandomMove() { | |
// Get all available cells | |
const availableCells = board.map((cell, index) => cell === '' ? index : null).filter(val => val !== null); | |
// Return a random available cell | |
return availableCells[Math.floor(Math.random() * availableCells.length)]; | |
} | |
function getMediumAIMove() { | |
// First, check if AI can win in the next move | |
for (let i = 0; i < board.length; i++) { | |
if (board[i] === '') { | |
board[i] = currentPlayer; | |
if (checkWin()) { | |
board[i] = ''; // Undo the move | |
return i; | |
} | |
board[i] = ''; // Undo the move | |
} | |
} | |
// Then, check if player can win in the next move and block them | |
const opponent = currentPlayer === 'x' ? 'o' : 'x'; | |
for (let i = 0; i < board.length; i++) { | |
if (board[i] === '') { | |
board[i] = opponent; | |
if (checkWin()) { | |
board[i] = ''; // Undo the move | |
return i; | |
} | |
board[i] = ''; // Undo the move | |
} | |
} | |
// If neither, make a random move | |
return getRandomMove(); | |
} | |
function getBestMove() { | |
// Minimax algorithm implementation | |
function minimax(board, depth, isMaximizing) { | |
const result = checkWin(); | |
if (result) { | |
return result.winner === currentPlayer ? 10 - depth : depth - 10; | |
} | |
if (checkDraw()) { | |
return 0; | |
} | |
if (isMaximizing) { | |
let bestScore = -Infinity; | |
for (let i = 0; i < board.length; i++) { | |
if (board[i] === '') { | |
board[i] = currentPlayer; | |
const score = minimax(board, depth + 1, false); | |
board[i] = ''; | |
bestScore = Math.max(score, bestScore); | |
} | |
} | |
return bestScore; | |
} else { | |
let bestScore = Infinity; | |
const opponent = currentPlayer === 'x' ? 'o' : 'x'; | |
for (let i = 0; i < board.length; i++) { | |
if (board[i] === '') { | |
board[i] = opponent; | |
const score = minimax(board, depth + 1, true); | |
board[i] = ''; | |
bestScore = Math.min(score, bestScore); | |
} | |
} | |
return bestScore; | |
} | |
} | |
let bestScore = -Infinity; | |
let bestMove; | |
for (let i = 0; i < board.length; i++) { | |
if (board[i] === '') { | |
board[i] = currentPlayer; | |
const score = minimax(board, 0, false); | |
board[i] = ''; | |
if (score > bestScore) { | |
bestScore = score; | |
bestMove = i; | |
} | |
} | |
} | |
return bestMove; | |
} | |
}); | |
</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=LMLK/neon-tic-tac-toe" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
</html> |