Enable Advanced Ixian auction knowledge questions
Verify and publish checkpoint container / container (push) Canceled after 0s
Verify and publish checkpoint container / container (push) Canceled after 0s
This commit is contained in:
1 parent
3a27c29a03
commit
88c0ab1547
10 files changed
+161
-13
No files matched your search
@@ -47,6 +47,17 @@ void test('canonical Richese inspectors describe available previews and retain c
|
||||
assert.doesNotMatch(cardPresentation(definition.card).availability ?? '', /expansion starts remain disabled/i);
|
||||
});
|
||||
|
||||
void test('Semuta inspector explains the supported Basic and Advanced previews', () => {
|
||||
const semuta = RICHESE_CARD_DEFINITIONS.find(row => row.card.effect === 'semutaDrug');
|
||||
assert.ok(semuta);
|
||||
const guide = richeseCardActionBlock(semuta.card) ?? '';
|
||||
assert.match(guide, /fresh Basic Richese games/);
|
||||
assert.match(guide, /Advanced games with Richese, the CHOAM deck and classic or CHOAM opponents/);
|
||||
assert.match(guide, /some discard reactions/);
|
||||
assert.match(guide, /Older games and other module previews keep their existing scope/);
|
||||
assert.match(guide, /Commit before inspecting private discarded faces/);
|
||||
});
|
||||
|
||||
void test('missing, altered and merely similar Richese identities receive no canonical full guide', () => {
|
||||
for (const card of richeseCards()) {
|
||||
const impostors: Card[] = [
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import test from 'node:test';
|
||||
import { applyAction, createGame, joinGame, newPlayer, viewGame, type Game } from '../game/engine';
|
||||
import { botActions } from '../game/bots';
|
||||
import { DIFFICULTIES } from '../game/bot-profiles';
|
||||
import { knowledgeFactAnswer, truthKnowledgeOf } from '../game/truthtrance-knowledge';
|
||||
import { TRUTH_CARD_NAMES } from '../game/truthtrance-card-names';
|
||||
import { nextStrongholdFactionsNativeStep } from './fixture-stronghold-factions';
|
||||
import { moveSemutaEmperorCard } from './fixture-semuta-emperor-share';
|
||||
import { truthMovementInventory } from './fixture-truth-movement';
|
||||
import { askKnowledge, rejectKnowledge } from './truthtrance-knowledge-fixture';
|
||||
|
||||
function advance(state: Game, done: (game: Game) => boolean): Game {
|
||||
let game = state;
|
||||
for (let i = 0; i < 500 && !done(game); i++) {
|
||||
const next = nextStrongholdFactionsNativeStep(game);
|
||||
assert.ok(next, 'Native setup or Bidding must have a legal next action');
|
||||
game = applyAction(game, next.actor, next.action);
|
||||
}
|
||||
assert.ok(done(game), 'Native Bidding must reach the requested boundary');
|
||||
return game;
|
||||
}
|
||||
|
||||
/** Start the actual public Advanced expansion preview and retain genuine Ixian
|
||||
* auction inspection. Only the question card and first Spice Blow are staged,
|
||||
* by moving original physical cards; no auction or knowledge record is forged. */
|
||||
function inspected(exchange: boolean) {
|
||||
let game = createGame('ADVIXTRUTH', newPlayer('atreides', 'Atreides', 'atreides'), true, ['ix']);
|
||||
for (const faction of ['ixians', 'emperor', 'fremen'] as const)
|
||||
joinGame(game, newPlayer(faction, faction, faction));
|
||||
for (const player of game.players) game = applyAction(game, player.id, { type: 'ready' });
|
||||
game = applyAction(game, 'atreides', { type: 'start', advancedPreview: true });
|
||||
assert.equal(game.status, 'setup');
|
||||
game = advance(game, state => state.status === 'playing');
|
||||
const cards = truthMovementInventory(game);
|
||||
const truth = [...game.deck, ...game.discard,
|
||||
...game.players.filter(player => player.id !== 'ixians').flatMap(player => player.hand)]
|
||||
.find(card => card.effect === 'truthtrance');
|
||||
assert.ok(truth, 'An original Truthtrance remains outside the Ixian starting hand');
|
||||
moveSemutaEmperorCard(game, truth.id, 'atreides');
|
||||
assert.deepEqual(truthMovementInventory(game), cards);
|
||||
const index = game.spiceDeck.findIndex(card => 'territory' in card && !card.discovery);
|
||||
assert.ok(index >= 0);
|
||||
const [blow] = game.spiceDeck.splice(index, 1);
|
||||
game.spiceDeck.unshift(blow);
|
||||
game = advance(game, state => state.decision?.kind === 'ixAuction' && !state.response);
|
||||
const pool = game.ixAuction!.cards;
|
||||
assert.ok(pool.length > 1);
|
||||
const returned = pool[0].id;
|
||||
const retained = pool.slice(1).map(card => card.id).sort();
|
||||
game = applyAction(game, 'ixians', { type: 'decision', card: returned, position: 'bottom' });
|
||||
assert.equal(game.decision?.kind, 'ixTechnology');
|
||||
assert.deepEqual(game.ixAuctionKnown!.cards.map(card => card.id).sort(), retained);
|
||||
const before = [...game.ixAuctionKnown!.cards];
|
||||
const incoming = game.auction!.cards[game.auction!.index];
|
||||
const offered = game.players.find(player => player.id === 'ixians')!.hand[0];
|
||||
assert.ok(incoming && offered);
|
||||
game = applyAction(game, 'ixians', exchange
|
||||
? { type: 'decision', card: offered.id }
|
||||
: { type: 'decision', decline: true });
|
||||
if (exchange) {
|
||||
// A response with no eligible counter resolves automatically; otherwise
|
||||
// the native pass queue completes the same original exchange.
|
||||
if (game.response) assert.equal(game.response.kind, 'ixTechnology');
|
||||
game = advance(game, state => !state.response && !state.decision && !state.phaseOpening && !!state.auction);
|
||||
assert.equal(game.auction!.cards[game.auction!.index].id, offered.id);
|
||||
assert.ok(game.players.find(player => player.id === 'ixians')!.hand.some(card => card.id === incoming.id));
|
||||
assert.deepEqual(game.ixAuctionKnown!.cards.map(card => card.id).sort(),
|
||||
[...before.filter(card => card.id !== incoming.id && card.id !== offered.id), offered].map(card => card.id).sort());
|
||||
} else game = advance(game, state => !state.response && !state.decision && !state.phaseOpening && !!state.auction);
|
||||
assert.deepEqual(truthMovementInventory(game), cards);
|
||||
return { game, cards, returned, offered };
|
||||
}
|
||||
|
||||
for (const exchange of [false, true]) void test(`Advanced native Ixian group knowledge after Technology ${exchange ? 'exchange' : 'decline'}`, () => {
|
||||
const { game, cards, offered } = inspected(exchange);
|
||||
const group = structuredClone(game.ixAuctionKnown!.cards);
|
||||
const name = exchange ? offered.name : group[0].name;
|
||||
const questioned = askKnowledge(game, 'ixians', { kind: 'ixPoolPeek', name });
|
||||
assert.equal(viewGame(questioned, 'ixians').truthAnswer, 'yes');
|
||||
for (const id of ['atreides', 'emperor', 'fremen']) assert.equal(viewGame(questioned, id).truthAnswer, null);
|
||||
rejectKnowledge(questioned, 'ixians', { type: 'truthAnswer', answer: 'no' });
|
||||
for (const difficulty of DIFFICULTIES) {
|
||||
const view = viewGame(questioned, 'ixians');
|
||||
view.players.find(player => player.id === 'ixians')!.bot = difficulty;
|
||||
const actions = botActions(view);
|
||||
assert.deepEqual(actions, [{ type: 'truthAnswer', answer: 'yes' }]);
|
||||
const done = applyAction(questioned, 'ixians', actions[0]);
|
||||
assert.equal(done.truthHistory!.at(-1)!.answer, 'yes');
|
||||
assert.deepEqual(done.ixAuctionKnown!.cards, group);
|
||||
assert.deepEqual(truthMovementInventory(done), cards);
|
||||
}
|
||||
});
|
||||
|
||||
void test('Advanced auction-group answers require current native Ixian knowledge', () => {
|
||||
const { game } = inspected(false);
|
||||
const ix = game.players.find(player => player.id === 'ixians')!;
|
||||
const other = game.players.find(player => player.id === 'emperor')!;
|
||||
const name = TRUTH_CARD_NAMES.find(candidate => !game.ixAuctionKnown!.cards.some(card => card.name === candidate));
|
||||
assert.ok(name);
|
||||
const fact = { kind: 'ixPoolPeek', name } as const;
|
||||
assert.equal(knowledgeFactAnswer(fact, truthKnowledgeOf(game, ix)), 'no');
|
||||
assert.equal(knowledgeFactAnswer(fact, truthKnowledgeOf(game, other)), 'unknown');
|
||||
assert.equal(knowledgeFactAnswer(fact, truthKnowledgeOf({ ...game, phase: 4 }, ix)), 'unknown');
|
||||
assert.equal(knowledgeFactAnswer(fact, truthKnowledgeOf({ ...game, ixAuctionKnown: null }, ix)), 'unknown');
|
||||
assert.equal(knowledgeFactAnswer(fact, truthKnowledgeOf({ ...game, ixAuctionKnown: { ...game.ixAuctionKnown!, turn: game.turn - 1 } }, ix)), 'unknown');
|
||||
assert.equal(knowledgeFactAnswer(fact, truthKnowledgeOf({ ...game, ixAuctionKnown: { ...game.ixAuctionKnown!, controller: other.id } }, ix)), 'unknown');
|
||||
});
|
||||
@@ -14,7 +14,7 @@ void test('Ixian pool question accepts only canonical card names and describes t
|
||||
assert.match(knowledgeFactText({ kind: 'ixPoolPeek', name: 'Shield' }), /recorded Ixian auction group includes Shield/);
|
||||
});
|
||||
|
||||
void test('only native Basic Ixians with the current recorded inspection can answer', () => {
|
||||
void test('only native Ixians with the current recorded inspection can answer', () => {
|
||||
const game = createGame('IXKNOW', newPlayer('ix', 'Ixians', 'ixians'), false, ['ix']);
|
||||
const ix = game.players[0];
|
||||
const other = newPlayer('other', 'Other', 'atreides');
|
||||
@@ -28,7 +28,7 @@ void test('only native Basic Ixians with the current recorded inspection can ans
|
||||
assert.equal(knowledgeFactAnswer(no, truthKnowledgeOf(game, ix)), 'no');
|
||||
assert.equal(knowledgeFactAnswer(yes, truthKnowledgeOf(game, other)), 'unknown');
|
||||
assert.equal(knowledgeFactAnswer(yes, truthKnowledgeOf({ ...game, phase: 4 }, ix)), 'unknown');
|
||||
assert.equal(knowledgeFactAnswer(yes, truthKnowledgeOf({ ...game, advanced: true }, ix)), 'unknown');
|
||||
assert.equal(knowledgeFactAnswer(yes, truthKnowledgeOf({ ...game, advanced: true }, ix)), 'yes');
|
||||
assert.equal(knowledgeFactAnswer(yes, truthKnowledgeOf({ ...game, ixAuctionKnown: null }, ix)), 'unknown');
|
||||
assert.equal(knowledgeFactAnswer(yes, truthKnowledgeOf({ ...game, ixAuctionKnown: { turn: game.turn - 1, cards: [shield] } }, ix)), 'unknown');
|
||||
assert.equal(knowledgeFactAnswer(yes, truthKnowledgeOf({ ...game, ixAuctionKnown: { turn: game.turn, cards: [shield], controller: 'other' } }, ix)), 'unknown');
|
||||
|
||||
@@ -46,7 +46,7 @@ void test('recorded Ixian pool selector appears in standalone and compound facts
|
||||
assert.match(single, /Recorded Ixian inspected auction pool includes/);
|
||||
assert.match(single, /value="Shield" selected/);
|
||||
assert.match(single, /pool itself remains private/);
|
||||
assert.match(single, /In Basic Bidding/);
|
||||
assert.match(single, /During Bidding/);
|
||||
assert.match(single, /If that knowledge is unavailable/);
|
||||
const compound: TruthFact = { kind: 'or', terms: [fact, { kind: 'spice', compare: 'gte', value: 3 }] };
|
||||
const grouped = renderToStaticMarkup(createElement(TruthFactExpressionFields, { value: compound, game: view, target: 'b', disabled: false, onChange() {} }));
|
||||
|
||||
Reference in new issue
Block a user