Files
Dune/game/nexus-emperor-secret-ally.ts
T

151 lines
4.5 KiB
TypeScript

import type { Game, Player } from './engine';
import { eliteRevivalRemaining } from './revival';
import { classicNexusModulesProfile } from './nexus-module-profile';
import { classicNexusLeaderSkillsProfile } from './leader-skill-profile';
export const EMPEROR_NEXUS_REVIVALS = 3;
export type EmperorNexusPools = {
reserves: number;
tanks: number;
eliteReserves: number;
eliteTanks: number;
eliteRevived: number;
revived: number;
freeForcesRevived: number | null;
spice: number;
};
export type NexusEmperorRevival = {
kind: 'revival';
event: string;
owner: string;
turn: number;
phase: 4;
sequence: number;
faction: Player['faction'];
advanced: boolean;
elite: number;
before: EmperorNexusPools;
after: EmperorNexusPools;
signature: string;
};
export function emperorNexusPools(p: Player): EmperorNexusPools {
return {
reserves: p.reserves,
tanks: p.tanks,
eliteReserves: p.elites?.reserves ?? 0,
eliteTanks: p.elites?.tanks ?? 0,
eliteRevived: p.elites?.revived ?? 0,
revived: p.revived,
freeForcesRevived: p.freeForcesRevived ?? null,
spice: p.spice,
};
}
export function emperorNexusEvent(
turn: number,
phase: number,
owner: string,
sequence: number,
): string {
return JSON.stringify([
'nexusEmperorSecretAlly',
turn,
phase,
owner,
sequence,
]);
}
export function emperorNexusSignature(record: object): string {
return JSON.stringify({ ...record, signature: undefined });
}
export function emperorNexusModeSupported(g: Game): boolean {
return (
!g.expansions.length &&
g.players.every((p) =>
['atreides', 'harkonnen', 'fremen', 'guild', 'beneGesserit'].includes(
p.faction,
),
) &&
(classicNexusModulesProfile(g) || (!!g.leaderSkills && classicNexusLeaderSkillsProfile(g)) ||
(!g.homeworlds &&
(!g.leaderSkills || classicNexusLeaderSkillsProfile(g)) &&
!g.discoveryEnabled &&
!g.discoveries &&
!g.discoveryStash &&
!g.greatMaker &&
((!g.techTokens && !g.strongholdCards) || classicNexusModulesProfile(g) ||
(!!g.leaderSkills && classicNexusLeaderSkillsProfile(g)))))
);
}
/** A fixed three-counter grant preserves the separate Advanced elite cap. */
export function emperorNexusRevivalElites(
p: Player,
advanced: boolean,
): number[] {
const choices: number[] = [];
for (let elite = 0; elite <= 3; elite++)
if (
elite <= eliteRevivalRemaining(p, advanced) &&
elite <= (p.elites?.tanks ?? 0) &&
3 - elite <= p.tanks - (p.elites?.tanks ?? 0)
)
choices.push(elite);
return choices;
}
export function validateEmperorNexusRevival(
g: Game,
r: NexusEmperorRevival,
): void {
const p = r && g.players.find((p) => p.id === r.owner);
const validPools = (pool: EmperorNexusPools) =>
pool &&
Object.keys(pool).sort().join(',') ===
'eliteReserves,eliteRevived,eliteTanks,freeForcesRevived,reserves,revived,spice,tanks' &&
Object.entries(pool).every(
([key, n]) =>
(key === 'freeForcesRevived' && n === null) ||
(Number.isSafeInteger(n) && n! >= 0),
) &&
pool.eliteReserves <= pool.reserves &&
pool.eliteTanks <= pool.tanks &&
pool.reserves + pool.tanks <= 20;
if (
!p ||
Object.keys(r).sort().join(',') !==
'advanced,after,before,elite,event,faction,kind,owner,phase,sequence,signature,turn' ||
r.kind !== 'revival' ||
r.phase !== 4 ||
!Number.isSafeInteger(r.turn) ||
r.turn < 1 ||
r.turn > g.turn ||
r.faction !== p.faction ||
r.advanced !== g.advanced ||
!Number.isSafeInteger(r.sequence) ||
r.sequence < 0 ||
!Number.isSafeInteger(r.elite) ||
r.elite < 0 ||
r.elite > 3 ||
r.event !== emperorNexusEvent(r.turn, 4, r.owner, r.sequence) ||
r.signature !== emperorNexusSignature(r) ||
!validPools(r.before) ||
!validPools(r.after) ||
r.before.tanks < 3 ||
r.elite > r.before.eliteTanks ||
3 - r.elite > r.before.tanks - r.before.eliteTanks ||
(g.advanced && r.before.eliteRevived + r.elite > 1) ||
(p.faction !== 'fremen' &&
(r.elite || r.before.eliteTanks || r.before.eliteReserves)) ||
JSON.stringify(r.after) !==
JSON.stringify({
...r.before,
reserves: r.before.reserves + 3,
tanks: r.before.tanks - 3,
eliteReserves: r.before.eliteReserves + r.elite,
eliteTanks: r.before.eliteTanks - r.elite,
eliteRevived: r.before.eliteRevived + r.elite,
})
)
throw new Error(
'Emperor Nexus revival has lost its exact original physical return or allowance.',
);
}