246 lines
7.3 KiB
JavaScript
246 lines
7.3 KiB
JavaScript
|
// © Головин Г.Г., Логика игрового процесса, 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; yy<height; yy++)
|
|||
|
for (let xx=0; xx<wight; xx++)
|
|||
|
if (currentFigure.shape[yy][xx]>0)
|
|||
|
if (y+yy>=0 && y+yy<rows && x+xx>=0 && x+xx<columns)
|
|||
|
if (field[y+yy][x+xx]!=0)
|
|||
|
return false;
|
|||
|
return true;
|
|||
|
}
|
|||
|
// разместить текущую фигуру в пределах границ поля
|
|||
|
function doPlaceFigure(y, x, type=11) {
|
|||
|
const height = currentFigure.shape.length;
|
|||
|
const wight = currentFigure.shape[0].length;
|
|||
|
for (let yy=0; yy<height; yy++)
|
|||
|
for (let xx=0; xx<wight; xx++)
|
|||
|
if (currentFigure.shape[yy][xx]>0)
|
|||
|
if (y+yy>=0 && y+yy<rows && x+xx>=0 && x+xx<columns)
|
|||
|
field[y+yy][x+xx]=type;
|
|||
|
}
|
|||
|
// для вызова из контроллера
|
|||
|
function moveFigureLeft() {
|
|||
|
if (isSpaceLeft())
|
|||
|
doStepLeft();
|
|||
|
repaint();
|
|||
|
}
|
|||
|
// свободное место слева от текущей фигуры
|
|||
|
function isSpaceLeft() {
|
|||
|
for (let x = 1; x < columns; 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==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();
|
|||
|
});
|