Provisionally apply first/last to the current ordinary or native Advanced normal Black Market lot. Preserve bids, funding and passes; skip passed seats until a real raise. Use a local full cycle and actual post-discard hand capacity. Keep the original Black Market opener in both settlement consumers and clear ordinary overrides for the next lot. Connect persistent controls, native disposal and existing minimal AI options. Validation:10-file134/134, types/lint/build and eight original-action/rendered-table continuations. Public setup/cache1, real lobby circle choice, native Black Market and paid full hand preserve prior1,2/3/4 raises, one payment4 and original next opener or final-lot Revival. Eight minimal policies are legal. Corrected fixture opener/completion assumptions and external default-history/final-lot assertions; only runtime was rerun against identical source. Bounded consumer review found no concrete defect. Other timing scopes, direct combined Semuta claim/capture, full modes, final assurance and browser/deployed acceptance remain open. No saved-room/reset/restart/deployment operation.
105 lines
5.6 KiB
TypeScript
105 lines
5.6 KiB
TypeScript
'use client';
|
||
|
||
import { useId } from 'react';
|
||
import type { Action, GameView } from '@/game/engine';
|
||
import { richeseCardDefinition } from '@/game/richese-cards';
|
||
import { Button } from './ui/button';
|
||
import { CardInspector } from './card-inspector';
|
||
|
||
/** Reasons use public timing only; exact card reservation checks stay on the server. */
|
||
function unavailableReason(game: GameView) {
|
||
if (game.status !== 'playing')
|
||
return 'Order changes are available during play.';
|
||
if (game.truthtrance || game.response || game.decision || game.phaseOpening)
|
||
return 'Finish the current interaction before changing turn order.';
|
||
if (game.phase === 3) {
|
||
if (game.richeseAuction?.method === 'silent')
|
||
return 'Silent bids are simultaneous. These controls do not change their tie order.';
|
||
if (game.auction || game.richeseAuction?.method === 'normal')
|
||
return 'No cyclic order change is available for your pending bid. A passed player waits for a new raise; the current high bidder has no pending bid. Already-held positions and committed cards are unavailable.';
|
||
return 'No order change is available for you in this lot. You must not have bid or passed; discarding this card can open a hand slot, but a completed opportunity cannot be repeated. A card already committed elsewhere cannot be used.';
|
||
}
|
||
if (game.phase === 5)
|
||
return 'Movement order can change only before the current combined shipment and movement turn begins. First requires all remaining turns to be unstarted and the Advanced Guild to have finished or be absent. Completed turns stay completed. Already-held positions and committed cards are unavailable.';
|
||
if (game.phase === 6)
|
||
return game.battle
|
||
? 'Become aggressor only during your open pre-plan opportunity, before declaring ready or committing a plan. Already-held priority and committed cards are unavailable. Finish the current battle and its aftermath before changing remaining battle-choice order.'
|
||
: 'You need an unresolved battle and an available first or last position. Completed battles stay resolved, and a card committed elsewhere cannot be used.';
|
||
return 'These controls support Once Around and cyclic bidding, movement order, remaining battle-choice order and pre-plan aggressor. Later battle intervention and other phase modes remain unfinished.';
|
||
}
|
||
|
||
export function JuiceOfSapho({
|
||
game,
|
||
act,
|
||
busy,
|
||
}: {
|
||
game: GameView;
|
||
act: (action: Action) => void;
|
||
busy: boolean;
|
||
}) {
|
||
const id = useId();
|
||
const me = game.players.find((p) => p.id === game.me);
|
||
const card = me?.hand?.find(
|
||
(c) => richeseCardDefinition(c)?.card.effect === 'juiceOfSapho',
|
||
);
|
||
if (!card) return null;
|
||
const options = game.saphoOptions ?? [];
|
||
return (
|
||
<details className="my-4 min-w-0">
|
||
<summary className="min-h-11 cursor-pointer py-3">
|
||
Juice of Sapho: change order or become aggressor
|
||
</summary>
|
||
<div className="flex min-w-0 flex-col gap-4 py-3">
|
||
<p>
|
||
Choose one available order change, then discard Juice of Sapho. This
|
||
does not grant an extra bid, shipment, movement or battle.
|
||
</p>
|
||
<CardInspector card={card} />
|
||
{options.length ? (
|
||
options.map((option) => {
|
||
const once = option.scope === 'onceAround';
|
||
const cyclic = option.scope === 'cyclicAuction';
|
||
const battle = option.scope === 'battleOrder';
|
||
const aggressor = option.scope === 'battleAggressor';
|
||
return (
|
||
<div
|
||
key={`${option.scope}-${option.event}-${option.mode}`}
|
||
className="flex min-w-0 flex-col gap-2"
|
||
>
|
||
<p>
|
||
{aggressor
|
||
? 'Become this battle’s aggressor before plans. You win ordinary ties; a Habbanya Stronghold advantage still takes precedence. The participants and later battle choices stay unchanged.'
|
||
: cyclic
|
||
? `Bid ${option.mode} in the current cyclic lot. Provisional duration: this lot only. Existing bids, funding and passes stay committed. Later circuits use the changed cycle; the next lot returns to its normal opener.`
|
||
: once
|
||
? `Bid ${option.mode} in the current Once Around lot. Each eligible player still has only one bidding opportunity.`
|
||
: battle
|
||
? `Choose your remaining battles ${option.mode}. Completed battles stay resolved. This changes who chooses next, not the aggressor or tie advantage of a battle. Other players may still choose battles against you before your turn.`
|
||
: `Take the ${option.mode} remaining combined shipment and movement turn. Completed turns stay completed${option.mode === 'last' ? ', and the Guild cannot move behind you' : ''}.`}
|
||
</p>
|
||
<Button
|
||
className="min-h-11 whitespace-normal motion-reduce:transition-none"
|
||
disabled={busy}
|
||
onClick={() => {
|
||
if (!busy) act({ type: 'card', card: card.id, ...option });
|
||
}}
|
||
>
|
||
{aggressor ? 'Become aggressor and discard Juice of Sapho' :
|
||
`${once || cyclic ? 'Bid' : battle ? 'Choose battles' : 'Take your movement turn'} ${option.mode} and discard Juice of Sapho`}
|
||
</Button>
|
||
</div>
|
||
);
|
||
})
|
||
) : (
|
||
<p className="notice" id={`${id}-reason`}>
|
||
{unavailableReason(game)}
|
||
</p>
|
||
)}
|
||
<p className="text-sm">
|
||
Later aggressor intervention and other phase modes are still unfinished.
|
||
</p>
|
||
</div>
|
||
</details>
|
||
);
|
||
}
|