feat: Schocken - erstes spielbares Grundgerüst (admin only)

This commit is contained in:
2026-07-05 00:08:26 +02:00
parent 772cbc62ba
commit 19619cb57c
5 changed files with 1204 additions and 8 deletions

View File

@@ -0,0 +1,581 @@
const express = require('express');
const db = require('../../db');
const { authenticate } = require('../../middleware/auth');
const router = express.Router();
// ── DB-Migration ──────────────────────────────────────────────────────────────
(function migrate() {
db.exec(`
CREATE TABLE IF NOT EXISTS schocken_games (
id INTEGER PRIMARY KEY AUTOINCREMENT,
players TEXT NOT NULL, -- JSON: [{id, username, order}]
status TEXT NOT NULL DEFAULT 'lobby',
-- status: lobby | half1 | half2 | endkampf | finished
phase_status TEXT NOT NULL DEFAULT 'playing',
-- phase_status: playing | finished
stock INTEGER NOT NULL DEFAULT 15, -- Scheiben auf dem Stock
player_chips TEXT NOT NULL DEFAULT '{}', -- JSON: {userId: chipCount}
has_16th INTEGER, -- userId der die 16. Scheibe hat
loser_h1 INTEGER, -- userId Verlierer Hälfte 1
loser_h2 INTEGER, -- userId Verlierer Hälfte 2
current_round TEXT, -- JSON: aktueller Rundenstand
round_number INTEGER NOT NULL DEFAULT 1,
beginner_id INTEGER, -- wer fängt die Runde an
current_player_idx INTEGER NOT NULL DEFAULT 0,
max_rolls INTEGER, -- vom Beginner bestimmt
first_round INTEGER NOT NULL DEFAULT 1, -- 1 = erste Runde der Hälfte
drink_losses TEXT NOT NULL DEFAULT '{}', -- {userId: count}
created_at TEXT NOT NULL DEFAULT (datetime('now','localtime')),
updated_at TEXT NOT NULL DEFAULT (datetime('now','localtime'))
);
`);
})();
// ── Würfellogik ───────────────────────────────────────────────────────────────
function rollDice(count = 3) {
return Array.from({ length: count }, () => Math.floor(Math.random() * 6) + 1);
}
// Würfel klassifizieren
function classifyDice(dice) {
const sorted = [...dice].sort((a,b) => a-b);
const [a,b,c] = sorted;
// Schock aus: 1-1-1
if (a===1 && b===1 && c===1) return { type:'schock_aus', scheiben: 0, label:'Schock aus! 💀' };
// Julchen: 4-2-1 (unsortiert erkannt)
if (dice.includes(1) && dice.includes(2) && dice.includes(4)) return { type:'julchen', scheiben:7, label:'Julchen! 🎉' };
// General: 3x gleich (außer 3x1)
if (a===b && b===c) return { type:'general', scheiben:3, label:`General ${a}er! 🎲`, value: a };
// Schock: 2x Eins + andere
if (a===1 && b===1) {
const schockVal = c;
const label = schockVal === 2 ? 'Schock doof 😅' : `Schock ${schockVal}!`;
return { type:'schock', scheiben: schockVal, label, value: schockVal };
}
// Straße: aufeinanderfolgende Zahlen (nur wenn alle 3 auf einmal gewürfelt)
if (c-a===2 && b-a===1) return { type:'strasse', scheiben:2, label:`Straße ${a}-${b}-${c}!`, value: a };
// Normale Zahl
return { type:'normal', scheiben:1, label:'Normal', value: sorted.reduce((s,v)=>s+v,0) };
}
// Wertungsvergleich: wer hat schlechtere Würfel (bekommt Scheiben)
// Gibt -1 wenn a schlechter, 1 wenn b schlechter, 0 bei echtem Gleichstand
const TYPE_ORDER = ['normal','strasse','general','schock','julchen','schock_aus'];
function compareResults(a, b) {
const ai = TYPE_ORDER.indexOf(a.result.type);
const bi = TYPE_ORDER.indexOf(b.result.type);
if (ai !== bi) return ai < bi ? -1 : 1; // niedrigerer Typ = schlechter
// Gleicher Typ — Detailvergleich
if (a.result.type === 'normal') {
// Niedrigere Summe ist schlechter — aber beide haben immer 1 Scheibe, kein Unterschied im Typ
// Tatsächlich keine Hierarchie bei normalen Würfen außer Scheiben=1
return 0;
}
if (a.result.type === 'strasse') {
if (a.result.value !== b.result.value) return a.result.value < b.result.value ? -1 : 1;
return 0;
}
if (a.result.type === 'schock') {
if (a.result.value !== b.result.value) return a.result.value < b.result.value ? -1 : 1;
return 0;
}
if (a.result.type === 'general') {
if (a.result.value !== b.result.value) return a.result.value < b.result.value ? -1 : 1;
return 0;
}
return 0;
}
function sendPush(userId, title, message) {
const cfg = db.prepare('SELECT user_key, app_token FROM pushover_settings WHERE user_id=?').get(userId);
if (!cfg?.app_token || !cfg?.user_key) return;
fetch('https://api.pushover.net/1/messages.json', {
method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({ token:cfg.app_token, user:cfg.user_key, title, message, priority:0 }),
}).catch(() => {});
}
const uid = req => req.user.id;
// ── Alle Spiele des Users ─────────────────────────────────────────────────────
router.get('/', authenticate, (req, res) => {
const me = uid(req);
const games = db.prepare(`
SELECT * FROM schocken_games
WHERE players LIKE ? ORDER BY updated_at DESC LIMIT 20
`).all(`%"id":${me}%`);
res.json(games.map(g => ({
...g,
players: JSON.parse(g.players),
player_chips: JSON.parse(g.player_chips),
drink_losses: JSON.parse(g.drink_losses),
current_round: g.current_round ? JSON.parse(g.current_round) : null,
})));
});
// ── Users für Einladen ────────────────────────────────────────────────────────
router.get('/users', authenticate, (req, res) => {
const me = uid(req);
const isAdmin = req.user?.role === 'admin';
const users = isAdmin
? db.prepare('SELECT id, username FROM users WHERE id != ? ORDER BY username').all(me)
: db.prepare('SELECT id, username FROM users WHERE id != ? AND hidden != 1 ORDER BY username').all(me);
res.json(users);
});
// ── Einzelnes Spiel ───────────────────────────────────────────────────────────
router.get('/:id', authenticate, (req, res) => {
const me = uid(req);
const game = db.prepare('SELECT * FROM schocken_games WHERE id=?').get(req.params.id);
if (!game) return res.status(404).json({ error: 'Nicht gefunden' });
const players = JSON.parse(game.players);
if (!players.find(p => p.id === me)) return res.status(403).json({ error: 'Kein Zugriff' });
res.json({
...game,
players,
player_chips: JSON.parse(game.player_chips),
drink_losses: JSON.parse(game.drink_losses),
current_round: game.current_round ? JSON.parse(game.current_round) : null,
});
});
// ── Neues Spiel erstellen ─────────────────────────────────────────────────────
router.post('/', authenticate, (req, res) => {
const me = uid(req);
const { player_ids = [] } = req.body;
if (player_ids.length < 1) return res.status(400).json({ error: 'Mindestens 1 weiterer Spieler' });
const allIds = [me, ...player_ids.filter(id => id !== me)];
const userRows = db.prepare(`SELECT id, username FROM users WHERE id IN (${allIds.map(()=>'?').join(',')}) ORDER BY username`).all(...allIds);
// Reihenfolge: Ersteller zuerst, dann die anderen
const ordered = [
userRows.find(u => u.id === me),
...allIds.slice(1).map(id => userRows.find(u => u.id === id)).filter(Boolean),
];
const players = ordered.map((u, i) => ({ id: u.id, username: u.username, order: i }));
const playerChips = Object.fromEntries(players.map(p => [p.id, 0]));
const drinkLosses = Object.fromEntries(players.map(p => [p.id, 0]));
const result = db.prepare(`
INSERT INTO schocken_games (players, stock, player_chips, drink_losses, beginner_id, status)
VALUES (?, 15, ?, ?, ?, 'lobby')
`).run(JSON.stringify(players), JSON.stringify(playerChips), JSON.stringify(drinkLosses), me);
// Pushover an andere Spieler
const myName = ordered[0].username;
for (const p of players.slice(1)) {
sendPush(p.id, '🎲 Schocken', `${myName} lädt dich zu einer Runde Schocken ein!`);
}
res.json({ id: result.lastInsertRowid });
});
// ── Spiel starten (Lobby → Hälfte 1) ─────────────────────────────────────────
router.post('/:id/start', authenticate, (req, res) => {
const me = uid(req);
const game = db.prepare('SELECT * FROM schocken_games WHERE id=?').get(req.params.id);
if (!game) return res.status(404).json({ error: 'Nicht gefunden' });
const players = JSON.parse(game.players);
if (players[0].id !== me) return res.status(403).json({ error: 'Nur der Ersteller kann starten' });
if (game.status !== 'lobby') return res.status(400).json({ error: 'Spiel läuft schon' });
db.prepare(`UPDATE schocken_games SET status='half1', first_round=1, beginner_id=?,
current_player_idx=0, updated_at=datetime('now','localtime') WHERE id=?`).run(me, game.id);
for (const p of players.slice(1)) {
sendPush(p.id, '🎲 Schocken', 'Das Spiel beginnt! Du bist dran.');
}
res.json(getGame(game.id, me));
});
// ── Würfeln ───────────────────────────────────────────────────────────────────
router.post('/:id/roll', authenticate, (req, res) => {
const me = uid(req);
const { keep_dice = [], flip_sixes = false, go_dark = false } = req.body;
// keep_dice: array of indices (0-2) der Würfel die stehen bleiben
// flip_sixes: zwei Sechsen zu Eins umdrehen
// go_dark: dunkel legen nach diesem Wurf
const game = db.prepare('SELECT * FROM schocken_games WHERE id=?').get(req.params.id);
if (!game) return res.status(404).json({ error: 'Nicht gefunden' });
const players = JSON.parse(game.players);
const activePlayers = getActivePlayers(game);
const currentPlayer = activePlayers[game.current_player_idx % activePlayers.length];
if (!currentPlayer || currentPlayer.id !== me)
return res.status(400).json({ error: 'Nicht dein Zug' });
let round = game.current_round ? JSON.parse(game.current_round) : initRound(game, activePlayers);
const myState = round.playerStates[me];
if (myState.done) return res.status(400).json({ error: 'Du hast schon gewürfelt' });
if (myState.dark) return res.status(400).json({ error: 'Du bist dunkel' });
// Flip sixes: nur wenn noch Würfe übrig und nicht letzter Wurf
if (flip_sixes) {
const sixes = myState.dice.reduce((acc, v, i) => v === 6 ? [...acc, i] : acc, []);
if (sixes.length >= 2) {
myState.dice[sixes[0]] = 1;
myState.dice[sixes[1]] = 1;
// Kostet keinen Wurf, braucht aber noch einen
}
}
// Würfeln
const rollCount = myState.roll_count;
const isFirstRound = !!game.first_round;
// Neue Würfel für nicht-stehen-gelassene
if (!isFirstRound || rollCount === 0) {
const newDice = rollDice(3 - keep_dice.length);
let newIdx = 0;
for (let i = 0; i < 3; i++) {
if (!keep_dice.includes(i)) myState.dice[i] = newDice[newIdx++];
}
}
myState.roll_count++;
// Straße nur gültig wenn im ersten Wurf (keine stehen gelassenen Würfel)
const classified = classifyDice(myState.dice);
if (classified.type === 'strasse' && (keep_dice.length > 0 || myState.roll_count > 1)) {
// Straße ungültig — als normal werten
classified.type = 'normal';
classified.scheiben = 1;
classified.label = 'Normal (Straße ungültig)';
}
// Max rolls durch Beginner bestimmt
const maxRolls = round.max_rolls || 3;
const isDark = go_dark || myState.roll_count >= maxRolls || isFirstRound;
const isDone = isDark || myState.roll_count >= maxRolls || isFirstRound;
myState.dark = go_dark || (myState.roll_count >= maxRolls && !isDone) || isFirstRound;
myState.done = isDone;
myState.result = isDone && !go_dark ? classified : null; // bei dunkel: result erst am Ende
// Wenn Beginner (erster Spieler): max_rolls setzen
if (currentPlayer.id === round.beginner_id && round.max_rolls === null) {
round.max_rolls = myState.roll_count;
}
// Nächsten Spieler
const nextIdx = (game.current_player_idx + 1) % activePlayers.length;
const allDone = activePlayers.every(p => round.playerStates[p.id]?.done);
round.playerStates[me] = myState;
if (allDone) {
// Alle dunkel aufdecken
for (const p of activePlayers) {
const ps = round.playerStates[p.id];
if (!ps.result) {
ps.result = classifyDice(ps.dice);
// Straße bei dunkel-legen auch ungültig wenn keep_dice vorhanden (nicht trackbar nachträglich)
}
}
round.phase = 'reveal';
}
db.prepare(`UPDATE schocken_games SET current_round=?, current_player_idx=?,
updated_at=datetime('now','localtime') WHERE id=?`)
.run(JSON.stringify(round), allDone ? game.current_player_idx : nextIdx, game.id);
// Pushover an nächsten Spieler
if (!allDone) {
const next = activePlayers[nextIdx];
if (next && next.id !== me) {
sendPush(next.id, '🎲 Schocken', `${currentPlayer.username} hat gewürfelt — du bist dran!`);
}
}
res.json(getGame(game.id, me));
});
// ── Runde auswerten ───────────────────────────────────────────────────────────
router.post('/:id/evaluate', authenticate, (req, res) => {
const me = uid(req);
const game = db.prepare('SELECT * FROM schocken_games WHERE id=?').get(req.params.id);
if (!game) return res.status(404).json({ error: 'Nicht gefunden' });
const players = JSON.parse(game.players);
const activePlayers = getActivePlayers(game);
if (players[0].id !== me && activePlayers[0]?.id !== me)
return res.status(403).json({ error: 'Kein Zugriff' });
const round = JSON.parse(game.current_round);
if (round.phase !== 'reveal') return res.status(400).json({ error: 'Noch nicht alle fertig' });
const playerChips = JSON.parse(game.player_chips);
let stock = game.stock;
// Schock aus prüfen
const schockAus = activePlayers.find(p => round.playerStates[p.id]?.result?.type === 'schock_aus');
if (schockAus) {
return handleSchockAus(game, round, activePlayers, res, me);
}
// Bestes Ergebnis finden (höchster Typ)
const results = activePlayers.map(p => ({
player: p,
state: round.playerStates[p.id],
result: round.playerStates[p.id]?.result,
rolls: round.playerStates[p.id]?.roll_count || 1,
})).filter(r => r.result);
// Schlechtestes Ergebnis = bekommt Scheiben
results.sort((a, b) => {
const cmp = compareResults(a, b);
if (cmp !== 0) return cmp; // -1 wenn a schlechter
// Gleicher Typ: mehr Würfe = schlechter
if (a.rolls !== b.rolls) return b.rolls - a.rolls; // mehr Würfe = schlechter (hinten)
// Nachleger: später in Reihenfolge = schlechter
return activePlayers.indexOf(b.player) - activePlayers.indexOf(a.player);
});
const loser = results[0]; // schlechtestes Ergebnis
const scheiben = loser.result.scheiben;
// Scheiben verteilen
let fromStock = 0;
let newStock = stock;
if (stock >= scheiben) {
fromStock = scheiben;
newStock = stock - scheiben;
} else {
fromStock = stock;
newStock = 0;
}
const fromOthers = scheiben - fromStock;
playerChips[loser.player.id] = (playerChips[loser.player.id] || 0) + scheiben;
// Wenn vom Stock nicht genug: Rest von aktivsten Spielern nehmen (noch nicht implementiert vereinfacht)
if (fromOthers > 0) {
// Scheiben von anderen Spielern umverteilen (von denen mit meisten Scheiben)
const donors = activePlayers
.filter(p => p.id !== loser.player.id && playerChips[p.id] > 0)
.sort((a,b) => playerChips[b.id] - playerChips[a.id]);
let remaining = fromOthers;
for (const donor of donors) {
if (remaining <= 0) break;
const take = Math.min(playerChips[donor.id], remaining);
playerChips[donor.id] -= take;
remaining -= take;
}
}
// Hälfte/Endkampf vorbei? Prüfen ob Stock leer und nur einer Scheiben hat
const playersWithChips = activePlayers.filter(p => playerChips[p.id] > 0);
const phaseOver = newStock === 0 && playersWithChips.length <= 1;
let newStatus = game.status;
let newPhaseStatus = 'playing';
let loserH1 = game.loser_h1;
let loserH2 = game.loser_h2;
let has16th = game.has_16th;
let eventMsg = null;
if (phaseOver) {
const phaseLoser = playersWithChips[0] || loser.player;
if (game.status === 'half1') {
// Verlierer H1 bestimmt
loserH1 = phaseLoser.id;
has16th = phaseLoser.id;
newStatus = 'half2';
// Chips reset, Stock reset
Object.keys(playerChips).forEach(k => { playerChips[k] = 0; });
newStock = 15;
eventMsg = `${phaseLoser.username} hat die erste Hälfte verloren. Die erste Hälfte kostet nur Nerven! 😅`;
// Pushover
for (const p of players) {
sendPush(p.id, '🎲 Schocken', eventMsg);
}
} else if (game.status === 'half2') {
loserH2 = phaseLoser.id;
if (loserH1 === loserH2) {
// Doppelfeige!
newStatus = 'finished';
newPhaseStatus = 'finished';
const dl = JSON.parse(game.drink_losses);
dl[phaseLoser.id] = (dl[phaseLoser.id] || 0) + 1;
eventMsg = `${phaseLoser.username} ist DOPPELFEIGE! 🪶 Die Runde ist vorbei.`;
db.prepare(`UPDATE schocken_games SET status=?, phase_status=?, loser_h2=?, has_16th=?,
player_chips=?, stock=?, drink_losses=?, current_round=NULL,
updated_at=datetime('now','localtime') WHERE id=?`)
.run(newStatus, newPhaseStatus, loserH2, has16th, JSON.stringify(playerChips),
newStock, dl, game.id);
for (const p of players) sendPush(p.id, '🎲 Schocken', eventMsg);
return res.json(getGame(game.id, me));
}
// Endkampf
newStatus = 'endkampf';
Object.keys(playerChips).forEach(k => { playerChips[k] = 0; });
newStock = 15;
eventMsg = `${phaseLoser.username} verliert die zweite Hälfte! Endkampf: ${players.find(p=>p.id===loserH1)?.username} vs ${phaseLoser.username}`;
for (const p of players) sendPush(p.id, '🎲 Schocken', eventMsg);
} else if (game.status === 'endkampf') {
newStatus = 'finished';
newPhaseStatus = 'finished';
const dl = JSON.parse(game.drink_losses);
dl[phaseLoser.id] = (dl[phaseLoser.id] || 0) + 1;
eventMsg = `${phaseLoser.username} hat den Endkampf verloren und muss die nächste Runde zahlen! 🍺`;
db.prepare(`UPDATE schocken_games SET status=?, phase_status=?, loser_h2=?,
player_chips=?, stock=?, drink_losses=?, current_round=NULL,
updated_at=datetime('now','localtime') WHERE id=?`)
.run(newStatus, newPhaseStatus, loserH2, JSON.stringify(playerChips), newStock, JSON.stringify(dl), game.id);
for (const p of players) sendPush(p.id, '🎲 Schocken', eventMsg);
return res.json(getGame(game.id, me));
}
}
// Nächste Runde vorbereiten
const nextBeginner = loser.player;
db.prepare(`UPDATE schocken_games SET player_chips=?, stock=?, status=?, loser_h1=?, loser_h2=?,
has_16th=?, beginner_id=?, current_round=NULL, current_player_idx=0, first_round=0,
updated_at=datetime('now','localtime') WHERE id=?`)
.run(JSON.stringify(playerChips), newStock, newStatus, loserH1, loserH2,
has16th, nextBeginner.id, game.id);
// Pushover an Verlierer der Runde (er fängt an)
sendPush(loser.player.id, '🎲 Schocken', `Du hast die Runde verloren und fängst die nächste an! (${scheiben} Scheibe${scheiben!==1?'n':''})`);
res.json({ ...getGame(game.id, me), round_result: { loser: loser.player, scheiben, eventMsg } });
});
// ── Becher umdrehen (Runde bereit signalisieren) ──────────────────────────────
router.post('/:id/ready', authenticate, (req, res) => {
const me = uid(req);
const game = db.prepare('SELECT * FROM schocken_games WHERE id=?').get(req.params.id);
if (!game) return res.status(404).json({ error: 'Nicht gefunden' });
const players = JSON.parse(game.players);
const playerChips = JSON.parse(game.player_chips);
const activePlayers = getActivePlayers(game);
// Spieler der eigentlich raus wäre aber trotzdem ready drückt → wird wieder aktiv (Falle!)
const isActuallyActive = activePlayers.find(p => p.id === me);
const isOut = !isActuallyActive && playerChips[me] === 0 && game.stock === 0;
let round = game.current_round ? JSON.parse(game.current_round) : null;
if (!round) {
round = initRound(game, isOut ? [...activePlayers, players.find(p=>p.id===me)] : activePlayers);
}
round.ready = round.ready || {};
round.ready[me] = true;
// Wenn isOut und drückt trotzdem: wird als "gefangen" markiert
if (isOut) {
round.trapped = round.trapped || [];
if (!round.trapped.includes(me)) round.trapped.push(me);
}
db.prepare(`UPDATE schocken_games SET current_round=?, updated_at=datetime('now','localtime') WHERE id=?`)
.run(JSON.stringify(round), game.id);
res.json(getGame(game.id, me));
});
// ── Hilfsfunktionen ───────────────────────────────────────────────────────────
function getActivePlayers(game) {
const players = JSON.parse(game.players);
const playerChips = JSON.parse(game.player_chips);
const stock = game.stock;
if (game.status === 'endkampf') {
return players.filter(p => p.id === game.loser_h1 || p.id === game.loser_h2);
}
// Wenn Stock noch voll oder erste Runde: alle mitspielen
if (stock > 0) return players;
// Stock leer: nur Spieler mit Scheiben
return players.filter(p => playerChips[p.id] > 0);
}
function initRound(game, activePlayers) {
const playerStates = {};
for (const p of activePlayers) {
playerStates[p.id] = {
dice: [null, null, null],
roll_count: 0,
done: false,
dark: false,
result: null,
};
}
return {
phase: 'rolling',
beginner_id: game.beginner_id,
max_rolls: game.first_round ? 1 : null,
ready: {},
trapped: [],
playerStates,
};
}
function handleSchockAus(game, round, activePlayers, res, me) {
const players = JSON.parse(game.players);
const playerChips = JSON.parse(game.player_chips);
// Alle Scheiben (Stock + Spieler) → schlechtester Spieler
const allChips = Object.values(playerChips).reduce((s,v) => s+v, 0) + game.stock;
// Wer hat die wenigsten Punkte (außer dem der Schock aus hat)?
const schockAusPlayer = activePlayers.find(p => round.playerStates[p.id]?.result?.type === 'schock_aus');
const others = activePlayers.filter(p => p.id !== schockAusPlayer?.id);
// Vereinfacht: Schock aus Spieler hat gewonnen, alle Scheiben gehen an den schlechtesten der anderen
// (Schock aus steht über allem)
const newChips = Object.fromEntries(Object.keys(playerChips).map(k => [k, 0]));
// Verlierer = wer die wenigsten Punkte hat unter den anderen
// Bei nur einem anderen: der verliert
const phaseLoser = others.length > 0 ? others[others.length - 1] : schockAusPlayer;
newChips[phaseLoser.id] = allChips;
let newStatus = game.status;
let loserH1 = game.loser_h1;
let has16th = game.has_16th;
if (game.status === 'half1') {
loserH1 = phaseLoser.id;
has16th = phaseLoser.id;
newStatus = 'half2';
Object.keys(newChips).forEach(k => { newChips[k] = 0; });
const eventMsg = `SCHOCK AUS! ${phaseLoser.username} verliert die erste Hälfte! Die erste Hälfte kostet nur Nerven! 😅`;
for (const p of players) sendPush(p.id, '🎲 Schocken', eventMsg);
db.prepare(`UPDATE schocken_games SET player_chips=?, stock=15, status=?, loser_h1=?, has_16th=?,
beginner_id=?, current_round=NULL, current_player_idx=0, first_round=1,
updated_at=datetime('now','localtime') WHERE id=?`)
.run(JSON.stringify(newChips), newStatus, loserH1, has16th, phaseLoser.id, game.id);
}
res.json({ ...getGame(game.id, me), schock_aus: true, phase_loser: phaseLoser });
}
function getGame(id, requesterId) {
const game = db.prepare('SELECT * FROM schocken_games WHERE id=?').get(id);
if (!game) return null;
return {
...game,
players: JSON.parse(game.players),
player_chips: JSON.parse(game.player_chips),
drink_losses: JSON.parse(game.drink_losses),
current_round: game.current_round ? JSON.parse(game.current_round) : null,
};
}
module.exports = router;