Gameplay: Nochmal-Synchronisation + Echtzeit-Commit-Zähler + Host-Button umbenannt
All checks were successful
Pin to IPFS / pin (push) Successful in 11s

- Host-Lobby: 'Münzwurf starten' → 'Spiel starten'
- 'Nochmal': warten bis alle bereit (ready_next/ready_next_all), erst
  dann 'Münzwurf starten' anzeigen
- Progress zeigt Commits/Reveals in Echtzeit (auch vor eigenem Start)
- totalPeers bereits im Konstruktor setzen für sofortige Anzeige
This commit is contained in:
Ole 2026-06-14 22:40:15 +02:00
parent a7933315f3
commit fae9393e8c
3 changed files with 52 additions and 34 deletions

62
app.js
View file

@ -5,6 +5,7 @@
let protocol = null; let protocol = null;
let role = null; let role = null;
let scanningHost = false; let scanningHost = false;
let _nextReady = new Set();
const $ = id => document.getElementById(id); const $ = id => document.getElementById(id);
@ -232,14 +233,26 @@
}); });
} }
/* ─── Show start-protocol button after all ready ─── */
function _showStartButton() {
$('btn-start-protocol').hidden = false;
$('result-area').hidden = true;
$('results-container').hidden = true;
$('protocol-text').textContent = 'Bereit zum Münzwurf';
$('btn-retry').disabled = false;
$('btn-retry').textContent = 'Nochmal';
updatePhases('IDLE');
}
/* ─── Game setup ─── */ /* ─── Game setup ─── */
function setupGame(autostart) { function setupGame() {
_nextReady = new Set();
show('game'); show('game');
renderPlayers('game-player-list'); renderPlayers('game-player-list');
$('btn-start-protocol').hidden = autostart || role !== 'host'; $('btn-start-protocol').hidden = false;
$('results-container').hidden = true; $('results-container').hidden = true;
$('result-area').hidden = true; $('result-area').hidden = true;
$('protocol-text').textContent = autostart ? 'Starte Protokoll...' : 'Bereit zum Münzwurf'; $('protocol-text').textContent = 'Bereit zum Münzwurf';
updatePhases('IDLE'); updatePhases('IDLE');
protocol = new CoinFlipProtocol(mesh); protocol = new CoinFlipProtocol(mesh);
@ -264,23 +277,23 @@
}; };
mesh.ondata = (peerId, msg) => { mesh.ondata = (peerId, msg) => {
if (msg.type === 'protocol_start') { if (msg.type === 'ready_next') {
if (protocol) protocol.reset(); _nextReady.add(peerId);
$('results-container').hidden = true; const all = [mesh.myPeerId, ...mesh.getPeerIds()];
$('result-area').hidden = true; if (_nextReady.size >= all.length) {
$('btn-start-protocol').hidden = true; mesh.broadcast({ type: 'ready_next_all' });
updatePhases('IDLE'); _showStartButton();
protocol.start(); }
return;
}
if (msg.type === 'ready_next_all') {
_showStartButton();
return; return;
} }
if (msg.type === 'protocol' && protocol) { if (msg.type === 'protocol' && protocol) {
protocol.handleMessage(peerId, msg); protocol.handleMessage(peerId, msg);
} }
}; };
if (autostart) {
protocol.start();
}
} }
/* ─── Start screen ─── */ /* ─── Start screen ─── */
@ -392,8 +405,8 @@
}; };
mesh.ondata = (peerId, msg) => { mesh.ondata = (peerId, msg) => {
if (msg.type === 'protocol_start') { if (msg.type === 'game_start') {
setupGame(true); setupGame();
} }
}; };
@ -426,13 +439,15 @@
/* ─── Host: start game button (in host screen) ─── */ /* ─── Host: start game button (in host screen) ─── */
$('btn-start-game').addEventListener('click', () => { $('btn-start-game').addEventListener('click', () => {
setupGame(false); if (!mesh) return;
mesh.broadcast({ type: 'game_start' });
setupGame();
}); });
/* ─── Host: start protocol button (in game screen) ─── */ /* ─── Host: start protocol button (in game screen) ─── */
$('btn-start-protocol').addEventListener('click', async () => { $('btn-start-protocol').addEventListener('click', async () => {
if (!mesh || !protocol) return; if (!mesh || !protocol) return;
mesh.broadcast({ type: 'protocol_start' }); _nextReady = new Set();
$('btn-start-protocol').hidden = true; $('btn-start-protocol').hidden = true;
await protocol.start(); await protocol.start();
}); });
@ -440,11 +455,10 @@
/* ─── Retry / again ─── */ /* ─── Retry / again ─── */
$('btn-retry').addEventListener('click', () => { $('btn-retry').addEventListener('click', () => {
if (protocol) protocol.reset(); if (protocol) protocol.reset();
$('results-container').hidden = true; _nextReady.add(mesh.myPeerId);
$('result-area').hidden = true; mesh.broadcast({ type: 'ready_next' });
$('btn-start-protocol').hidden = role !== 'host'; $('btn-retry').disabled = true;
$('protocol-text').textContent = 'Bereit zum Münzwurf'; $('btn-retry').textContent = 'Warte...';
updatePhases('IDLE');
}); });
/* ─── Result screen → play again ─── */ /* ─── Result screen → play again ─── */
@ -452,7 +466,7 @@
if (protocol) protocol.reset(); if (protocol) protocol.reset();
$('results-container').hidden = true; $('results-container').hidden = true;
$('result-area').hidden = true; $('result-area').hidden = true;
setupGame(false); setupGame();
}); });
/* ─── Back to start ─── */ /* ─── Back to start ─── */

View file

@ -50,7 +50,7 @@
<button id="btn-host-answer-paste" class="btn-small">Antwort einspielen</button> <button id="btn-host-answer-paste" class="btn-small">Antwort einspielen</button>
</details> </details>
</div> </div>
<button id="btn-start-game" class="btn-primary" disabled>Münzwurf starten</button> <button id="btn-start-game" class="btn-primary" disabled>Spiel starten</button>
</div> </div>
</section> </section>

View file

@ -6,7 +6,7 @@ class CoinFlipProtocol {
this.commits = new Map(); this.commits = new Map();
this.reveals = new Map(); this.reveals = new Map();
this.result = null; this.result = null;
this.totalPeers = 0; this.totalPeers = 1 + mesh.getPeerIds().length;
this.expectedPeers = new Set(); this.expectedPeers = new Set();
this.TIMEOUT_MS = 12000; this.TIMEOUT_MS = 12000;
this._commitTimer = null; this._commitTimer = null;
@ -26,7 +26,7 @@ class CoinFlipProtocol {
this.secret = await generateSecret(); this.secret = await generateSecret();
const myCommit = await commit(this.secret); const myCommit = await commit(this.secret);
this.commits.set(this.mesh.myPeerId, myCommit); this.commits.set(this.mesh.myPeerId, myCommit);
if (this.onprogress) this.onprogress('commit', 1, this.totalPeers); if (this.onprogress) this.onprogress('commit', this.commits.size, this.totalPeers);
if (this.onstatechange) this.onstatechange('COMMITTING'); if (this.onstatechange) this.onstatechange('COMMITTING');
this.mesh.broadcast({ type: 'protocol', phase: 'commit', data: bufToBase64(myCommit) }); this.mesh.broadcast({ type: 'protocol', phase: 'commit', data: bufToBase64(myCommit) });
this.state = 'WAITING_FOR_COMMITS'; this.state = 'WAITING_FOR_COMMITS';
@ -38,20 +38,24 @@ class CoinFlipProtocol {
handleMessage(peerId, msg) { handleMessage(peerId, msg) {
if (msg.type !== 'protocol') return; if (msg.type !== 'protocol') return;
if (msg.phase === 'commit' && (this.state === 'WAITING_FOR_COMMITS' || this.state === 'COMMITTING' || this.state === 'STARTING')) { if (msg.phase === 'commit') {
if (!this.commits.has(peerId)) { if (!this.commits.has(peerId)) {
this.commits.set(peerId, base64ToBuf(msg.data)); this.commits.set(peerId, base64ToBuf(msg.data));
if (this.onprogress) this.onprogress('commit', this.commits.size, this.totalPeers); if (this.onprogress)
this._checkCommits(); this.onprogress('commit', this.commits.size, this.totalPeers);
if (this.state === 'WAITING_FOR_COMMITS') this._checkCommits();
} }
return;
} }
if (msg.phase === 'reveal' && (this.state === 'WAITING_FOR_REVEALS' || this.state === 'REVEALING')) { if (msg.phase === 'reveal') {
if (!this.reveals.has(peerId)) { if (!this.reveals.has(peerId)) {
this.reveals.set(peerId, base64ToBuf(msg.data)); this.reveals.set(peerId, base64ToBuf(msg.data));
if (this.onprogress) this.onprogress('reveal', this.reveals.size, this.totalPeers); if (this.onprogress)
this._checkReveals(); this.onprogress('reveal', this.reveals.size, this.totalPeers);
if (this.state === 'WAITING_FOR_REVEALS') this._checkReveals();
} }
return;
} }
} }
@ -112,7 +116,7 @@ class CoinFlipProtocol {
if (this.onstatechange) this.onstatechange('REVEALING'); if (this.onstatechange) this.onstatechange('REVEALING');
this.mesh.broadcast({ type: 'protocol', phase: 'reveal', data: bufToBase64(this.secret) }); this.mesh.broadcast({ type: 'protocol', phase: 'reveal', data: bufToBase64(this.secret) });
this.reveals.set(this.mesh.myPeerId, this.secret); this.reveals.set(this.mesh.myPeerId, this.secret);
if (this.onprogress) this.onprogress('reveal', 1, this.totalPeers); if (this.onprogress) this.onprogress('reveal', this.reveals.size, this.totalPeers);
this.state = 'WAITING_FOR_REVEALS'; this.state = 'WAITING_FOR_REVEALS';
if (this.onstatechange) this.onstatechange('WAITING_FOR_REVEALS'); if (this.onstatechange) this.onstatechange('WAITING_FOR_REVEALS');
this._startRevealTimer(); this._startRevealTimer();