Prototype charity Truthtrance and coalition rule foundations
Verify and publish checkpoint container / container (push) Successful in 10m14s
Verify and publish checkpoint container / container (push) Successful in 10m14s
This commit is contained in:
1 parent
e896570f07
commit
cea310b030
17 files changed
+1024
-35
No files matched your search
@@ -1,11 +1,13 @@
|
||||
import type { Player } from './engine';
|
||||
import { advisorStanceAfterSettlement } from './advisor-automatic-flip';
|
||||
import type { ForcePresence } from './force-presence';
|
||||
import { presenceByLocation } from './force-presence';
|
||||
import { presenceAt, presenceByLocation } from './force-presence';
|
||||
import { isHomeworldNoFieldLocation } from './richese-no-field';
|
||||
import { fighterCount, isAdvisor } from './advisors';
|
||||
import { quoteAidRefunds, PhaseResourceError } from './phase-resource-quote';
|
||||
import { ecazOccupancyIdentity } from './ecaz-occupy';
|
||||
import { ecazOccupancyIdentity, ecazOccupancyRelation } from './ecaz-occupy';
|
||||
import { quoteAlliedSeparation } from './allied-separation';
|
||||
import { areCoalitionAllies, coalitionAllies, coalitionEdgeTurn, coalitionGroups, type CoalitionState } from './coalition-alliance';
|
||||
import type { SharedSpiceLot } from './ecaz-spice-allocation';
|
||||
import {
|
||||
gameDistance,
|
||||
@@ -23,7 +25,7 @@ function requireBoard(condition: unknown, message: string): asserts condition {
|
||||
}
|
||||
const whole = (n: unknown): n is number =>
|
||||
Number.isSafeInteger(n) && (n as number) >= 0;
|
||||
export type BoardSeat = Pick<Player, 'id' | 'faction' | 'ally' | 'advisors'> &
|
||||
export type BoardSeat = Pick<Player, 'id' | 'faction' | 'ally' | 'allySinceTurn' | 'advisors'> &
|
||||
ForcePresence;
|
||||
export type BoardContext = MobileBoard & {
|
||||
advanced: boolean;
|
||||
@@ -32,7 +34,20 @@ export type BoardContext = MobileBoard & {
|
||||
storm: number;
|
||||
order: readonly string[];
|
||||
players: readonly BoardSeat[];
|
||||
coalitionState?: CoalitionState;
|
||||
};
|
||||
|
||||
/** Three-member board rules are provisionally restricted to Basic without Ecaz. */
|
||||
function validateBoardCoalitions(g: BoardContext) {
|
||||
if (g.coalitionState === undefined) return;
|
||||
try {
|
||||
for (const group of coalitionGroups(g.players, g.coalitionState))
|
||||
if (group.members.length === 3 && (g.advanced || g.players.some(player => player.faction === 'ecaz')))
|
||||
throw new Error('Three-member board rules require Basic play without Ecaz.');
|
||||
} catch (error) {
|
||||
throw new BoardResolutionError((error as Error).message);
|
||||
}
|
||||
}
|
||||
export type AdvisorRelease = { player: string; territory: string };
|
||||
function validKey(g: MobileBoard, key: string) {
|
||||
const p = splitLocation(key);
|
||||
@@ -95,6 +110,7 @@ export function settledBoard(g: BoardContext) {
|
||||
id: p.id,
|
||||
faction: p.faction,
|
||||
ally: p.ally,
|
||||
allySinceTurn: p.allySinceTurn,
|
||||
forces: p.forces,
|
||||
noField: p.noField,
|
||||
advisors,
|
||||
@@ -111,6 +127,7 @@ export type BattleBoardBattle = {
|
||||
};
|
||||
export function quoteBattleBoard(g: BoardContext) {
|
||||
const { players, released } = settledBoard(g);
|
||||
validateBoardCoalitions(g);
|
||||
const battles: BattleBoardBattle[] = [];
|
||||
const connected = (a: BoardSeat, b: BoardSeat, site: string) => {
|
||||
const left = Object.entries(presenceByLocation(a))
|
||||
@@ -137,6 +154,7 @@ export function quoteBattleBoard(g: BoardContext) {
|
||||
if (
|
||||
other.id === id ||
|
||||
other.id === p.ally ||
|
||||
(g.coalitionState !== undefined && areCoalitionAllies(players, g.coalitionState, id, other.id)) ||
|
||||
!fighterCount(other, t.id) ||
|
||||
g.order.indexOf(other.id) < g.order.indexOf(id)
|
||||
)
|
||||
@@ -156,6 +174,44 @@ export function quoteBattleBoard(g: BoardContext) {
|
||||
}
|
||||
return { released, battles };
|
||||
}
|
||||
|
||||
/** Union the per-edge Basic separation obligations without inventing an age for
|
||||
* an older pair when a third player joins. No forces are removed by this quote. */
|
||||
export function quoteCoalitionAlliedSeparation(
|
||||
g: BoardContext & { turn: number; remaining: readonly string[] },
|
||||
player: string,
|
||||
): string[] {
|
||||
settledBoard(g);
|
||||
validateBoardCoalitions(g);
|
||||
requireBoard(whole(g.turn) && g.turn > 0 && Array.isArray(g.remaining) &&
|
||||
new Set(g.remaining).size === g.remaining.length &&
|
||||
g.remaining.every(id => g.order.includes(id)),
|
||||
'Allied separation needs the current turn and remaining player order.');
|
||||
const own = g.players.find(p => p.id === player);
|
||||
requireBoard(own, 'Allied separation needs a seated player.');
|
||||
const allies = g.coalitionState === undefined
|
||||
? (own.ally ? [own.ally] : [])
|
||||
: coalitionAllies(g.players, g.coalitionState, player);
|
||||
const losses = new Set<string>();
|
||||
for (const id of allies) {
|
||||
const ally = g.players.find(p => p.id === id);
|
||||
requireBoard(ally, 'Allied separation needs a seated ally.');
|
||||
const edgeTurn = g.coalitionState === undefined ? null : coalitionEdgeTurn(g.players, g.coalitionState, player, id);
|
||||
for (const site of quoteAlliedSeparation({
|
||||
advanced: g.advanced, turn: g.turn, player, ally: id,
|
||||
playerAllySinceTurn: g.coalitionState === undefined ? own.allySinceTurn : edgeTurn ?? undefined,
|
||||
allySinceTurn: g.coalitionState === undefined ? ally.allySinceTurn : edgeTurn ?? undefined,
|
||||
remaining: g.remaining,
|
||||
territories: gameTerritories(g).map(t => ({
|
||||
territory: t.id, polar: t.type === 'polar', ownPresent: presenceAt(own, t.id) > 0,
|
||||
allyPresent: presenceAt(ally, t.id) > 0, ownAdvisors: isAdvisor(own, t.id),
|
||||
allyAdvisors: isAdvisor(ally, t.id),
|
||||
ecazCoexist: ecazOccupancyRelation(g.players, player, id, { kind: 'territory', id: t.id }) === 'ecazAlliance',
|
||||
})),
|
||||
})) losses.add(site);
|
||||
}
|
||||
return gameTerritories(g).map(t => t.id).filter(id => losses.has(id));
|
||||
}
|
||||
export type CollectionContext = Omit<BoardContext, 'players'> & {
|
||||
players: readonly (BoardSeat & Pick<Player, 'spice' | 'elites'>)[];
|
||||
spice: Readonly<Record<string, number>>;
|
||||
|
||||
Reference in new issue
Block a user