1/jekyll_site/js/tetris-model.js

246 lines
7.3 KiB
JavaScript
Raw Permalink Normal View History

2023-12-17 07:55:25 +03:00
// © Головин Г.Г., Логика игрового процесса, 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();
});