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