// © Головин Г.Г., Логика игрового процесса, 2023 'use strict'; // игровое поле и его размеры let field, rows = 20, columns = 10; // ускорение падения фигур const REDUCTION_STEP = 25; const REDUCTION_SNAIL = 5; let reduction = REDUCTION_STEP; // скорость падения фигур const START_DELAY = 600; const MIN_DELAY = 80; let stepDelay = START_DELAY; let sleepTimeout, rapidFall = false; // уровень, следующий уровень, счёт let level, nextLevel, score; // массив фигур тетрамино const figures = FIGURE.set(); // текущая фигура, следующая фигура let currentFigure, nextFigure; // статусы игры const GAME = {RUN:0,LEVEL:1,PAUSE:2,OVER:3}; // текущий статус let status = GAME.PAUSE; // подготовить новую игру function prepareNewGame() { field = []; for (let i = 0; i < rows; i++) { field[i] = []; for (let j = 0; j < columns; j++) field[i][j] = 0; } status = GAME.PAUSE; level = 0; score = 0; nextLevel = (10 * columns) * (level + 1); stepDelay = START_DELAY; startFigureFall(); repaint(); } // начало падения фигуры function startFigureFall() { currentFigure = nextFigure; const x = Math.floor(columns/2+columns%2-1); if (isFreeSpace(0, x)) { doPlaceFigure(0, x); repaint(); } else { status=GAME.OVER; for (let y=-1; y>-4; y--) if (isFreeSpace(y, x, false)) { doPlaceFigure(y, x); break; } repaint(); return; } const rnd = Math.floor(Math.random() * figures.length); nextFigure = figures[rnd].clone(); if (status==GAME.LEVEL) return; setTimeout(figureFall, 60); } // падение фигуры function figureFall() { if (rapidFall) stepDown(); else sleepTimeout = setTimeout(stepDown, stepDelay); } // шаг вниз function stepDown() { sleepTimeout = undefined; if (status==GAME.PAUSE) return; if (isSpaceDown()) { doStepDown(); setTimeout(figureFall, 60); } else { mergeFigure(currentFigure.type); let fullRows = 0; for (let i = 0; i < rows; i++) if (isFullRow(i)) { for (let c = 3; c >= 0; c--) setTimeout(blinkFullRow, 400 * fullRows + 100 * c, i, c); setTimeout(slideDown, 400 * fullRows + 400, i); fullRows++; } score += columns * [0,1,3,5,10][fullRows]; if (score >= nextLevel) { level++; status = GAME.LEVEL; nextLevel = (10 * columns) * (level + 1); stepDelay = Math.max(MIN_DELAY,stepDelay-reduction); } startFigureFall(); } } // свободное место ниже текущей фигуры function isSpaceDown() { for (let y = rows-2; y >= 0; y--) for (let x = 0; x < columns; x++) if (field[y][x]==11 && field[y+1][x]>0 && field[y+1][x]<11 || field[y+1][x]==11 && y==rows-2) return false; return true; } // сдвинуть текущую фигуру вниз function doStepDown() { for (let y = rows-2; y >= 0; y--) for (let x = 0; x < columns; x++) if (field[y][x]==11) { field[y][x]=0; field[y+1][x]=11; } repaint(); } // завершение движения текущей фигуры function mergeFigure(type) { for (let x = 0; x < columns; x++) for (let y = 0; y < rows; y++) if (field[y][x] == 11) field[y][x] = type; } // заполненная строка function isFullRow(row) { for (let x = 0; x < columns; x++) if (field[row][x] == 0) return false; return true; } // моргание заполненной строки function blinkFullRow(row, color) { for (let x = 0; x < columns; x++) field[row][x] = color; repaint(); } // сдвинуть поле вниз function slideDown(row) { for (let y = row-1; y >= 0; y--) for (let x = 0; x < columns; x++) if (field[y+1][x]!=11 && field[y][x]!=11) field[y+1][x]=field[y][x]; repaint(); } // свободное место для текущей фигуры в пределах границ поля function isFreeSpace(y, x, fullSize=true) { const height = currentFigure.shape.length; const wight = currentFigure.shape[0].length; if (fullSize && (y<0 || y+height>rows || x<0 || x+wight>columns)) return false; for (let yy=0; yy0) if (y+yy>=0 && y+yy=0 && x+xx0) if (y+yy>=0 && y+yy=0 && x+xx0 && field[y][x-1]<11 || field[y][x-1]==11 && x==1) return false; return true; } // сдвинуть текущую фигуру влево function doStepLeft() { for (let x = 1; x < columns; x++) for (let y = 0; y < rows; y++) if (field[y][x]==11) { field[y][x]=0; field[y][x-1]=11; } } // для вызова из контроллера function moveFigureRight() { if (isSpaceRight()) doStepRight(); repaint(); } // свободное место справа от текущей фигуры function isSpaceRight() { for (let x = columns-2; x >=0; x--) for (let y = 0; y < rows; y++) if (field[y][x]==11 && field[y][x+1]>0 && field[y][x+1]<11 || field[y][x+1]==11 && x==columns-2) return false; return true; } // сдвинуть текущую фигуру вправо function doStepRight() { for (let x = columns-2; x >=0; x--) for (let y = 0; y < rows; y++) if (field[y][x]==11) { field[y][x]=0; field[y][x+1]=11; } } // для вызова из контроллера, поворот фигуры function rotateFigure() { let y = rows, x = columns; for (let yy = 0; yy < rows; yy++) for (let xx = 0; xx < columns; xx++) if (field[yy][xx] == 11) { if (y > yy) y = yy; if (x > xx) x = xx; } if (y == rows || x == columns) return; const old = currentFigure.shape; doPlaceFigure(y, x, 0); currentFigure.rotate(); if (isFreeSpace(y, x)) doPlaceFigure(y, x); else if (isFreeSpace(y, x-1)) doPlaceFigure(y, x-1); else { currentFigure.shape = old; doPlaceFigure(y, x); } repaint(); } // после загрузки всех частей страницы, запускаем игру document.addEventListener('DOMContentLoaded', function() { addEventListener('keydown', keyPressed); addEventListener('keyup', keyReleased); const rnd = Math.floor(Math.random() * figures.length); nextFigure = figures[rnd].clone(); prepareNewGame(); });