1/jekyll_site/js/tetris-view.js
2023-12-31 00:01:26 +03:00

235 lines
8.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// © Головин Г.Г., Визуализация игрового процесса, 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();
// угол поворота игрового поля с кубиками
let deg = {}; deg.setDefault = function() {
this.x=-1;
this.y=0;
this.z=0;
};
deg.setDefault();
// параллельная проекция: центр и экран наблюдателя
let d1, tv1 = {}; tv1.reCalc = function() {
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();
// перспективная проекция: центр и экран наблюдателя
let d2, d2max, tv2 = {}; tv2.reCalc = function() {
this.x = columns*(size+gap)/2;
this.y = rows*(size+gap)/2;
this.z = (size+gap)*2;
this.show=false;
d2 = Math.max(rows,columns)*(size+gap); d2max = d2*2;
};
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);
}
}();