Lobby: dice are thrown onto the table and rest at random spots; fix duplicate dice for the roller
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJhXuLD2Z6hv3Wqeq9UL9K
This commit is contained in:
+46
-30
@@ -116,11 +116,14 @@ function useFeed(id, onEvent, onRoll) {
|
||||
const live = useRef(false) // false until the history is loaded: old rolls are not animated again
|
||||
const cb = useRef({})
|
||||
cb.current = { onEvent, onRoll }
|
||||
const busy = useRef(false) // two overlapping polls (timer + right after a roll) would both deliver the same entries
|
||||
const poll = useCallback(async () => {
|
||||
if (document.hidden) return
|
||||
if (document.hidden || busy.current) return
|
||||
busy.current = true
|
||||
const r = await fetch(`/api/lobbies/${id}/messages?after=${last.current}`).catch(() => null)
|
||||
busy.current = false
|
||||
if (!r || !r.ok) return
|
||||
const fresh = await r.json()
|
||||
const fresh = (await r.json()).filter((m) => m.id > last.current)
|
||||
const wasLive = live.current
|
||||
live.current = true
|
||||
if (!fresh.length) return
|
||||
@@ -306,37 +309,50 @@ function Die({ sides, value, delay, mystery }) {
|
||||
)
|
||||
}
|
||||
|
||||
// the tray is the middle of the table: dice in the colour of whoever threw them
|
||||
// dice are thrown onto the table from a random side and come to rest at a random spot, in the colour of whoever threw them
|
||||
const placed = [] // spots of the dice still lying on the table, so new ones do not land on top of them
|
||||
function spots(n) {
|
||||
const out = []
|
||||
for (let i = 0; i < n; i++) {
|
||||
let p, tries = 0
|
||||
do { p = { x: 12 + Math.random() * 76, y: 22 + Math.random() * 58 }; tries++ } while (tries < 40 && [...placed, ...out].some((q) => Math.hypot((q.x - p.x) * 1.6, q.y - p.y) < 14))
|
||||
placed.push(p); if (placed.length > 12) placed.shift()
|
||||
const a = Math.random() * Math.PI * 2
|
||||
out.push({ ...p, sx: Math.round(Math.cos(a) * 700), sy: Math.round(Math.sin(a) * 450), sr: Math.round(Math.random() * 720 - 360), er: Math.round(Math.random() * 50 - 25) })
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
function ThrownDice({ m }) {
|
||||
const sec = m.kind === 'roll' && m.roll.mystery && !m.mine
|
||||
const n = m.kind === 'check' ? 1 : m.roll.n
|
||||
const [pos] = useState(() => spots(n))
|
||||
return pos.map((p, i) => (
|
||||
<span key={i} className="thrown" style={{ left: `${p.x}%`, top: `${p.y}%`, '--sx': `${p.sx}px`, '--sy': `${p.sy}px`, '--sr': `${p.sr}deg`, '--er': `${p.er}deg`,
|
||||
'--dc': sec ? '#8b8f85' : m.color || 'var(--noxium)', animationDelay: `${i * 90}ms` }}>
|
||||
{m.kind === 'check' ? <Die sides={100} value={m.check.roll} delay={0} />
|
||||
: <Die sides={m.roll.sides} value={m.roll.results ? m.roll.results[i] : 0} delay={i * 120} mystery={!m.roll.results} />}
|
||||
</span>
|
||||
))
|
||||
}
|
||||
|
||||
function DiceTray({ rolls }) {
|
||||
if (!rolls.length) return null
|
||||
return (
|
||||
<div className="dicetray" aria-hidden="true">
|
||||
{rolls.map((m) => {
|
||||
const style = { '--dc': m.color || 'var(--noxium)' }
|
||||
if (m.kind === 'check') {
|
||||
const c = m.check
|
||||
return (
|
||||
<div key={m.key} className="trayroll" style={style}>
|
||||
<div className="small"><b style={{ color: m.color }}>{m.mine ? 'You' : m.from}</b> – {c.what}{c.manual ? ' · own dice' : ''}</div>
|
||||
<div className="trayrow"><Die sides={100} value={c.roll} delay={0} />
|
||||
<span className="trayout"><span className={`out ${c.outcome}`}>{OUTCOME[c.outcome]}</span>
|
||||
{c.target !== undefined && <small>{c.roll} vs {c.target}</small>}</span></div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const t = rollText(m)
|
||||
const sec = m.roll.mystery && !m.mine
|
||||
return (
|
||||
<div key={m.key} className="trayroll" style={sec ? { '--dc': '#8b8f85' } : style}>
|
||||
<div className="small"><b style={{ color: m.color }}>{m.mine ? 'You' : m.from}</b> rolled <strong>{t.dice}</strong>
|
||||
{m.to ? (m.to === m.from ? ' · only you' : ` · private${m.mine ? ` to ${m.to}` : ''}`) : ''}{m.roll.manual ? ' · own dice' : ''}</div>
|
||||
<div className="trayrow">
|
||||
{Array.from({ length: t.n }, (_, i) => <Die key={i} sides={m.roll.sides} value={m.roll.results ? m.roll.results[i] : 0} delay={i * 120} mystery={!m.roll.results} />)}
|
||||
{t.n > 1 && t.sum !== null && <span className="traytotal">= {t.sum}</span>}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
{rolls.map((m) => <ThrownDice key={m.key} m={m} />)}
|
||||
<div className="tablelog">
|
||||
{rolls.map((m) => {
|
||||
const who = <b style={{ color: m.color }}>{m.mine ? 'You' : m.from}</b>
|
||||
if (m.kind === 'check') {
|
||||
const c = m.check
|
||||
return <div key={m.key} className="trayroll">{who} – {c.what}{c.manual ? ' · own dice' : ''}: <span className={`out ${c.outcome}`}>{OUTCOME[c.outcome]}</span>{c.target !== undefined && <small> ({c.roll} vs {c.target})</small>}</div>
|
||||
}
|
||||
const t = rollText(m)
|
||||
return <div key={m.key} className="trayroll">{who} rolled <strong>{t.dice}</strong>{m.to ? (m.to === m.from ? ' · only you' : ` · private${m.mine ? ` to ${m.to}` : ''}`) : ''}{m.roll.manual ? ' · own dice' : ''}
|
||||
{t.sum !== null ? <> = <b>{t.sum}</b></> : ' = ?'}</div>
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -505,7 +521,7 @@ function LobbyView({ id, rules, user }) {
|
||||
const onRoll = useCallback((m) => {
|
||||
const key = `${m.id}`
|
||||
setTray((t) => [...t.slice(-2), { ...m, key }])
|
||||
setTimeout(() => setTray((t) => t.filter((x) => x.key !== key)), 9000)
|
||||
setTimeout(() => setTray((t) => t.filter((x) => x.key !== key)), 20000)
|
||||
}, [])
|
||||
const feed = useFeed(id, load, onRoll)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user