226 lines
5.8 KiB
JavaScript
226 lines
5.8 KiB
JavaScript
// 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)
|
|
} |