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'; const INSPECTOR_TEMPLATE_SELECTOR = '.inspectors template'; const INSPECTORS_SECTION_SELECTOR = '.inspectors'; 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; #inspectorTemplate; #inspectorsSection; 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.#inspectorTemplate = this.querySelector(INSPECTOR_TEMPLATE_SELECTOR); this.#inspectorsSection = this.querySelector(INSPECTORS_SECTION_SELECTOR); console.debug('bjc found template', this.#inspectorTemplate, this.#inspectorsSection); 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() { console.debug('bjc pre inspector', this.#inspectors); const i = this.#robos.length; 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); // the only reliable way i have of talking to the element // that's attached is to append the template, then fetch that // out of the dom. neither cloneNode(true|false) nor // document.importNode(true|false) worked to fire the event // listener. this.#inspectorsSection.appendChild(this.#inspectorTemplate.content); const inspector = this.#inspectorsSection.children[this.#inspectorsSection.children.length-1]; inspector.addEventListener(Inspector.compileRequest, e => { console.debug('compiling for worker', e.detail.text, robo.worker); robo.worker.postMessage({ kind: 'compile', text: e.detail.text }); }); this.#robos.push(robo); this.#inspectors.push(inspector); } #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); } }