๐ŸŽฎArcadeLab

Tic-Tac-Toe: vs Computer / Two Players + Auto Restart

by HyperTurtle93
547 lines15.0 KB
โ–ถ Play
<!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.