programmierhub/public/js/net.js
Stefan Franke 502131f07c VR ProgrammierHub — begehbarer 3D-Lernraum für hybride Programmier-Lehre
Erste lauffähige Fassung: 3D-Raum, Echtzeit-Multiplayer (WebSocket),
Python-IDE via Pyodide im Browser, eigener Arbeitsplatz mit Live-Code-Monitor.
Modularer, self-hostbarer Aufbau (Three.js + Node/Express + ws), ohne CDN-
Abhängigkeit zur Laufzeit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-30 13:35:11 +00:00

66 lines
2.3 KiB
JavaScript

// net.js — WebSocket-Client: Verbindung, Reconnect, Protokoll (siehe Spec 9.2).
// Kennt die 3D-Welt nicht — reicht Ereignisse per Callbacks nach oben.
export class Net {
/**
* @param {object} handlers - onWelcome(id,users), onJoin(user), onLeave(id),
* onUpdate(id,p,rot), onStatus(id,label), onHelp(id,on), onNet(text,color)
*/
constructor(handlers) {
this.h = handlers;
this.ws = null;
this.myId = null;
this.started = false;
this.name = 'Gast';
this.role = 'student';
this.startPos = [0, 0, 8];
}
connect(name, role, startPos) {
this.name = name;
this.role = role;
if (startPos) this.startPos = [startPos.x, startPos.y, startPos.z];
this.started = true;
this._open();
}
_open() {
const url = (location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + '/ws';
this._net('verbinde …');
const ws = new WebSocket(url);
this.ws = ws;
ws.onopen = () => {
this._net('verbunden', '#a6e3a1');
ws.send(JSON.stringify({ t: 'hello', name: this.name, role: this.role, p: this.startPos }));
};
ws.onclose = () => {
this._net('getrennt — reconnect …', '#f38ba8');
this.myId = null;
setTimeout(() => { if (this.started) this._open(); }, 1500);
};
ws.onerror = () => this._net('Fehler', '#f38ba8');
ws.onmessage = (ev) => this._recv(ev);
}
_recv(ev) {
let m;
try { m = JSON.parse(ev.data); } catch { return; }
const h = this.h;
switch (m.t) {
case 'welcome': this.myId = m.id; h.onWelcome(m.id, m.users); break;
case 'join': h.onJoin(m.user); break;
case 'leave': h.onLeave(m.id); break;
case 'update': h.onUpdate(m.id, m.p, m.rot); break;
case 'status': h.onStatus(m.id, m.label); break;
case 'help': h.onHelp(m.id, m.on); break;
}
}
get connected() { return this.ws && this.ws.readyState === 1 && this.myId != null; }
sendMove(p, rot, anim) { if (this.connected) this.ws.send(JSON.stringify({ t: 'move', p, rot, anim })); }
sendHelp(on) { if (this.connected) this.ws.send(JSON.stringify({ t: 'help', on })); }
sendStatus(label) { if (this.connected) this.ws.send(JSON.stringify({ t: 'status', label })); }
_net(text, color) { if (this.h.onNet) this.h.onNet(text, color); }
}