feat: Schocken - Würfel bei Aufdecken sichtbar, Admin-Abbrechen
This commit is contained in:
@@ -586,4 +586,19 @@ function getGame(id, requesterId) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Spiel abbrechen (Admin only, keine Punkte) ───────────────────────────────
|
||||||
|
router.post('/:id/cancel', authenticate, (req, res) => {
|
||||||
|
if (req.user?.role !== 'admin') return res.status(403).json({ error: 'Nur Admin' });
|
||||||
|
const game = db.prepare('SELECT * FROM schocken_games WHERE id=?').get(req.params.id);
|
||||||
|
if (!game) return res.status(404).json({ error: 'Nicht gefunden' });
|
||||||
|
if (game.status === 'finished') return res.status(400).json({ error: 'Spiel bereits beendet' });
|
||||||
|
db.prepare(`UPDATE schocken_games SET status='finished', phase_status='cancelled',
|
||||||
|
updated_at=datetime('now','localtime') WHERE id=?`).run(game.id);
|
||||||
|
const players = JSON.parse(game.players);
|
||||||
|
for (const p of players) {
|
||||||
|
sendPush(p.id, '🎲 Schocken', 'Das Spiel wurde vom Admin abgebrochen.');
|
||||||
|
}
|
||||||
|
res.json({ ok: true });
|
||||||
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -147,19 +147,63 @@ function DiceArea({ game, myId, onRoll, onReady, onEvaluate }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auswertungsphase
|
// Auswertungsphase — alle Würfel sichtbar
|
||||||
if (allDone) {
|
if (allDone) {
|
||||||
const isFirstPlayer = activePlayers[0]?.id === myId || players[0]?.id === myId;
|
const isFirstPlayer = activePlayers[0]?.id === myId || players[0]?.id === myId;
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{color:MY_COLOR,fontFamily:'monospace',fontSize:12,textAlign:'center',marginBottom:12}}>
|
<div style={{color:MY_COLOR,fontFamily:'monospace',fontSize:12,textAlign:'center',marginBottom:14}}>
|
||||||
Alle haben gewürfelt — Ergebnis auswerten!
|
🎲 Alle Becher oben — wer hat was?
|
||||||
</div>
|
</div>
|
||||||
|
{/* Alle Spieler mit Würfeln und Ergebnis */}
|
||||||
|
{activePlayers.map(p => {
|
||||||
|
const ps = round.playerStates?.[p.id];
|
||||||
|
const isP = p.id === myId;
|
||||||
|
return (
|
||||||
|
<div key={p.id} style={{
|
||||||
|
...S.card, padding:'10px 14px', marginBottom:8,
|
||||||
|
border: isP ? '1px solid rgba(78,205,196,0.3)' : '1px solid rgba(255,255,255,0.07)',
|
||||||
|
}}>
|
||||||
|
<div style={{display:'flex',alignItems:'center',gap:10,marginBottom:8}}>
|
||||||
|
<span style={{color:isP?MY_COLOR:'rgba(255,255,255,0.7)',fontFamily:'monospace',fontSize:12,fontWeight:700}}>
|
||||||
|
{p.username}{isP?' (du)':''}
|
||||||
|
</span>
|
||||||
|
{ps?.dark && <span style={{color:'rgba(255,255,255,0.4)',fontSize:10,fontFamily:'monospace'}}>🌑 war dunkel</span>}
|
||||||
|
{ps?.roll_count && <span style={{color:'rgba(255,255,255,0.3)',fontSize:10,fontFamily:'monospace',marginLeft:'auto'}}>{ps.roll_count}. Wurf</span>}
|
||||||
|
</div>
|
||||||
|
<div style={{display:'flex',gap:8,marginBottom:8}}>
|
||||||
|
{(ps?.dice||[null,null,null]).map((d,i) => <Die key={i} value={d} size={44}/>)}
|
||||||
|
</div>
|
||||||
|
{ps?.result && (
|
||||||
|
<div style={{
|
||||||
|
padding:'4px 10px', borderRadius:6, display:'inline-block',
|
||||||
|
background: ps.result.type==='schock_aus'?'rgba(239,68,68,0.15)':
|
||||||
|
ps.result.type==='julchen'?'rgba(255,230,109,0.15)':
|
||||||
|
ps.result.type==='general'?'rgba(78,205,196,0.12)':'rgba(255,255,255,0.05)',
|
||||||
|
border: ps.result.type==='schock_aus'?'1px solid rgba(239,68,68,0.4)':
|
||||||
|
ps.result.type==='julchen'?'1px solid rgba(255,230,109,0.4)':
|
||||||
|
ps.result.type==='general'?'1px solid rgba(78,205,196,0.3)':'1px solid rgba(255,255,255,0.08)',
|
||||||
|
color: ps.result.type==='schock_aus'?'#ef4444':
|
||||||
|
ps.result.type==='julchen'?'#ffe66d':
|
||||||
|
ps.result.type==='general'?MY_COLOR:'rgba(255,255,255,0.6)',
|
||||||
|
fontFamily:'monospace', fontSize:11,
|
||||||
|
}}>
|
||||||
|
{ps.result.label} {ps.result.scheiben > 0 ? `→ ${ps.result.scheiben} Scheibe${ps.result.scheiben!==1?'n':''}` : ''}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
{isFirstPlayer && (
|
{isFirstPlayer && (
|
||||||
<button onClick={onEvaluate} style={{...S.btn('#ffe66d'),width:'100%'}}>
|
<button onClick={onEvaluate} style={{...S.btn('#ffe66d'),width:'100%',marginTop:4}}>
|
||||||
✓ Runde auswerten & Scheiben verteilen
|
✓ Scheiben verteilen
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
|
{!isFirstPlayer && (
|
||||||
|
<div style={{color:'rgba(255,255,255,0.3)',fontFamily:'monospace',fontSize:11,textAlign:'center',padding:'8px 0'}}>
|
||||||
|
Warte auf {activePlayers[0]?.username} zum Auswerten…
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -613,6 +657,17 @@ export default function Schocken({ toast }) {
|
|||||||
<h2 style={{margin:0,fontSize:15,fontFamily:'monospace',color:'rgba(255,255,255,0.55)',letterSpacing:2,fontWeight:400}}>
|
<h2 style={{margin:0,fontSize:15,fontFamily:'monospace',color:'rgba(255,255,255,0.55)',letterSpacing:2,fontWeight:400}}>
|
||||||
🎲 SCHOCKEN
|
🎲 SCHOCKEN
|
||||||
</h2>
|
</h2>
|
||||||
|
<div style={{flex:1}}/>
|
||||||
|
{activeId && game?.status !== 'finished' && isAdmin && (
|
||||||
|
<button onClick={async () => {
|
||||||
|
if (!window.confirm('Spiel wirklich abbrechen? Keine Punkte werden vergeben.')) return;
|
||||||
|
try {
|
||||||
|
await apiAs(myRealId, `/tools/schocken/${activeId}/cancel`, { body:{} });
|
||||||
|
setActiveId(null); setGame(null); loadGames();
|
||||||
|
toast('Spiel abgebrochen.');
|
||||||
|
} catch(e) { toast(e.message||'Fehler','error'); }
|
||||||
|
}} style={S.btn('#ff6b9d', true)}>✕ Abbrechen</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!activeId
|
{!activeId
|
||||||
|
|||||||
Reference in New Issue
Block a user