- add lobby, game, leaderboard, results pages - add scripts: storage, lobby, game, drawing, scoring, countries - add styles: main, lobby, game - unify header/footer across all pages - show lobby name in lobby view - remove clear board from leaderboard
149 lines
3.6 KiB
JavaScript
149 lines
3.6 KiB
JavaScript
// drawing.js — canvas drawing module
|
|
|
|
const Drawing = (() => {
|
|
let canvas, ctx;
|
|
let isDrawing = false;
|
|
let points = []; // [{x, y}, ...]
|
|
let cities = []; // [{name, x, y}, ...] (% coords)
|
|
|
|
const STROKE_COLOR = "#1a7fc4";
|
|
const STROKE_WIDTH = 2.5;
|
|
|
|
function init(canvasEl) {
|
|
canvas = canvasEl;
|
|
ctx = canvas.getContext("2d");
|
|
|
|
// Pointer events (mouse + touch)
|
|
canvas.addEventListener("pointerdown", onDown);
|
|
canvas.addEventListener("pointermove", onMove);
|
|
canvas.addEventListener("pointerup", onUp);
|
|
canvas.addEventListener("pointerleave", onUp);
|
|
canvas.style.touchAction = "none";
|
|
|
|
_resize();
|
|
window.addEventListener("resize", _resize);
|
|
}
|
|
|
|
function _resize() {
|
|
if (!canvas) return;
|
|
const { width, height } = canvas.getBoundingClientRect();
|
|
canvas.width = width * window.devicePixelRatio;
|
|
canvas.height = height * window.devicePixelRatio;
|
|
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
|
|
_redraw();
|
|
}
|
|
|
|
function _pos(e) {
|
|
const r = canvas.getBoundingClientRect();
|
|
return {
|
|
x: (e.clientX - r.left),
|
|
y: (e.clientY - r.top),
|
|
};
|
|
}
|
|
|
|
function onDown(e) {
|
|
e.preventDefault();
|
|
isDrawing = true;
|
|
const p = _pos(e);
|
|
points.push(p);
|
|
ctx.beginPath();
|
|
ctx.moveTo(p.x, p.y);
|
|
}
|
|
|
|
function onMove(e) {
|
|
if (!isDrawing) return;
|
|
e.preventDefault();
|
|
const p = _pos(e);
|
|
points.push(p);
|
|
ctx.lineTo(p.x, p.y);
|
|
ctx.strokeStyle = STROKE_COLOR;
|
|
ctx.lineWidth = STROKE_WIDTH;
|
|
ctx.lineJoin = "round";
|
|
ctx.lineCap = "round";
|
|
ctx.stroke();
|
|
}
|
|
|
|
function onUp(e) {
|
|
if (!isDrawing) return;
|
|
isDrawing = false;
|
|
e.preventDefault();
|
|
}
|
|
|
|
function clear() {
|
|
points = [];
|
|
if (!ctx) return;
|
|
const w = canvas.getBoundingClientRect().width;
|
|
const h = canvas.getBoundingClientRect().height;
|
|
ctx.clearRect(0, 0, w, h);
|
|
_drawCities();
|
|
}
|
|
|
|
function setCities(cityList) {
|
|
cities = cityList || [];
|
|
_drawCities();
|
|
}
|
|
|
|
function _drawCities() {
|
|
if (!ctx || !cities.length) return;
|
|
const w = canvas.getBoundingClientRect().width;
|
|
const h = canvas.getBoundingClientRect().height;
|
|
|
|
cities.forEach(city => {
|
|
const cx = (city.x / 100) * w;
|
|
const cy = (city.y / 100) * h;
|
|
|
|
// Dot
|
|
ctx.beginPath();
|
|
ctx.arc(cx, cy, 5, 0, Math.PI * 2);
|
|
ctx.fillStyle = "rgba(240,180,40,0.9)";
|
|
ctx.fill();
|
|
ctx.strokeStyle = "rgba(255,255,255,0.9)";
|
|
ctx.lineWidth = 1.5;
|
|
ctx.stroke();
|
|
|
|
// Label
|
|
ctx.font = "bold 11px 'DM Sans', sans-serif";
|
|
ctx.fillStyle = "#0b1f2a";
|
|
ctx.textAlign = "center";
|
|
|
|
// White pill background
|
|
const textW = ctx.measureText(city.name).width + 10;
|
|
const textH = 16;
|
|
const tx = cx;
|
|
const ty = cy - 14;
|
|
|
|
ctx.save();
|
|
ctx.fillStyle = "rgba(255,255,255,0.88)";
|
|
ctx.beginPath();
|
|
ctx.roundRect(tx - textW / 2, ty - textH / 2 - 1, textW, textH, 4);
|
|
ctx.fill();
|
|
ctx.restore();
|
|
|
|
ctx.fillStyle = "#0b1f2a";
|
|
ctx.fillText(city.name, tx, ty + 4);
|
|
});
|
|
}
|
|
|
|
function _redraw() {
|
|
_drawCities();
|
|
if (!points.length) return;
|
|
ctx.beginPath();
|
|
ctx.strokeStyle = STROKE_COLOR;
|
|
ctx.lineWidth = STROKE_WIDTH;
|
|
ctx.lineJoin = "round";
|
|
ctx.lineCap = "round";
|
|
points.forEach((p, i) => i === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y));
|
|
ctx.stroke();
|
|
}
|
|
|
|
function getPoints() {
|
|
return [...points];
|
|
}
|
|
|
|
function destroy() {
|
|
window.removeEventListener("resize", _resize);
|
|
}
|
|
|
|
return { init, clear, setCities, getPoints, destroy };
|
|
})();
|