Separate Guild transport permission, purchase price and loan approval
This commit is contained in:
1 parent
a12250320c
commit
11adb7dbf3
39 files changed
+1576
-121
No files matched your search
@@ -0,0 +1,121 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { viewGame, type Action, type Game } from '../game/engine';
|
||||
import { homeworldShipmentChoice, type HomeworldShipmentSources } from '../game/homeworld-shipment-options';
|
||||
import { guildHomeworldShipmentChoice } from '../game/guild-homeworld-shipment-options';
|
||||
import { createGuildAmbassadorIncomeArrival, passNativeGuildTransportPermission } from './fixture-guild-ambassador-income-arrival';
|
||||
import {
|
||||
createNativeGuildHomeworldRate, createNativeGuildHomeworldRateScene,
|
||||
type NativeGuildHomeworldRoute,
|
||||
} from './fixture-native-guild-homeworld-rate';
|
||||
import { holdGuildCunningCard } from './fixture-nexus-guild-cunning';
|
||||
import {
|
||||
pairedE3NexusReload, stepPairedE3Nexus, type PairedE3NexusStep,
|
||||
} from './fixture-paired-e3-nexus-modules';
|
||||
|
||||
export type PurchasedHomeworldRoute = 'world' | 'guildWorld' | 'arrakisForeign' | 'return';
|
||||
export interface PurchasedHomeworldRateFixture {
|
||||
route: PurchasedHomeworldRoute;
|
||||
shipper: string;
|
||||
ecaz: string;
|
||||
guild: string;
|
||||
entrant: string;
|
||||
karama: string;
|
||||
source: string;
|
||||
destination: string;
|
||||
sources: HomeworldShipmentSources;
|
||||
allyPayment: number;
|
||||
beforePurchase: Game;
|
||||
beforeAction: Game;
|
||||
action: Action;
|
||||
actions: PairedE3NexusStep[];
|
||||
provenance: string[];
|
||||
}
|
||||
|
||||
function purchase(base: {
|
||||
beforeAction: Game; ecaz: string; guild: string; entrant: string; karama: string;
|
||||
actions: PairedE3NexusStep[]; provenance: string[];
|
||||
}, route: PurchasedHomeworldRoute, source: string, destination: string, allyPayment: number) {
|
||||
const actions = [...base.actions];
|
||||
const shipper = route === 'world' ? base.entrant : base.guild;
|
||||
const beforePurchase = pairedE3NexusReload(base.beforeAction);
|
||||
assert.equal(beforePurchase.active, shipper);
|
||||
assert.ok(beforePurchase.players.find(p => p.id === base.ecaz)!.hand.some(c => c.id === base.karama));
|
||||
const game = stepPairedE3Nexus(beforePurchase, { actor: base.ecaz,
|
||||
action: { type: 'card', mode: 'shipment', card: base.karama, target: shipper } }, actions);
|
||||
assert.deepEqual(game.karamaShipping, { player: shipper, owner: base.ecaz, card: base.karama });
|
||||
const sources = { [source]: { normal: 3, elite: 0 } };
|
||||
const view = viewGame(game, shipper);
|
||||
const quote = route === 'world' || route === 'guildWorld'
|
||||
? homeworldShipmentChoice(view, destination, sources, allyPayment)
|
||||
: guildHomeworldShipmentChoice(view, destination, sources, allyPayment);
|
||||
assert.equal(quote.blocked, null);
|
||||
assert.equal(quote.cost, 2);
|
||||
assert.ok(quote.action);
|
||||
return { ...base, route, shipper, source, destination, sources, allyPayment,
|
||||
actions, beforePurchase, beforeAction: pairedE3NexusReload(game), action: quote.action,
|
||||
provenance: [...base.provenance,
|
||||
'Ecaz plays its actual held Karama in shipment mode for the active beneficiary; no card identity is transferred.',
|
||||
'The original typed three-normal-counter route and explicit approved share are quoted after the independent purchase.'],
|
||||
} satisfies PurchasedHomeworldRateFixture;
|
||||
}
|
||||
|
||||
/** Original printed setup and auctioned Ecaz card from the native Homeworld
|
||||
* factory. No played hand, wallet, army, phase, grant or decision is assigned.
|
||||
* The shared factories disclose conserved undealt Treachery/Spice ordering. */
|
||||
export function createPurchasedHomeworldRate(route: PurchasedHomeworldRoute = 'world') {
|
||||
if (route === 'world') {
|
||||
const base = createGuildAmbassadorIncomeArrival({ homeworlds: true });
|
||||
return purchase(base, route, 'homeworld:moritani', 'homeworld:ecaz', 0);
|
||||
}
|
||||
const nativeRoute: NativeGuildHomeworldRoute = route === 'guildWorld' ? 'world' : route;
|
||||
const base = createNativeGuildHomeworldRate(nativeRoute);
|
||||
return purchase(base, route, base.source, base.destination, 1);
|
||||
}
|
||||
|
||||
/** Actual declaration; the original automatic stop policy may complete it immediately. */
|
||||
export function declarePurchasedHomeworldRate(f: PurchasedHomeworldRateFixture) {
|
||||
const actions: PairedE3NexusStep[] = [];
|
||||
const game = stepPairedE3Nexus(f.beforeAction, { actor: f.shipper, action: f.action }, actions);
|
||||
assert.notEqual(game.response?.kind, 'guildRate');
|
||||
return { game, actions };
|
||||
}
|
||||
|
||||
export function createPurchasedHomeworldRateScene(route: PurchasedHomeworldRoute = 'world') {
|
||||
const fixture = createPurchasedHomeworldRate(route);
|
||||
const declared = declarePurchasedHomeworldRate(fixture);
|
||||
return { ...fixture, game: declared.game, declarationActions: declared.actions,
|
||||
allow: declared.game.decision?.kind === 'homeworldShipmentGuild'
|
||||
? { actor: fixture.guild, action: { type: 'decision',
|
||||
event: declared.game.decision.event, allow: true } as Action } : null };
|
||||
}
|
||||
|
||||
/** Continue only actual Guild interception/permission controls, never a native rate or income. */
|
||||
export function completePurchasedHomeworldRate(state: Game) {
|
||||
const actions: PairedE3NexusStep[] = [];
|
||||
let game = pairedE3NexusReload(state);
|
||||
if (game.decision?.kind === 'homeworldShipmentGuild')
|
||||
game = stepPairedE3Nexus(game, { actor: game.decision.player,
|
||||
action: { type: 'decision', event: game.decision.event, allow: true } }, actions);
|
||||
const permission = passNativeGuildTransportPermission(game);
|
||||
return { game: permission.game, actions: [...actions, ...permission.actions] };
|
||||
}
|
||||
|
||||
/** Explicitly controlled SECOND card acquisition only: the first auctioned
|
||||
* Ecaz Karama really cancels an unaffordable native Guild return. The existing
|
||||
* controlled physical-card helper supplies a second Karama to Ecaz; its later
|
||||
* purchase and typed return are real actions. Not original action-only custody. */
|
||||
export function createControlledPurchasedHomeworldCanceledRate() {
|
||||
const native = createNativeGuildHomeworldRateScene('return', { unaffordable: true });
|
||||
const actions = [...native.actions, ...native.declarationActions];
|
||||
let game = stepPairedE3Nexus(native.game, native.cancel, actions);
|
||||
assert.equal(viewGame(game, native.guild).guildRateCanceled, true);
|
||||
assert.ok(!game.pendingHomeworldShipment);
|
||||
assert.equal(game.players.find(p => p.id === native.guild)!.shipped, false);
|
||||
const karama = holdGuildCunningCard(game, native.ecaz, 'karama').id;
|
||||
game = pairedE3NexusReload(game);
|
||||
const fixture = purchase({ ...native, beforeAction: game, karama, actions,
|
||||
provenance: [...native.provenance,
|
||||
'CONTROLLED second physical Karama acquisition via the existing held-card helper; no original-auction claim for this second card.'],
|
||||
}, 'return', native.source, native.destination, 1);
|
||||
return { ...fixture, canceledKarama: native.karama };
|
||||
}
|
||||
Reference in new issue
Block a user