Tic-Tac-Toe HTML Code
Tic-Tac-Toe HTML Code
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Futuristic Tic Tac Toe</title>
<link href="https://fonts.googleapis.com/css2?
family=Orbitron:wght@400;700&display=swap" rel="stylesheet">
<style>
/* Base Styles */
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
/* Use min-height: 100vh; for full viewport height */
min-height: 100vh;
margin: 0;
background: linear-gradient(135deg, #0a0a2a, #2a0a2a, #0a2a2a);
color: #00ffff; /* Futuristic light blue */
overflow: hidden; /* Prevent scrolling */
transition: background 0.5s ease; /* For theme changes */
padding: 10px; /* Reduced padding for smaller screens, will be adjusted by media
query */
box-sizing: border-box;
}
/* Theming */
body.theme-neon-blue {
background: linear-gradient(135deg, #0a0a2a, #001f3f, #0a2a2a);
color: #00ffff;
}
body.theme-plasma-green {
background: linear-gradient(135deg, #0a2a0a, #1f3f00, #2a2a0a);
color: #39ff14; /* Neon green */
}
body.theme-crimson-red {
background: linear-gradient(135deg, #2a0a0a, #3f001f, #2a2a0a);
color: #ff0033; /* Crimson red */
}
/* Score Board */
.score-board {
display: flex;
justify-content: space-around;
width: 90%; /* Use percentage for flexibility */
max-width: 380px; /* Max width to prevent it from becoming too large */
margin-bottom: 25px;
background-color: rgba(0, 255, 255, 0.15);
border-radius: 10px;
padding: 15px 10px;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.4), inset 0 0 10px rgba(0, 255, 255, 0.2);
text-align: center;
transition: background-color 0.3s ease, box-shadow 0.3s ease;
box-sizing: border-box;
}
.score-item {
flex: 1;
padding: 5px;
font-size: 1.1em;
font-weight: bold;
color: inherit;
text-shadow: 0 0 8px currentColor;
}
.score-item span {
display: block;
font-size: 1.8em;
margin-top: 5px;
color: #fff;
text-shadow: 0 0 10px #fff, 0 0 20px #fff;
animation: scoreUpdate 0.5s ease-out;
}
.score-item.player-x span { color: #ff00ff; text-shadow: 0 0 10px #ff00ff, 0 0 20px
#ff00ff; }
.score-item.player-o span { color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px
#00ff00; }
.board {
display: grid;
/* Use a flexible unit based on viewport width for cell size */
grid-template-columns: repeat(3, 11vw); /* Roughly 1/3 of viewport width */
grid-template-rows: repeat(3, 11vw);
gap: 10px;
background-color: rgba(0, 255, 255, 0.1);
border-radius: 15px;
padding: 15px;
box-shadow: 0 0 30px rgba(0, 255, 255, 0.5), inset 0 0 15px rgba(0, 255, 255, 0.3);
transform-style: preserve-3d;
transform: rotateX(20deg) rotateY(-20deg);
transition: background-color 0.3s ease, box-shadow 0.3s ease, transform 0.5s ease-
in-out;
position: relative;
box-sizing: border-box; /* Include padding in total dimensions */
}
/* Cells */
.cell {
/* Inherit width/height from grid, which uses vw */
width: 100%;
height: 100%;
background-color: rgba(0, 100, 100, 0.5);
border: 2px solid #00ffff;
border-radius: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 3em; /* Will be adjusted by media query for smaller screens */
font-weight: bold;
cursor: pointer;
transition: background-color 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease,
border-color 0.3s ease;
box-shadow: inset 0 0 10px rgba(0, 255, 255, 0.2);
position: relative;
overflow: hidden;
box-sizing: border-box;
}
.cell:hover:not(.x):not(.o) {
background-color: rgba(0, 150, 150, 0.7);
transform: translateY(-5px) scale(1.02);
box-shadow: inset 0 0 15px rgba(0, 255, 255, 0.5), 0 5px 15px rgba(0, 255, 255,
0.3);
border-color: #ffffff;
}
.cell.x, .cell.o {
pointer-events: none;
}
.cell.x::before, .cell.o::before {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotateY(0deg);
animation: symbolEntry 0.5s ease forwards;
text-shadow: 0 0 10px currentColor, 0 0 20px currentColor;
}
.cell.x::before {
content: 'X';
color: #ff00ff;
}
.cell.o::before {
content: 'O';
color: #00ff00;
}
/* Winning Cell Animation */
.cell.win-cell {
animation: winCellGlow 1s infinite alternate;
}
/* Status Display */
#status {
font-size: 1.5em;
margin-bottom: 20px;
color: #00ffff;
text-shadow: 0 0 10px #00ffff;
transition: color 0.3s ease, text-shadow 0.3s ease;
text-align: center;
padding: 0 10px; /* Prevent text overflow */
box-sizing: border-box;
}
/* Buttons */
.button-group {
display: flex;
gap: 15px;
margin-top: 20px;
flex-wrap: wrap; /* Allow buttons to wrap on smaller screens */
justify-content: center; /* Center buttons when wrapped */
width: 90%; /* Responsive width */
max-width: 400px;
}
button {
padding: 15px 30px;
font-size: 1.2em;
background: linear-gradient(45deg, #00ffff, #008888);
color: #0a0a2a;
border: none;
border-radius: 8px;
cursor: pointer;
box-shadow: 0 0 20px rgba(0, 255, 255, 0.7);
transition: background 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease;
font-weight: bold;
flex: 1 1 auto; /* Allow buttons to grow/shrink, take full width if needed */
min-width: 140px; /* Minimum width to prevent them from becoming too small */
}
button:hover {
background: linear-gradient(45deg, #00e0e0, #00aaaa);
transform: translateY(-3px);
box-shadow: 0 0 30px rgba(0, 255, 255, 1);
}
.settings-button:hover {
background: rgba(0, 255, 255, 0.4);
transform: rotate(15deg) scale(1.05);
box-shadow: 0 0 25px rgba(0, 255, 255, 0.8);
}
.settings-button svg {
fill: #00ffff;
width: 30px;
height: 30px;
filter: drop-shadow(0 0 5px #00ffff);
transition: fill 0.3s ease, filter 0.3s ease;
}
.settings-button:hover svg {
fill: #ffffff;
filter: drop-shadow(0 0 10px #ffffff);
}
.settings-modal-overlay.active {
opacity: 1;
visibility: visible;
}
.settings-modal-overlay.active .settings-panel {
transform: scale(1) translateY(0);
opacity: 1;
}
.settings-panel h3 {
margin-top: 0;
color: inherit;
text-shadow: 0 0 8px currentColor;
}
.setting-group {
margin-bottom: 15px;
display: flex;
flex-direction: column;
align-items: center;
}
.setting-group label {
margin-bottom: 5px;
font-weight: bold;
text-shadow: 0 0 5px currentColor;
}
.setting-group input[type="text"],
.setting-group select {
width: 90%; /* More responsive width */
padding: 8px;
border: 1px solid #00ffff;
border-radius: 5px;
background-color: rgba(0, 50, 50, 0.7);
color: #fff;
font-size: 1em;
text-align: center;
box-shadow: inset 0 0 5px rgba(0, 255, 255, 0.3);
transition: border-color 0.3s ease, box-shadow 0.3s ease;
box-sizing: border-box;
}
.setting-group input[type="text"]:focus,
.setting-group select:focus {
outline: none;
border-color: #fff;
box-shadow: inset 0 0 8px rgba(0, 255, 255, 0.5), 0 0 10px rgba(0, 255, 255, 0.3);
}
.setting-group select option {
background-color: #0a0a2a;
color: #fff;
}
.toggle-switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
margin-top: 5px;
}
.toggle-switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: .4s;
border-radius: 34px;
box-shadow: inset 0 0 5px rgba(0,0,0,0.3);
}
.slider:before {
position: absolute;
content: "";
height: 26px;
width: 26px;
left: 4px;
bottom: 4px;
background-color: white;
transition: .4s;
border-radius: 50%;
box-shadow: 0 0 5px rgba(0,0,0,0.5);
}
input:checked + .slider {
background-color: #2196F3;
box-shadow: inset 0 0 5px rgba(0, 255, 255, 0.5);
}
input:checked + .slider:before {
transform: translateX(26px);
background-color: #00ffff;
}
.made-by-text {
font-size: 1.2em; /* Smaller font for "Made By" */
margin-bottom: 20px;
letter-spacing: 1px;
}
.score-board {
width: 95%; /* Wider on small screens */
padding: 10px 5px; /* Adjust padding */
margin-bottom: 15px;
}
.score-item {
font-size: 0.9em; /* Smaller score item text */
}
.score-item span {
font-size: 1.4em; /* Smaller score numbers */
}
.board {
/* Make cells slightly smaller to fit well on most phones */
grid-template-columns: repeat(3, 10vw); /* Adjusted cell size based on viewport
width */
grid-template-rows: repeat(3, 10vw);
gap: 5px; /* Smaller gap */
padding: 8px; /* Smaller padding */
}
.cell {
font-size: 2.2em; /* Smaller symbol size */
}
#status {
font-size: 1.1em; /* Smaller status font */
margin-bottom: 15px;
}
.button-group {
flex-direction: column; /* Stack buttons vertically */
gap: 8px; /* Smaller gap between stacked buttons */
width: 95%; /* Make buttons take more width */
}
button {
padding: 10px 20px; /* Smaller button padding */
font-size: 1em; /* Smaller button font */
min-width: unset; /* Remove min-width for full flexibility */
}
.settings-button {
width: 45px; /* Slightly smaller settings button */
height: 45px;
top: 10px; /* Closer to top */
right: 10px; /* Closer to right */
}
.settings-button svg {
width: 25px;
height: 25px;
}
.settings-panel {
padding: 15px; /* Smaller padding inside modal */
width: 95%; /* Wider modal for small screens */
}
.setting-group input[type="text"],
.setting-group select {
width: 95%; /* Fuller width for inputs in modal */
font-size: 0.9em;
}
}
/* Further optimization for extremely small screens (e.g., iPhone SE 1st Gen) */
@media (max-width: 375px) {
.made-by-text {
font-size: 1em;
}
.score-item {
font-size: 0.8em;
}
.score-item span {
font-size: 1.2em;
}
.board {
grid-template-columns: repeat(3, 10vw);
grid-template-rows: repeat(3, 10vw);
gap: 4px;
padding: 6px;
}
.cell {
font-size: 2em;
}
#status {
font-size: 1em;
}
button {
font-size: 0.9em;
padding: 8px 15px;
}
}
/* Keyframe Animations */
@keyframes symbolEntry {
0% {
opacity: 0;
transform: translate(-50%, -50%) rotateY(90deg) scale(0.5);
}
100% {
opacity: 1;
transform: translate(-50%, -50%) rotateY(0deg) scale(1);
}
}
@keyframes winCellGlow {
0% { box-shadow: inset 0 0 20px rgba(255, 255, 0, 0.8), 0 0 30px rgba(255, 255, 0,
0.7); }
50% { box-shadow: inset 0 0 30px rgba(255, 255, 0, 1), 0 0 50px rgba(255, 255, 0,
1); }
100% { box-shadow: inset 0 0 20px rgba(255, 255, 0, 0.8), 0 0 30px rgba(255, 255,
0, 0.7); }
}
@keyframes scoreUpdate {
0% { transform: scale(1); opacity: 0.7; }
50% { transform: scale(1.1); opacity: 1; }
100% { transform: scale(1); opacity: 1; }
}
@keyframes pulseGlow {
0% { text-shadow: 0 0 15px #39ff14, 0 0 30px #39ff14, 0 0 45px rgba(57, 255, 20,
0.5); }
100% { text-shadow: 0 0 20px #39ff14, 0 0 40px #39ff14, 0 0 60px rgba(57, 255, 20,
0.8); }
}
</style>
</head>
<body>
<div class="made-by-text">Made By Hashir Ali</div>
<div class="score-board">
<div class="score-item player-x"><span id="playerXName">X</span> Wins: <span
id="xScore">0</span></div>
<div class="score-item">Draws: <span id="drawScore">0</span></div>
<div class="score-item player-o"><span id="playerOName">O</span> Wins: <span
id="oScore">0</span></div>
</div>
<div id="status">Player X's Turn</div>
<div class="game-container" id="gameContainer">
<div class="board" id="gameBoard">
<div class="cell" data-cell-index="0"></div>
<div class="cell" data-cell-index="1"></div>
<div class="cell" data-cell-index="2"></div>
<div class="cell" data-cell-index="3"></div>
<div class="cell" data-cell-index="4"></div>
<div class="cell" data-cell-index="5"></div>
<div class="cell" data-cell-index="6"></div>
<div class="cell" data-cell-index="7"></div>
<div class="cell" data-cell-index="8"></div>
</div>
</div>
<div class="button-group">
<button id="playAgainButton">Play Again</button>
<button id="resetScoresButton">Reset Scores</button>
</div>
<div class="settings-button" id="openSettingsBtn">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12 20c-1.1 0-2.0625-.84531-2.20312-1.95625l-.26563-
1.6375c-.48437-.21562-.9375-.49062-1.34062-.81562l-1.52812.825c-1.025.55312-
2.3125.16562-2.86562-.8625l-1.125-1.947c-.55312-1.025-.16562-2.3125.8625-
2.86562l1.52812-.825c.00312-.00312.00625-.00625.00937-.00937c-.00312-.00312-.00625-
.00625-.00937-.00937l-1.52812-.825c-1.025-.55312-1.4125-1.84062-.8625-
2.86562l1.125-1.947c.55312-1.025 1.84062-1.4125
2.86562-.8625l1.52812.825c.40312-.325.85625-.6 1.34062-.81562l.26563-1.6375c.14062-
1.11094 1.09375-1.95625 2.20312-1.95625h2.25c1.11094 0 2.0625.84531 2.20312
1.95625l.26563 1.6375c.48437.21562.9375.49062
1.34062.81562l1.52812-.825c1.025-.55312 2.3125-.16562 2.86562.8625l1.125
1.947c.55312 1.025.16562 2.3125-.8625 2.86562l-
1.52812.825c-.00312.00312-.00625.00625-.00937.00937c.00312.00312.00625.00625.00937.
00937.00937l1.52812.825c1.025.55312 1.4125 1.84062.8625 2.86562l-1.125
1.947c-.55312 1.025-1.84062 1.4125-2.86562.8625l-1.52812-.825c-.40312.325-.85625.6-
1.34062.81562l-.26563 1.6375c-.14062 1.11094-1.09375 1.95625-2.20312 1.95625h-
2.25zM12 15.5c1.9375 0 3.5-1.5625 3.5-3.5s-1.5625-3.5-3.5-3.5c-1.9375 0-3.5 1.5625-
3.5 3.5s1.5625 3.5 3.5 3.5z"/>
</svg>
</div>
<audio id="playerXClickSound"
src="https://s51.aconvert.com/convert/p3r68-cdx67/xu12k-tyscn.mp3"
preload="auto"></audio>
<audio id="playerOClickSound" src="https://s33.aconvert.com/convert/p3r68-
cdx67/4yh35-tqz9w.mp3" preload="auto"></audio>
<audio id="winSound" src="https://s33.aconvert.com/convert/p3r68-cdx67/qdwmb-
vba8i.mp3" preload="auto"></audio> <audio id="drawSound"
src="https://assets.mixkit.co/sfx/preview/mixkit-arcade-retro-game-over-213.mp3"
preload="auto"></audio>
<audio id="backgroundMusic" src="https://s17.aconvert.com/convert/p3r68-
cdx67/6sduw-2321e.mp3" loop preload="auto"></audio>
<script>
// DOM Elements
const gameBoard = document.getElementById('gameBoard');
const cells = document.querySelectorAll('.cell');
const statusDisplay = document.getElementById('status');
const gameContainer = document.getElementById('gameContainer');
// Buttons
const playAgainButton = document.getElementById('playAgainButton');
const resetScoresButton = document.getElementById('resetScoresButton');
// Scoreboard elements
const playerXNameDisplay = document.getElementById('playerXName');
const playerONameDisplay = document.getElementById('playerOName');
const xScoreDisplay = document.getElementById('xScore');
const oScoreDisplay = document.getElementById('oScore');
const drawScoreDisplay = document.getElementById('drawScore');
// Settings elements
const playerXInput = document.getElementById('playerXInput');
const playerOInput = document.getElementById('playerOInput');
const gameModeSelect = document.getElementById('gameModeSelect');
const themeSelect = document.getElementById('themeSelect');
const soundToggle = document.getElementById('soundToggle');
const musicToggle = document.getElementById('musicToggle');
// Audio elements
const playerXClickSound = document.getElementById('playerXClickSound');
const playerOClickSound = document.getElementById('playerOClickSound');
const winSound = document.getElementById('winSound'); // Now refers to your new
sound
const drawSound = document.getElementById('drawSound');
const backgroundMusic = document.getElementById('backgroundMusic');
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
];
function initializeGame() {
// Load settings from localStorage or set defaults
playerXName = localStorage.getItem('playerXName') || "Player X";
playerOName = localStorage.getItem('playerOName') || "Player O";
gameMode = localStorage.getItem('gameMode') || "two-player";
isSoundEnabled = localStorage.getItem('isSoundEnabled') === 'true'; // localStorage
stores as string
isMusicEnabled = localStorage.getItem('isMusicEnabled') === 'true';
const savedTheme = localStorage.getItem('theme') || 'neon-blue';
// Attempt to play music only if enabled and it's the first load
if (isMusicEnabled) {
backgroundMusic.play().catch(e => console.log("Music autoplay blocked:", e));
}
function handleCellClick(event) {
const clickedCell = event.target;
const clickedCellIndex = parseInt(clickedCell.getAttribute('data-cell-index'));
// Play the appropriate click sound effect based on the current player
if (isSoundEnabled) {
if (currentPlayer === 'X') {
playerXClickSound.currentTime = 0;
playerXClickSound.play();
} else { // Current player is 'O'
playerOClickSound.currentTime = 0;
playerOClickSound.play();
}
}
makeMove(clickedCellIndex, currentPlayer);
}
checkForWinner();
}
function checkForWinner() {
let roundWon = false;
let winningLineCells = [];
if (roundWon) {
statusDisplay.textContent = `${currentPlayer === 'X' ? playerXName : playerOName}
Wins!`;
gameActive = false;
if (currentPlayer === 'X') {
xWins++;
xScoreDisplay.textContent = xWins;
} else {
oWins++;
oScoreDisplay.textContent = oWins;
}
highlightWinningCells(winningLineCells);
if (isSoundEnabled) winSound.play(); // Play the new win sound
return;
}
if (!board.includes('')) {
statusDisplay.textContent = 'It\'s a Draw!';
gameActive = false;
draws++;
drawScoreDisplay.textContent = draws;
if (isSoundEnabled) drawSound.play();
return;
}
function highlightWinningCells(winningCells) {
winningCells.forEach(index => {
const cell = cells[index];
cell.classList.add('win-cell');
});
}
function startBoardDrag(e) {
isDraggingBoard = true;
lastMouseX = e.clientX || (e.touches ? e.touches[0].clientX : 0);
lastMouseY = e.clientY || (e.touches ? e.touches[0].clientY : 0);
gameContainer.style.cursor = 'grabbing';
gameBoard.style.transition = 'none';
}
function dragBoard(e) {
if (!isDraggingBoard) return;
e.preventDefault();
lastMouseX = currentX;
lastMouseY = currentY;
}
function stopBoardDrag() {
isDraggingBoard = false;
gameContainer.style.cursor = 'grab';
gameBoard.style.transition = 'transform 0.5s ease-in-out';
}
function playAgain() {
resetGame();
}
function resetAllScores() {
xWins = 0;
oWins = 0;
draws = 0;
xScoreDisplay.textContent = xWins;
oScoreDisplay.textContent = oWins;
drawScoreDisplay.textContent = draws;
resetGame();
}
function resetGame() {
board = ['', '', '', '', '', '', '', '', ''];
gameActive = true;
currentPlayer = 'X';
statusDisplay.textContent = `${currentPlayer === 'X' ? playerXName : playerOName}'s
Turn`;
cells.forEach(cell => {
cell.textContent = '';
cell.classList.remove('x', 'o', 'win-cell');
cell.removeAttribute('data-symbol');
cell.style.backgroundColor = 'rgba(0, 100, 100, 0.5)';
cell.style.boxShadow = 'inset 0 0 10px rgba(0, 255, 255, 0.2)';
});
function aiMove() {
let move = null;
const emptyCells = getEmptyCells(board);
// Minimax Algorithm
function evaluate(currentBoard) {
for (let i = 0; i < winningConditions.length; i++) {
const [a, b, c] = winningConditions[i];
if (currentBoard[a] === currentBoard[b] && currentBoard[b] === currentBoard[c]) {
if (currentBoard[a] === 'O') return 10;
else if (currentBoard[a] === 'X') return -10;
}
}
return 0;
}
if (isMaximizingPlayer) {
let best = -Infinity;
getEmptyCells(currentBoard).forEach(index => {
currentBoard[index] = 'O';
best = Math.max(best, minimax(currentBoard, depth + 1, false));
currentBoard[index] = '';
});
return best;
} else {
let best = Infinity;
getEmptyCells(currentBoard).forEach(index => {
currentBoard[index] = 'X';
best = Math.min(best, minimax(currentBoard, depth + 1, true));
currentBoard[index] = '';
});
return best;
}
}
emptyCells.forEach(index => {
currentBoard[index] = player;
let moveVal = minimax(currentBoard, 0, player === 'X');
currentBoard[index] = '';
function updatePlayerNames() {
playerXName = playerXInput.value.trim() || "Player X";
playerOName = playerOInput.value.trim() || "Player O";
playerXNameDisplay.textContent = playerXName;
playerONameDisplay.textContent = playerOName;
localStorage.setItem('playerXName', playerXName);
localStorage.setItem('playerOName', playerOName);
statusDisplay.textContent = `${currentPlayer === 'X' ? playerXName : playerOName}'s
Turn`;
}
function updateGameMode() {
gameMode = gameModeSelect.value;
localStorage.setItem('gameMode', gameMode);
if (gameActive && currentPlayer === 'O' && gameMode.startsWith('ai-')) {
setTimeout(aiMove, 700);
}
resetGame();
}
function applyTheme(theme) {
document.body.className = '';
document.body.classList.add(`theme-${theme}`);
localStorage.setItem('theme', theme);
}
function toggleSound() {
isSoundEnabled = soundToggle.checked;
localStorage.setItem('isSoundEnabled', isSoundEnabled);
}
function toggleMusic() {
isMusicEnabled = musicToggle.checked;
localStorage.setItem('isMusicEnabled', isMusicEnabled);
if (isMusicEnabled) {
backgroundMusic.volume = 0.5;
backgroundMusic.play().catch(e => console.log("Music play blocked by browser:",
e));
} else {
backgroundMusic.pause();
}
}
function closeSettingsModal() {
settingsModal.classList.remove('active');
}
// --- Event Listeners ---