Files
Dune/tests/betrayal-mentat-question.test.ts

138 lines
7.7 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { applyAction, viewGame, type Action, type Game } from '../game/engine';
import { botActions } from '../game/bots';
import { DIFFICULTIES } from '../game/bot-profiles';
import {
betrayalMentatResources, betrayalMentatSeat, createBetrayalMentatQuestionFixture,
prepareBetrayalMentatPlans, reloadBetrayalMentat,
} from './fixture-betrayal-mentat-question';
function rejectBetrayalMentat(game: Game, actor: string, action: Action) {
const before = structuredClone(game);
assert.throws(() => applyAction(game, actor, action));
assert.deepEqual(game, before);
}
void test('native Betrayal Mentat exact weapon shows privately before original posture and accepts card-free native plans', () => {
const fixture = createBetrayalMentatQuestionFixture();
const { owner, target, observer, leader } = fixture;
let game = reloadBetrayalMentat(fixture.battle);
assert.equal(game.decision?.kind, 'mentatQuestion');
assert.equal(game.decision?.player, owner);
assert.equal(game.battle!.preparation, undefined);
assert.equal(game.battle!.leaderSkillHidden, undefined);
assert.equal(viewGame(game, owner).mentat.pending!.stage, 'name');
const event = game.battle!.mentatQuestionEvent!;
const resources = betrayalMentatResources(game);
assert.deepEqual(resources, betrayalMentatResources(fixture.paid));
assert.ok(fixture.shipmentGateEvents.length);
const ask: Action = { type: 'decision', event, weapon: 'Crysknife' };
rejectBetrayalMentat(game, target, ask);
rejectBetrayalMentat(game, owner, { ...ask, event: 'stale' });
game = applyAction(game, owner, ask);
assert.equal(game.decision?.kind, 'mentatQuestion');
assert.equal(game.decision?.player, target);
assert.equal(game.battle!.preparation, undefined);
const held = betrayalMentatSeat(game, target).hand;
const weapon = held.find(card => card.name === 'Crysknife')!;
const other = held.find(card => card.name === 'Snooper')!;
assert.deepEqual(viewGame(game, target).mentat.pending!.cards,
held.filter(card => card.name === 'Crysknife'));
for (const id of [owner, observer, 'shipper'])
assert.deepEqual(viewGame(game, id).mentat.pending!.cards, []);
const show: Action = { type: 'decision', event, card: weapon.id };
rejectBetrayalMentat(game, owner, show);
rejectBetrayalMentat(game, target, { ...show, event: 'stale' });
rejectBetrayalMentat(game, target, { ...show, card: other.id });
game = applyAction(reloadBetrayalMentat(game), target, show);
assert.equal(game.decision?.kind, 'leaderSkillVisibility');
assert.equal(game.battle!.preparation, undefined);
assert.deepEqual(betrayalMentatResources(game), resources);
for (const id of [owner, target])
assert.equal(viewGame(game, id).mentat.history[0].card.id, weapon.id);
for (const id of [observer, 'shipper']) {
assert.deepEqual(viewGame(game, id).mentat.history, []);
assert.ok(!JSON.stringify(viewGame(game, id)).includes(weapon.id));
}
assert.ok(!JSON.stringify(game.log).includes(weapon.id));
rejectBetrayalMentat(game, target, show);
game = prepareBetrayalMentatPlans(game);
assert.equal(game.decision, null);
assert.equal(game.battle!.preparation, undefined);
assert.equal(game.battle!.leaderSkillHidden![owner], true);
assert.deepEqual(betrayalMentatResources(game), resources);
const targetSkill = game.leaderSkills!.assignments.find(a => a.owner === target)!.leader;
const targetLeader = betrayalMentatSeat(game, target).leaders.find(l =>
!l.dead && !l.capturedBy && l.id !== targetSkill)!;
game = applyAction(game, owner, { type: 'battlePlan', dial: 0, leader });
game = applyAction(game, target, { type: 'battlePlan', dial: 0, leader: targetLeader.id });
assert.equal(game.battle!.plans[owner].leader, leader);
assert.equal(game.battle!.plans[target].leader, targetLeader.id);
assert.equal(game.battle!.plans[target].weapon, null);
assert.equal(game.battle!.plans[target].defense, null);
assert.ok(betrayalMentatSeat(game, target).hand.some(card => card.id === weapon.id));
assert.deepEqual(betrayalMentatResources(game), resources);
});
void test('native Betrayal absent weapon offers only actual held alternatives and retains the freely shown defense', () => {
const fixture = createBetrayalMentatQuestionFixture();
const { owner, target, observer } = fixture;
const before = betrayalMentatResources(fixture.battle);
let game = applyAction(reloadBetrayalMentat(fixture.battle), owner,
{ type: 'decision', event: fixture.battle.battle!.mentatQuestionEvent, weapon: 'Lasgun' });
assert.equal(game.decision?.kind, 'mentatQuestion');
assert.equal(game.decision?.player, target);
const pending = viewGame(game, target).mentat.pending!;
assert.deepEqual(pending.cards, betrayalMentatSeat(game, target).hand);
const shown = pending.cards.find(card => card.name === 'Snooper')!;
rejectBetrayalMentat(game, observer, { type: 'decision', event: pending.event, card: shown.id });
rejectBetrayalMentat(game, target, { type: 'decision', event: pending.event, decline: true });
rejectBetrayalMentat(game, target, { type: 'decision', event: pending.event, card: 'not-held' });
game = applyAction(reloadBetrayalMentat(game), target,
{ type: 'decision', event: pending.event, card: shown.id });
assert.equal(viewGame(game, owner).mentat.history[0].card.id, shown.id);
assert.deepEqual(viewGame(game, observer).mentat.history, []);
assert.deepEqual(betrayalMentatResources(game), before);
game = prepareBetrayalMentatPlans(game);
const targetSkill = game.leaderSkills!.assignments.find(a => a.owner === target)!.leader;
const targetLeader = betrayalMentatSeat(game, target).leaders.find(l =>
!l.dead && !l.capturedBy && l.id !== targetSkill)!;
game = applyAction(game, owner, { type: 'battlePlan', dial: 0, leader: fixture.leader });
game = applyAction(game, target, { type: 'battlePlan', dial: 0, leader: targetLeader.id });
assert.equal(game.battle!.plans[target].defense, null);
assert.ok(betrayalMentatSeat(game, target).hand.some(card => card.id === shown.id));
assert.deepEqual(betrayalMentatResources(game), before);
});
void test('all four legal policies apply the composed private question and response through the original next battle preparation', () => {
const fixture = createBetrayalMentatQuestionFixture();
for (const difficulty of DIFFICULTIES) {
let game = reloadBetrayalMentat(fixture.battle);
const resources = betrayalMentatResources(game);
const own = viewGame(game, fixture.owner);
own.players.find(p => p.id === fixture.owner)!.bot = difficulty;
assert.deepEqual(botActions(viewGame(game, fixture.target)), []);
assert.deepEqual(botActions(viewGame(game, fixture.observer)), []);
const ask = botActions(own)[0];
assert.ok(own.mentat.pending!.weapons.includes(ask.weapon as string));
game = applyAction(game, fixture.owner, ask);
const target = viewGame(game, fixture.target);
target.players.find(p => p.id === fixture.target)!.bot = difficulty;
const show = botActions(target)[0];
assert.ok(target.mentat.pending!.cards.some(card => card.id === show.card));
game = applyAction(game, fixture.target, show);
assert.equal(viewGame(game, fixture.owner).mentat.history.length, 1);
assert.deepEqual(viewGame(game, fixture.observer).mentat.history, []);
assert.equal(game.decision?.kind, 'leaderSkillVisibility');
const actor = game.decision!.player;
const posture = viewGame(game, actor);
posture.players.find(p => p.id === actor)!.bot = difficulty;
game = applyAction(game, actor, botActions(posture)[0]);
game = prepareBetrayalMentatPlans(game);
assert.equal(game.decision, null);
assert.equal(game.battle!.preparation, undefined);
assert.deepEqual(betrayalMentatResources(game), resources);
}
});