Files
Dune/components/caladan-reinforcement.tsx
T

112 lines
4.0 KiB
TypeScript

'use client';
import { useState } from 'react';
import type { Action, GameView } from '@/game/engine';
import {
caladanReinforcementCanAct,
caladanReinforcementChoice,
} from '@/game/caladan-reinforcement-options';
import { Button } from './ui/button';
type Props = { game: GameView; act: (action: Action) => void; busy: boolean };
export function CaladanReinforcement({ game, act, busy }: Props) {
if (
!game.caladanReinforcement ||
game.caladanReinforcement.player !== game.me ||
game.decision?.kind !== 'caladanReinforcement' ||
game.decision.player !== game.me
)
return null;
return (
<ReinforcementChoice
key={game.caladanReinforcement.event}
game={game}
act={act}
busy={busy}
/>
);
}
function ReinforcementChoice({ game, act, busy }: Props) {
const offer = game.caladanReinforcement!;
const [destination, setDestination] = useState(
offer.destinations.find((target) => !target.blocked)?.id ?? '',
);
const canAct = caladanReinforcementCanAct(game);
const choice = caladanReinforcementChoice(game, destination);
return (
<section aria-label="Caladan reinforcement" className="space-y-3">
<p>
Add one reserve force at the battle location, or leave it in reserves.
</p>
<p className="fine" aria-label="Caladan arrival rules">
This force joins the original surviving winner at the original battle location, including inside
the Hidden Mobile Stronghold. Its direct-shipment ban does not block this placement. No spice,
shipment, movement or revival allowance is spent, and no Spiritual Advisor opportunity is created.
HMS entry is provisional. Storm-sector entry remains unavailable.
</p>
<p className="fine" aria-label="Provisional Caladan arrival reactions">
{game.advanced && 'Advanced Bene Gesserit Intrusion resolves first. '}
Provisional arrival classification: eligible Ambassador and Terror reactions then follow their
original arrival order.
</p>
<p className="fine" aria-label="Provisional Face Dance order">
Provisional order: this optional force joins the original victorious
army before any later Face Dance. If replacement occurs, it returns with
the surviving army and counts toward the current replacement limit.
This does not create another victory or grant another Caladan reinforcement.
</p>
<label className="block space-y-1">
<span>Reinforcement destination</span>
<select
aria-label="Reinforcement destination"
className="w-full"
value={destination}
disabled={busy || !canAct || !!offer.blocked}
onChange={(event) => setDestination(event.target.value)}
>
{!offer.destinations.some((target) => !target.blocked) && (
<option value="">No legal reinforcement destination</option>
)}
{offer.destinations.map((target) => (
<option
key={target.id}
value={target.id}
disabled={!!target.blocked}
>
{target.name}
{target.blocked ? ` — ${target.blocked}` : ''}
</option>
))}
</select>
</label>
{choice.blocked && (
<p className="fine" aria-live="polite">
{choice.blocked}
</p>
)}
<div className="flex flex-wrap gap-2">
<Button
className="game-action"
disabled={busy || !choice.action}
onClick={() => {
if (!busy && choice.action) act(choice.action);
}}
>
Add one reserve force
</Button>
<Button
className="game-action"
variant="outline"
disabled={busy || !canAct}
onClick={() => { if (!busy && canAct)
act({ type: 'decision', event: offer.event, decline: true }); }}
>
Leave in reserves
</Button>
</div>
</section>
);
}