1/jekyll_site/js/tetris-view.js

236 lines
8.5 KiB
JavaScript
Raw Normal View History

2023-12-17 07:55:25 +03:00
// © Головин Г.Г., Визуализация игрового процесса, 2023
'use strict';
// размер клеточки, отступ
const size = 30, gap = 2;
// массив статусов игры
const statuses = ["ИГРА","УРОВЕНЬ","ПАУЗА","ЗАВЕРШЕНО"];
// массив цветов для фигур
const colors = [
'rgba(240,240,240,1)',
'rgba(150,0,0,1)',
'rgba(0,0,150,1)',
'rgba(150,150,0,1)',
'rgba(0,150,150,1)',
'rgba(0,150,0,1)',
'rgba(150,0,150,1)',
'rgba(150,80,80,1)'];
// прозрачность фигур
colors.setDefault = function() {
this.alpha = 0;
}
colors.setDefault();
// получаем цвет, добавляем прозрачность только фигурам
colors.get = function(...numbers) {
for (let num of numbers)
if (num <= 0) return this[0];
else if (num < colors.length)
return this[num].replace('1)', (100-this.alpha)/100 + ')');
return this[0];
}
// массив для кубиков
let field3D = [];
// центральная точка для поворотов
const t0 = {}; t0.reCalc = function() {
this.x = columns*(size+gap)/2;
this.y = rows*(size+gap);
this.z = (size+gap)*2;
};
t0.reCalc();
// угол поворота игрового поля с кубиками
2023-12-31 00:01:26 +03:00
let deg = {}; deg.setDefault = function() {
2023-12-17 07:55:25 +03:00
this.x=-1;
this.y=0;
this.z=0;
};
deg.setDefault();
// параллельная проекция: центр и экран наблюдателя
2023-12-31 00:01:26 +03:00
let d1, tv1 = {}; tv1.reCalc = function() {
2023-12-17 07:55:25 +03:00
this.x=columns*(size+gap)/2;
this.y=(size+gap)*2;
this.z=(size+gap)*2;
d1 = Math.max(rows,columns)*(size+gap);
}
tv1.reCalc();
// перспективная проекция: центр и экран наблюдателя
2023-12-31 00:01:26 +03:00
let d2, d2max, tv2 = {}; tv2.reCalc = function() {
2023-12-17 07:55:25 +03:00
this.x = columns*(size+gap)/2;
this.y = rows*(size+gap)/2;
this.z = (size+gap)*2;
this.show=false;
2023-12-31 00:01:26 +03:00
d2 = Math.max(rows,columns)*(size+gap); d2max = d2*2;
2023-12-17 07:55:25 +03:00
};
tv2.reCalc();
// стакан с игрой
const container = {};
container.canvas = document.getElementById('container');
container.context = container.canvas.getContext('2d');
container.changeSize = function() {
this.canvas.width = columns*size+(columns+1)*gap;
this.canvas.height = rows*size+(rows+1)*gap;
};
container.changeSize();
// перетаскивание центральной точки мышью
container.msBtnPressed = false;
container.canvas.onmouseup = ()=> container.msBtnPressed=false;
container.canvas.onmousedown = (caller)=> {
container.msBtnPressed=true;
container.canvas.onmousemove(caller);
}
container.canvas.onmousemove = (caller)=> {
if (vol==VOLUME.PERSPECTIVE && container.msBtnPressed && tv2.show) {
tv2.x = caller.offsetX;
tv2.y = caller.offsetY;
repaint(false);
refreshParams();
}
}
// следующая фигура
const next = {};
next.canvas = document.getElementById('next');
next.context = next.canvas.getContext('2d');
next.changeSize = function() {
this.canvas.width = 2*size+3*gap;
this.canvas.height = 4*size+5*gap;
};
next.changeSize();
// состояние игры
const statusView = {};
statusView.level = document.getElementById('levelView');
statusView.score = document.getElementById('scoreView');
statusView.nextLevel = document.getElementById('nextLevelView');
statusView.speed = document.getElementById('speedometer')
statusView.status = document.getElementById('statusView');
statusView.refresh = function() {
this.level.innerText = level;
this.score.innerText = score;
this.nextLevel.innerText = nextLevel;
this.speed.value = speedometer();
this.status.innerText = statuses[status];
}
// обновление изображения
function repaint(refresh3D=true) {
// состояние игры
statusView.refresh();
// стакан с игрой
if (vol==VOLUME.FLAT) {
drawCells(container.canvas, container.context, field, currentFigure.type);
} else {
if (refresh3D || field3D.length==0)
prepare3D();
repaint3D();
}
// следующая фигура
drawCells(next.canvas, next.context, nextFigure.shape);
}
// рисуем массив клеточек
function drawCells(canvas, context, array, color) {
// очищаем весь холст целиком
context.clearRect(0, 0, canvas.width, canvas.height);
// обходим массив, рисуем клеточки
for (let y=0; y<array.length; y++)
for (let x=0; x<array[y].length; x++) {
context.fillStyle = colors.get(array[y][x], color);
context.fillRect(gap+x*(size+gap), gap+y*(size+gap), size, size);
}
}
// обходим поле, создаём кубики
function prepare3D() {
field3D = [];
for (let y=0; y<field.length; y++)
for (let x=0; x<field[y].length; x++) {
let cube = new Cube(gap+x*(size+gap),gap+y*(size+gap),size+gap,size+gap);
// в пустых кубиках оставляем только заднюю стенку
if (field[y][x] == 0) {
for (let i=0;i<3;i++) cube.faces.shift();
for (let i=0;i<2;i++) cube.faces.pop();
}
cube.rotate(deg, t0);
cube.color = field[y][x] < 11 ? field[y][x] : currentFigure.type;
field3D.push(cube);
}
}
// рисуем массив кубиков
function repaint3D() {
// проекции граней кубиков
let proj = [];
// получаем массив проекций
for (let cube of field3D) {
let cProj;
if (vol==VOLUME.PARALLEL)
cProj = cube.projection('parallel',tv1,d1);
else
cProj = cube.projection('perspective',tv2,d2);
for (let face of cProj)
face.color = cube.color;
proj = proj.concat(cProj);
}
// сортируем грани по удалённости от центра проекции
proj.sort((a,b) => b.dist-a.dist);
// удаляем смежные стенки между соседними кубиками
for (let i=0, j=1; i<proj.length-1; j=++i+1)
while (j<proj.length && Cube.pEquidistant(proj[i],proj[j]))
if (Cube.pAdjacent(proj[i],proj[j])) {
proj.splice(j,1);
proj.splice(i,1);
i--; j=proj.length;
} else j++;
if (vol==VOLUME.PARALLEL)
// сортируем грани разных кубиков по удалённости и внутри одного кубика по наклону
proj.sort((a,b)=>Math.abs(b.dist-a.dist)>size ? b.dist-a.dist : b.clock-a.clock);
// обновляем стакан с игрой
drawCubes(container.canvas, container.context, proj);
// центральная точка перспективной проекции
if (vol==VOLUME.PERSPECTIVE && tv2.show) centerPoint(container.context);
}
// обходим массив, рисуем грани по точкам
function drawCubes(canvas, context, array) {
// очищаем весь холст целиком
context.clearRect(0, 0, canvas.width, canvas.height);
// рисуем только видимые грани
const visible = function(face) {
// если есть хотя бы одна точка, которую можно нарисовать
for (let j = 0; j < face.length; j++)
if (face[j].x>-size && face[j].x<canvas.width+size
&& face[j].y>-size && face[j].y<canvas.height+size)
return true;
return false;
};
// обходим массив граней куба
for (let i = 0; i < array.length; i++) {
// рисуем только видимые грани
if (!visible(array[i])) continue;
// обходим массив точек и соединяем их линиями
context.beginPath();
for (let j = 0; j < array[i].length; j++)
if (j==0) context.moveTo(array[i][j].x, array[i][j].y);
else context.lineTo(array[i][j].x, array[i][j].y);
context.closePath();
// рисуем линии на холсте
context.lineWidth = 1.7;
context.lineJoin = 'round';
context.fillStyle = colors.get(array[i].color);
context.strokeStyle = '#ffff';
context.fill();
context.stroke();
}
}
// центральная точка перспективной проекции
function centerPoint(context) {
context.beginPath();
context.lineWidth = 3.2;
context.strokeStyle = '#66bb6a';
context.arc(tv2.x, tv2.y, 6.5, 0, 2*Math.PI);
context.stroke();
}
// скорость падения фигуры
const speedometer = function() {
let speed = 0;
return function() {
if (rapidFall)
speed = Math.min(START_DELAY, speed + 80);
else
speed = Math.max(0, speed - 80);
return Math.max(speed, START_DELAY - stepDelay);
}
}();