Files
Dune/components/moritani-special-karama.tsx

128 lines
4.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useId, useState } from 'react';
import type { Action } from '@/game/engine';
import type { MoritaniSpecialKaramaView } from '@/game/moritani-special-karama';
import { Button } from './ui/button';
import { CardInspector, CardRules } from './card-inspector';
type Props = {
quote: MoritaniSpecialKaramaView | null;
act: (action: Action) => void;
busy: boolean;
/** The engine owns the opportunity's decline action; no Karama is spent. */
declineAction: Action;
};
type Disposition = 'unchanged' | 'keep' | 'discard';
export function MoritaniSpecialKarama({ quote, act, busy, declineAction }: Props) {
if (!quote) return null;
return (
<MoritaniSpecialKaramaChoice
key={`${quote.source.event}:${quote.source.owner}:${quote.source.opponent}`}
quote={quote}
act={act}
busy={busy}
declineAction={declineAction}
/>
);
}
function MoritaniSpecialKaramaChoice({ quote, act, busy, declineAction }: Omit<Props, 'quote'> & {
quote: MoritaniSpecialKaramaView;
}) {
const id = useId();
const [activation, setActivation] = useState('');
const [choices, setChoices] = useState<Record<string, Disposition>>({});
const karama = quote.karamas.find(card => card.id === activation) ?? quote.karamas[0];
const keep = quote.playedCards
.filter(card => choices[card.id] === 'keep' && quote.keepableIds.includes(card.id))
.map(card => card.id);
const discard = quote.playedCards.filter(card => choices[card.id] === 'discard').map(card => card.id);
const reason = !karama
? 'You need an available Karama Card.'
: keep.length + discard.length === 0
? 'Force at least one card to be kept or discarded, or decline without spending a Karama.'
: null;
return (
<section className="my-4 flex flex-col gap-4" aria-labelledby={`${id}-heading`}>
<h3 id={`${id}-heading`}>Moritani special Karama: opponent’s played cards</h3>
<p>
After your battle loss, spend one of your Karama Cards to force your opponent
to keep or discard selected publicly revealed cards they played. Unchanged
cards remain their choice. This uses your once-per-game special power.
</p>
<p className="fine">{quote.boundary}</p>
<label htmlFor={`${id}-activation`}>Karama to spend</label>
<select
id={`${id}-activation`}
className="min-h-11"
value={karama?.id ?? ''}
disabled={busy || !quote.karamas.length}
onChange={event => setActivation(event.target.value)}
>
{quote.karamas.map(card => (
<option value={card.id} key={card.id}>{card.name}</option>
))}
</select>
{karama && <CardInspector card={karama} />}
{quote.playedCards.map((card, index) => {
const keepable = quote.keepableIds.includes(card.id);
const selected = choices[card.id] ?? 'unchanged';
const disposition = selected === 'keep' && !keepable ? 'unchanged' : selected;
return (
<div className="flex flex-col gap-3" key={card.id}>
<label htmlFor={`${id}-card-${index}`}>{card.name}: disposition</label>
<select
id={`${id}-card-${index}`}
className="min-h-11"
value={disposition}
disabled={busy}
onChange={event => setChoices(previous => ({
...previous,
[card.id]: event.target.value as Disposition,
}))}
>
<option value="unchanged">Unchanged — opponent chooses normally</option>
<option value="keep" disabled={!keepable}>Force keep</option>
<option value="discard">Force discard</option>
</select>
{!keepable && <p className="fine">Force keep is unavailable under the provisional mandatory-disposal precedence.</p>}
<CardRules card={card} />
<CardInspector card={card} />
</div>
);
})}
{reason && <p className="notice" id={`${id}-reason`}>{reason}</p>}
<div className="flex flex-wrap gap-3">
<Button
className="min-h-11 whitespace-normal"
disabled={busy || !!reason}
aria-describedby={reason ? `${id}-reason` : undefined}
onClick={() => {
if (!busy && !reason && karama) act({
type: 'card',
mode: 'special',
card: karama.id,
event: quote.source.event,
keep,
discard,
});
}}
>
Spend Karama and force {keep.length + discard.length} card disposition(s)
</Button>
<Button
className="min-h-11 whitespace-normal"
disabled={busy}
onClick={() => { if (!busy) act(declineAction); }}
>
Decline — keep your Karama
</Button>
</div>
</section>
);
}