summaryrefslogtreecommitdiffstats
path: root/site/arena.mjs
blob: 5ca3c32c2f7274a75780b69e4f4ce5d2f3f06dfd (plain)
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
80
81
82
83
84
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() {
        const [x, y] = [this.#canvas.width, this.#canvas.height].map(len =>
            this.constructor.radius + Math.floor(Math.random() * (len - 2*this.constructor.radius)));
        return {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, i) => {
            // 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(i, x, y);
        });
    }

    #colors = ['rgb(200 0 0)', 'rgb(0 0 200)'];
    #colorFor(i) {
        return this.#colors[i % this.#colors.length];
    }

    #renderRobo(i, x, y) {
        this.#ctx.fillStyle = this.#colorFor(i);
        this.#ctx.beginPath();
        this.#ctx.arc(x, y, this.constructor.radius, 0, 2 * Math.PI);
        this.#ctx.fill();
    }
}