From 826e3d959d2339af285cc3417315efe16a8ff07d Mon Sep 17 00:00:00 2001 From: brian cully Date: Wed, 24 Dec 2025 11:55:01 -0500 Subject: part 2: move core logic to game module, which owns robos --- site/arena.mjs | 4 +- site/game.mjs | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ site/index.html | 4 +- site/main.css | 4 +- site/main.mjs | 103 +--------------------------------------- site/robo.mjs | 1 + 6 files changed, 152 insertions(+), 107 deletions(-) create mode 100644 site/game.mjs diff --git a/site/arena.mjs b/site/arena.mjs index c76f7dc..83f0f05 100644 --- a/site/arena.mjs +++ b/site/arena.mjs @@ -30,10 +30,10 @@ export default class extends HTMLElement { return [xx, yy]; } - randStart(robo) { + 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 {...robo, x, y}; + return {x, y}; } renderFPS(now) { diff --git a/site/game.mjs b/site/game.mjs new file mode 100644 index 0000000..0b1de90 --- /dev/null +++ b/site/game.mjs @@ -0,0 +1,143 @@ +import Arena from './arena.mjs'; +import Inspector from './inspector.mjs'; + +const ARENA_SELECTOR = 'x-arena'; +const TICK_BUTTON_SELECTOR = '#tick'; +const TICK_RATE_SELECTOR = '#tick-rate-select'; +const RUN_BUTTON_SELECTOR = '#run'; + +export default class extends HTMLElement { + static name = 'x-game'; + static register() { + console.debug('registering custom element', this.name, this); + self.customElements.define(this.name, this); + } + + #running = false; + #robos = []; + #inspectors = []; + #arena; + #tickButton; + #runButton; + #tickRate; + + constructor() { + super(); + } + + connectedCallback() { + console.debug('connectedCallback()', this); + this.#arena = this.querySelector(ARENA_SELECTOR); + this.#tickButton = this.querySelector(TICK_BUTTON_SELECTOR); + this.#runButton = this.querySelector(RUN_BUTTON_SELECTOR); + this.#tickRate = this.querySelector(TICK_RATE_SELECTOR); + this.#inspectors = this.querySelectorAll(Inspector.name); + + this.#tickButton.onclick = this.#tickHandler.bind(this); + this.#runButton.onclick = this.#blinkenHandler.bind(this); + this.#tickRate.onchange = this.#tickRateHandler.bind(this); + + this.#tickRate.onchange({ target: this.#tickRate }); + this.addRobo(); + } + + addRobo() { + const { x, y } = this.#arena.randStart(); + const robo = { + worker: new Worker('robo.mjs', { type: 'module' }), + lastTick: undefined, + heading: 0, + speed: 0, + speedx: 0, + speedy: 0, + x, + y, + } + robo.worker.onmessage = msg => this.#messageHandler(i, msg); + robo.worker.onerror = msg => this.#errorHander(i, msg); + this.#robos.push(robo); + const i = this.#robos.length - 1; + this.#inspectors[i].addEventListener(Inspector.compileRequest, e => { + console.debug('compiling for worker', i, e.detail.text, this.#robos[i].worker); + this.#robos[i].worker.postMessage({ kind: 'compile', text: e.detail.text }); + }); + } + + #messageHandler(i, e) { + const { kind, res, trans } = e.data; + switch (kind) { + case 'compile': + if (res) { + this.#arena.render(this.#robos); + this.#inspectors[i].render(trans, true); + } + break; + case 'tick': + const robo = this.#robos[i]; + robo.lastTick = document.timeline.currentTime; + + [robo.x, robo.y] = this.#arena.clamp(Arena.radius, robo.x + robo.speedx, robo.y + robo.speedy); + this.#arena.render(this.#robos, robo.lastTick); + + robo.heading = trans.vars.heading; + robo.speed = trans.vars.speed; + [robo.speedx, robo.speedy] = [ + Math.cos(2 * Math.PI * robo.heading / 360), + Math.sin(2 * Math.PI * robo.heading / 360) + ].map(x => robo.speed * x); + + this.#inspectors[i].render(trans); + break; + default: + console.error('invalid message from robo worker', e); + } + } + + #errorHander(i, e) { + console.error('error in roboWorker', this, i, e); + } + + #tickHandler(e) { + console.debug('tick clicked', this, e); + this.#robos.forEach(robo => robo.worker.postMessage({ kind: 'tick' })); + } + + #blinkenHandler(e) { + console.debug('blinken clicked', this, e); + + this.#running = !this.#running; + if (this.#running) { + e.currentTarget.classList.remove('halten'); + e.currentTarget.classList.add('blinken'); + e.currentTarget.querySelector('title').textContent = 'halten'; + this.#timeoutHandler(); + this.#renderFrame(document.timeline.currentTime); + } else { + e.currentTarget.classList.remove('blinken'); + e.currentTarget.classList.add('halten'); + e.currentTarget.querySelector('title').textContent = 'blinken'; + this.#arena.invalidateFPS(); + } + } + + #tickRateHandler(e) { + console.debug('tick rate changed', this, e, Number(e.target.value)); + this.#arena.tickMS = 1_000 / e.target.value; + } + + #timeoutHandler() { + if (!this.#running) { + return; + } + self.setTimeout(this.#timeoutHandler.bind(this), this.#arena.tickMS); + this.#robos.forEach(robo => robo.worker.postMessage({ kind: 'tick' })); + } + + #renderFrame(t) { + if (!this.#running) { + return; + } + self.requestAnimationFrame(this.#renderFrame.bind(this)); + this.#arena.render(this.#robos, t); + } +} diff --git a/site/index.html b/site/index.html index ace176c..a935339 100644 --- a/site/index.html +++ b/site/index.html @@ -10,7 +10,7 @@

automathon

-
+

arena

@@ -97,7 +97,7 @@
-
+ diff --git a/site/main.css b/site/main.css index 7e7ed18..4f00319 100644 --- a/site/main.css +++ b/site/main.css @@ -5,12 +5,12 @@ html { box-sizing: inherit; } -.flex-col { +x-game { display: flex; flex-direction: row; } -.flex-col section { +x-game section { flex: 1; } diff --git a/site/main.mjs b/site/main.mjs index e2cdc3e..0d79e7c 100644 --- a/site/main.mjs +++ b/site/main.mjs @@ -1,110 +1,11 @@ import Arena from './arena.mjs'; +import Game from './game.mjs'; import Inspector from './inspector.mjs'; -// one per page -const TICK_BUTTON_SELECTOR = '#tick'; -const TICK_RATE_SELECTOR = '#tick-rate-select'; -const RUN_BUTTON_SELECTOR = '#run'; - async function loaded() { Arena.register(); + Game.register(); Inspector.register(); - - const robos = [{ - worker: new Worker('robo.mjs', { type: 'module' }), - lastTick: undefined, - heading: 0, - speed: 0, - speedx: 0, - speedy: 0, - }].map(r => document.querySelector(Arena.name).randStart(r)); - robos.forEach((robo, i) => { - robo.worker.onmessage = e => { - const { kind, res, trans } = e.data; - switch (kind) { - case 'compile': - if (res) { - document.querySelector(Arena.name).render(robos); - document.querySelectorAll(Inspector.name)[i].render(trans, true); - } - break; - case 'tick': - robo.lastTick = document.timeline.currentTime; - - [robo.x, robo.y] = document.querySelector(Arena.name).clamp(Arena.radius, robo.x + robo.speedx, robo.y + robo.speedy); - document.querySelector(Arena.name).render(robos, robo.lastTick); - - robo.heading = trans.vars.heading; - robo.speed = trans.vars.speed; - [robo.speedx, robo.speedy] = [ - Math.cos(2 * Math.PI * robo.heading / 360), - Math.sin(2 * Math.PI * robo.heading / 360) - ].map(x => robo.speed * x); - - document.querySelectorAll(Inspector.name)[i].render(trans); - break; - default: - console.error('invalid message from robo worker', e.data); - } - }; - robo.worker.onerror = e => { - console.error('error in roboWorker', e); - }; - }); - - document.querySelectorAll(Inspector.name).forEach((elt, i) => { - elt.addEventListener(Inspector.compileRequest, e => { - console.debug('compiling', e.detail.text); - robos[i].worker.postMessage({ kind: 'compile', text: e.detail.text }); - }); - }); - - document.querySelector(TICK_BUTTON_SELECTOR).onclick = e => { - console.debug('tick clicked', e); - robos.forEach(robo => robo.worker.postMessage({ kind: 'tick' })); - }; - - document.querySelector(TICK_RATE_SELECTOR).onchange = e => { - console.debug('tick rate changed', e, Number(e.target.value)); - document.querySelector(Arena.name).tickMS = 1_000 / e.target.value; - } - document.querySelectorAll(TICK_RATE_SELECTOR).forEach(elt => elt.onchange({ target: elt })); - - let blinkenRun = false; - function renderFrame(t) { - if (!blinkenRun) { - return; - } - self.requestAnimationFrame(renderFrame); - document.querySelector(Arena.name).render(robos, t); - } - - function timeout() { - if (!blinkenRun) { - return - } - self.setTimeout(timeout, document.querySelector(Arena.name).tickMS); - - robos.forEach(robo => robo.worker.postMessage({ kind: 'tick' })); - } - - document.querySelector(RUN_BUTTON_SELECTOR).onclick = e => { - console.debug('blinken clicked', e); - - blinkenRun = !blinkenRun; - if (blinkenRun) { - e.currentTarget.classList.remove('halten'); - e.currentTarget.classList.add('blinken'); - e.currentTarget.querySelector('title').textContent = 'halten'; - setTimeout(timeout); - renderFrame(document.timeline.currentTime); - } else { - e.currentTarget.classList.remove('blinken'); - e.currentTarget.classList.add('halten'); - e.currentTarget.querySelector('title').textContent = 'blinken'; - document.querySelector(Arena.name).invalidateFPS(); - } - } } document.addEventListener('DOMContentLoaded', loaded); diff --git a/site/robo.mjs b/site/robo.mjs index d80baad..ba879fa 100644 --- a/site/robo.mjs +++ b/site/robo.mjs @@ -18,6 +18,7 @@ function tick() { } async function messageHandler(e) { + console.debug('worker messageHandler') const { kind } = e.data; switch (kind) { case 'compile': -- cgit v1.3