235 lines
8.5 KiB
JavaScript
235 lines
8.5 KiB
JavaScript
// © Головин Г.Г., Визуализация игрового процесса, 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 = 600, 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 = 600, 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);
|
||
};
|
||
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);
|
||
}
|
||
}();
|