summaryrefslogtreecommitdiffstats
path: root/site/arena.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'site/arena.mjs')
-rw-r--r--site/arena.mjs79
1 files changed, 79 insertions, 0 deletions
diff --git a/site/arena.mjs b/site/arena.mjs
new file mode 100644
index 0000000..c76f7dc
--- /dev/null
+++ b/site/arena.mjs
@@ -0,0 +1,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();
+ }
+}