Support board-wide Truthtrance force 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
f1007a4ea8
commit
89589878fc
12 files changed
+144
-29
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { cardPresentation } from '../game/card-presentation';
|
||||
import { cardPresentation, richeseCardActionBlock } from '../game/card-presentation';
|
||||
import type { Card } from '../game/cards';
|
||||
import { RICHESE_CARD_DEFINITIONS, richeseCards } from '../game/richese-cards';
|
||||
|
||||
@@ -36,7 +36,11 @@ void test('canonical Richese inspectors describe available previews and retain c
|
||||
const karamaAvailability = cardPresentation(karama.card).availability;
|
||||
assert.match(saphoAvailability ?? '', /supported Richese and expansion starts are available as unfinished previews/i);
|
||||
assert.match(saphoAvailability ?? '', /Once Around first or last/);
|
||||
assert.match(saphoAvailability ?? '', /other phase or auction modes remain unfinished/);
|
||||
assert.match(saphoAvailability ?? '', /both combatants finish final battle actions/);
|
||||
assert.match(saphoAvailability ?? '', /older battles retain their pre-plan opportunity/);
|
||||
const saphoAction = richeseCardActionBlock(sapho.card);
|
||||
assert.match(saphoAction ?? '', /both combatants finish their final battle actions/);
|
||||
assert.match(saphoAction ?? '', /older battles retain their pre-plan opportunity/);
|
||||
assert.match(karamaAvailability ?? '', /supported Richese and expansion starts are available as unfinished previews/i);
|
||||
assert.match(karamaAvailability ?? '', /bounded card handlers/);
|
||||
for (const definition of RICHESE_CARD_DEFINITIONS)
|
||||
|
||||
@@ -14,7 +14,7 @@ function markup(value = fact, game: MobileBoard = {}, target = player) {
|
||||
|
||||
void test('force fact controls name physical pools and comparisons without revealing a hidden marker value', () => {
|
||||
const html = markup();
|
||||
for (const value of ['reserves', 'tanks', 'territory', 'location', 'total', 'normal', 'elite', 'eq', 'gte', 'lte'])
|
||||
for (const value of ['reserves', 'tanks', 'board', 'territory', 'location', 'total', 'normal', 'elite', 'eq', 'gte', 'lte'])
|
||||
assert.match(html, new RegExp(`value="${value}"`));
|
||||
assert.match(html, /Number of force counters/);
|
||||
assert.match(html, /Each physical counter counts once, including advisors/);
|
||||
@@ -23,6 +23,15 @@ void test('force fact controls name physical pools and comparisons without revea
|
||||
assert.match(html, /min="0"/); assert.match(html, /max="9007199254740991"/);
|
||||
});
|
||||
|
||||
void test('the whole board choice needs no selected territory or sector', () => {
|
||||
const board: ForceCountFact = { ...fact, zone: { kind: 'board' } };
|
||||
assert.equal(forceCountInputError(board, {}, player), null);
|
||||
const html = markup(board);
|
||||
assert.match(html, /Entire Arrakis board/);
|
||||
assert.doesNotMatch(html, /Force territory/);
|
||||
assert.doesNotMatch(html, /Force sector/);
|
||||
});
|
||||
|
||||
void test('whole-territory choice names the territory without requiring a sector', () => {
|
||||
const whole: ForceCountFact = { ...fact, zone: { kind: 'territory', territory: 'wind_pass' } };
|
||||
assert.equal(forceCountInputError(whole, {}, player), null);
|
||||
|
||||
@@ -37,6 +37,7 @@ void test('parser accepts exact live pools and rejects extra, mutually exclusive
|
||||
for (const zone of [
|
||||
{ kind: 'reserves' },
|
||||
{ kind: 'tanks' },
|
||||
{ kind: 'board' },
|
||||
{ kind: 'territory', territory: 'imperial_basin' },
|
||||
{ kind: 'location', territory: 'imperial_basin', sector: 9 },
|
||||
])
|
||||
@@ -48,6 +49,7 @@ void test('parser accepts exact live pools and rejects extra, mutually exclusive
|
||||
for (const malformed of [
|
||||
{ ...input, privateHint: true },
|
||||
{ ...input, zone: { kind: 'reserves', territory: 'arrakeen' } },
|
||||
{ ...input, zone: { kind: 'board', territory: 'arrakeen' } },
|
||||
{ ...input, zone: { kind: 'territory', territory: 'unknown' } },
|
||||
{ ...input, zone: { kind: 'territory', territory: 'arrakeen', sector: 10 } },
|
||||
{
|
||||
@@ -85,6 +87,24 @@ void test('parser accepts exact live pools and rejects extra, mutually exclusive
|
||||
assert.throws(() => parseForceCountFact({ ...input, value }, locations));
|
||||
});
|
||||
|
||||
void test('board pool sums only the supplied active locations and preserves typed counters', () => {
|
||||
const p: ForceCountPlayer = { faction: 'ixians', reserves: 6, tanks: 2,
|
||||
forces: { 'arrakeen:10': 4, 'imperial_basin:9': 3, 'hidden_mobile_stronghold:0': 6, 'cistern:0': 5, 'invalid:0': 10 },
|
||||
elites: { reserves: 0, tanks: 0, forces: { 'arrakeen:10': 1, 'imperial_basin:9': 2, 'hidden_mobile_stronghold:0': 3 } } };
|
||||
const zone = { kind: 'board' } as const;
|
||||
for (const [counter, n] of [['total', 7], ['normal', 4], ['elite', 3]] as const)
|
||||
assert.equal(forceCountFactMatches(p, fact(zone, counter, 'eq', n), locations), true);
|
||||
const withHms = [...locations, { territory: 'hidden_mobile_stronghold', sector: 0 }];
|
||||
assert.equal(forceCountFactMatches(p, fact(zone, 'total', 'eq', 13), withHms), true);
|
||||
assert.equal(forceCountFactMatches(p, fact(zone, 'elite', 'eq', 6), withHms), true);
|
||||
assert.throws(() => forceCountFactMatches(p, fact(zone)), /board locations/);
|
||||
assert.throws(() => forceCountFactMatches(p, fact(zone), []), /board locations/);
|
||||
assert.throws(() => forceCountFactMatches(p, fact(zone), [...locations, locations[0]]), /duplicate/);
|
||||
assert.throws(() => forceCountFactMatches(p, fact(zone), [{ territory: 'arrakeen', sector: 999 }]), /malformed/);
|
||||
assert.equal(forceCountFactText(fact(zone, 'total', 'eq', 7)),
|
||||
'you currently have exactly 7 physical forces on the current Arrakis board');
|
||||
});
|
||||
|
||||
void test('territory-wide facts sum physical sectors and their typed subsets without counting nearby or interior territories', () => {
|
||||
const player: ForceCountPlayer = {
|
||||
faction: 'ixians', reserves: 9, tanks: 0,
|
||||
|
||||
@@ -4,7 +4,8 @@ import { applyAction, normalizeAutomaticGame, viewGame, type Action, type Game }
|
||||
import { botActions } from '../game/bots';
|
||||
import { DIFFICULTIES } from '../game/bot-profiles';
|
||||
import { MOBILE_LOCATION, MOBILE_STRONGHOLD } from '../game/board';
|
||||
import type { TruthFact } from '../game/truthtrance';
|
||||
import { truthFactAnswer, type TruthFact } from '../game/truthtrance';
|
||||
import { truthKnowledgeOf } from '../game/truthtrance-knowledge';
|
||||
import { knowledgeGame, knowledgePhysical } from './truthtrance-knowledge-fixture';
|
||||
import { completedIxSkillsGame, assertIxSkillsCustody } from './ix-skills-fixture';
|
||||
import { discoveryFixture, enterDiscoveryCollection } from './fixture-discovery';
|
||||
@@ -114,6 +115,27 @@ void test('a whole-territory Basic question counts separate sectors and every bo
|
||||
}
|
||||
});
|
||||
|
||||
void test('a whole-board Basic force question counts multiple territories and every bot answers the actual fact', () => {
|
||||
const initial = knowledgeGame(false);
|
||||
const fremen = player(initial, 'f');
|
||||
fremen.forces = { 'wind_pass:14': 4, 'imperial_basin:9': 6 };
|
||||
const zone: ForceFact['zone'] = { kind: 'board' };
|
||||
const question = fact(zone, 'total', 10);
|
||||
assert.equal(truthFactAnswer(fremen, question), 'unknown');
|
||||
assert.equal(truthFactAnswer(fremen, { kind: 'and', terms: [question, question] }), 'unknown');
|
||||
answer(ask(initial, 'f', question), 'f', 'yes');
|
||||
answer(ask(initial, 'f', fact(zone, 'total', 11, 'gte')), 'f', 'no');
|
||||
for (const difficulty of DIFFICULTIES) {
|
||||
const waiting = ask(initial, 'f', question);
|
||||
const view = viewGame(waiting, 'f');
|
||||
view.players.find(candidate => candidate.id === 'f')!.bot = difficulty;
|
||||
const response = botActions(view);
|
||||
assert.deepEqual(response, [{ type: 'truthAnswer', answer: 'yes' }]);
|
||||
const done = act(waiting, 'f', response[0]);
|
||||
assert.equal(done.truthHistory!.at(-1)!.answer, 'yes');
|
||||
}
|
||||
});
|
||||
|
||||
void test('whole-territory typed facts aggregate elite and normal counters across sectors', () => {
|
||||
const initial = typedGame();
|
||||
const fremen = player(initial, 'f');
|
||||
@@ -262,6 +284,12 @@ void test('the live HMS interior is an exact physical location distinct from its
|
||||
assert.equal(initial.mobileStronghold!.location, 'polar_sink:0');
|
||||
assert.equal(player(initial, 'i').forces[MOBILE_LOCATION], 6);
|
||||
assert.equal(player(initial, 'i').elites!.forces[MOBILE_LOCATION], 3);
|
||||
answer(ask(initial, 'i', fact({ kind: 'board' }, 'total', 6), 'e'), 'i', 'yes');
|
||||
answer(ask(initial, 'i', fact({ kind: 'board' }, 'elite', 3), 'e'), 'i', 'yes');
|
||||
const unplaced = structuredClone(initial);
|
||||
unplaced.mobileStronghold!.location = null;
|
||||
assert.equal(truthFactAnswer(player(unplaced, 'i'), fact({ kind: 'board' }, 'total', 0),
|
||||
truthKnowledgeOf(unplaced, player(unplaced, 'i'))), 'yes');
|
||||
for (const [counter, value] of [['total', 6], ['normal', 3], ['elite', 3]] as const)
|
||||
assertIxSkillsCustody(answer(ask(initial, 'i', fact({ kind: 'location', territory: MOBILE_STRONGHOLD, sector: 0 }, counter, value), 'e'), 'i', 'yes'));
|
||||
answer(ask(initial, 'i', fact({ kind: 'territory', territory: MOBILE_STRONGHOLD }, 'total', 6), 'e'), 'i', 'yes');
|
||||
|
||||
Reference in new issue
Block a user