This commit is contained in:
Fasterino
2025-10-20 21:08:52 +03:00
commit 4aaa436079
20 changed files with 6824 additions and 0 deletions

226
static/game.js Normal file
View File

@@ -0,0 +1,226 @@
// CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
// if (w < 2 * r) r = w / 2;
// if (h < 2 * r) r = h / 2;
// this.beginPath();
// this.moveTo(x+r, y);
// this.arcTo(x+w, y, x+w, y+h, r);
// this.arcTo(x+w, y+h, x, y+h, r);
// this.arcTo(x, y+h, x, y, r);
// this.arcTo(x, y, x+w, y, r);
// this.closePath();
// return this;
// }
function startGame() {
const canvas = document.querySelector("canvas");
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
ctx.font = "24px Montserrat"
ctx.imageSmoothingEnabled = true
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (x < 0 || y < 0 || x > rect.width || y > rect.height) return;
// Adjust for potential canvas scaling (e.g. high DPI)
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
socket.emit("cursor", x * scaleX, y * scaleY);
})
canvas.addEventListener('click', e => click(true, e, canvas))
canvas.addEventListener('contextmenu', e => click(false, e, canvas))
gameLoop(ctx)
}
/**
*
* @param {boolean} left
* @param {PointerEvent} e
* @param {HTMLCanvasElement} canvas
*/
function click(left, e, canvas) {
e.preventDefault()
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
if (x < 0 || y < 0 || x > rect.width || y > rect.height) return;
// Adjust for potential canvas scaling (e.g. high DPI)
const scaleX = canvas.width / rect.width;
const scaleY = canvas.height / rect.height;
socket.emit("click", left, Math.floor((x * scaleX - offsetX()) / CELL_SIZE), Math.floor((y * scaleY - offsetY()) / CELL_SIZE));
}
/**
*
* @param {CanvasRenderingContext2D} ctx
*/
function gameLoop(ctx) {
ctx.save()
ctx.clearRect(0, 0, 800, 600)
for (let x = 0; x < data.field.length; x++)
for (let y = 0; y < data.field[x].length; y++)
drawCell(ctx, x, y)
const x = 400
const y = 600 - (600 - (CELL_SIZE * MAX_HEIGTH)) * 0.5
ctx.textAlign = 'center'
ctx.textBaseline = 'middle'
ctx.fillStyle = getColor('fg')
const w = data.field.length
const h = w && data.field[0].length
ctx.fillText(`${data.text} 🚩 ${data.flags} 💣 ${data.bombs} Поле ${w}x${h}`.trim(), x, y)
for (const sid of Object.keys(data.cursors))
drawCursor(ctx, sid)
ctx.restore();
window.requestAnimationFrame(() => gameLoop(ctx))
}
const HALF_CELL_SIZE = 16
const CELL_SIZE = HALF_CELL_SIZE * 2
const MAX_WIDTH = 25
const MAX_HEIGTH = 15
function offsetX() {
return (MAX_WIDTH - data.field.length) * HALF_CELL_SIZE
}
function offsetY() {
return (MAX_HEIGTH - (data.field.length && data.field[0].length)) * HALF_CELL_SIZE
}
/**
*
* @param {CanvasRenderingContext2D} ctx
* @param {number} cx
* @param {number} cy
*/
function drawCell(ctx, cx, cy) {
let cell = data.field[cx][cy]
switch (cell) {
case "":
case "🚩":
case "❓":
ctx.fillStyle = getColor("bgCellClosed")
break;
case " ":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
ctx.fillStyle = getColor("bgCellOpen")
break
default:
ctx.fillStyle = getColor("bgCellBomb")
break
}
ctx.strokeStyle = getColor("bg")
ctx.lineWidth = 4
const x = cx * CELL_SIZE + offsetX();
const y = cy * CELL_SIZE + offsetY();
ctx.fillRect(x, y, CELL_SIZE, CELL_SIZE)
ctx.strokeRect(x, y, CELL_SIZE, CELL_SIZE)
switch (cell) {
case "":
case " ":
return
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
ctx.fillStyle = getColor('fgCell' + cell)
break
default:
ctx.fillStyle = "white"
break
}
ctx.textAlign = "center"
ctx.textBaseline = "middle"
ctx.fillText(cell, x + HALF_CELL_SIZE, y + HALF_CELL_SIZE)
}
/**
*
* @param {CanvasRenderingContext2D} ctx
* @param {string} sid
* @param {number} x
* @param {number} y
* @param {number} w
* @param {number} h
*/
function drawCursor(ctx, sid, w = 15, h = 20) {
const { x, y, nickname } = data.cursors[sid];
const a = sid.charCodeAt(0) % 256;
const b = sid.charCodeAt(1) % 256;
const c = sid.charCodeAt(2) % 3;
const aStr = a < 16 ? "0" + a.toString(16) : a.toString(16);
const bStr = b < 16 ? "0" + b.toString(16) : b.toString(16);
let color = "#";
switch (c) {
case 0:
color += "ff" + aStr + bStr;
break;
case 1:
color += aStr + "ff" + bStr;
break;
default:
color += aStr + bStr + "ff";
break;
}
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(x + w, y + h * 0.6);
ctx.lineTo(x + w * 0.6, y + h * 0.5);
ctx.lineTo(x + w, y + h * 0.9);
ctx.lineTo(x + w * 0.9, y + h);
ctx.lineTo(x + w * 0.5, y + h * 0.6);
ctx.lineTo(x + w * 0.6, y + h);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
ctx.strokeStyle = "black"
ctx.lineWidth = 1
ctx.stroke();
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.textBaseline = 'top';
ctx.fillStyle = "#00000077";
const { width: tw, ideographicBaseline: th } = ctx.measureText(nickname)
ctx.beginPath();
ctx.roundRect(x + w * 2, y, tw + w * 2, Math.abs(th), [w])
ctx.fill()
ctx.fillStyle = "#ffffff"
ctx.fillText(nickname, x + w * 3, y)
}