Skip to content

Commit 550f842

Browse files
committed
test: add unit test for FakeHuman MIRV retaliation logic
1 parent 73d25b1 commit 550f842

File tree

1 file changed

+144
-0
lines changed

1 file changed

+144
-0
lines changed

tests/FakeHumanMIRV.test.ts

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import { FakeHumanExecution } from "../src/core/execution/FakeHumanExecution";
2+
import { MirvExecution } from "../src/core/execution/MIRVExecution";
3+
import {
4+
Cell,
5+
Nation,
6+
PlayerInfo,
7+
PlayerType,
8+
UnitType,
9+
} from "../src/core/game/Game";
10+
import { setup } from "./util/Setup";
11+
import { executeTicks } from "./util/utils";
12+
13+
describe("FakeHuman MIRV Retaliation", () => {
14+
test("fakehuman retaliates with MIRV when attacked by MIRV", async () => {
15+
// Setup game
16+
const game = await setup("big_plains", {
17+
infiniteGold: true,
18+
instantBuild: true,
19+
});
20+
21+
// Create two players
22+
const attackerInfo = new PlayerInfo(
23+
"attacker",
24+
PlayerType.Human,
25+
null,
26+
"attacker_id",
27+
);
28+
const fakehumanInfo = new PlayerInfo(
29+
"defender_fakehuman",
30+
PlayerType.FakeHuman,
31+
null,
32+
"fakehuman_id",
33+
);
34+
35+
game.addPlayer(attackerInfo);
36+
game.addPlayer(fakehumanInfo);
37+
38+
// Skip spawn phase
39+
while (game.inSpawnPhase()) {
40+
game.executeNextTick();
41+
}
42+
43+
const attacker = game.player("attacker_id");
44+
const fakehuman = game.player("fakehuman_id");
45+
46+
// Give attacker territory and missile silo
47+
for (let x = 5; x < 15; x++) {
48+
for (let y = 5; y < 15; y++) {
49+
const tile = game.ref(x, y);
50+
if (game.map().isLand(tile)) {
51+
attacker.conquer(tile);
52+
}
53+
}
54+
}
55+
attacker.buildUnit(UnitType.MissileSilo, game.ref(10, 10), {});
56+
57+
// Give fakehuman territory and missile silo
58+
for (let x = 45; x < 55; x++) {
59+
for (let y = 45; y < 55; y++) {
60+
const tile = game.ref(x, y);
61+
if (game.map().isLand(tile)) {
62+
fakehuman.conquer(tile);
63+
}
64+
}
65+
}
66+
fakehuman.buildUnit(UnitType.MissileSilo, game.ref(50, 50), {});
67+
68+
// Give both players enough gold for MIRVs
69+
attacker.addGold(100_000_000n);
70+
fakehuman.addGold(100_000_000n);
71+
72+
// Verify preconditions
73+
expect(attacker.units(UnitType.MissileSilo)).toHaveLength(1);
74+
expect(fakehuman.units(UnitType.MissileSilo)).toHaveLength(1);
75+
expect(attacker.gold()).toBeGreaterThan(35_000_000n);
76+
expect(fakehuman.gold()).toBeGreaterThan(35_000_000n);
77+
78+
// Attacker launches a MIRV at the fakehuman
79+
const targetTile = Array.from(fakehuman.tiles())[0];
80+
game.addExecution(new MirvExecution(attacker, targetTile));
81+
82+
// Execute a few ticks so the MIRV is in flight
83+
executeTicks(game, 5);
84+
85+
// Verify attacker's MIRV is in flight
86+
expect(attacker.units(UnitType.MIRV).length).toBeGreaterThan(0);
87+
88+
// Track MIRVs before fakehuman retaliates
89+
const mirvCountBefore = fakehuman.units(UnitType.MIRV).length;
90+
91+
// Initialize fakehuman with FakeHumanExecution to enable retaliation logic
92+
const fakehumanNation = new Nation(new Cell(50, 50), 1, fakehuman.info());
93+
94+
// Try different game IDs to find one that passes the 35% MIRV failure rate
95+
// Since random is seeded, we try multiple seeds to ensure at least one passes
96+
const gameIds = Array.from({ length: 20 }, (_, i) => `game_${i}`);
97+
let retaliationSuccessful = false;
98+
99+
for (const gameId of gameIds) {
100+
const testExecution = new FakeHumanExecution(gameId, fakehumanNation);
101+
testExecution.init(game);
102+
103+
// Execute fakehuman's tick logic - need to run many iterations because:
104+
// 1. Fakehuman only runs on ticks matching attackRate/attackTick pattern
105+
// 2. First run initializes behavior, subsequent runs execute MIRV logic
106+
for (let tick = 0; tick < 200; tick++) {
107+
testExecution.tick(game.ticks() + tick);
108+
// Allow the game to process executions
109+
if (tick % 10 === 0) {
110+
game.executeNextTick();
111+
}
112+
if (fakehuman.units(UnitType.MIRV).length > mirvCountBefore) {
113+
retaliationSuccessful = true;
114+
break;
115+
}
116+
}
117+
118+
if (retaliationSuccessful) break;
119+
}
120+
121+
// Assert that retaliation was successful
122+
expect(retaliationSuccessful).toBe(true);
123+
124+
// Process the retaliation
125+
executeTicks(game, 2);
126+
127+
// Assert: Fakehuman launched a retaliatory MIRV
128+
const mirvCountAfter = fakehuman.units(UnitType.MIRV).length;
129+
expect(mirvCountAfter).toBeGreaterThan(mirvCountBefore);
130+
131+
// Verify the retaliatory MIRV targets the attacker's territory
132+
const fakehumanMirvs = fakehuman.units(UnitType.MIRV);
133+
expect(fakehumanMirvs.length).toBeGreaterThan(0);
134+
135+
const retaliationMirv = fakehumanMirvs[fakehumanMirvs.length - 1];
136+
const retaliationTarget = retaliationMirv.targetTile();
137+
expect(retaliationTarget).toBeDefined();
138+
139+
if (retaliationTarget) {
140+
const targetOwner = game.owner(retaliationTarget);
141+
expect(targetOwner).toBe(attacker);
142+
}
143+
});
144+
});

0 commit comments

Comments
 (0)