Initial commit
This commit is contained in:
commit
8c709fb52f
771 files changed
+222983
No files matched your search
@@ -0,0 +1,162 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import {
|
||||
applyAction,
|
||||
createGame,
|
||||
newPlayer,
|
||||
viewGame,
|
||||
type Action,
|
||||
type Game,
|
||||
} from '../game/engine';
|
||||
import { baseDeck } from '../game/cards';
|
||||
import {
|
||||
acquireDuke,
|
||||
createDukeVidal,
|
||||
DUKE_VIDAL_ID,
|
||||
} from '../game/duke-vidal';
|
||||
|
||||
function fixture() {
|
||||
const g = createGame('DUKETRUTH', newPlayer('m', 'Moritani', 'moritani'));
|
||||
g.players.push(newPlayer('e', 'Emperor', 'emperor'));
|
||||
g.status = 'playing';
|
||||
g.phase = 6;
|
||||
g.turn = 2;
|
||||
g.active = 'm';
|
||||
g.order = ['e', 'm'];
|
||||
g.storm = 18;
|
||||
g.deck = baseDeck();
|
||||
for (const p of g.players) {
|
||||
p.forces = { 'arrakeen:10': 2 };
|
||||
p.reserves = 18;
|
||||
p.spice = 20;
|
||||
p.hand = [];
|
||||
p.traitors = [];
|
||||
}
|
||||
const index = g.deck.findIndex((card) => card.effect === 'truthtrance');
|
||||
g.players[1].hand.push(g.deck.splice(index, 1)[0]);
|
||||
g.dukeVidal = acquireDuke(createDukeVidal(), 'm', 2, 'moritani');
|
||||
g.battle = {
|
||||
territory: 'arrakeen',
|
||||
attacker: 'm',
|
||||
defender: 'e',
|
||||
plans: {},
|
||||
revealed: false,
|
||||
traitorCalls: {},
|
||||
prepared: true,
|
||||
};
|
||||
return g;
|
||||
}
|
||||
function priority(state = fixture()) {
|
||||
let g = applyAction(state, 'e', {
|
||||
type: 'card',
|
||||
card: state.players[1].hand[0].id,
|
||||
});
|
||||
while (g.truthtrance?.stage === 'priority') {
|
||||
const p = g.players.find((p) => !g.truthtrance!.passed.includes(p.id))!;
|
||||
g = applyAction(g, p.id, { type: 'truthPass' });
|
||||
}
|
||||
return g;
|
||||
}
|
||||
const question: Action = {
|
||||
type: 'truthAsk',
|
||||
question: {
|
||||
kind: 'battlePlan',
|
||||
target: 'm',
|
||||
claim: { kind: 'leader', leader: DUKE_VIDAL_ID },
|
||||
},
|
||||
};
|
||||
function reject(state: Game, id: string, action: Action) {
|
||||
const before = structuredClone(state);
|
||||
assert.throws(() => applyAction(state, id, action));
|
||||
assert.deepEqual(state, before);
|
||||
}
|
||||
|
||||
void test('Truthtrance asks and logs a Duke battle-plan claim, then binds the answer to the actual legal shared-disc plan', () => {
|
||||
const initial = fixture();
|
||||
const native = initial.players.map((p) => structuredClone(p.leaders));
|
||||
const asked = applyAction(priority(initial), 'e', question);
|
||||
assert.equal(asked.truthtrance?.stage, 'answer');
|
||||
assert.ok(asked.log.at(-1)!.text.includes('Duke Prad Vidal'));
|
||||
assert.equal(asked.log.at(-1)!.text.includes(DUKE_VIDAL_ID), false);
|
||||
assert.ok(viewGame(asked, 'm').truthBattleAnswers?.includes('yes'));
|
||||
assert.equal(viewGame(asked, 'e').truthBattleAnswers, null);
|
||||
const answered = applyAction(JSON.parse(JSON.stringify(asked)), 'm', {
|
||||
type: 'truthAnswer',
|
||||
answer: 'yes',
|
||||
});
|
||||
assert.equal(answered.truthtrance, null);
|
||||
assert.equal(answered.truthHistory!.at(-1)!.answer, 'yes');
|
||||
reject(answered, 'm', {
|
||||
type: 'battlePlan',
|
||||
dial: 0,
|
||||
leader: answered.players[0].leaders[0].id,
|
||||
});
|
||||
const planned = applyAction(answered, 'm', {
|
||||
type: 'battlePlan',
|
||||
dial: 0,
|
||||
leader: DUKE_VIDAL_ID,
|
||||
});
|
||||
assert.equal(planned.battle!.plans.m.leader, DUKE_VIDAL_ID);
|
||||
assert.equal(planned.dukeVidal!.controller, 'm');
|
||||
assert.deepEqual(
|
||||
planned.players.map((p) => p.leaders),
|
||||
native,
|
||||
);
|
||||
assert.ok(planned.players.every((p) => !p.traitors.includes(DUKE_VIDAL_ID)));
|
||||
});
|
||||
|
||||
void test('an unavailable Duke remains a valid battle identity question but cannot be promised as a legal plan', () => {
|
||||
const initial = fixture();
|
||||
initial.dukeVidal!.controller = null;
|
||||
initial.dukeVidal!.source = null;
|
||||
initial.dukeVidal!.acquiredTurn = null;
|
||||
const asked = applyAction(priority(initial), 'e', question);
|
||||
assert.ok(asked.log.at(-1)!.text.includes('Duke Prad Vidal'));
|
||||
assert.equal(viewGame(asked, 'm').truthBattleAnswers?.includes('yes'), false);
|
||||
assert.ok(viewGame(asked, 'm').truthBattleAnswers?.includes('no'));
|
||||
reject(asked, 'm', { type: 'truthAnswer', answer: 'yes' });
|
||||
const answered = applyAction(asked, 'm', {
|
||||
type: 'truthAnswer',
|
||||
answer: 'no',
|
||||
});
|
||||
assert.equal(answered.truthtrance, null);
|
||||
});
|
||||
|
||||
void test('adding Duke to battle claims does not permit a nonexistent Duke traitor fact or absent shared disc', () => {
|
||||
const g = priority();
|
||||
reject(g, 'e', {
|
||||
type: 'truthAsk',
|
||||
question: {
|
||||
kind: 'fact',
|
||||
target: 'm',
|
||||
fact: { kind: 'traitor', leader: DUKE_VIDAL_ID },
|
||||
},
|
||||
});
|
||||
const absent = fixture();
|
||||
delete absent.dukeVidal;
|
||||
reject(priority(absent), 'e', question);
|
||||
});
|
||||
|
||||
void test('native leader battle and traitor questions retain their original validation and readable log labels', () => {
|
||||
const g = priority();
|
||||
const leader = g.players[0].leaders[0];
|
||||
const battle = applyAction(g, 'e', {
|
||||
type: 'truthAsk',
|
||||
question: {
|
||||
kind: 'battlePlan',
|
||||
target: 'm',
|
||||
claim: { kind: 'leader', leader: leader.id },
|
||||
},
|
||||
});
|
||||
assert.ok(battle.log.at(-1)!.text.includes(leader.name));
|
||||
const traitor = applyAction(g, 'e', {
|
||||
type: 'truthAsk',
|
||||
question: {
|
||||
kind: 'fact',
|
||||
target: 'm',
|
||||
fact: { kind: 'traitor', leader: leader.id },
|
||||
},
|
||||
});
|
||||
assert.ok(traitor.log.at(-1)!.text.includes(leader.name));
|
||||
assert.equal(viewGame(traitor, 'm').truthAnswer, 'no');
|
||||
});
|
||||
Reference in new issue
Block a user