1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
const CANVAS_SELECTOR = 'canvas';
const FPS_SELECTOR = '#fps';
export default class extends HTMLElement {
static name = 'x-arena';
static register() {
console.debug('registering custom element', this.name, this);
self.customElements.define(this.name, this);
}
static radius = 25;
#canvas;
#ctx;
#lastTime;
tickMS;
connectedCallback() {
console.debug('connectedCallback', this);
this.#canvas = this.querySelector(CANVAS_SELECTOR);
this.#ctx = this.#canvas.getContext('2d');
}
invalidateFPS() {
document.querySelector(FPS_SELECTOR).textContent = 'n/a';
}
clamp(radius, x, y) {
const xx = Math.min(Math.max(radius, x), this.#canvas.width-radius);
const yy = Math.min(Math.max(radius, y), this.#canvas.height-radius);
return [xx, yy];
}
randStart(robo) {
const [x, y] = [this.#canvas.width, this.#canvas.height].map(len =>
this.constructor.radius + Math.floor(Math.random() * (len - 2*this.constructor.radius)));
return {...robo, x, y};
}
renderFPS(now) {
const delta = now - (this.#lastTime ?? now);
if (delta > 0) {
document.querySelector(FPS_SELECTOR).textContent = Math.floor(1_000 / delta);
}
}
render(robos, now=0) {
// we often get called with the same time stamp many times in
// a row due to fingerprint-reduction tech.
if (now === this.#lastTime) {
return;
}
this.renderFPS(now);
this.#lastTime = now;
this.#ctx.clearRect(0, 0, this.#canvas.width, this.#canvas.height);
robos.forEach(robo => {
// interpolation factor for smoother movement independent
// of tick rate, but never tween more than 1 tick.
const delta = robo.lastTick ? now - robo.lastTick : 0;
const timeScale = this.tickMS > 0 ? Math.min(delta / this.tickMS, 1.0) : 1;
let [x, y] = [robo.x, robo.y];
if (delta > 0) {
const [velx, vely] = [
robo.speedx, robo.speedy
].map(x => timeScale * x);
[x, y] = this.clamp(this.constructor.radius, robo.x + velx, robo.y + vely);
}
this.#renderRobo(x, y);
});
}
#renderRobo(x, y) {
this.#ctx.fillStyle = 'rgb(200 0 0)';
this.#ctx.beginPath();
this.#ctx.arc(x, y, this.constructor.radius, 0, 2 * Math.PI);
this.#ctx.fill();
}
}
|