H.html

<!DOCTYPE html>

<html lang="ru">

<head>

  <meta charset="UTF-8">

  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <title>3 в ряд: умный сдвиг клеток</title>

  <style>

    body {

      font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;

      background-color: #f0f2f5;

      display: flex;

      flex-direction: column;

      align-items: center;

      padding: 10px;

      margin: 0;

      user-select: none;

    }


    h1 { margin-bottom: 8px; color: #2c3e50; text-align: center; }


    #stage-info {

      background: #fff;

      border-radius: 8px;

      padding: 8px 16px;

      box-shadow: 0 2px 4px rgba(0,0,0,0.1);

      margin-bottom: 12px;

      font-weight: bold;

      color: #34495e;

    }


    #status {

      margin-bottom: 14px;

      font-size: 17px;

      min-height: 24px;

      text-align: center;

      font-weight: 600;

      color: #2d3436;

    }


    .board-wrapper {

      max-width: 98%;

      width: 100%;

      overflow: hidden;

      box-sizing: border-box;

    }


    .board {

      display: grid;

      gap: 3px;

      background-color: #bdc3c7;

      padding: 3px;

      border-radius: 8px;

      box-sizing: border-box;

      width: 100%;

    }


    .cell {

      width: 100%;

      aspect-ratio: 1 / 1;

      background-color: white;

      font-size: clamp(22px, 5vw, 36px);

      line-height: 1;

      display: flex;

      align-items: center;

      justify-content: center;

      cursor: pointer;

      border: none;

      touch-action: manipulation;

    }

    .cell:hover:not(:disabled) { background-color: #ecf0f1; }

    .cell.x { color: #e74c3c; }

    .cell.o { color: #2c3e50; }


    button {

      padding: 8px 14px;

      border: none;

      border-radius: 6px;

      cursor: pointer;

      font-size: 13px;

    }

    .btn-reset {

      background-color: #c0392b;

      color: white;

      margin-top: 14px;

      width: 100%;

      max-width: 320px;

    }

    .btn-reset:hover { background-color: #a93226; }

  </style>

</head>

<body>


  <h1>3 в ряд: сдвиг по схеме</h1>

  

  <div id="stage-info">

    Этап: <span id="current-stage">Старт</span> | Ходов: <span id="moves-count">0</span>

  </div>


  <div id="status">Нажми клетку, чтобы начать</div>


  <div class="board-wrapper">

    <div class="board" id="board"></div>

  </div>

  

  <button class="btn-reset" id="resetBtn">Начать заново</button>


  <script>

    const statusEl = document.getElementById('status');

    const boardEl = document.getElementById('board');

    const resetBtn = document.getElementById('resetBtn');

    const stageEl = document.getElementById('current-stage');

    const movesEl = document.getElementById('moves-count');


    // Этапы: размер поля и сколько ходов длится этап

    const STAGES = [

      { size: 3, moves: 5 }, // 5 ходов на 3x3

      { size: 5, moves: 3 }, // 3 хода на 5x5

      { size: 7, moves: Infinity } // дальше 7x7 до конца

    ];


    let currentStageIndex = 0;

    let movesMade = 0; // Всего ходов (человек + ИИ)

    let board = [];    // Логическое поле (плоский массив)

    let size = STAGES[0].size;

    let currentPlayer = 'X';

    let gameOver = false;


    function getCurrentStageConfig() {

      return STAGES[currentStageIndex];

    }


    function initGame() {

      currentStageIndex = 0;

      movesMade = 0;

      const startSize = STAGES[0].size;

      size = startSize;

      board = Array(size * size).fill('');

      currentPlayer = 'X';

      gameOver = false;

      updateStageDisplay();

      renderBoard();

      statusEl.textContent = `Твой ход: ${currentPlayer}`;

    }


    function updateStageDisplay() {

      const config = getCurrentStageConfig();

      stageEl.textContent = `${config.size}×${config.size}`;

      movesEl.textContent = movesMade;

    }


    // ГЛАВНОЕ: умная перестановка клеток по твоей схеме

    function shiftBoard(oldSize, newSize) {

      const newBoard = Array(newSize * newSize).fill('');


      // Твоя логика: 1→7, 2→8, 3→9, 4→12, и т.д.

      // Реализуем как: индекс_новый = (индекс_старый) + (смещение)

      // Но чтобы работало универсально, используем простую формулу:

      // Мы хотим, чтобы старые клетки «уехали» вниз-вправо, а не просто скопировались в угол.

      

      const rowShift = Math.floor((newSize - oldSize) / 2); // сдвиг по строкам

      const colShift = Math.floor((newSize - oldSize) / 2); // сдвиг по столбцам


      for (let r = 0; r < oldSize; r++) {

        for (let c = 0; c < oldSize; c++) {

          const oldIndex = r * oldSize + c;

          const newR = r + rowShift;

          const newC = c + colShift;

          const newIndex = newR * newSize + newC;


          if (newR < newSize && newC < newSize) {

            newBoard[newIndex] = board[oldIndex];

          }

        }

      }

      return newBoard;

    }


    function checkStageProgress() {

      const config = getCurrentStageConfig();


      let movesNeededToReachNext = 0;

      for (let i = 0; i <= currentStageIndex; i++) {

        movesNeededToReachNext += STAGES[i].moves;

      }


      if (movesMade >= movesNeededToReachNext && currentStageIndex < STAGES.length - 1) {

        // Переход на следующий этап

        currentStageIndex++;

        const nextConfig = getCurrentStageConfig();

        const oldSize = size;

        size = nextConfig.size;


        board = shiftBoard(oldSize, size); // Применяем твою схему сдвига


        updateStageDisplay();

        renderBoard();

        statusEl.textContent = `Переход на ${size}×${size}! Ход игрока ${currentPlayer}`;

      }

    }


    function renderBoard() {

      boardEl.innerHTML = '';

      boardEl.style.gridTemplateColumns = `repeat(${size}, 1fr)`;

      boardEl.style.gridTemplateRows = `repeat(${size}, 1fr)`;


      board.forEach((val, index) => {

        const cell = document.createElement('button');

        cell.className = `cell ${val ? val.toLowerCase() : ''}`;

        cell.textContent = val;

        cell.disabled = gameOver || val !== '';


        if (!gameOver && val === '') {

          cell.addEventListener('click', () => humanMove(index), { once: true });

        }

        boardEl.appendChild(cell);

      });

    }


    function humanMove(index) {

      if (gameOver || board[index] !== '') return;


      board[index] = currentPlayer;

      movesMade++;

      checkStageProgress(); // Сначала проверяем этап (может поле вырасти)

      renderBoard();       // Потом перерисовываем


      const result = checkWin();

      if (result) {

        endGame(result);

        return;

      }


      currentPlayer = 'O';

      statusEl.textContent = `Ход ИИ...`;

      setTimeout(aiMove, 400);

    }


    function aiMove() {

      if (gameOver) return;


      const emptyCells = [];

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

        if (board[i] === '') emptyCells.push(i);

      }

      if (emptyCells.length === 0) return;


      // 1. Попытка выиграть

      for (const idx of emptyCells) {

        board[idx] = 'O';

        if (checkWin() === 'O') {

          renderBoard();

          endGame('O');

          return;

        }

        board[idx] = '';

      }


      // 2. Блокировка

      for (const idx of emptyCells) {

        board[idx] = 'X';

        if (checkWin() === 'X') {

          board[idx] = 'O';

          renderBoard();

          const res = checkWin();

          if (res) { endGame(res); return; }

          currentPlayer = 'X';

          statusEl.textContent = `Твой ход: X`;

          return;

        }

        board[idx] = '';

      }


      // 3. Случайный ход

      const randomIndex = emptyCells[Math.floor(Math.random() * emptyCells.length)];

      board[randomIndex] = 'O';


      movesMade++; // ИИ тоже делает ход

      checkStageProgress();

      renderBoard();


      const result = checkWin();

      if (result) {

        endGame(result);

      } else {

        currentPlayer = 'X';

        statusEl.textContent = `Твой ход: X`;

      }

    }


    function checkWin() {

      const len = 3; // Всегда 3 в ряд


      for (let r = 0; r < size; r++) {

        for (let c = 0; c <= size - len; c++) {

          const start = r * size + c;

          if (board[start] && checkLine(start, 1, len)) return board[start];

        }

      }

      for (let c = 0; c < size; c++) {

        for (let r = 0; r <= size - len; r++) {

          const start = r * size + c;

          if (board[start] && checkLine(start, size, len)) return board[start];

        }

      }

      for (let r = 0; r <= size - len; r++) {

        for (let c = 0; c <= size - len; c++) {

          const start = r * size + c;

          if (board[start] && checkLine(start, size + 1, len)) return board[start];

        }

      }

      for (let r = 0; r <= size - len; r++) {

        for (let c = len - 1; c < size; c++) {

          const start = r * size + c;

          if (board[start] && checkLine(start, size - 1, len)) return board[start];

        }

      }


      if (!board.includes('')) return 'draw';

      return null;

    }


    function checkLine(start, step, len) {

      const symbol = board[start];

      for (let i = 1; i < len; i++) {

        if (board[start + step * i] !== symbol) return false;

        if (start + step * i >= board.length) return false;

      }

      return true;

    }


    function endGame(result) {

      gameOver = true;

      if (result === 'draw') {

        statusEl.textContent = 'Ничья!';

        statusEl.style.color = '#7f8c8d';

      } else {

        statusEl.textContent = `Победил ${result}!`;

        statusEl.style.color = result === 'X' ? '#e74c3c' : '#2c3e50';

      }

    }


    function resetGame() {

      initGame();

    }


    resetBtn.addEventListener('click', resetGame);

    initGame();

  </script>

</body>

</html>