Multiplayer: Spielräume mit Code, Ready-Start, geheime Rankings auf jedem Handy (Game-API + Sync-Frontend)
Build & Deploy / deploy (push) Successful in 19s

This commit is contained in:
Kimi Work
2026-10-10 17:22:55 +02:00
parent ad6230c335
commit 4e2eda855a
6 changed files with 972 additions and 390 deletions
+114
View File
@@ -0,0 +1,114 @@
// ── Client für die Game-API (Multiplayer-Spielräume) ──────
export type RaumStatus = 'lobby' | 'question' | 'ranking' | 'reveal' | 'scoreboard' | 'finished'
export interface SpielerInfo {
name: string
ready: boolean
istHost: boolean
rankingAbgegeben: boolean
}
export interface PlatzDetail {
platz: number
person: string
stimmen: number
punkte: number
}
export interface Ergebnis {
mehrheit: string[]
punkte: Record<string, number>
platzDetails: PlatzDetail[]
}
export interface RaumZustand {
code: string
status: RaumStatus
runde: number
anzahlRunden: number
frage: string
fragesteller: string
spieler: SpielerInfo[]
minSpieler: number
maxSpieler: number
alleReady: boolean
rankingsFertig: number
punkte: Record<string, number>
ergebnis: Ergebnis | null
ich: string
}
export interface RaumSession {
code: string
token: string
name: string
}
const SESSION_KEY = 'einsbisacht-raum-v1'
export function ladeSession(): RaumSession | null {
try {
const roh = localStorage.getItem(SESSION_KEY)
return roh ? (JSON.parse(roh) as RaumSession) : null
} catch {
return null
}
}
export function speichereSession(s: RaumSession | null) {
if (s) localStorage.setItem(SESSION_KEY, JSON.stringify(s))
else localStorage.removeItem(SESSION_KEY)
}
class ApiFehler extends Error {}
async function anfrage(methode: 'GET' | 'POST', pfad: string, body?: object, token?: string): Promise<RaumZustand> {
const res = await fetch(`/api/game/room${pfad}`, {
method: methode,
headers: {
'Content-Type': 'application/json',
...(token ? { 'X-Player-Token': token } : {}),
},
...(body ? { body: JSON.stringify(body) } : {}),
})
const daten = await res.json().catch(() => ({}))
if (!res.ok) throw new ApiFehler(daten.fehler || `Fehler ${res.status}`)
if (daten.zustand) return daten.zustand as RaumZustand
throw new ApiFehler('Unerwartete Antwort der Spiel-API')
}
/** Raum eröffnen; liefert Session direkt mit */
export async function raumErstellen(name: string): Promise<{ session: RaumSession; zustand: RaumZustand }> {
const res = await fetch('/api/game/room', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
})
const daten = await res.json().catch(() => ({}))
if (!res.ok || !daten.token) throw new ApiFehler(daten.fehler || 'Raum konnte nicht erstellt werden')
const session: RaumSession = { code: daten.code, token: daten.token, name }
speichereSession(session)
return { session, zustand: daten.zustand }
}
/** Raum beitreten; liefert Session direkt mit */
export async function raumBeitreten(code: string, name: string): Promise<{ session: RaumSession; zustand: RaumZustand }> {
const res = await fetch(`/api/game/room/${code.toUpperCase()}/join`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name }),
})
const daten = await res.json().catch(() => ({}))
if (!res.ok || !daten.token) throw new ApiFehler(daten.fehler || 'Beitritt fehlgeschlagen')
const session: RaumSession = { code: code.toUpperCase(), token: daten.token, name }
speichereSession(session)
return { session, zustand: daten.zustand }
}
export const zustandLaden = (s: RaumSession) => anfrage('GET', `/${s.code}`, undefined, s.token)
export const readySetzen = (s: RaumSession, ready: boolean) => anfrage('POST', `/${s.code}/ready`, { ready }, s.token)
export const frageStellen = (s: RaumSession, frage: string) => anfrage('POST', `/${s.code}/question`, { frage }, s.token)
export const rankingAbgeben = (s: RaumSession, reihenfolge: string[]) =>
anfrage('POST', `/${s.code}/ranking`, { reihenfolge }, s.token)
export const naechstePhase = (s: RaumSession) => anfrage('POST', `/${s.code}/next`, {}, s.token)
+487 -386
View File
File diff suppressed because it is too large Load Diff