neon-tic-tac-toe / index.html
LMLK's picture
Add 3 files
12998d4 verified
<!DOCTYPE html>
<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">&times;</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>