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>
66 lines
2.3 KiB
JavaScript
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); }
|
|
}
|