245 lines
7.3 KiB
JavaScript
245 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();
|
||
});
|