Tic-Tac-Toe: vs Computer / Two Players + Auto Restart
by HyperTurtle93547 lines15.0 KB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>Tic-Tac-Toe: vs Computer / Two Players + Auto Restart</title>
<style>
* {
box-sizing: border-box;
user-select: none;
}
body {
background: linear-gradient(145deg, #1a2a3a 0%, #0f1a24 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
font-family: 'Segoe UI', 'Poppins', 'Roboto', system-ui, sans-serif;
margin: 0;
padding: 1.5rem;
}
.game-container {
background: rgba(22, 34, 48, 0.65);
backdrop-filter: blur(2px);
border-radius: 3rem;
padding: 1.8rem 1.8rem 2.2rem;
box-shadow: 0 25px 40px rgba(0, 0, 0, 0.5), inset 0 1px 1px rgba(255, 255, 255, 0.1);
border: 1px solid rgba(255, 255, 255, 0.2);
transition: all 0.2s;
}
h1 {
text-align: center;
font-size: 2.5rem;
margin: 0 0 0.3rem 0;
font-weight: 700;
letter-spacing: 2px;
background: linear-gradient(135deg, #FFD966, #FFA559);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
text-shadow: 0 2px 5px rgba(0,0,0,0.2);
}
.mode-buttons {
display: flex;
gap: 1rem;
justify-content: center;
margin: 0.5rem 0 1rem;
}
.mode-btn {
background: #2c3e44;
border: none;
padding: 0.5rem 1.2rem;
border-radius: 2rem;
font-weight: bold;
font-family: inherit;
font-size: 0.9rem;
cursor: pointer;
transition: all 0.2s;
color: #e0e0e0;
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
}
.mode-btn.active {
background: linear-gradient(95deg, #ff9a3c, #ff6a22);
color: #1e2a2f;
box-shadow: 0 0 8px #ffaa55;
}
.mode-btn:active {
transform: scale(0.96);
}
.sub {
text-align: center;
color: #b9d0e6;
font-weight: 500;
margin-bottom: 0.8rem;
font-size: 0.9rem;
border-bottom: 1px dashed rgba(255,215,150,0.4);
display: inline-block;
width: auto;
margin-left: auto;
margin-right: auto;
padding-bottom: 5px;
}
.difficulty-badge {
background: #3a4a5e;
display: inline-block;
margin-top: 6px;
padding: 4px 12px;
border-radius: 40px;
font-size: 0.7rem;
font-weight: bold;
letter-spacing: 1px;
color: #ffe2b5;
box-shadow: inset 0 0 2px #ffd966, 0 1px 2px black;
}
.board {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 14px;
background-color: #1e2f3c;
padding: 20px;
border-radius: 2rem;
box-shadow: inset 0 0 8px #0a1117, 0 20px 30px -8px black;
margin: 1rem 0;
}
.cell {
aspect-ratio: 1 / 1;
background: radial-gradient(circle at 30% 25%, #2c3e44, #1a2a32);
border-radius: 1.2rem;
display: flex;
align-items: center;
justify-content: center;
font-size: 4rem;
font-weight: 800;
font-family: 'Segoe UI', 'Fredoka One', cursive;
color: white;
text-shadow: 0 5px 12px rgba(0,0,0,0.4);
cursor: pointer;
transition: all 0.2s ease;
box-shadow: 0 8px 0 #0f1a1f;
border: 1px solid rgba(255,215,140,0.3);
}
.cell.X-move {
color: #6ee7ff;
text-shadow: 0 0 8px #00a6ff;
}
.cell.O-move {
color: #ffbe76;
text-shadow: 0 0 8px #ff8c42;
}
.cell:active {
transform: translateY(3px);
box-shadow: 0 4px 0 #0f1a1f;
}
.status-area {
background: #0f1c24c9;
border-radius: 3rem;
padding: 0.8rem 1rem;
margin: 1rem 0 1.2rem;
text-align: center;
font-weight: bold;
font-size: 1.3rem;
letter-spacing: 1px;
backdrop-filter: blur(4px);
color: #f5e7d9;
box-shadow: inset 0 1px 3px #2d4a5a, 0 5px 10px rgba(0,0,0,0.2);
border: 1px solid #ffd96660;
}
.restart-btn {
background: linear-gradient(95deg, #ff9a3c, #ff6a22);
border: none;
width: 100%;
padding: 0.9rem;
font-size: 1.2rem;
font-weight: bold;
font-family: inherit;
border-radius: 2rem;
color: #1e2a2f;
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 6px 0 #993d00;
letter-spacing: 1px;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.restart-btn:active {
transform: translateY(3px);
box-shadow: 0 2px 0 #993d00;
}
.restart-btn:hover {
background: linear-gradient(95deg, #ffae54, #ff8230);
color: #0e1a1f;
}
footer {
text-align: center;
font-size: 0.7rem;
margin-top: 1rem;
color: #7c9eb3;
}
.auto-restart-note {
font-size: 0.7rem;
text-align: center;
margin-top: 0.5rem;
background: #1e2f3c80;
border-radius: 20px;
padding: 4px;
color: #ffd699;
}
@media (max-width: 480px) {
.game-container {
padding: 1rem;
}
.cell {
font-size: 2.8rem;
}
.board {
gap: 10px;
padding: 12px;
}
.status-area {
font-size: 1rem;
}
.mode-btn {
padding: 0.4rem 1rem;
font-size: 0.8rem;
}
}
</style>
</head>
<body>
<div class="game-container">
<h1>โ๏ธ TIC-TAC-TOE ใ</h1>
<div class="mode-buttons">
<button id="vsComputerBtn" class="mode-btn active">๐ค VS COMPUTER (Medium)</button>
<button id="twoPlayerBtn" class="mode-btn">๐ฅ TWO PLAYERS</button>
</div>
<div style="text-align: center;">
<span class="sub" id="modeSubtext">โก human vs smart AI โก</span>
<div class="difficulty-badge" id="modeBadge">๐ง BLOCKS & ATTACKS</div>
</div>
<div class="board" id="board"></div>
<div class="status-area" id="statusMsg">Your turn (X) โ click any cell</div>
<button class="restart-btn" id="restartGame">โณ RESTART GAME</button>
<div class="auto-restart-note">๐ Game restarts automatically after 3 seconds when match ends</div>
<footer>๐ฎ Twoโplayer: X starts ยท vs Computer: X = you, O = AI</footer>
</div>
<script>
// DOM elements
const boardContainer = document.getElementById('board');
const statusDiv = document.getElementById('statusMsg');
const restartBtn = document.getElementById('restartGame');
const vsComputerBtn = document.getElementById('vsComputerBtn');
const twoPlayerBtn = document.getElementById('twoPlayerBtn');
const modeSubtext = document.getElementById('modeSubtext');
const modeBadge = document.getElementById('modeBadge');
// Game State
let board = Array(9).fill('');
let gameActive = true;
let currentTurn = 'player'; // 'player' (X) or 'computer' (O) for vsComputer
let gameMode = 'vsComputer'; // 'vsComputer' or 'twoPlayer'
let twoPlayerCurrentSymbol = 'X'; // for two-player mode
let autoRestartTimer = null;
let cellElements = [];
const winPatterns = [
[0,1,2], [3,4,5], [6,7,8],
[0,3,6], [1,4,7], [2,5,8],
[0,4,8], [2,4,6]
];
function getWinner(boardState) {
for (let pattern of winPatterns) {
const [a,b,c] = pattern;
if (boardState[a] && boardState[a] === boardState[b] && boardState[a] === boardState[c]) {
return boardState[a];
}
}
return null;
}
function isBoardFull(boardState) {
return boardState.every(cell => cell !== '');
}
// Medium AI logic
function getMediumAIMove(currentBoard) {
// 1) Win
for (let i = 0; i < 9; i++) {
if (currentBoard[i] === '') {
currentBoard[i] = 'O';
if (getWinner(currentBoard) === 'O') {
currentBoard[i] = '';
return i;
}
currentBoard[i] = '';
}
}
// 2) Block
for (let i = 0; i < 9; i++) {
if (currentBoard[i] === '') {
currentBoard[i] = 'X';
if (getWinner(currentBoard) === 'X') {
currentBoard[i] = '';
return i;
}
currentBoard[i] = '';
}
}
// 3) Center, corners, edges
const center = 4;
const corners = [0, 2, 6, 8];
const edges = [1, 3, 5, 7];
if (currentBoard[center] === '') return center;
const shuffledCorners = [...corners];
for (let i = shuffledCorners.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffledCorners[i], shuffledCorners[j]] = [shuffledCorners[j], shuffledCorners[i]];
}
for (let corner of shuffledCorners) {
if (currentBoard[corner] === '') return corner;
}
const availableEdges = edges.filter(edge => currentBoard[edge] === '');
if (availableEdges.length > 0) {
return availableEdges[Math.floor(Math.random() * availableEdges.length)];
}
for (let i = 0; i < 9; i++) {
if (currentBoard[i] === '') return i;
}
return -1;
}
function updateBoardUI() {
for (let i = 0; i < cellElements.length; i++) {
const cell = cellElements[i];
const mark = board[i];
cell.innerText = mark;
if (mark === 'X') {
cell.classList.add('X-move');
cell.classList.remove('O-move');
} else if (mark === 'O') {
cell.classList.add('O-move');
cell.classList.remove('X-move');
} else {
cell.classList.remove('X-move', 'O-move');
}
}
}
function updateStatusMessage() {
if (!gameActive) {
const winner = getWinner(board);
if (winner === 'X') {
statusDiv.innerHTML = gameMode === 'vsComputer' ? '๐ YOU WIN! ๐ โจ Great! โจ' : '๐ PLAYER X WINS! ๐';
} else if (winner === 'O') {
statusDiv.innerHTML = gameMode === 'vsComputer' ? '๐ค COMPUTER WINS ๐ค' : '๐ PLAYER O WINS! ๐';
} else if (isBoardFull(board)) {
statusDiv.innerHTML = '๐ค DRAW! ๐ค';
} else {
statusDiv.innerHTML = 'โก Game over โก';
}
return;
}
if (gameMode === 'vsComputer') {
if (currentTurn === 'player') {
statusDiv.innerHTML = '๐จ YOUR TURN (X) โ click any empty cell';
} else {
statusDiv.innerHTML = '๐ง MEDIUM AI THINKING (O) ...';
}
} else {
statusDiv.innerHTML = `๐ฅ ${twoPlayerCurrentSymbol}'s turn โ click an empty cell`;
}
}
function updateModeUI() {
if (gameMode === 'vsComputer') {
modeSubtext.innerText = 'โก human vs smart AI โก';
modeBadge.innerText = '๐ง BLOCKS & ATTACKS (Medium)';
vsComputerBtn.classList.add('active');
twoPlayerBtn.classList.remove('active');
} else {
modeSubtext.innerText = '๐ฅ two players: X vs O ๐ฅ';
modeBadge.innerText = '๐ญ local multiplayer';
vsComputerBtn.classList.remove('active');
twoPlayerBtn.classList.add('active');
}
}
function clearAutoRestartTimer() {
if (autoRestartTimer) {
clearTimeout(autoRestartTimer);
autoRestartTimer = null;
}
}
function scheduleAutoRestart() {
clearAutoRestartTimer();
autoRestartTimer = setTimeout(() => {
resetGame();
autoRestartTimer = null;
}, 3000);
}
function checkAndHandleGameOver() {
const winner = getWinner(board);
if (winner !== null) {
gameActive = false;
updateBoardUI();
updateStatusMessage();
scheduleAutoRestart();
return true;
}
if (isBoardFull(board)) {
gameActive = false;
updateBoardUI();
updateStatusMessage();
scheduleAutoRestart();
return true;
}
return false;
}
function applyMove(index, symbol) {
if (!gameActive) return false;
if (board[index] !== '') return false;
if (gameMode === 'vsComputer') {
if (symbol === 'X' && currentTurn !== 'player') return false;
if (symbol === 'O' && currentTurn !== 'computer') return false;
}
board[index] = symbol;
updateBoardUI();
const gameEnded = checkAndHandleGameOver();
if (!gameEnded) {
if (gameMode === 'vsComputer') {
currentTurn = (symbol === 'X') ? 'computer' : 'player';
} else {
twoPlayerCurrentSymbol = (twoPlayerCurrentSymbol === 'X') ? 'O' : 'X';
}
updateStatusMessage();
} else {
updateStatusMessage();
}
return true;
}
function computerMove() {
if (!gameActive) return;
if (gameMode !== 'vsComputer') return;
if (currentTurn !== 'computer') return;
setTimeout(() => {
if (!gameActive || gameMode !== 'vsComputer' || currentTurn !== 'computer') return;
const aiIndex = getMediumAIMove([...board]);
if (aiIndex !== -1 && board[aiIndex] === '') {
applyMove(aiIndex, 'O');
} else {
const emptyIdx = board.findIndex(cell => cell === '');
if (emptyIdx !== -1) applyMove(emptyIdx, 'O');
else checkAndHandleGameOver();
}
}, 50);
}
function handleCellClick(index) {
if (!gameActive) return false;
if (gameMode === 'vsComputer') {
if (currentTurn !== 'player') return false;
if (board[index] !== '') return false;
const success = applyMove(index, 'X');
if (success && gameActive && currentTurn === 'computer') {
computerMove();
}
return success;
} else {
if (board[index] !== '') return false;
return applyMove(index, twoPlayerCurrentSymbol);
}
}
function resetGame() {
clearAutoRestartTimer();
board = Array(9).fill('');
gameActive = true;
if (gameMode === 'vsComputer') {
currentTurn = 'player';
} else {
twoPlayerCurrentSymbol = 'X';
}
updateBoardUI();
updateStatusMessage();
}
function setMode(mode) {
if (mode === gameMode) return;
gameMode = mode;
updateModeUI();
resetGame();
}
function buildBoardUI() {
boardContainer.innerHTML = '';
cellElements = [];
for (let i = 0; i < 9; i++) {
const cell = document.createElement('div');
cell.classList.add('cell');
cell.setAttribute('data-index', i);
cell.innerText = '';
cell.addEventListener('click', (e) => {
e.stopPropagation();
const idx = parseInt(cell.getAttribute('data-index'), 10);
handleCellClick(idx);
});
boardContainer.appendChild(cell);
cellElements.push(cell);
}
}
vsComputerBtn.addEventListener('click', () => setMode('vsComputer'));
twoPlayerBtn.addEventListener('click', () => setMode('twoPlayer'));
restartBtn.addEventListener('click', () => resetGame());
function init() {
buildBoardUI();
setMode('vsComputer');
}
init();
</script>
</body>
</html>Game Source: Tic-Tac-Toe: vs Computer / Two Players + Auto Restart
Creator: HyperTurtle93
Libraries: none
Complexity: complex (547 lines, 15.0 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: tic-tac-toe-vs-computer-two-players-auto-hyperturtle93" to link back to the original. Then publish at arcadelab.ai/publish.