diff --git a/dionysus/DionysusAlpha.tsx b/dionysus/DionysusAlpha.tsx index 417fb3fb..f2447ab4 100644 --- a/dionysus/DionysusAlpha.tsx +++ b/dionysus/DionysusAlpha.tsx @@ -73,6 +73,29 @@ type CreateUnitCombination = { weight: number; }; +// Define or import the necessary constants and types +const UnitType = { + Medic: 'Medic', + SupportShip: 'SupportShip', +}; + +const ResourceType = { + Metal: 'Metal', +}; + +const Medic = { + cost: 100, // Example cost, replace with actual cost + metalCost: 50, // Example metal cost, replace with actual cost + getInfo: () => ({ id: 'medic', type: UnitType.Medic, maxHp: 100, ...otherMedicProperties }), +}; + +const SupportShip = { + cost: 150, // Example cost, replace with actual cost + metalCost: 75, // Example metal cost, replace with actual cost + getInfo: () => ({ id: 'supportShip', type: UnitType.SupportShip, maxHp: 200, ...otherSupportShipProperties }), +}; + + // DionysusAlpha is the first AI, and it is the most aggressive one. // At each turn, it checks which unit can do the most damage to another unit and then attacks. export default class DionysusAlpha extends BaseAI { @@ -88,6 +111,7 @@ export default class DionysusAlpha extends BaseAI { this.fold(map) || this.createBuilding(map) || this.move(map) || + this.healUnit(map) || this.unfold(map) || this.buySkills(map) || this.createUnit(map) || @@ -364,6 +388,59 @@ export default class DionysusAlpha extends BaseAI { return null; } + +private healUnit(map: MapData): MapData | null { + const currentPlayer = map.getCurrentPlayer(); + const healers = map.units.filter( + (unit: any) => + !unit.isCompleted() && + unit.info.hasAbility(Ability.Heal) && + map.matchesPlayer(currentPlayer, unit), + ); + + if (!healers.size) { + return null; + } + + for (const [from, healer] of healers) { + const healableUnits = map.units + .filter((unit: any) => { + return ( + unit && + map.isPlayerUnit(currentPlayer, unit) && + unit.currentHp < unit.info.maxHp + ); + }) + .map((unit: any) => unit.position); + + for (const to of healableUnits) { + const healCost = this.getHealCost(healer.info, map.units.get(to)!.info); + if (currentPlayer.resources >= healCost) { + this.tryAttacking(); + return this.execute(map, this.HealUnitAction(from, to)); + } + } + } + + return null; + } + + + + + HealUnitAction(from: Vector, to: Vector): { from: Vector, to: Vector } { + return { from, to }; + } + + getHealCost(unit: Unit, map: MapData): number { + const healCostPerHp = 10; + const maxHp = unit.info.maxHp; + const missingHp = maxHp - unit.currentHp; + return missingHp * healCostPerHp; + } + + + private rescue(map: MapData): MapData | null { if (!map.hasNeutralUnits()) { return null; @@ -665,6 +742,33 @@ export default class DionysusAlpha extends BaseAI { return null; } + //logic to consider building a Medic or Support Ship + const shouldBuildMedicOrSupportShip = + Math.random() < 0.1 && // Adjust the probability + currentPlayer.funds >= Medic.cost && + currentPlayer.resources.some(resource => resource.type === ResourceType.Metal && resource.amount >= Medic.metalCost); + + // Check if Medic or Support Ship should be built + if (shouldBuildMedicOrSupportShip) { + const availableBuildings = [...buildings.values()]; + const validBuildings = availableBuildings.filter(building => + building.canBuildUnitType(currentPlayer, UnitType.Medic) || + building.canBuildUnitType(currentPlayer, UnitType.SupportShip) + ); + + if (validBuildings.length > 0) { + const building = validBuildings[Math.floor(Math.random() * validBuildings.length)]; + const unitType = Math.random() < 0.5 ? UnitType.Medic : UnitType.SupportShip; // Randomly choose between Medic and Support Ship + const unitInfo = unitType === UnitType.Medic ? Medic.getInfo() : SupportShip.getInfo(); + const deployableVectors = getDeployableVectors(map, unitInfo, building.position, currentPlayer.id); + + if (deployableVectors.length > 0) { + const to = deployableVectors[Math.floor(Math.random() * deployableVectors.length)]; + return this.execute(map, CreateUnitAction(building.position, unitInfo.id, to)); + } + } + } + const buildCapabilities = getPossibleUnitAbilitiesForBuildings( [...buildings.values()], currentPlayer, @@ -1270,3 +1374,6 @@ const shouldBuildNavalUnits = ( const hasTooManyOfType = (units: ReadonlyArray, unitInfo: UnitInfo) => units.length > 10 && units.filter(({ id }) => id === unitInfo.id).length / units.length > 0.2; + + + \ No newline at end of file diff --git a/tests/__tests__/AIBehavior.test.tsx b/tests/__tests__/AIBehavior.test.tsx index a912ad1f..44059aab 100644 --- a/tests/__tests__/AIBehavior.test.tsx +++ b/tests/__tests__/AIBehavior.test.tsx @@ -43,6 +43,7 @@ import MapData, { SizeVector } from '@deities/athena/MapData.tsx'; import AIRegistry from '@deities/dionysus/AIRegistry.tsx'; import { expect, test } from 'vitest'; import snapshotGameState from '../snapshotGameState.tsx'; +import { Abilities, Ability } from '../../athena/info/Unit.tsx'; const initialMap = MapData.createMap({ config: { @@ -846,3 +847,63 @@ test('AI will move onto escort vectors even if it is a long-range unit', () => { GameEnd { condition: { hidden: false, label: [ 2 ], players: [ 2 ], reward: null, type: 4, vectors: [ '5,4' ] }, conditionId: 1, toPlayer: 2 }" `); }); + +test('AI heals units when healers are available', () => { + const map = new MapData(); + const currentPlayer = map.getCurrentPlayer(); + const healerUnit = new Unit({ player: currentPlayer, abilities: [Ability.Heal], currentHp: 100, maxHp: 100 }); + const injuredUnit = new Unit({ player: currentPlayer, currentHp: 50, maxHp: 100 }); + + map.units.set(new Vector(1, 1), healerUnit); + map.units.set(new Vector(2, 2), injuredUnit); + + const ai = new AIBehavior(); + const result = ai.healUnit(map); + + expect(result).not.toBeNull(); + expect(injuredUnit.currentHp).toBeGreaterThan(50); // Check if the unit has been healed +}); + +test('AI does not heal when no healable units are present', () => { + const map = new MapData(); + const currentPlayer = map.getCurrentPlayer(); + const healerUnit = new Unit({ player: currentPlayer, abilities: [Ability.Heal], currentHp: 100, maxHp: 100 }); + const healthyUnit = new Unit({ player: currentPlayer, currentHp: 100, maxHp: 100 }); + + map.units.set(new Vector(1, 1), healerUnit); + map.units.set(new Vector(2, 2), healthyUnit); + + const ai = new AIBehavior(); + const result = ai.healUnit(map); + + expect(result).toBeNull(); // No heal should happen since the unit is already at max health +}); + +test('AI considers building a Medic or Support Ship', () => { + const map = new MapData(); + const currentPlayer = new Player({ id: 1, funds: 200, resources: [{ type: ResourceType.Metal, amount: 100 }] }); + map.setCurrentPlayer(currentPlayer); + + const building = new Building({ player: currentPlayer, buildableUnitTypes: [UnitType.Medic, UnitType.SupportShip] }); + map.buildings.set(new Vector(1, 1), building); + + const ai = new AIBehavior(); + const result = ai.createUnit(map); + + expect(result).not.toBeNull(); + expect([UnitType.Medic, UnitType.SupportShip]).toContain(result.unitType); +}); + +test('AI does not build Medic or Support Ship when funds are insufficient', () => { + const map = new MapData(); + const currentPlayer = new Player({ id: 1, funds: 50, resources: [{ type: ResourceType.Metal, amount: 50 }] }); + map.setCurrentPlayer(currentPlayer); + + const building = new Building({ player: currentPlayer, buildableUnitTypes: [UnitType.Medic, UnitType.SupportShip] }); + map.buildings.set(new Vector(1, 1), building); + + const ai = new AIBehavior(); + const result = ai.createUnit(map); + + expect(result).toBeNull(); +});