54 lines
2.8 KiB
TypeScript
54 lines
2.8 KiB
TypeScript
import { selectedPhaseModuleRoster, type FactionModuleProfile } from './faction-module-profile';
|
|
|
|
/** Public uncertainty about a possible concealed Duke capture. Fresh games
|
|
* explicitly begin false; some early legacy states can prove safety publicly. */
|
|
export type DukeCapturePolicyContext = Omit<Partial<FactionModuleProfile>, 'players'> & {
|
|
advanced: boolean;
|
|
players: readonly { faction: string }[];
|
|
dukeCaptureUncertain?: boolean;
|
|
status?: string;
|
|
turn?: number;
|
|
phase?: number;
|
|
battle?: unknown;
|
|
lastBattleContext?: unknown;
|
|
pendingCapture?: unknown;
|
|
response?: { kind: string } | null;
|
|
decision?: { kind: string } | null;
|
|
};
|
|
|
|
export const DUKE_CAPTURE_UNCERTAIN_BLOCK =
|
|
'Duke Vidal acquisition is unavailable while Harkonnen capture history may be unresolved.';
|
|
|
|
/** Before the first battle of the first turn, a legacy game has no capture
|
|
* history. Use public phase and continuation state only, never Duke custody. */
|
|
export function legacyDukeCaptureHistoryClear(g: DukeCapturePolicyContext): boolean {
|
|
return g.dukeCaptureUncertain === undefined && g.status === 'playing' && g.turn === 1 &&
|
|
typeof g.phase === 'number' && Number.isInteger(g.phase) && g.phase >= 0 && g.phase <= 5 &&
|
|
g.battle == null && g.lastBattleContext == null && g.pendingCapture == null &&
|
|
g.response?.kind !== 'capture' && g.decision?.kind !== 'captureOffer' &&
|
|
g.decision?.kind !== 'capturedLeader';
|
|
}
|
|
|
|
/** This result depends only on public table configuration and the public
|
|
* uncertainty receipt, never on actual Duke custody or capture identity. */
|
|
export function dukeCaptureUncertaintyBlock(g: DukeCapturePolicyContext): string | null {
|
|
return g.advanced && g.players.some(player => player.faction === 'harkonnen') &&
|
|
g.dukeCaptureUncertain !== false && !legacyDukeCaptureHistoryClear(g)
|
|
? DUKE_CAPTURE_UNCERTAIN_BLOCK : null;
|
|
}
|
|
|
|
/** Original native faction tables with no selected optional module. Native
|
|
* Ecaz Occupy and Moritani assassination stamps are faction rules, not modules. */
|
|
export function plainAdvancedEcazDukeProfile(g: DukeCapturePolicyContext): boolean {
|
|
return g.advanced === true && Array.isArray(g.expansions) && g.expansions.includes('ecaz') &&
|
|
selectedPhaseModuleRoster({ expansions: g.expansions, players: g.players }) &&
|
|
g.players.some(player => player.faction === 'ecaz') &&
|
|
g.players.some(player => player.faction === 'harkonnen') &&
|
|
!g.leaderSkills && !g.homeworlds && !g.nexusCards && !g.techTokens && !g.strongholdCards &&
|
|
!g.discoveryEnabled && !g.discoveries && !g.discoveryStash && !g.greatMaker &&
|
|
!g.ecazTreachery && !g.semutaPreview && !g.advancedPreview && !g.kullPreview &&
|
|
!g.nexusKullPreview && !g.guildBetrayalPreview && !g.richeseBetrayalPreview &&
|
|
!g.nexusIxianReplacementPreview && !g.nexusIxianBetrayalPreview && !g.nexusHarkonnenBetrayalPreview &&
|
|
!g.spiceBankerIncomePreview;
|
|
}
|