Allow Distrans before the first ordinary auction bid

This commit is contained in:
Quintine committed 2026-10-26 08:40:44 +11:00
1 parent 34dccdfd99
commit 2ea3c58f68
19 files changed
+906 -42

No files matched your search

+47
View File
@@ -0,0 +1,47 @@
'use client';
import type { Card } from '@/game/cards';
import type { Action } from '@/game/engine';
import { Button } from './ui/button';
export interface DistransTransferActionProps {
card: Pick<Card, 'id'>;
recipient: { id: string; name?: string } | undefined;
given: Pick<Card, 'id' | 'name'> | undefined;
reason: string | null;
reasonId: string;
busy: boolean;
act: (action: Action) => void;
}
export function DistransTransferAction({
card,
recipient,
given,
reason,
reasonId,
busy,
act,
}: DistransTransferActionProps) {
const disabled = busy || !!reason || !recipient || !given;
return (
<Button
className="min-h-11 whitespace-normal motion-reduce:transition-none"
data-testid="distrans-transfer"
disabled={disabled}
aria-describedby={reason ? reasonId : undefined}
onClick={() => {
if (!disabled && recipient && given)
act({
type: 'card',
card: card.id,
target: recipient.id,
give: given.id,
});
}}
>
Give {given?.name ?? 'a card'} to {recipient?.name ?? 'the recipient'}{' '}
and discard Distrans
</Button>
);
}
+16 -18
View File
@@ -2,8 +2,8 @@
import { useId, useState } from 'react';
import type { Action, GameView } from '@/game/engine';
import { Button } from './ui/button';
import { CardInspector, CardRules } from './card-inspector';
import { DistransTransferAction } from './distrans-transfer-action';
export function Distrans({
game,
@@ -49,6 +49,12 @@ export function Distrans({
discard Distrans. No spice is paid. The transfer happens immediately,
without a recipient confirmation or Karama response.
</p>
<p className="m-0 text-sm leading-6" aria-label="Provisional Distrans bid timing">
Provisional bid timing: you may transfer before the first bid or pass
on an ordinary auction lot. Once anyone bids or passes, that lot stays
blocked while unresolved. Special auctions keep their existing
restrictions.
</p>
<CardInspector card={distrans.card} />
{choice && (
<>
@@ -103,23 +109,15 @@ export function Distrans({
{reason}
</p>
)}
<Button
className="min-h-11 whitespace-normal motion-reduce:transition-none"
disabled={busy || !!reason}
aria-describedby={reason ? `${id}-reason` : undefined}
onClick={() => {
if (!busy && !reason && choice && given)
act({
type: 'card',
card: distrans.card.id,
target: choice.recipient,
give: given.id,
});
}}
>
Give {given?.name ?? 'a card'} to {recipient?.name ?? 'the recipient'}{' '}
and discard Distrans
</Button>
<DistransTransferAction
card={distrans.card}
recipient={choice ? { id: choice.recipient, name: recipient?.name } : undefined}
given={given}
reason={reason}
reasonId={`${id}-reason`}
busy={busy}
act={act}
/>
{!!choice?.unavailable.length && (
<div
className="flex min-w-0 flex-col gap-3"
+5 -1
View File
@@ -3,6 +3,7 @@ import { TraitorDecisionPromises } from './traitor-decision-promises';
import { MovementPromises } from './movement-promises';
import { HomeworldEcazDuke } from './homeworld-ecaz-duke';
import { integerInputValue } from '../lib/integer-input';
import { normalAuctionHandBlock } from '@/game/normal-auction';
import { EcazSetup } from './ecaz-setup';
import { StormCardInspector, StormCardLogInspector } from './storm-cards';
import { stormCardDistance } from '@/game/storm-cards';
@@ -4319,6 +4320,8 @@ export function GameTable({
</label>
<p className="fine">
Minimum bid: {minimumBid} spice.
{normalAuctionHandBlock(me.hand?.length ?? me.handCount ?? me.handLimit, me.handLimit) &&
' Your Treachery hand is full; pass this bid.'}
{maximumBid < minimumBid &&
' You cannot afford a higher bid; pass this card.'}
</p>
@@ -4329,7 +4332,8 @@ export function GameTable({
amount: bidAmount,
...payment,
},
!Number.isSafeInteger(bidAmount) ||
!!normalAuctionHandBlock(me.hand?.length ?? me.handCount ?? me.handLimit, me.handLimit) ||
!Number.isSafeInteger(bidAmount) ||
bidAmount < minimumBid ||
bidAmount > maximumBid,
)}