import { useEffect, useMemo, useRef, useState } from 'react'; import { playSound, useCollection, useIdentity } from '@deplixo/sdk'; import { Lobby } from './components/Lobby.jsx'; import { GameRoom } from './components/GameRoom.jsx'; const SCORE_COLLECTION = 'player-scores'; const CORRECT_GUESS_POINTS = 10; const ROUND_TIME_SECONDS = 60; const WARNING_TIME_SECONDS = 10; function getPlayerKey(user) { return user?.id || user?.email || user?.name || 'guest'; } function getDisplayName(user) { return user?.name || user?.displayName || user?.email || 'Player'; } function guessPointsForElapsedSeconds(elapsedSeconds) { const clamped = Math.max(0, Math.min(ROUND_TIME_SECONDS, Math.floor(elapsedSeconds || 0))); const speedBonus = Math.ceil((ROUND_TIME_SECONDS - clamped) / 6); return Math.max(2, CORRECT_GUESS_POINTS + speedBonus); } function App() { const { user, loading } = useIdentity(); const [roomId, setRoomId] = useState(null); const [currentDrawer, setCurrentDrawer] = useState(null); const [roundStartedAt, setRoundStartedAt] = useState(null); const [lastCorrectGuess, setLastCorrectGuess] = useState(null); const [timeWarningTriggered, setTimeWarningTriggered] = useState(false); const [lastRoundAnnouncedAt, setLastRoundAnnouncedAt] = useState(null); const [lastGuessSoundAt, setLastGuessSoundAt] = useState(0); const { items: scoreItems = [], loading: scoresLoading, add: addScore, update: updateScore } = useCollection(SCORE_COLLECTION, { personal: true }); const scoreMap = useMemo(() => { const map = new Map(); scoreItems.forEach((item) => { const key = item.playerKey || item.userId || item.id; if (key) map.set(key, item); }); return map; }, [scoreItems]); useEffect(() => { if (!user) return; const playerKey = getPlayerKey(user); const existing = scoreMap.get(playerKey); if (!existing && !scoresLoading) { addScore({ playerKey, userId: user.id || null, name: getDisplayName(user), score: 0, updatedAt: Date.now(), }).catch(() => {}); } }, [user, addScore, scoreMap, scoresLoading]); useEffect(() => { if (!roomId || !roundStartedAt) return; setTimeWarningTriggered(false); setLastRoundAnnouncedAt(null); playSound('@ding'); setLastRoundAnnouncedAt(roundStartedAt); }, [roomId, roundStartedAt]); useEffect(() => { if (!roomId || !roundStartedAt) return; const tick = () => { const elapsedSeconds = (Date.now() - roundStartedAt) / 1000; const remaining = ROUND_TIME_SECONDS - elapsedSeconds; if (remaining <= WARNING_TIME_SECONDS && remaining > 0 && !timeWarningTriggered) { playSound('@beep'); setTimeWarningTriggered(true); } if (remaining > WARNING_TIME_SECONDS && timeWarningTriggered) { setTimeWarningTriggered(false); } }; tick(); const id = setInterval(tick, 1000); return () => clearInterval(id); }, [roomId, roundStartedAt, timeWarningTriggered]); async function recordCorrectGuess({ guesser, elapsedSeconds }) { if (!guesser) return; const now = Date.now(); if (now - lastGuessSoundAt > 600) { playSound('@success'); setLastGuessSoundAt(now); } const playerKey = getPlayerKey(guesser); const points = guessPointsForElapsedSeconds(elapsedSeconds); const existing = scoreMap.get(playerKey); const nextScore = (existing?.score || 0) + points; const payload = { playerKey, userId: guesser.id || null, name: getDisplayName(guesser), score: nextScore, updatedAt: Date.now(), lastCorrectGuessAt: Date.now(), lastRoundPoints: points, }; try { if (existing?.id) { await updateScore(existing.id, payload); } else { await addScore(payload); } setLastCorrectGuess({ name: getDisplayName(guesser), points, elapsedSeconds, at: Date.now() }); } catch (err) { console.error('Failed to record score', err); } } if (loading) { return (

Entering the studio...

); } return (
{!roomId ? ( ) : ( setRoomId(null)} scores={scoreItems} currentDrawer={currentDrawer} onDrawerChange={setCurrentDrawer} roundStartedAt={roundStartedAt} onRoundStart={setRoundStartedAt} onCorrectGuess={recordCorrectGuess} lastCorrectGuess={lastCorrectGuess} /> )}
); } function WordBankManager() { const { items: words = [], loading, add, remove } = useCollection('word-bank'); const [word, setWord] = useState(''); const [error, setError] = useState(''); const [saving, setSaving] = useState(false); async function handleAdd(e) { e.preventDefault(); const value = word.trim(); if (!value) { setError('Enter a word to add.'); return; } setSaving(true); setError(''); try { await add({ word: value.toLowerCase(), createdAt: Date.now() }); setWord(''); } catch (err) { setError('Could not add word.'); } finally { setSaving(false); } } return (

Word Bank

setWord(e.target.value)} placeholder="Add a word" />
{error ?

{error}

: null} {loading ? (

Loading stored words...

) : ( )}
); } ReactDOM.createRoot(document.getElementById("root")).render();