From c80d0cc2bc8f1790e4212965392936a8d716f7d6 Mon Sep 17 00:00:00 2001 From: cpojer Date: Wed, 15 May 2024 03:09:57 +0900 Subject: [PATCH] Add the ability to use a different AI for each player. GitOrigin-RevId: af7fc55a9ab791ebbe29bf735634d96b7a9d365a --- apollo/actions/executeGameAction.tsx | 28 +++++++--- athena/lib/__tests__/validateTeams.test.tsx | 6 +++ athena/lib/validateMap.tsx | 35 +++++++------ athena/lib/validateTeams.tsx | 1 + athena/map/Player.tsx | 23 ++++++-- athena/map/Serialization.tsx | 9 +++- dionysus/AIRegistry.tsx | 8 +++ hera/hooks/useClientGameAction.tsx | 4 +- tests/__tests__/AIBehavior.test.tsx | 58 ++++++++++----------- tests/__tests__/AITransportMove.test.tsx | 4 +- tests/__tests__/FogMove.test.tsx | 8 +-- tests/__tests__/HaltingProblem.test.tsx | 4 +- tests/__tests__/HiddenAction.test.tsx | 4 +- tests/__tests__/Spawn.test.tsx | 4 +- tests/__tests__/Statistics.test.tsx | 32 +++++++++++- tests/__tests__/WinConditions.test.tsx | 16 +++++- tests/executeGameActions.tsx | 4 +- 17 files changed, 174 insertions(+), 74 deletions(-) create mode 100644 dionysus/AIRegistry.tsx diff --git a/apollo/actions/executeGameAction.tsx b/apollo/actions/executeGameAction.tsx index 7e5ce3ec..e05f8d3f 100644 --- a/apollo/actions/executeGameAction.tsx +++ b/apollo/actions/executeGameAction.tsx @@ -1,3 +1,4 @@ +import { hasAI } from '@deities/athena/map/Player.tsx'; import MapData from '@deities/athena/MapData.tsx'; import { VisionT } from '@deities/athena/Vision.tsx'; import { Action, execute, MutateActionResponseFn } from '../Action.tsx'; @@ -7,19 +8,24 @@ import applyConditions from '../lib/applyConditions.tsx'; import gameHasEnded from '../lib/gameHasEnded.tsx'; import { GameState } from '../Types.tsx'; -type AIClass = { - new (effects: Effects): AIType; -}; - type AIType = { act(map: MapData): MapData | null; retrieveEffects(): Effects; retrieveGameState(): GameState; }; +export type AIRegistryEntry = Readonly<{ + class: { + new (effects: Effects): AIType; + }; + name: string; +}>; + +export type AIRegistryT = ReadonlyMap; + export function executeAIAction( activeMap: MapData | null, - AIClass: AIClass, + AIRegistry: AIRegistryT, effects: Effects, gameState: GameState = [], ): [GameState, Effects] { @@ -31,6 +37,12 @@ export function executeAIAction( activeMap.active.length > 1 && activeMap.getCurrentPlayer().isBot() ) { + const player = activeMap.getCurrentPlayer(); + const AIClass = ((hasAI(player) && + player.ai != null && + AIRegistry.get(player.ai)) || + AIRegistry.get(0))!.class; + const ai = new AIClass(effects); while (activeMap) { activeMap = ai.act(activeMap); @@ -72,7 +84,7 @@ export default function executeGameAction( vision: VisionT, effects: Effects, action: Action, - AIClass: AIClass | null, + AIRegistry: AIRegistryT | null, mutateAction?: MutateActionResponseFn, ): [ActionResponse, MapData, GameState, Effects] | [null, null, null, null] { const actionResult = execute(map, vision, action, mutateAction); @@ -91,7 +103,7 @@ export default function executeGameAction( activeMap = gameState.at(-1)![1]; } const shouldInvokeAI = !!( - AIClass && + AIRegistry && !gameHasEnded(gameState) && (gameState.at(-1)?.[1] || activeMap).getCurrentPlayer().isBot() ); @@ -99,7 +111,7 @@ export default function executeGameAction( actionResponse, activeMap, ...((shouldInvokeAI && - executeAIAction(activeMap, AIClass, newEffects, gameState)) || [ + executeAIAction(activeMap, AIRegistry, newEffects, gameState)) || [ gameState, newEffects, ]), diff --git a/athena/lib/__tests__/validateTeams.test.tsx b/athena/lib/__tests__/validateTeams.test.tsx index 50581c7a..e3490bee 100644 --- a/athena/lib/__tests__/validateTeams.test.tsx +++ b/athena/lib/__tests__/validateTeams.test.tsx @@ -73,6 +73,7 @@ test('validates teams', () => { "name": "", "players": [ { + "ai": undefined, "funds": 0, "id": 1, "skills": [], @@ -87,6 +88,7 @@ test('validates teams', () => { "name": "", "players": [ { + "ai": undefined, "funds": 0, "id": 2, "skills": [], @@ -101,6 +103,7 @@ test('validates teams', () => { "name": "", "players": [ { + "ai": undefined, "funds": 0, "id": 3, "skills": [], @@ -115,6 +118,7 @@ test('validates teams', () => { "name": "", "players": [ { + "ai": undefined, "funds": 0, "id": 4, "skills": [], @@ -153,6 +157,7 @@ test('validates teams', () => { "name": "", "players": [ { + "ai": undefined, "funds": 0, "id": 1, "skills": [], @@ -167,6 +172,7 @@ test('validates teams', () => { "name": "", "players": [ { + "ai": undefined, "funds": 0, "id": 2, "skills": [], diff --git a/athena/lib/validateMap.tsx b/athena/lib/validateMap.tsx index bee8226f..f049aa6d 100644 --- a/athena/lib/validateMap.tsx +++ b/athena/lib/validateMap.tsx @@ -1,5 +1,6 @@ import isPositiveInteger from '@deities/hephaestus/isPositiveInteger.tsx'; import ImmutableMap from '@nkzw/immutable-map'; +import AIRegistry from '../../dionysus/AIRegistry.tsx'; import { Behavior, getBuildingInfo, @@ -25,6 +26,7 @@ import { } from '../map/Configuration.tsx'; import Entity from '../map/Entity.tsx'; import Player, { + hasAI, PlaceholderPlayer, PlayerID, toPlayerID, @@ -376,25 +378,28 @@ export default function validateMap( } const teams = ImmutableMap( - active.map( - (id) => - [ + active.map((id) => { + const player = map.getPlayer(id); + return [ + id, + new Team( id, - new Team( - id, - '', - ImmutableMap().set( + '', + ImmutableMap().set( + toPlayerID(id), + new PlaceholderPlayer( toPlayerID(id), - new PlaceholderPlayer( - toPlayerID(id), - id, - 0, - map.getPlayer(id).skills, - ), + id, + 0, + hasAI(player) && player.ai != null && AIRegistry.has(player.ai) + ? player.ai + : undefined, + player.skills, ), ), - ] as const, - ), + ), + ] as const; + }), ); const newMap = map.copy({ diff --git a/athena/lib/validateTeams.tsx b/athena/lib/validateTeams.tsx index 86e32708..42d65f16 100644 --- a/athena/lib/validateTeams.tsx +++ b/athena/lib/validateTeams.tsx @@ -67,6 +67,7 @@ export default function validateTeams( id, teamId, 0, + undefined, validateSkills( { skillSlots: DefaultMapSkillSlots, skills: Skills }, map.getPlayer(id).skills, diff --git a/athena/map/Player.tsx b/athena/map/Player.tsx index 29a26ffd..2735bc61 100644 --- a/athena/map/Player.tsx +++ b/athena/map/Player.tsx @@ -41,11 +41,13 @@ export type PlainPlayerType = BasePlainPlayerType & type PlainBotType = BasePlainPlayerType & Readonly<{ + ai?: number; name: string; }>; type PlaceholderPlayerType = Readonly<{ activeSkills?: undefined; + ai?: number; funds: number; id: PlayerID; skills?: ReadonlyArray; @@ -175,17 +177,20 @@ export class PlaceholderPlayer extends Player { id: PlayerID, teamId: PlayerID, funds: number, + public readonly ai: number | undefined, skills: ReadonlySet, ) { super(id, teamId, funds, skills, new Set(), 0, null, 0); } copy({ + ai, funds, id, skills, teamId, }: { + ai?: number | undefined; funds?: number; id?: PlayerID; skills?: ReadonlySet; @@ -195,13 +200,14 @@ export class PlaceholderPlayer extends Player { id ?? this.id, teamId ?? this.teamId, funds ?? this.funds, + ai ?? this.ai, skills ?? this.skills, ) as this; } toJSON(): PlaceholderPlayerType { - const { funds, id, skills } = this; - return { funds, id, skills: [...skills] }; + const { ai, funds, id, skills } = this; + return { ai, funds, id, skills: [...skills] }; } static from(player: Player): PlaceholderPlayer { @@ -211,6 +217,7 @@ export class PlaceholderPlayer extends Player { player.id, player.teamId, player.funds, + player.isBot() ? player.ai : undefined, player.skills, ); } @@ -224,6 +231,7 @@ export class Bot extends Player { public readonly name: string, teamId: PlayerID, funds: number, + public readonly ai: number | undefined, skills: ReadonlySet, activeSkills: ReadonlySet, charge: number, @@ -235,6 +243,7 @@ export class Bot extends Player { copy({ activeSkills, + ai, charge, funds, id, @@ -245,6 +254,7 @@ export class Bot extends Player { teamId, }: { activeSkills?: ReadonlySet; + ai?: number | undefined; charge?: number; funds?: number; id?: PlayerID; @@ -259,6 +269,7 @@ export class Bot extends Player { name ?? this.name, teamId ?? this.teamId, funds ?? this.funds, + ai ?? this.ai, skills ?? this.skills, activeSkills ?? this.activeSkills, charge ?? this.charge, @@ -268,10 +279,11 @@ export class Bot extends Player { } toJSON(): PlainBotType { - const { activeSkills, charge, funds, id, misses, name, skills, stats } = + const { activeSkills, ai, charge, funds, id, misses, name, skills, stats } = this; return { activeSkills: [...activeSkills], + ai, charge, funds, id, @@ -290,6 +302,7 @@ export class Bot extends Player { name, player.teamId, player.funds, + player.isPlaceholder() ? player.ai : undefined, player.skills, player.activeSkills, player.charge, @@ -495,3 +508,7 @@ export function isHumanPlayer(player: Player): player is HumanPlayer { export function isBot(player: Player): player is Bot { return player.isBot(); } + +export function hasAI(player: Player): player is Bot | PlaceholderPlayer { + return isBot(player) || player.isPlaceholder(); +} diff --git a/athena/map/Serialization.tsx b/athena/map/Serialization.tsx index 75e77a9e..98a61d0a 100644 --- a/athena/map/Serialization.tsx +++ b/athena/map/Serialization.tsx @@ -65,13 +65,20 @@ export function decodePlayers( player.name, teamId, player.funds, + player.ai, skills, activeSkills, player.charge || 0, decodePlayerStatistics(player.stats), player.misses || 0, ) - : new PlaceholderPlayer(playerID, teamId, player.funds, skills), + : new PlaceholderPlayer( + playerID, + teamId, + player.funds, + player.ai, + skills, + ), ); }), ); diff --git a/dionysus/AIRegistry.tsx b/dionysus/AIRegistry.tsx new file mode 100644 index 00000000..c02a7248 --- /dev/null +++ b/dionysus/AIRegistry.tsx @@ -0,0 +1,8 @@ +import { AIRegistryT } from '@deities/apollo/actions/executeGameAction.tsx'; +import DionysusAlpha from './DionysusAlpha.tsx'; + +const AIRegistry: AIRegistryT = new Map([ + [0, { class: DionysusAlpha, name: 'Alpha' }], +]); + +export default AIRegistry; diff --git a/hera/hooks/useClientGameAction.tsx b/hera/hooks/useClientGameAction.tsx index 11391e60..5ebc21c9 100644 --- a/hera/hooks/useClientGameAction.tsx +++ b/hera/hooks/useClientGameAction.tsx @@ -10,7 +10,7 @@ import dropLabelsFromGameState from '@deities/apollo/lib/dropLabelsFromGameState import { GameActionResponse, GameState } from '@deities/apollo/Types.tsx'; import MapData from '@deities/athena/MapData.tsx'; import { getHiddenLabels } from '@deities/athena/WinConditions.tsx'; -import DionysusAlpha from '@deities/dionysus/DionysusAlpha.tsx'; +import AIRegistry from '@deities/dionysus/AIRegistry.tsx'; import onGameEnd from '@deities/hermes/game/onGameEnd.tsx'; import toClientGame, { ClientGame, @@ -52,7 +52,7 @@ export default function useClientGameAction( vision, game.effects, action, - DionysusAlpha, + AIRegistry, mutateAction, ) || [null, null, null]; } catch (error) { diff --git a/tests/__tests__/AIBehavior.test.tsx b/tests/__tests__/AIBehavior.test.tsx index 88372104..a912ad1f 100644 --- a/tests/__tests__/AIBehavior.test.tsx +++ b/tests/__tests__/AIBehavior.test.tsx @@ -40,7 +40,7 @@ import withModifiers from '@deities/athena/lib/withModifiers.tsx'; import { AIBehavior } from '@deities/athena/map/AIBehavior.tsx'; import vec from '@deities/athena/map/vec.tsx'; import MapData, { SizeVector } from '@deities/athena/MapData.tsx'; -import DionysusAlpha from '@deities/dionysus/DionysusAlpha.tsx'; +import AIRegistry from '@deities/dionysus/AIRegistry.tsx'; import { expect, test } from 'vitest'; import snapshotGameState from '../snapshotGameState.tsx'; @@ -83,7 +83,7 @@ test('attempt to attack new units when they are revealed after a move', async () map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameState)).toMatchInlineSnapshot(` @@ -115,7 +115,7 @@ test('attempt to attack new units when they are revealed after creating a unit', map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameState)).toMatchInlineSnapshot(` @@ -143,7 +143,7 @@ test('attempt to attack new units when they are revealed after unfolding', async map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameState)).toMatchInlineSnapshot(` @@ -169,7 +169,7 @@ test('A unit with `stay` behavior will never move or fold', () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameState)).toMatchInlineSnapshot( @@ -188,7 +188,7 @@ test('A unit with `stay` behavior will never move or fold', () => { currentMap.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(secondGameState)).toMatchInlineSnapshot(` @@ -210,7 +210,7 @@ test('A unit with `stay` behavior will never move or fold', () => { thirdMap.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(thirdGameState)).toMatchInlineSnapshot( @@ -240,7 +240,7 @@ test('A unit with `stay` behavior will never move, but it might attack, build or map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); if (!gameState) { @@ -276,7 +276,7 @@ test('A unit with `adaptive` behavior will change to `attack` behavior after eng map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameState)).toMatchInlineSnapshot(` @@ -321,7 +321,7 @@ test('AI behavior from buildings carries over in a round-robin fashion', () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameState)).toMatchInlineSnapshot(` @@ -335,7 +335,7 @@ test('AI behavior from buildings carries over in a round-robin fashion', () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(secondGameState)).toMatchInlineSnapshot(` @@ -367,7 +367,7 @@ test('AI behavior will not use `Stay` on units that do not have an attack', () = map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameState)).toMatchInlineSnapshot(` @@ -392,7 +392,7 @@ test('AI behavior will not use `Stay` on units that do not have an attack', () = currentMap.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(secondGameState)).toMatchInlineSnapshot(` @@ -424,7 +424,7 @@ test('AI will not attempt to create a unit it cannot deploy', () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameState)).toMatchInlineSnapshot(` @@ -437,7 +437,7 @@ test('AI will not attempt to create a unit it cannot deploy', () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); // The AI tries to build the strongest unit (likely 'Mammoth') which requires rails, but there are none. @@ -463,7 +463,7 @@ test('AI will not attack if the damage is too low', () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateA)).toMatchInlineSnapshot(` @@ -478,7 +478,7 @@ test('AI will not attack if the damage is too low', () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateB)).toMatchInlineSnapshot(` @@ -508,7 +508,7 @@ test('AI will prefer to rescue over capture', () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateA)).toMatchInlineSnapshot(` @@ -523,7 +523,7 @@ test('AI will prefer to rescue over capture', () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateB)).toMatchInlineSnapshot(` @@ -550,7 +550,7 @@ test('AI is able to sabotage other units', () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateA)).toMatchInlineSnapshot(` @@ -577,7 +577,7 @@ test('AI will prefer attacks over sabotage against weaker units', () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateA)).toMatchInlineSnapshot(` @@ -602,7 +602,7 @@ test('AI does not crash when moving away from a unit which it can no longer see map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateA)).toMatchInlineSnapshot(` @@ -644,7 +644,7 @@ test('AI keeps attacking even if one unit gets blocked', async () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateA)).toMatchInlineSnapshot(` @@ -715,7 +715,7 @@ test('AI does not keep building naval units if the opponent does not have any na mapWithOpponentShips.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateA)).toMatchInlineSnapshot(` @@ -728,7 +728,7 @@ test('AI does not keep building naval units if the opponent does not have any na mapWithoutOpponentShips.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateB)).toMatchInlineSnapshot(` @@ -741,7 +741,7 @@ test('AI does not keep building naval units if the opponent does not have any na mapWithAIShips.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateC)).toMatchInlineSnapshot(` @@ -756,7 +756,7 @@ test('AI does not keep building naval units if the opponent does not have any na mapWithShips.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateD)).toMatchInlineSnapshot(` @@ -785,7 +785,7 @@ test('AI will prefer funds generating buildings over factories if it has no inco map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateA)).toMatchInlineSnapshot(` @@ -838,7 +838,7 @@ test('AI will move onto escort vectors even if it is a long-range unit', () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameStateA)).toMatchInlineSnapshot(` diff --git a/tests/__tests__/AITransportMove.test.tsx b/tests/__tests__/AITransportMove.test.tsx index 591143a6..debfba44 100644 --- a/tests/__tests__/AITransportMove.test.tsx +++ b/tests/__tests__/AITransportMove.test.tsx @@ -11,7 +11,7 @@ import withModifiers from '@deities/athena/lib/withModifiers.tsx'; import { HumanPlayer } from '@deities/athena/map/Player.tsx'; import vec from '@deities/athena/map/vec.tsx'; import MapData from '@deities/athena/MapData.tsx'; -import DionysusAlpha from '@deities/dionysus/DionysusAlpha.tsx'; +import AIRegistry from '@deities/dionysus/AIRegistry.tsx'; import { expect, test } from 'vitest'; import executeGameActions from '../executeGameActions.tsx'; import { printGameState } from '../printGameState.tsx'; @@ -165,7 +165,7 @@ test('AI will hop between islands if necessary', async () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, )[2]; const gameState = execute(map); diff --git a/tests/__tests__/FogMove.test.tsx b/tests/__tests__/FogMove.test.tsx index 31baeb1a..6bbe26d3 100644 --- a/tests/__tests__/FogMove.test.tsx +++ b/tests/__tests__/FogMove.test.tsx @@ -5,7 +5,7 @@ import { Forest } from '@deities/athena/info/Tile.tsx'; import { AntiAir, APU, Infantry } from '@deities/athena/info/Unit.tsx'; import vec from '@deities/athena/map/vec.tsx'; import MapData from '@deities/athena/MapData.tsx'; -import DionysusAlpha from '@deities/dionysus/DionysusAlpha.tsx'; +import AIRegistry from '@deities/dionysus/AIRegistry.tsx'; import { expect, test } from 'vitest'; import snapshotGameState from '../snapshotGameState.tsx'; @@ -88,7 +88,7 @@ test('units will hide in hidden fields in fog', async () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameState)).toMatchInlineSnapshot(` @@ -102,7 +102,7 @@ test('units will hide in hidden fields in fog', async () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(secondGameState)).toMatchInlineSnapshot(` @@ -147,7 +147,7 @@ test('does not hide in hidden fields too far from the target', async () => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); expect(snapshotGameState(gameState)).toMatchInlineSnapshot(` diff --git a/tests/__tests__/HaltingProblem.test.tsx b/tests/__tests__/HaltingProblem.test.tsx index 74b771df..319da41e 100644 --- a/tests/__tests__/HaltingProblem.test.tsx +++ b/tests/__tests__/HaltingProblem.test.tsx @@ -21,7 +21,7 @@ import withModifiers from '@deities/athena/lib/withModifiers.tsx'; import { Biome, Biomes } from '@deities/athena/map/Biome.tsx'; import { Bot, HumanPlayer } from '@deities/athena/map/Player.tsx'; import MapData, { SizeVector } from '@deities/athena/MapData.tsx'; -import DionysusAlpha from '@deities/dionysus/DionysusAlpha.tsx'; +import AIRegistry from '@deities/dionysus/AIRegistry.tsx'; import chalk from 'chalk'; import { expect, test } from 'vitest'; import { printGameState } from '../printGameState.tsx'; @@ -48,7 +48,7 @@ const play = async (map: MapData) => { map.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); if (!gameState?.length) { diff --git a/tests/__tests__/HiddenAction.test.tsx b/tests/__tests__/HiddenAction.test.tsx index 036d1cf3..1e1ba654 100644 --- a/tests/__tests__/HiddenAction.test.tsx +++ b/tests/__tests__/HiddenAction.test.tsx @@ -17,7 +17,7 @@ import { Biome } from '@deities/athena/map/Biome.tsx'; import { HumanPlayer } from '@deities/athena/map/Player.tsx'; import vec from '@deities/athena/map/vec.tsx'; import MapData from '@deities/athena/MapData.tsx'; -import DionysusAlpha from '@deities/dionysus/DionysusAlpha.tsx'; +import AIRegistry from '@deities/dionysus/AIRegistry.tsx'; import { expect, test } from 'vitest'; import executeGameActions from '../executeGameActions.tsx'; import { printGameState } from '../printGameState.tsx'; @@ -258,7 +258,7 @@ test('do not attempt attacking a building via long-range', async () => { newMap.createVisionObject(player1), new Map(), EndTurnAction(), - DionysusAlpha, + AIRegistry, ); }).not.toThrow(); }); diff --git a/tests/__tests__/Spawn.test.tsx b/tests/__tests__/Spawn.test.tsx index d3ce6de6..a73a3fc2 100644 --- a/tests/__tests__/Spawn.test.tsx +++ b/tests/__tests__/Spawn.test.tsx @@ -48,6 +48,7 @@ test('spawns units and adds new players', async () => { 'Test Player', 1, 500, + undefined, new Set(), new Set(), 0, @@ -71,6 +72,7 @@ test('spawns units and adds new players', async () => { 'Test Player', 5, 500, + undefined, new Set(), new Set(), 0, @@ -108,7 +110,7 @@ test('spawns units and adds new players', async () => { expect( snapshotEncodedActionResponse(encodedGameActionResponse), ).toMatchInlineSnapshot( - `"Spawn { units: [2,2 → Fighter Jet { id: 18, health: 100, player: 1, fuel: 50, ammo: [ [ 1, 8 ] ] }, 1,3 → Bomber { id: 19, health: 100, player: 4, fuel: 40, ammo: [ [ 1, 5 ] ] }, 3,2 → Bomber { id: 19, health: 100, player: 2, fuel: 40, ammo: [ [ 1, 5 ] ] }], teams: [ { id: 1, name: '', players: [ { activeSkills: [], charge: 0, funds: 500, id: 4, misses: 0, name: 'Test Player', skills: [], stats: [ 0, 0, 0, 0, 0, 0, 0, 0 ] } ] }, { id: 5, name: '', players: [ { activeSkills: [], charge: 0, funds: 500, id: 5, misses: 0, name: 'Test Player', skills: [], stats: [ 0, 0, 0, 0, 0, 0, 0, 0 ] } ] } ] }"`, + `"Spawn { units: [2,2 → Fighter Jet { id: 18, health: 100, player: 1, fuel: 50, ammo: [ [ 1, 8 ] ] }, 1,3 → Bomber { id: 19, health: 100, player: 4, fuel: 40, ammo: [ [ 1, 5 ] ] }, 3,2 → Bomber { id: 19, health: 100, player: 2, fuel: 40, ammo: [ [ 1, 5 ] ] }], teams: [ { id: 1, name: '', players: [ { activeSkills: [], ai: undefined, charge: 0, funds: 500, id: 4, misses: 0, name: 'Test Player', skills: [], stats: [ 0, 0, 0, 0, 0, 0, 0, 0 ] } ] }, { id: 5, name: '', players: [ { activeSkills: [], ai: undefined, charge: 0, funds: 500, id: 5, misses: 0, name: 'Test Player', skills: [], stats: [ 0, 0, 0, 0, 0, 0, 0, 0 ] } ] } ] }"`, ); printGameState('Last State', screenshot); diff --git a/tests/__tests__/Statistics.test.tsx b/tests/__tests__/Statistics.test.tsx index 2ce78bb2..450ec0a7 100644 --- a/tests/__tests__/Statistics.test.tsx +++ b/tests/__tests__/Statistics.test.tsx @@ -292,7 +292,21 @@ test('tracks statistics for players of the same team in fog', async () => { 1, '', ImmutableMap([ - [3, new Bot(3, 'Bot', 1, 300, new Set(), new Set(), 0, null, 0)], + [ + 3, + new Bot( + 3, + 'Bot', + 1, + 300, + undefined, + new Set(), + new Set(), + 0, + null, + 0, + ), + ], ]), ), ], @@ -351,7 +365,21 @@ test('increases the `destroyedUnits` count of other players when a unit self-des 3, '', ImmutableMap([ - [3, new Bot(3, 'Bot', 3, 300, new Set(), new Set(), 0, null, 0)], + [ + 3, + new Bot( + 3, + 'Bot', + 3, + 300, + undefined, + new Set(), + new Set(), + 0, + null, + 0, + ), + ], ]), ), ), diff --git a/tests/__tests__/WinConditions.test.tsx b/tests/__tests__/WinConditions.test.tsx index 267a4ac2..b67c67cd 100644 --- a/tests/__tests__/WinConditions.test.tsx +++ b/tests/__tests__/WinConditions.test.tsx @@ -1615,7 +1615,21 @@ test('rescue label win criteria loses when destroying the rescuable unit', async 3, '', ImmutableMap([ - [3, new Bot(3, 'Bot', 3, 300, new Set(), new Set(), 0, null, 0)], + [ + 3, + new Bot( + 3, + 'Bot', + 3, + 300, + undefined, + new Set(), + new Set(), + 0, + null, + 0, + ), + ], ]), ), ], diff --git a/tests/executeGameActions.tsx b/tests/executeGameActions.tsx index 4727d5f4..41822631 100644 --- a/tests/executeGameActions.tsx +++ b/tests/executeGameActions.tsx @@ -9,7 +9,7 @@ import { GameState, } from '@deities/apollo/Types.tsx'; import MapData from '@deities/athena/MapData.tsx'; -import DionysusAlpha from '@deities/dionysus/DionysusAlpha.tsx'; +import AIRegistry from '@deities/dionysus/AIRegistry.tsx'; import onGameEnd from '@deities/hermes/game/onGameEnd.tsx'; export default function executeGameActions( @@ -47,7 +47,7 @@ export default function executeGameActions( currentMap.createVisionObject(currentMap.getCurrentPlayer()), newEffects || new Map(), action, - DionysusAlpha, + AIRegistry, mutateAction, ); if (!actionResponse || !activeMap || !currentGameState) {