Prototype Semuta after Recruits in Basic play
Verify and publish checkpoint container / container (push) Successful in 10m20s
Verify and publish checkpoint container / container (push) Successful in 10m20s
This commit is contained in:
1 parent
0e9209531a
commit
1ba942138d
9 files changed
+311
-18
No files matched your search
@@ -0,0 +1,101 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { applyAction, newPlayer, viewGame, type Action, type Game } from '../game/engine';
|
||||
import { botActions } from '../game/bots';
|
||||
import { DIFFICULTIES } from '../game/bot-profiles';
|
||||
import { RECRUITS_CARD_ID } from '../game/recruits';
|
||||
import { SEMUTA_DRUG_ID } from '../game/semuta-drug';
|
||||
import {
|
||||
actSemutaRecruits as act, createSemutaRecruitsFixture as fixture,
|
||||
semutaRecruitsInventory as inventory, semutaRecruitsPlayer as player,
|
||||
} from './fixture-semuta-recruits';
|
||||
|
||||
function rejected(game: Game, actor: string, action: Action) {
|
||||
const before = structuredClone(game);
|
||||
assert.throws(() => applyAction(game, actor, action));
|
||||
assert.deepEqual(game, before);
|
||||
}
|
||||
|
||||
for (const claim of [false, true]) {
|
||||
void test(`public Basic Richese and Ecaz-card Recruits ${claim ? 'claim' : 'pass'} preserves its real free-revival benefit`, () => {
|
||||
const f = fixture(), before = structuredClone(f.game);
|
||||
assert.ok(f.lobby.lobbyOptions?.ecazTreachery);
|
||||
assert.equal(f.setup.semutaRecruitsPreview, true);
|
||||
assert.equal(f.setup.semutaPreview, undefined);
|
||||
assert.equal(viewGame(f.game, f.owner).semutaRecruitsPreview, true);
|
||||
assert.ok(viewGame(f.game, f.owner).recruitsPreview?.play && !viewGame(f.game, f.owner).recruitsPreview!.play!.blocked);
|
||||
assert.equal(viewGame(f.game, f.owner).revival.freeRemaining, 2);
|
||||
const pending = act(f.game, f.owner, f.play);
|
||||
assert.deepEqual(f.game, before);
|
||||
const frame = pending.pendingTreacheryDiscard; assert.ok(frame);
|
||||
assert.equal(frame.reaction?.stage, 'offer');
|
||||
assert.equal(frame.continuation.kind, 'ordinaryCardDiscard');
|
||||
assert.deepEqual(frame.batch.entries.map(entry => entry.card.id), [RECRUITS_CARD_ID]);
|
||||
assert.equal(pending.recruits?.turn, pending.turn); assert.equal(pending.recruits?.player, f.owner);
|
||||
for (const seat of pending.players) {
|
||||
const reaction = viewGame(pending, seat.id).semutaReaction; assert.ok(reaction);
|
||||
assert.deepEqual(reaction.candidates, []);
|
||||
assert.equal(reaction.canCommit, seat.id === f.holder);
|
||||
}
|
||||
rejected(pending, f.owner, {type: 'revive', amount: 3, elite: 0});
|
||||
const event = frame.batch.event;
|
||||
let resumed: Game = pending;
|
||||
if (claim) {
|
||||
resumed = act(resumed, f.holder, {type: 'semutaCommit', event});
|
||||
if (resumed.pendingTreacheryDiscard?.reaction?.stage === 'select')
|
||||
resumed = act(resumed, f.holder, {type: 'semutaSelect', event, card: RECRUITS_CARD_ID});
|
||||
} else {
|
||||
for (const seat of pending.players) resumed = act(resumed, seat.id, {type: 'semutaPass', event});
|
||||
}
|
||||
assert.equal(resumed.pendingTreacheryDiscard, null);
|
||||
assert.equal(viewGame(resumed, f.owner).revival.freeRemaining, 4);
|
||||
assert.equal(viewGame(resumed, f.owner).revival.limit, 7);
|
||||
assert.equal(player(resumed, f.holder).hand.some(card => card.id === RECRUITS_CARD_ID), claim);
|
||||
assert.equal(resumed.discard.filter(card => card.id === RECRUITS_CARD_ID).length, Number(!claim));
|
||||
assert.equal(resumed.discard.filter(card => card.id === SEMUTA_DRUG_ID).length, Number(claim));
|
||||
assert.deepEqual(inventory(resumed), f.originalInventory);
|
||||
rejected(resumed, f.holder, {type: 'semutaCommit', event});
|
||||
if (claim) rejected(resumed, f.holder, f.play);
|
||||
const old = {reserves: player(resumed, f.owner).reserves, tanks: player(resumed, f.owner).tanks,
|
||||
spice: player(resumed, f.owner).spice};
|
||||
const revived = act(resumed, f.owner, {type: 'revive', amount: 3, elite: 0});
|
||||
assert.equal(player(revived, f.owner).tanks, old.tanks - 3);
|
||||
assert.equal(player(revived, f.owner).reserves, old.reserves + 3);
|
||||
assert.equal(player(revived, f.owner).spice, old.spice);
|
||||
assert.equal(player(revived, f.owner).freeForcesRevived, 3);
|
||||
assert.equal(revived.recruits?.turn, resumed.turn);
|
||||
});
|
||||
}
|
||||
|
||||
void test('each AI difficulty can legally answer the Recruits discard offer', () => {
|
||||
const f = fixture();
|
||||
const pending = act(f.game, f.owner, f.play);
|
||||
for (const difficulty of DIFFICULTIES) {
|
||||
const game = structuredClone(pending);
|
||||
player(game, f.holder).bot = difficulty;
|
||||
const action = botActions(viewGame(game, f.holder)).find(item =>
|
||||
item.type === 'semutaCommit' || item.type === 'semutaPass');
|
||||
assert.ok(action);
|
||||
assert.doesNotThrow(() => act(game, f.holder, action));
|
||||
}
|
||||
});
|
||||
|
||||
void test('a changed revival parent cannot resume a saved Recruits discard', () => {
|
||||
const f = fixture();
|
||||
const pending = act(f.game, f.owner, f.play);
|
||||
const stale = structuredClone(pending);
|
||||
stale.freeRevival.push(f.owner);
|
||||
assert.throws(() => viewGame(stale, f.owner), /completed effect/);
|
||||
assert.ok(viewGame(pending, f.owner).semutaReaction);
|
||||
});
|
||||
|
||||
void test('a native CHOAM seat does not silently enable this narrow preview', () => {
|
||||
const f = fixture(), lobby = structuredClone(f.lobby);
|
||||
const index = lobby.players.findIndex(seat => seat.id === 'emperor');
|
||||
assert.ok(index >= 0);
|
||||
lobby.players[index] = newPlayer('emperor', 'CHOAM', 'choam');
|
||||
lobby.players[index].ready = true;
|
||||
const game = act(lobby, lobby.host, {type: 'start'});
|
||||
assert.equal(game.semutaRecruitsPreview, undefined);
|
||||
assert.equal(game.semutaPreview, undefined);
|
||||
});
|
||||
Reference in new issue
Block a user