Time sequential Richese auctions
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
e7520e8679
commit
7fc6f1cd43
15 files changed
+692
-38
No files matched your search
@@ -272,6 +272,7 @@ import {
|
||||
} from '@/game/board';
|
||||
import type { Action, GameView, Plan } from '@/game/engine';
|
||||
import { AuctionCountdown } from './auction-countdown';
|
||||
import { RicheseAuctionCountdown } from './richese-auction-countdown';
|
||||
import { PLAYER_CIRCLE_SECTORS } from '@/game/player-positions';
|
||||
import { SeatAutopilot } from './seat-autopilot';
|
||||
import {
|
||||
@@ -2140,6 +2141,11 @@ export function GameTable({
|
||||
playerName={g.players.find(player => player.id === g.auction!.active)?.name ?? 'The current bidder'}
|
||||
paused={g.roomControl?.paused || g.roomControl?.closed} />
|
||||
)}
|
||||
{g.status === 'playing' && g.phase === 3 && g.richeseAuction && g.richeseAuction.method !== 'silent' && (
|
||||
<RicheseAuctionCountdown clock={g.auctionClock} method={g.richeseAuction.method}
|
||||
playerName={g.players.find(player => player.id === g.richeseAuction!.active)?.name ?? 'The current bidder'}
|
||||
paused={g.roomControl?.paused || g.roomControl?.closed} />
|
||||
)}
|
||||
{g.guildBetrayalReaction ? (
|
||||
<NexusGuildBetrayal game={g} act={act} busy={reactionBusy} />
|
||||
) : g.richeseBetrayalReaction ? (
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
import type { AuctionClockView } from '@/game/auction-clock';
|
||||
import type { RicheseAuctionMethod } from '@/game/richese-auction';
|
||||
|
||||
/** Display only: the authoritative room clock performs the expiry action. */
|
||||
export function RicheseAuctionCountdown({ clock, playerName, method, paused = false }: {
|
||||
clock: AuctionClockView | null;
|
||||
playerName: string;
|
||||
method: Exclude<RicheseAuctionMethod, 'silent'>;
|
||||
paused?: boolean;
|
||||
}) {
|
||||
const [local, setLocal] = useState<{ clock: AuctionClockView; remainingMs: number } | null>(null);
|
||||
const running = !!clock?.event && clock.running && !paused;
|
||||
useEffect(() => {
|
||||
if (!clock || !running || clock.remainingMs <= 0) return;
|
||||
const startedAt = performance.now();
|
||||
const timer = setInterval(() => {
|
||||
const remainingMs = Math.max(0, clock.remainingMs - Math.max(0, performance.now() - startedAt));
|
||||
setLocal({ clock, remainingMs });
|
||||
if (remainingMs === 0) clearInterval(timer);
|
||||
}, 200);
|
||||
return () => clearInterval(timer);
|
||||
}, [clock, running]);
|
||||
|
||||
if (!clock) return null;
|
||||
const remainingMs = running && local?.clock === clock ? local.remainingMs : Math.max(0, clock.remainingMs);
|
||||
const seconds = Math.ceil(remainingMs / 1000);
|
||||
const name = method === 'onceAround' ? 'Once Around' : 'Black Market';
|
||||
const status = clock.event !== null && seconds === 0
|
||||
? `${paused ? 'Auction paused. ' : ''}Awaiting server resolution.`
|
||||
: clock.event === null
|
||||
? `${paused ? 'Auction paused. ' : ''}Clock starts when bidding is ready.`
|
||||
: paused ? 'Auction paused.'
|
||||
: !clock.running ? 'Bidding clock held while the current game decision finishes.'
|
||||
: `${playerName} may bid or pass.`;
|
||||
return <section className="notice space-y-2" aria-label={`${name} auction countdown`} data-testid="richese-auction-countdown">
|
||||
<output className="block">{status}</output>
|
||||
<p role="timer" aria-live="off" aria-atomic="true" aria-label={`${playerName}’s remaining bidding time`}
|
||||
className="font-semibold tabular-nums">{seconds} {seconds === 1 ? 'second' : 'seconds'} {clock.event === null ? 'available' : 'remaining'}</p>
|
||||
<p className="fine">This timed {name} auction gives each bidder 10 seconds to bid or pass. The server controls the deadline and resolves expiry.</p>
|
||||
{method === 'onceAround' && <>
|
||||
<p className="fine">Once Around timing is a provisional online interpretation of the standard bid deadline.</p>
|
||||
<p className="fine">If passing would break a binding Truthtrance answer, the server submits only its required legal bid; normal payment still applies.</p>
|
||||
</>}
|
||||
</section>;
|
||||
}
|
||||
Reference in new issue
Block a user