290 lines
22 KiB
TypeScript
290 lines
22 KiB
TypeScript
import assert from 'node:assert/strict';
|
|
import test from 'node:test';
|
|
import { botActions } from '../game/bots';
|
|
import { DIFFICULTIES } from '../game/bot-profiles';
|
|
import { spiceDeck, treacheryDeck } from '../game/cards';
|
|
import { viewGame, type Action, type Game } from '../game/engine';
|
|
import { CHEAP_HERO_TRAITOR, traitorDeck } from '../game/traitors';
|
|
import { truthFactAnswer } from '../game/truthtrance';
|
|
import { windowCardOpportunity } from '../game/window-card-play';
|
|
import { beforeIxSpiceDraw, orderIxSpiceCards } from './fixture-ix-spice-cards';
|
|
import {
|
|
actTruthTurnAmal as act, advanceTruthTurnAmal as advance, answerTruthTurnAmal as answer,
|
|
askTruthTurnAmal as ask, bindTruthTurnAmal as bind, closeTruthTurnAmal as close,
|
|
createTruthTurnAmalFixture as fixture, declareTruthTurnAmal as declare, finishTruthTurnAmal as finish,
|
|
nextTruthTurnAmalNative as nativeStep, settleTruthTurnAmal as settle,
|
|
truthTurnAmalFact as fact, truthTurnAmalGroup as group, truthTurnAmalInventory as inventory,
|
|
truthTurnAmalPayment as payment, truthTurnAmalPlay as play, truthTurnAmalPlayer as player,
|
|
truthTurnAmalPromise as promise, truthTurnAmalQuiet as quiet, truthTurnAmalSpice as spice,
|
|
truthTurnAmalTraitors as traitors, type TruthTurnAmalFixture, type TruthTurnAmalStep,
|
|
} from './fixture-truth-turn-amal';
|
|
|
|
function rejected(game: Game, actor: string, action: Action) {
|
|
const before = structuredClone(game); assert.throws(() => act(game, actor, action)); assert.deepEqual(game, before);
|
|
}
|
|
function physical(game: Game, f: TruthTurnAmalFixture, copies: 1 | 2 = 1) {
|
|
for (const truth of f.truths.slice(0, copies)) assert.equal(game.discard.filter(card => card.id === truth).length, 1);
|
|
assert.deepEqual(inventory(game), f.originalCards); assert.deepEqual(traitors(game), f.originalTraitors);
|
|
assert.deepEqual(spice(game), f.originalSpice);
|
|
}
|
|
function receipt(game: Game, index = 0) {
|
|
const stage = game.turnActionPromises![index].windowCards?.amal; assert.ok(stage); return stage;
|
|
}
|
|
|
|
void test('native Amal sources retain original selected-Ix inventories and real first Storm, Spice and Mentat openings', () => {
|
|
for (const advanced of [false, true]) for (const phase of [0, 1, 8] as const) {
|
|
const f = fixture({ phase, advanced }); assert.equal(f.initial.status, 'lobby'); assert.equal(f.setup.status, 'setup');
|
|
assert.ok(f.setup.players.every(candidate => candidate.hand.length === 0));
|
|
assert.deepEqual(f.originalCards, treacheryDeck(['ix']).map(card => card.id).sort());
|
|
assert.deepEqual(f.originalTraitors, traitorDeck(f.setup.players, true).sort()); assert.ok(f.originalTraitors.includes(CHEAP_HERO_TRAITOR));
|
|
assert.equal(f.originalCards.length, 47); assert.equal(f.originalSpice.length, spiceDeck(true).length);
|
|
assert.deepEqual(player(f.game, f.asker).hand.map(card => card.id), f.truths);
|
|
assert.deepEqual(player(f.game, f.target).hand.map(card => card.id), [f.amal]);
|
|
assert.equal(f.game.phase, phase); assert.equal(f.game.turn, 1); assert.ok(f.game.phaseOpening);
|
|
assert.deepEqual(f.game.phaseOpening.passed, []);
|
|
const collectionIncome = f.game.players.map(candidate => f.collection
|
|
? player(f.collection.after, candidate.id).spice - player(f.collection.before, candidate.id).spice : 0);
|
|
assert.deepEqual(f.game.players.map(candidate => candidate.spice), f.afterSetup.players.map((candidate, index) => candidate.spice + collectionIncome[index]));
|
|
if (phase === 8) {
|
|
assert.ok(f.collection); assert.equal(f.collection.before.phase, 7); assert.ok(f.collection.before.phaseOpening);
|
|
assert.equal(f.collection.after.phase, 7); assert.equal(f.collection.after.phaseOpening, null);
|
|
assert.deepEqual(collectionIncome, advanced ? [2, 2, 0] : [0, 0, 0]);
|
|
assert.equal(f.sourceCounts.actualCollection, f.collection.steps.length);
|
|
assert.ok(f.collection.steps.some(step => step.action.type === 'ready'));
|
|
assert.equal(player(f.collection.before, f.asker).forces['carthag:11'], 10);
|
|
assert.equal(player(f.collection.before, f.observer).forces['arrakeen:10'], 10);
|
|
}
|
|
assert.equal(f.source, `phase-opening:1:${phase}`); assert.deepEqual(windowCardOpportunity(f.game, 'amal'), f.opportunity);
|
|
assert.equal(f.sourceActionCount, f.sourceActions.length); assert.equal(f.sourceCounts.selected, f.sourceActions.length);
|
|
assert.ok(f.sourceCounts.originalSetup <= f.sourceActionCount);
|
|
if (phase > 0) assert.ok(f.sourceActions.some(step => step.action.type === 'ready'));
|
|
assert.deepEqual(viewGame(f.game, f.target).amalCards, [f.amal]); assert.equal(viewGame(f.game, f.target).amalReady, true);
|
|
assert.deepEqual(viewGame(f.game, f.asker).amalCards, []); assert.equal(f.game.turnActionPromises?.length ?? 0, 0);
|
|
assert.deepEqual(inventory(f.game), f.originalCards); assert.deepEqual(traitors(f.game), f.originalTraitors);
|
|
assert.deepEqual(spice(f.game), f.originalSpice);
|
|
}
|
|
});
|
|
|
|
void test('native Amal AND payment quotes the real halving before payment and freezes the original spice fact', () => {
|
|
for (const advanced of [false, true]) for (const phase of [0, 1] as const) {
|
|
const f = fixture({ phase, advanced });
|
|
const mixed = group('and', fact(f.spiceFact), play(f.source), payment(f.recipient));
|
|
let game = bind(f, 'yes', mixed).game; rejected(game, f.target, f.payment);
|
|
const guide = viewGame(game, f.target).turnActionGuide; assert.ok(guide);
|
|
assert.equal(guide.action.type, 'card'); assert.equal(guide.action.card, f.amal);
|
|
game = settle(act(game, f.target, guide.action));
|
|
assert.deepEqual(game.players.map(candidate => candidate.spice), [5, 5, 5]); assert.ok(game.phaseOpening);
|
|
assert.deepEqual(game.phaseOpening.passed, []); assert.equal(truthFactAnswer(player(game, f.target), f.spiceFact), 'no');
|
|
assert.equal(receipt(game).played, true); assert.ok(!promise(game, f.target).fulfilled); rejected(game, f.target, f.payment);
|
|
game = close(game, f); game = act(game, f.target, f.payment);
|
|
assert.equal(player(game, f.target).spice, 0); assert.equal(player(game, f.recipient).bribes, 5);
|
|
assert.equal(promise(game, f.target).fulfilled, true); assert.deepEqual(promise(game, f.target).mixed, mixed);
|
|
assert.equal(game.discard.filter(card => card.id === f.amal).length, 1); physical(game, f);
|
|
}
|
|
// Absolute five-versus-six funding uses the closing Mentat opening. Early
|
|
// examples above prove an owned-current-purse path, not all future income.
|
|
const closing = fixture({ phase: 8 }), impossible = group('and', play(closing.source), payment(closing.recipient, 6));
|
|
const asked = ask(declare(closing).game, closing.asker, closing.target, impossible);
|
|
assert.deepEqual(viewGame(asked, closing.target).truthTurnActionAnswers, ['no']);
|
|
rejected(asked, closing.target, { type: 'truthAnswer', answer: 'yes' });
|
|
let funded = bind(closing).game; funded = settle(act(funded, closing.target, closing.nativeAction));
|
|
funded = close(funded, closing); funded = act(funded, closing.target, closing.payment);
|
|
assert.equal(player(funded, closing.target).spice, 0); assert.equal(promise(funded, closing.target).fulfilled, true); physical(funded, closing);
|
|
});
|
|
|
|
void test('whole Amal OR payment Yes preserves printed halving or an ordinary payment after passing without halving', () => {
|
|
const f = fixture(), mixed = group('or', play(f.source), payment(f.recipient));
|
|
const bound = bind(f, 'yes', mixed).game;
|
|
const played = settle(act(bound, f.target, f.nativeAction));
|
|
assert.equal(promise(played, f.target).fulfilled, true); assert.equal(player(played, f.recipient).bribes, 0);
|
|
assert.equal(player(played, f.target).spice, 5);
|
|
let paid = act(bound, f.target, { type: 'ready' }); paid = close(paid, f);
|
|
assert.equal(player(paid, f.target).spice, 10); assert.equal(receipt(paid).played, false);
|
|
paid = act(paid, f.target, f.payment); assert.equal(player(paid, f.target).spice, 5);
|
|
assert.equal(promise(paid, f.target).fulfilled, true); assert.ok(player(paid, f.target).hand.some(card => card.id === f.amal));
|
|
physical(played, f); physical(paid, f);
|
|
});
|
|
|
|
void test('whole Amal AND payment No limits the real payment after cost but permits payment after the unplayed opening closes', () => {
|
|
const f = fixture(); let played = bind(f, 'no').game;
|
|
played = settle(act(played, f.target, f.nativeAction)); played = close(played, f);
|
|
rejected(played, f.target, f.payment);
|
|
played = act(played, f.target, { type: 'bribe', target: f.recipient, amount: 4 });
|
|
rejected(played, f.target, { type: 'bribe', target: f.recipient, amount: 1 });
|
|
assert.equal(receipt(played).played, true); assert.ok(!promise(played, f.target).released);
|
|
played = finish(played); assert.equal(promise(played, f.target).fulfilled, true); physical(played, f);
|
|
let paid = close(bind(f, 'no').game, f); assert.equal(receipt(paid).played, false);
|
|
paid = act(paid, f.target, f.payment); assert.equal(player(paid, f.target).spice, 5);
|
|
paid = finish(paid); assert.equal(promise(paid, f.target).fulfilled, true); physical(paid, f);
|
|
});
|
|
|
|
void test('whole Amal OR payment No preserves its cumulative payment cap through the original opening and current turn', () => {
|
|
const f = fixture(), mixed = group('or', play(f.source), payment(f.recipient));
|
|
let game = bind(f, 'no', mixed).game; rejected(game, f.target, f.nativeAction); rejected(game, f.target, f.payment);
|
|
game = close(game, f); assert.deepEqual(receipt(game), { source: f.source, played: false, closed: true });
|
|
game = act(game, f.target, { type: 'bribe', target: f.recipient, amount: 4 });
|
|
rejected(game, f.target, { type: 'bribe', target: f.recipient, amount: 1 });
|
|
game = finish(game); assert.equal(promise(game, f.target).fulfilled, true); physical(game, f);
|
|
});
|
|
|
|
void test('native own opening Ready cannot evade a required Amal while foreign passes remain legal and the actual play resets them', () => {
|
|
const f = fixture({ phase: 1 }); let game = bind(f, 'yes', play(f.source)).game;
|
|
const view = viewGame(game, f.target); assert.equal(view.amalReady, true);
|
|
assert.equal(view.turnActionGuide?.action.type, 'card'); assert.equal(view.turnActionGuide?.action.card, f.amal);
|
|
rejected(game, f.target, { type: 'ready' });
|
|
game = act(game, f.asker, { type: 'ready' }); game = act(game, f.observer, { type: 'ready' });
|
|
assert.ok(game.phaseOpening?.passed.includes(f.asker) && game.phaseOpening.passed.includes(f.observer));
|
|
rejected(game, f.target, { type: 'ready' }); game = settle(act(game, f.target, f.nativeAction));
|
|
assert.deepEqual(game.phaseOpening?.passed, []); assert.equal(promise(game, f.target).fulfilled, true);
|
|
game = close(game, f); assert.equal(game.phaseOpening, null); physical(game, f);
|
|
});
|
|
|
|
void test('definite Yes and No including constants reopen a real earlier own opening pass', () => {
|
|
const f = fixture(), passed = act(f.game, f.target, { type: 'ready' }); assert.ok(passed.phaseOpening?.passed.includes(f.target));
|
|
for (const [selected, mixed] of [
|
|
['yes', play(f.source)], ['no', play(f.source)],
|
|
['yes', group('or', fact(f.spiceFact), play(f.source))],
|
|
['no', group('and', fact({ kind: 'spice', compare: 'gte', value: 100 }), play(f.source))],
|
|
] as const) {
|
|
const game = bind(f, selected, mixed, passed).game;
|
|
assert.ok(game.phaseOpening); assert.equal(game.phaseOpening.passed.includes(f.target), false);
|
|
assert.equal(windowCardOpportunity(game, 'amal')?.source, f.source); physical(game, f);
|
|
}
|
|
});
|
|
|
|
void test('temporary Truthtrance priority and question stages preserve the original actual Amal opening without halving', () => {
|
|
const f = fixture({ phase: 1 }), declared = declare(f);
|
|
assert.ok(declared.game.phaseOpening); assert.equal(windowCardOpportunity(declared.game, 'amal')?.source, f.source);
|
|
const asked = ask(declared.game, f.asker, f.target, f.mixed);
|
|
assert.ok(asked.phaseOpening); assert.equal(windowCardOpportunity(asked, 'amal')?.source, f.source);
|
|
assert.deepEqual(asked.players.map(candidate => candidate.spice), [10, 10, 10]);
|
|
assert.equal(player(asked, f.target).hand.some(card => card.id === f.amal), true);
|
|
const game = answer(asked, f.target, 'yes'); assert.deepEqual(game.players.map(candidate => candidate.spice), [10, 10, 10]);
|
|
assert.equal(receipt(game).played, false); assert.deepEqual(game.phaseOpening?.passed, []); physical(game, f);
|
|
});
|
|
|
|
void test('a foreign actual Mentat Amal may release another respondent full-purse payment without vetoing the cost or last pass', () => {
|
|
const f = fixture({ phase: 8 }), amount = player(f.game, f.observer).spice, mixed = payment(f.recipient, amount);
|
|
const asked = ask(declare(f).game, f.asker, f.observer, mixed);
|
|
let game = answer(asked, f.observer, 'yes'); const history = structuredClone(game.truthHistory!.at(-1));
|
|
const purses = game.players.map(candidate => candidate.spice);
|
|
game = settle(act(game, f.target, f.nativeAction));
|
|
assert.deepEqual(game.players.map(candidate => candidate.spice), purses.map(value => Math.floor(value / 2)));
|
|
assert.equal(promise(game, f.observer).released, true); assert.ok(!promise(game, f.observer).fulfilled);
|
|
assert.deepEqual(game.truthHistory!.at(-1), history); assert.deepEqual(game.turnActionPromises![0].payments, []);
|
|
game = act(game, f.asker, { type: 'ready' }); game = act(game, f.target, { type: 'ready' });
|
|
game = act(game, f.observer, { type: 'ready' }); assert.equal(game.phaseOpening, null);
|
|
assert.equal(promise(game, f.observer).released, true);
|
|
assert.equal(game.discard.filter(card => card.id === f.amal).length, 1); physical(game, f);
|
|
});
|
|
|
|
void test('actual queued phase7 bribes arrive only after Mentat opening halving and fund nine rather than ten', () => {
|
|
const f = fixture({ phase: 8, incoming: 4 }); assert.ok(f.incomingReceipt);
|
|
assert.equal(f.sourceCounts.actualIncomingPayment, f.incomingReceipt.steps.length);
|
|
assert.ok(f.incomingReceipt.steps.some(step => step.actor === f.asker && step.action.type === 'bribe' && step.action.amount === 4));
|
|
assert.equal(player(f.game, f.target).spice, 10); assert.equal(player(f.game, f.target).bribes, 4);
|
|
const impossible = group('and', play(f.source), payment(f.recipient, 10));
|
|
const asked = ask(declare(f).game, f.asker, f.target, impossible);
|
|
assert.deepEqual(viewGame(asked, f.target).truthTurnActionAnswers, ['no']);
|
|
const mixed = group('and', fact(f.spiceFact), play(f.source), payment(f.recipient, 9));
|
|
let game = bind(f, 'yes', mixed).game; game = settle(act(game, f.target, f.nativeAction));
|
|
assert.equal(player(game, f.target).spice, 5); assert.equal(player(game, f.target).bribes, 4);
|
|
rejected(game, f.target, { type: 'bribe', target: f.recipient, amount: 9 });
|
|
game = close(game, f); assert.equal(player(game, f.target).spice, 9); assert.equal(player(game, f.target).bribes, 0);
|
|
const guide = viewGame(game, f.target).turnActionGuide; assert.ok(guide);
|
|
assert.equal(guide.action.type, 'bribe'); assert.equal(guide.action.amount, 9); game = act(game, f.target, guide.action);
|
|
assert.equal(player(game, f.target).spice, 0); assert.equal(player(game, f.recipient).bribes, 9);
|
|
assert.equal(promise(game, f.target).fulfilled, true); physical(game, f);
|
|
});
|
|
|
|
void test('an original first-opening No cannot count or forbid the actual Amal promised by a second Truth in the next phase', () => {
|
|
const f = fixture(), actions: TruthTurnAmalStep[] = [];
|
|
let game = close(bind(f, 'no', play(f.source), f.game, 0, actions).game, f, actions);
|
|
const earlier = structuredClone(receipt(game)); assert.equal(promise(game, f.target).fulfilled, true);
|
|
assert.ok(player(game, f.target).hand.some(card => card.id === f.amal));
|
|
game = advance(game, state => state.phase === 1 && !!state.phaseOpening && quiet(state), actions, true);
|
|
const next = windowCardOpportunity(game, 'amal'); assert.ok(next); assert.notEqual(next.source, f.source);
|
|
game = bind(f, 'yes', play(next.source), game, 1, actions).game;
|
|
const guide = viewGame(game, f.target).turnActionGuide; assert.ok(guide); assert.equal(guide.event, game.turnActionPromises![1].event);
|
|
game = settle(act(game, f.target, guide.action, actions), actions);
|
|
assert.equal(game.turnActionPromises![1].fulfilled, true); assert.equal(receipt(game, 1).played, true);
|
|
assert.deepEqual(receipt(game), earlier); assert.equal(receipt(game).played, false);
|
|
assert.deepEqual(game.players.map(candidate => candidate.spice), [5, 5, 5]);
|
|
assert.equal(game.discard.filter(card => card.id === f.amal).length, 1); assert.ok(actions.length > f.sourceActionCount); physical(game, f, 2);
|
|
});
|
|
|
|
void test('exact Amal guides retain the original opening, physical owner and event while stale or late sources reject', () => {
|
|
const f = fixture({ phase: 1 }), declared = declare(f);
|
|
for (const source of ['phase-opening:1:0', 'phase-opening:2:1', `${f.source}:extra`])
|
|
rejected(declared.game, f.asker, { type: 'truthAsk', question: { ...f.question, mixed: play(source) } });
|
|
rejected(declared.game, f.target, { type: 'truthAsk', question: f.question });
|
|
rejected(declared.game, f.asker, { type: 'truthAsk', question: { ...f.question, mixed: { kind: 'action', action: {
|
|
kind: 'windowCardPlay', effect: 'amal', source: f.source, amount: 5,
|
|
} } } });
|
|
let game = bind(f, 'yes', play(f.source)).game;
|
|
const own = viewGame(game, f.target), guide = own.turnActionGuide; assert.ok(guide);
|
|
assert.deepEqual(Object.keys(guide.action).sort(), ['type', 'card', 'turnActionPromiseEvent', 'turnActionPromisePhase'].sort());
|
|
assert.equal(guide.action.type, 'card'); assert.equal(guide.action.card, f.amal);
|
|
assert.equal(guide.event, promise(game, f.target).event); assert.equal(guide.action.turnActionPromiseEvent, guide.event);
|
|
assert.equal(guide.action.turnActionPromisePhase, 1); assert.deepEqual(own.amalCards, [f.amal]); assert.equal(own.amalReady, true);
|
|
assert.deepEqual(viewGame(game, f.asker).amalCards, []);
|
|
assert.ok(Object.keys(promise(game, f.target)).every(key => ['event', 'player', 'asker', 'turn', 'mixed', 'answer', 'fulfilled', 'released'].includes(key)));
|
|
rejected(game, f.target, { type: 'ready' }); rejected(game, f.asker, guide.action);
|
|
rejected(game, f.target, { ...guide.action, mode: 'special' });
|
|
rejected(game, f.target, { ...guide.action, turnActionPromiseEvent: 'stale-amal-event' });
|
|
rejected(game, f.target, { ...guide.action, turnActionPromisePhase: 0 });
|
|
const packet: Action = JSON.parse(JSON.stringify(guide.action)); game = settle(act(game, f.target, packet));
|
|
rejected(game, f.target, packet); assert.equal(promise(game, f.target).fulfilled, true); physical(game, f);
|
|
const closed = close(f.game, f); rejected(closed, f.target, f.nativeAction);
|
|
const after = declare(f, closed); rejected(after.game, f.asker, { type: 'truthAsk', question: f.question });
|
|
});
|
|
|
|
void test('an actual bidding opening quote leaves the unopened auction pool untouched until real global passes initialize it', () => {
|
|
const f = fixture({ phase: 1 }); let game = close(f.game, f); assert.equal(beforeIxSpiceDraw(game), true);
|
|
orderIxSpiceCards(game, [{ territory: 'hagga_basin' }]);
|
|
game = advance(game, state => state.phase === 3 && !!state.phaseOpening && quiet(state), [], true);
|
|
assert.equal(game.auction, null); const deck = game.deck.map(card => card.id), current = windowCardOpportunity(game, 'amal'); assert.ok(current);
|
|
const mixed = group('and', play(current.source), payment(f.recipient));
|
|
game = bind(f, 'yes', mixed, game).game;
|
|
assert.equal(game.auction, null); assert.deepEqual(game.deck.map(card => card.id), deck);
|
|
assert.equal(player(game, f.target).spice, 10);
|
|
game = settle(act(game, f.target, f.nativeAction)); assert.equal(game.auction, null); assert.deepEqual(game.deck.map(card => card.id), deck);
|
|
game = close(game, { source: current.source }); assert.ok(game.auction); assert.ok(game.deck.length < deck.length);
|
|
game = act(game, f.target, f.payment); assert.equal(promise(game, f.target).fulfilled, true); physical(game, f);
|
|
});
|
|
|
|
void test('all four minimal profiles execute real Amal before payment and pass a No through native opening Ready', () => {
|
|
for (const phase of [0, 1] as const) {
|
|
const f = fixture({ phase }), asked = ask(declare(f).game, f.asker, f.target, f.mixed);
|
|
for (const difficulty of DIFFICULTIES) {
|
|
let game = structuredClone(asked); const actions: TruthTurnAmalStep[] = [];
|
|
const answering = viewGame(game, f.target); answering.players.find(candidate => candidate.id === f.target)!.bot = difficulty;
|
|
const response = botActions(answering).find(candidate => candidate.type === 'truthAnswer'); assert.ok(response, difficulty);
|
|
assert.equal(response.answer, 'yes'); game = act(game, f.target, response, actions);
|
|
for (let count = 0; count < 22 && !promise(game, f.target).fulfilled; count++) {
|
|
const view = viewGame(game, f.target); view.players.find(candidate => candidate.id === f.target)!.bot = difficulty;
|
|
const guide = view.turnActionGuide;
|
|
if (guide) {
|
|
const action = botActions(view).find(candidate => candidate.turnActionPromiseEvent === guide.event);
|
|
assert.ok(action, `${phase}/${difficulty}`); assert.deepEqual(action, guide.action);
|
|
game = act(game, f.target, action, actions);
|
|
} else {
|
|
const next = nativeStep(game); assert.ok(next, `${phase}/${difficulty}`); game = act(game, next.actor, next.action, actions);
|
|
}
|
|
}
|
|
assert.equal(promise(game, f.target).fulfilled, true, `${phase}/${difficulty}`);
|
|
const costAt = actions.findIndex(step => step.action.type === 'card' && step.action.card === f.amal);
|
|
const payAt = actions.findIndex(step => step.actor === f.target && step.action.type === 'bribe');
|
|
assert.ok(costAt >= 0 && payAt > costAt); assert.equal(player(game, f.target).spice, 0); physical(game, f);
|
|
const no = bind(f, 'no', play(f.source)).game, view = viewGame(no, f.target);
|
|
view.players.find(candidate => candidate.id === f.target)!.bot = difficulty;
|
|
assert.equal(view.amalReady, true); assert.deepEqual(view.amalCards, [f.amal]); rejected(no, f.target, f.nativeAction);
|
|
const ready = botActions(view)[0]; assert.equal(ready.type, 'ready'); assert.deepEqual(ready, view.turnActionGuide?.action);
|
|
const passed = act(no, f.target, ready), passedView = viewGame(passed, f.target);
|
|
assert.equal(passedView.amalReady, false); assert.deepEqual(passedView.amalCards, []);
|
|
const completed = close(passed, f);
|
|
assert.equal(promise(completed, f.target).fulfilled, true); assert.equal(receipt(completed).played, false); physical(completed, f);
|
|
}
|
|
}
|
|
});
|