Thanks to visit codestin.com
Credit goes to www.scribd.com

0% found this document useful (0 votes)
26 views20 pages

Tic-Tac-Toe HTML Code

The document is an HTML template for a futuristic Tic Tac Toe game, featuring a responsive design with various themes and animations. It includes styles for the game board, score display, buttons, and a settings modal, all designed with a neon aesthetic. The layout adapts to different screen sizes, ensuring a user-friendly experience on both desktop and mobile devices.

Uploaded by

aqsky4321
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
26 views20 pages

Tic-Tac-Toe HTML Code

The document is an HTML template for a futuristic Tic Tac Toe game, featuring a responsive design with various themes and animations. It includes styles for the game board, score display, buttons, and a settings modal, all designed with a neon aesthetic. The layout adapts to different screen sizes, ensuring a user-friendly experience on both desktop and mobile devices.

Uploaded by

aqsky4321
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 20

<!

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 */
}

/* Made By Hashir Ali Text */


.made-by-text {
font-family: 'Orbitron', sans-serif;
font-size: 1.8em;
color: #39ff14;
text-shadow: 0 0 15px #39ff14, 0 0 30px #39ff14;
margin-bottom: 30px;
letter-spacing: 2px;
animation: pulseGlow 2s infinite alternate;
text-align: center; /* Ensure text is centered */
padding: 0 10px; /* Add slight horizontal padding */
box-sizing: border-box;
}

/* 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; }

/* Game Container & Board */


.game-container {
perspective: 1000px;
margin-bottom: 20px;
transition: transform 0.5s ease-in-out;
/* Allow board to be slightly larger on wider mobile screens */
max-width: 350px;
width: 90%; /* Responsive width */
display: flex; /* To center the board within it */
justify-content: center;
align-items: center;
}

.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);
}

/* Gear Button for Settings */


.settings-button {
position: fixed; /* Fixed position for mobile apps */
top: 20px;
right: 20px;
background: rgba(0, 255, 255, 0.2);
border: 2px solid #00ffff;
border-radius: 50%;
width: 50px;
height: 50px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
box-shadow: 0 0 15px rgba(0, 255, 255, 0.5);
transition: background 0.3s ease, transform 0.2s ease, box-shadow 0.3s ease;
z-index: 100;
}

.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 */


.settings-modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
}

.settings-modal-overlay.active {
opacity: 1;
visibility: visible;
}

/* Settings Panel (inside modal) */


.settings-panel {
background-color: #1a1a3a;
border-radius: 10px;
padding: 20px;
width: 90%; /* Responsive width */
max-width: 380px; /* Max width to prevent it from becoming too large */
box-shadow: 0 0 30px rgba(0, 255, 255, 0.6), inset 0 0 15px rgba(0, 255, 255, 0.3);
text-align: center;
color: #00ffff;
position: relative;
transform: scale(0.8) translateY(-30px);
opacity: 0;
transition: transform 0.3s ease, opacity 0.3s ease;
box-sizing: border-box;
}

.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;
}

/* Close Button for Modal */


.close-button {
position: absolute;
top: 10px;
right: 10px;
background: none;
border: none;
font-size: 1.8em;
color: #00ffff;
cursor: pointer;
transition: color 0.2s ease, transform 0.2s ease;
text-shadow: 0 0 8px currentColor;
}
.close-button:hover {
color: #fff;
transform: scale(1.1);
text-shadow: 0 0 15px #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;
}

/* Mobile Responsiveness (Adjusted for smaller screens) */


@media (max-width: 600px) {
body {
padding: 5px; /* Even less padding on very small screens */
}

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

<div class="settings-modal-overlay" id="settingsModal">


<div class="settings-panel">
<button class="close-button" id="closeSettingsBtn">&times;</button>
<h3>Game Settings</h3>
<div class="setting-group">
<label for="playerXInput">Player X Name:</label>
<input type="text" id="playerXInput" value="Player X" maxlength="10">
</div>
<div class="setting-group">
<label for="playerOInput">Player O Name:</label>
<input type="text" id="playerOInput" value="Player O" maxlength="10">
</div>
<div class="setting-group">
<label for="gameModeSelect">Game Mode:</label>
<select id="gameModeSelect">
<option value="two-player">Two Player</option>
<option value="ai-easy">Vs. AI (Easy)</option>
<option value="ai-medium">Vs. AI (Medium)</option>
<option value="ai-hard">Vs. AI (Hard)</option>
</select>
</div>
<div class="setting-group">
<label for="themeSelect">Theme:</label>
<select id="themeSelect">
<option value="neon-blue">Neon Blue</option>
<option value="plasma-green">Plasma Green</option>
<option value="crimson-red">Crimson Red</option>
</select>
</div>
<div class="setting-group">
<label>Sound Effects:</label>
<label class="toggle-switch">
<input type="checkbox" id="soundToggle" checked>
<span class="slider"></span>
</label>
</div>
<div class="setting-group">
<label>Background Music:</label>
<label class="toggle-switch">
<input type="checkbox" id="musicToggle" checked>
<span class="slider"></span>
</label>
</div>
</div>
</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');

// New elements for settings modal


const openSettingsBtn = document.getElementById('openSettingsBtn');
const settingsModal = document.getElementById('settingsModal');
const closeSettingsBtn = document.getElementById('closeSettingsBtn');

// 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');

// Set click sound volumes


playerXClickSound.volume = 0.5;
playerOClickSound.volume = 0.5;

// Game State Variables


let currentPlayer = 'X';
let gameActive = true;
let board = ['', '', '', '', '', '', '', '', ''];
let xWins = 0;
let oWins = 0;
let draws = 0;
let playerXName = "Player X";
let playerOName = "Player O";
let gameMode = "two-player"; // "two-player", "ai-easy", "ai-medium", "ai-hard"
let isSoundEnabled = true;
let isMusicEnabled = true;

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

// --- Core Game Logic ---

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

// Apply loaded settings to UI


playerXInput.value = playerXName;
playerOInput.value = playerOName;
playerXNameDisplay.textContent = playerXName;
playerONameDisplay.textContent = playerOName;
gameModeSelect.value = gameMode;
soundToggle.checked = isSoundEnabled;
musicToggle.checked = isMusicEnabled;
themeSelect.value = savedTheme;
applyTheme(savedTheme);

// Set initial volume for the audio element


backgroundMusic.volume = 0.5;

// 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));
}

resetGame(); // Reset board but keep scores for new game


}

function handleCellClick(event) {
const clickedCell = event.target;
const clickedCellIndex = parseInt(clickedCell.getAttribute('data-cell-index'));

if (board[clickedCellIndex] !== '' || !gameActive) {


return;
}

// 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);
}

function makeMove(index, player) {


board[index] = player;
const clickedCell = cells[index];
clickedCell.classList.add(player.toLowerCase());
clickedCell.setAttribute('data-symbol', player);

checkForWinner();
}

function checkForWinner() {
let roundWon = false;
let winningLineCells = [];

for (let i = 0; i < winningConditions.length; i++) {


const winCondition = winningConditions[i];
let a = board[winCondition[0]];
let b = board[winCondition[1]];
let c = board[winCondition[2]];

if (a === '' || b === '' || c === '') {


continue;
}
if (a === b && b === c) {
roundWon = true;
winningLineCells = winCondition;
break;
}
}

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

currentPlayer = currentPlayer === 'X' ? 'O' : 'X';


statusDisplay.textContent = `${currentPlayer === 'X' ? playerXName : playerOName}'s
Turn`;

if (gameActive && currentPlayer === 'O' && gameMode.startsWith('ai-')) {


setTimeout(aiMove, 700);
}
}

// --- Visual Effects ---

function highlightWinningCells(winningCells) {
winningCells.forEach(index => {
const cell = cells[index];
cell.classList.add('win-cell');
});
}

let boardRotationX = 20;


let boardRotationY = -20;
let isDraggingBoard = false;
let lastMouseX, lastMouseY;

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();

const currentX = e.clientX || (e.touches ? e.touches[0].clientX : 0);


const currentY = e.clientY || (e.touches ? e.touches[0].clientY : 0);

const deltaX = currentX - lastMouseX;


const deltaY = currentY - lastMouseY;
boardRotationY += deltaX * 0.5;
boardRotationX -= deltaY * 0.5;

boardRotationX = Math.max(-80, Math.min(80, boardRotationX));

gameBoard.style.transform = `rotateX(${boardRotationX}deg) rotateY($


{boardRotationY}deg)`;

lastMouseX = currentX;
lastMouseY = currentY;
}

function stopBoardDrag() {
isDraggingBoard = false;
gameContainer.style.cursor = 'grab';
gameBoard.style.transition = 'transform 0.5s ease-in-out';
}

// --- Game Controls ---

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)';
});

if (gameActive && currentPlayer === 'O' && gameMode.startsWith('ai-')) {


setTimeout(aiMove, 700);
}
}

// --- AI Logic ---


function getEmptyCells(currentBoard) {
return currentBoard.map((cell, index) => cell === '' ? index : null).filter(index
=> index !== null);
}

function getWinningMove(currentBoard, player) {


for (let i = 0; i < winningConditions.length; i++) {
const [a, b, c] = winningConditions[i];
if (currentBoard[a] === player && currentBoard[b] === player && currentBoard[c] ===
'') return c;
if (currentBoard[a] === player && currentBoard[c] === player && currentBoard[b] ===
'') return b;
if (currentBoard[b] === player && currentBoard[c] === player && currentBoard[a] ===
'') return a;
}
return null;
}

function aiMove() {
let move = null;
const emptyCells = getEmptyCells(board);

if (gameMode === 'ai-easy') {


move = emptyCells[Math.floor(Math.random() * emptyCells.length)];
} else if (gameMode === 'ai-medium') {
// 1. Try to win
move = getWinningMove(board, 'O');
if (move !== null) {
makeMove(move, 'O');
return;
}
// 2. Block player X from winning
move = getWinningMove(board, 'X');
if (move !== null) {
makeMove(move, 'O');
return;
}
// 3. Take center if available
if (board[4] === '') {
move = 4;
} else { // 4. Take a random corner
const corners = [0, 2, 6, 8].filter(index => board[index] === '');
if (corners.length > 0) {
move = corners[Math.floor(Math.random() * corners.length)];
} else { // 5. Take any random empty cell
move = emptyCells[Math.floor(Math.random() * emptyCells.length)];
}
}
} else if (gameMode === 'ai-hard') {
move = findBestMove(board, 'O').index;
}

if (move !== null && board[move] === '') {


if (isSoundEnabled) {
playerOClickSound.currentTime = 0;
playerOClickSound.play();
}
makeMove(move, 'O');
}
}

// 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;
}

function minimax(currentBoard, depth, isMaximizingPlayer) {


let score = evaluate(currentBoard);

if (score === 10) return score - depth;


if (score === -10) return score + depth;

if (getEmptyCells(currentBoard).length === 0) 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;
}
}

function findBestMove(currentBoard, player) {


let bestVal = (player === 'O') ? -Infinity : Infinity;
let bestMove = -1;
const emptyCells = getEmptyCells(currentBoard);

emptyCells.forEach(index => {
currentBoard[index] = player;
let moveVal = minimax(currentBoard, 0, player === 'X');
currentBoard[index] = '';

if (player === 'O' && moveVal > bestVal) {


bestVal = moveVal;
bestMove = index;
} else if (player === 'X' && moveVal < bestVal) {
bestVal = moveVal;
bestMove = index;
}
});
return { index: bestMove, score: bestVal };
}

// --- Settings Handlers ---

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();
}
}

// --- Settings Modal Functions ---


function openSettingsModal() {
settingsModal.classList.add('active');
}

function closeSettingsModal() {
settingsModal.classList.remove('active');
}
// --- Event Listeners ---

// Game board clicks


cells.forEach(cell => cell.addEventListener('click', handleCellClick));

// Game control buttons


playAgainButton.addEventListener('click', playAgain);
resetScoresButton.addEventListener('click', resetAllScores);

// Settings input changes


playerXInput.addEventListener('change', updatePlayerNames);
playerOInput.addEventListener('change', updatePlayerNames);
gameModeSelect.addEventListener('change', updateGameMode);
themeSelect.addEventListener('change', (e) => applyTheme(e.target.value));
soundToggle.addEventListener('change', toggleSound);
musicToggle.addEventListener('change', toggleMusic);

// New event listeners for settings modal


openSettingsBtn.addEventListener('click', openSettingsModal);
closeSettingsBtn.addEventListener('click', closeSettingsBtn);
settingsModal.addEventListener('click', (e) => {
if (e.target === settingsModal) {
closeSettingsModal();
}
});

// Board rotation (Mouse/Touch)


gameContainer.addEventListener('mousedown', startBoardDrag);
gameContainer.addEventListener('mousemove', dragBoard);
gameContainer.addEventListener('mouseup', stopBoardDrag);
gameContainer.addEventListener('mouseleave', stopBoardDrag);

gameContainer.addEventListener('touchstart', startBoardDrag, { passive: false });


gameContainer.addEventListener('touchmove', dragBoard, { passive: false });
gameContainer.addEventListener('touchend', stopBoardDrag);

// Initialize the game when the page loads


document.addEventListener('DOMContentLoaded', initializeGame);
</script>
</body>
</html>

You might also like