Support AI opponents in two-faction games

This commit is contained in:
Quintine committed 2026-10-28 00:02:02 +11:00
1 parent aa6f77372f
commit b00d720e40
13 files changed
+395 -28

No files matched your search

+51
View File
@@ -25,6 +25,7 @@ function render(game: GameView, pendingEntry = false) {
return renderToStaticMarkup(createElement(TwoPlayerControls, {
game, disabled: pendingEntry, pendingEntry, entryRoom: game.code, entryProblem: '',
retryDisabled: false, onModeChange: noop, onSwitch: noop, onAdd: noop, onRetry: noop,
onAddBot: noop, onAddControlledBot: noop,
}));
}
@@ -51,3 +52,53 @@ void test('an unresolved request stays visible with an explicit retry', () => {
assert.match(html, /never retried automatically/);
assert.match(html, /<select[^>]*disabled/);
});
void test('host may add a computer opponent when the second controller slot is empty', () => {
const game = {
...base,
players: [base.players[0]],
twoPlayerControllers: { version: 1 as const, controllers: [{ id: 'a', seats: ['a'] }] },
controlledFactionSeats: [{ id: 'a', name: 'Ali', faction: 'atreides' as const }],
};
const html = render(game);
assert.match(html, /Add an AI player/);
assert.match(html, /The AI plays each faction independently/);
for (const difficulty of ['Easy', 'Medium', 'Hard', 'Brutal'])
assert.match(html, new RegExp(`value="${difficulty}"`));
assert.match(html, /value="harkonnen"/);
assert.doesNotMatch(html, /<option[^>]*value="atreides"/);
});
void test('host adds the second AI faction only for an incomplete AI controller', () => {
const human = base.players[0];
const ai = { ...base.players[1], bot: 'Hard' as const };
const game = {
...base,
players: [human, ai],
twoPlayerControllers: { version: 1 as const, controllers: [
{ id: 'a', seats: ['a'] }, { id: 'b', seats: ['b'] },
] },
controlledFactionSeats: [{ id: 'a', name: 'Ali', faction: 'atreides' as const }],
};
const html = render(game);
assert.match(html, /Player 2 \(AI\): Harkonnen/);
assert.match(html, /Add the AI player’s second faction/);
assert.match(html, /Add AI second faction/);
assert.doesNotMatch(html, /<option[^>]*value="harkonnen"/);
const nonHost = render({ ...game, me: 'b' });
assert.doesNotMatch(nonHost, /Add AI second faction/);
const complete = render({ ...game, twoPlayerControllers: { version: 1, controllers: [
{ id: 'a', seats: ['a'] }, { id: 'b', seats: ['b', 'c'] },
] } });
assert.doesNotMatch(complete, /Add AI second faction/);
});
void test('a human opponent does not expose controls to add an AI faction to their seat', () => {
const game = {
...base,
twoPlayerControllers: { version: 1 as const, controllers: [
{ id: 'a', seats: ['a', 'c'] }, { id: 'b', seats: ['b'] },
] },
};
assert.doesNotMatch(render(game), /Add AI second faction|Add an AI player/);
});