Files
OpenFrontIO/tests/NationMIRV.test.ts
FloPinguin 86e51ab790 Fix nation spawnkilling 🔧 (#3222)
## Description:

As far as I can remember, in v28 the spawn immunity applied to both
humans and nations.
With the configurable spawn immunity (added for v29) the spawn immunity
no longer applies to nations... Because its called PVP immunity now.
So right now it's possible to spawnkill nations. This is a big problem
for the 5M gold modifier games... And you can "cheat" in singleplayer.

This PR changes two things:
- Nations always have 5 seconds spawn immunity now, no matter whats
configured for the PVP immunity
- Nations attack TerraNullius earlier (Otherwise the easy nations would
sometimes do their first attack after the 5 seconds are over, spawnkills
would still be possible)

## Please complete the following:

- [X] I have added screenshots for all UI updates
- [X] I process any text displayed to the user through translateText()
and I've added it to the en.json file
- [X] I have added relevant tests to the test directory
- [X] I confirm I have thoroughly tested these changes and take full
responsibility for any bugs introduced

## Please put your Discord username so you can be contacted if a bug or
regression is found:

FloPinguin

---------

Co-authored-by: Ryan <7389646+ryanbarlow97@users.noreply.github.com>
2026-02-17 00:19:36 +00:00

770 lines
24 KiB
TypeScript

import { MirvExecution } from "../src/core/execution/MIRVExecution";
import { MissileSiloExecution } from "../src/core/execution/MissileSiloExecution";
import { NationExecution } from "../src/core/execution/NationExecution";
import {
Cell,
GameMode,
Nation,
PlayerInfo,
PlayerType,
UnitType,
} from "../src/core/game/Game";
import { setup } from "./util/Setup";
import { executeTicks } from "./util/utils";
describe("Nation MIRV Retaliation", () => {
test("nation retaliates with MIRV when attacked by MIRV", async () => {
const game = await setup("big_plains", {
infiniteGold: true,
instantBuild: true,
});
// Create two players
const attackerInfo = new PlayerInfo(
"attacker",
PlayerType.Human,
null,
"attacker_id",
);
const nationInfo = new PlayerInfo(
"defender_nation",
PlayerType.Nation,
null,
"nation_id",
);
game.addPlayer(attackerInfo);
game.addPlayer(nationInfo);
// Skip spawn phase
while (game.inSpawnPhase()) {
game.executeNextTick();
}
const attacker = game.player("attacker_id");
const nation = game.player("nation_id");
// Give attacker territory and missile silo
for (let x = 5; x < 15; x++) {
for (let y = 5; y < 15; y++) {
const tile = game.ref(x, y);
if (game.map().isLand(tile)) {
attacker.conquer(tile);
}
}
}
attacker.buildUnit(UnitType.MissileSilo, game.ref(10, 10), {});
// Give nation territory and missile silo
for (let x = 25; x < 75; x++) {
for (let y = 25; y < 75; y++) {
const tile = game.ref(x, y);
if (game.map().isLand(tile)) {
nation.conquer(tile);
}
}
}
nation.buildUnit(UnitType.MissileSilo, game.ref(50, 50), {});
// Register MissileSiloExecution so the silo can reload after firing
const nationSilo = nation.units(UnitType.MissileSilo)[0];
if (nationSilo) {
game.addExecution(new MissileSiloExecution(nationSilo));
}
// Give both players enough gold for MIRVs
attacker.addGold(1_000_000_000n);
nation.addGold(1_000_000_000n);
// Verify preconditions
expect(attacker.units(UnitType.MissileSilo)).toHaveLength(1);
expect(nation.units(UnitType.MissileSilo)).toHaveLength(1);
expect(attacker.gold()).toBeGreaterThan(35_000_000n);
expect(nation.gold()).toBeGreaterThan(35_000_000n);
// Track MIRVs before nation retaliates
const mirvCountBefore = nation.units(UnitType.MIRV).length;
// Initialize nation with NationExecution to enable retaliation logic
const testExecutionNation = new Nation(new Cell(50, 50), nation.info());
// Try different game IDs to account for hesitation odds
const gameIds = Array.from({ length: 20 }, (_, i) => `game_${i}`);
let retaliationAttempted = false;
for (const gameId of gameIds) {
// Advance game to clear any silo cooldowns from previous iteration
executeTicks(game, 100);
const testExecution = new NationExecution(gameId, testExecutionNation);
testExecution.init(game);
// Launch MIRV from attacker to nation
const targetTile = Array.from(nation.tiles())[0];
game.addExecution(new MirvExecution(attacker, targetTile));
// Execute nation's tick logic
for (let tick = 0; tick < 200; tick++) {
testExecution.tick(tick);
// Allow the game to process executions
if (tick % 10 === 0) {
game.executeNextTick();
}
// Check if nation attempted retaliation
if (nation.units(UnitType.MIRV).length > mirvCountBefore) {
retaliationAttempted = true;
break;
}
}
if (retaliationAttempted) break;
}
// Assert that retaliation was attempted
expect(retaliationAttempted).toBe(true);
// Process the retaliation
executeTicks(game, 2);
// Assert: Nation launched a retaliatory MIRV
const mirvCountAfter = nation.units(UnitType.MIRV).length;
expect(mirvCountAfter).toBeGreaterThan(mirvCountBefore);
// Verify the retaliatory MIRV targets the attacker's territory
const nationMirvs = nation.units(UnitType.MIRV);
expect(nationMirvs.length).toBeGreaterThan(0);
const retaliationMirv = nationMirvs[nationMirvs.length - 1];
const retaliationTarget = retaliationMirv.targetTile();
expect(retaliationTarget).toBeDefined();
if (retaliationTarget) {
const targetOwner = game.owner(retaliationTarget);
expect(targetOwner).toBe(attacker);
}
});
test("nation launches MIRV to prevent victory when player approaches win condition", async () => {
// Setup game
const game = await setup("big_plains", {
infiniteGold: true,
instantBuild: true,
});
// Create two players
const dominantPlayerInfo = new PlayerInfo(
"dominant_player",
PlayerType.Human,
null,
"dominant_id",
);
const nationInfo = new PlayerInfo(
"defender_nation",
PlayerType.Nation,
null,
"nation_id",
);
game.addPlayer(dominantPlayerInfo);
game.addPlayer(nationInfo);
// Skip spawn phase
while (game.inSpawnPhase()) {
game.executeNextTick();
}
const dominantPlayer = game.player("dominant_id");
const nation = game.player("nation_id");
// First, give nation a small territory and missile silo
let nationTiles = 0;
for (let x = 45; x < 55; x++) {
for (let y = 45; y < 55; y++) {
const tile = game.ref(x, y);
if (game.map().isLand(tile) && !game.map().hasOwner(tile)) {
nation.conquer(tile);
nationTiles++;
}
}
}
// If we didn't find enough tiles, try a different area
if (nationTiles === 0) {
for (let x = 60; x < 70; x++) {
for (let y = 60; y < 70; y++) {
const tile = game.ref(x, y);
if (game.map().isLand(tile) && !game.map().hasOwner(tile)) {
nation.conquer(tile);
nationTiles++;
if (nationTiles >= 10) break; // Need at least some territory
}
}
if (nationTiles >= 10) break;
}
}
// Build missile silo on one of the nation's tiles
const nationTile = Array.from(nation.tiles())[0];
if (nationTile) {
nation.buildUnit(UnitType.MissileSilo, nationTile, {});
// Register MissileSiloExecution so the silo can reload after firing
const silo = nation.units(UnitType.MissileSilo)[0];
if (silo) {
game.addExecution(new MissileSiloExecution(silo));
}
}
// Then give dominant player a large amount of territory
// This should trigger the victory denial threshold
const totalLandTiles = game.map().numLandTiles();
const targetTiles = Math.floor(totalLandTiles * 0.66);
let conqueredTiles = 0;
for (
let x = 0;
x < game.map().width() && conqueredTiles < targetTiles;
x++
) {
for (
let y = 0;
y < game.map().height() && conqueredTiles < targetTiles;
y++
) {
const tile = game.ref(x, y);
if (game.map().isLand(tile) && !game.map().hasOwner(tile)) {
dominantPlayer.conquer(tile);
conqueredTiles++;
}
}
}
// Give both players enough gold for MIRVs
dominantPlayer.addGold(100_000_000n);
nation.addGold(100_000_000n);
// Verify preconditions
expect(dominantPlayer.units(UnitType.MissileSilo)).toHaveLength(0);
expect(nation.units(UnitType.MissileSilo)).toHaveLength(1);
expect(nation.units(UnitType.MIRV)).toHaveLength(0);
expect(dominantPlayer.units(UnitType.MIRV)).toHaveLength(0);
expect(dominantPlayer.gold()).toBeGreaterThan(35_000_000n);
expect(nation.gold()).toBeGreaterThan(35_000_000n);
expect(nation.isAlive()).toBe(true);
expect(nation.numTilesOwned()).toBeGreaterThan(0);
// Verify dominant player has enough territory to trigger victory denial
const dominantTerritoryShare =
dominantPlayer.numTilesOwned() / game.map().numLandTiles();
expect(dominantTerritoryShare).toBeGreaterThan(0.65);
// Track MIRVs before nation considers victory denial
const mirvCountBefore = nation.units(UnitType.MIRV).length;
// Initialize nation with NationExecution to enable victory denial logic
const testExecutionNation = new Nation(new Cell(50, 50), nation.info());
// Try different game IDs to account for hesitation odds
const gameIds = Array.from({ length: 20 }, (_, i) => `game_${i}`);
let victoryDenialSuccessful = false;
for (const gameId of gameIds) {
// Advance game to clear any silo cooldowns from previous iteration
executeTicks(game, 100);
const testExecution = new NationExecution(gameId, testExecutionNation);
testExecution.init(game);
for (let tick = 0; tick < 200; tick++) {
testExecution.tick(tick);
// Allow the game to process executions
if (tick % 10 === 0) {
game.executeNextTick();
}
if (nation.units(UnitType.MIRV).length > mirvCountBefore) {
victoryDenialSuccessful = true;
break;
}
}
if (victoryDenialSuccessful) break;
}
// Assert that victory denial was successful
expect(victoryDenialSuccessful).toBe(true);
// Process the victory denial MIRV
executeTicks(game, 2);
// Assert: Nation launched a victory denial MIRV
const mirvCountAfter = nation.units(UnitType.MIRV).length;
expect(mirvCountAfter).toBeGreaterThan(mirvCountBefore);
// Verify the victory denial MIRV targets the dominant player's territory
const nationMirvs = nation.units(UnitType.MIRV);
expect(nationMirvs.length).toBeGreaterThan(0);
const victoryDenialMirv = nationMirvs[nationMirvs.length - 1];
const victoryDenialTarget = victoryDenialMirv.targetTile();
expect(victoryDenialTarget).toBeDefined();
if (victoryDenialTarget) {
const targetOwner = game.owner(victoryDenialTarget);
expect(targetOwner).toBe(dominantPlayer);
}
});
test("nation launches MIRV to stop steamrolling player with excessive cities", async () => {
// Setup game
const game = await setup("big_plains", {
infiniteGold: true,
instantBuild: true,
});
// Create three players
const steamrollerInfo = new PlayerInfo(
"steamroller",
PlayerType.Human,
null,
"steamroller_id",
);
const secondPlayerInfo = new PlayerInfo(
"second_player",
PlayerType.Human,
null,
"second_id",
);
const nationInfo = new PlayerInfo(
"defender_nation",
PlayerType.Nation,
null,
"nation_id",
);
game.addPlayer(steamrollerInfo);
game.addPlayer(secondPlayerInfo);
game.addPlayer(nationInfo);
// Skip spawn phase
while (game.inSpawnPhase()) {
game.executeNextTick();
}
const steamroller = game.player("steamroller_id");
const secondPlayer = game.player("second_id");
const nation = game.player("nation_id");
// Give nation a small territory and missile silo
for (let x = 45; x < 55; x++) {
for (let y = 45; y < 55; y++) {
const tile = game.ref(x, y);
if (game.map().isLand(tile) && !game.map().hasOwner(tile)) {
nation.conquer(tile);
}
}
}
const nationTile = Array.from(nation.tiles())[0];
if (nationTile) {
nation.buildUnit(UnitType.MissileSilo, nationTile, {});
// Register MissileSiloExecution so the silo can reload after firing
const silo = nation.units(UnitType.MissileSilo)[0];
if (silo) {
game.addExecution(new MissileSiloExecution(silo));
}
}
// Give second player some territory and cities
for (let x = 20; x < 30; x++) {
for (let y = 20; y < 30; y++) {
const tile = game.ref(x, y);
if (game.map().isLand(tile) && !game.map().hasOwner(tile)) {
secondPlayer.conquer(tile);
}
}
}
// Give second player 5 cities
for (let i = 0; i < 5; i++) {
const secondPlayerTile = Array.from(secondPlayer.tiles())[0];
if (secondPlayerTile) {
secondPlayer.buildUnit(UnitType.City, secondPlayerTile, {});
}
}
// Give steamroller territory and many cities
for (let x = 5; x < 25; x++) {
for (let y = 5; y < 25; y++) {
const tile = game.ref(x, y);
if (game.map().isLand(tile) && !game.map().hasOwner(tile)) {
steamroller.conquer(tile);
}
}
}
// Give steamroller cities
const minLeaderCities = 10;
for (let i = 0; i < minLeaderCities + 2; i++) {
const steamrollerTile = Array.from(steamroller.tiles())[0];
if (steamrollerTile) {
steamroller.buildUnit(UnitType.City, steamrollerTile, {});
}
}
// Give all players enough gold for MIRVs
steamroller.addGold(100_000_000n);
secondPlayer.addGold(100_000_000n);
nation.addGold(100_000_000n);
// Verify preconditions
expect(nation.units(UnitType.MissileSilo)).toHaveLength(1);
expect(steamroller.unitCount(UnitType.City)).toBe(minLeaderCities + 2);
expect(secondPlayer.unitCount(UnitType.City)).toBe(5);
expect(nation.units(UnitType.MIRV)).toHaveLength(0);
// Track MIRVs before nation considers steamroll stop
const mirvCountBefore = nation.units(UnitType.MIRV).length;
// Initialize nation with NationExecution to enable steamroll stop logic
const testExecutionNation = new Nation(new Cell(50, 50), nation.info());
// Try different game IDs to account for hesitation odds
const gameIds = Array.from({ length: 20 }, (_, i) => `game_${i}`);
let steamrollStopSuccessful = false;
for (const gameId of gameIds) {
// Advance game to clear any silo cooldowns from previous iteration
executeTicks(game, 100);
const testExecution = new NationExecution(gameId, testExecutionNation);
testExecution.init(game);
for (let tick = 0; tick < 200; tick++) {
testExecution.tick(tick);
// Allow the game to process executions
if (tick % 10 === 0) {
game.executeNextTick();
}
if (nation.units(UnitType.MIRV).length > mirvCountBefore) {
steamrollStopSuccessful = true;
break;
}
}
if (steamrollStopSuccessful) break;
}
// Assert that steamroll stop was successful
expect(steamrollStopSuccessful).toBe(true);
// Process the steamroll stop MIRV
executeTicks(game, 2);
// Assert: Nation launched a steamroll stop MIRV
const mirvCountAfter = nation.units(UnitType.MIRV).length;
expect(mirvCountAfter).toBeGreaterThan(mirvCountBefore);
// Verify the steamroll stop MIRV targets the steamroller's territory
const nationMirvs = nation.units(UnitType.MIRV);
expect(nationMirvs.length).toBeGreaterThan(0);
const steamrollStopMirv = nationMirvs[nationMirvs.length - 1];
const steamrollStopTarget = steamrollStopMirv.targetTile();
expect(steamrollStopTarget).toBeDefined();
if (steamrollStopTarget) {
const targetOwner = game.owner(steamrollStopTarget);
expect(targetOwner).toBe(steamroller);
}
});
test("nation does not launch MIRV for steamroll when leader has <= 10 cities", async () => {
// Setup game
const game = await setup("big_plains", {
infiniteGold: true,
instantBuild: true,
});
// Create three players
const steamrollerInfo = new PlayerInfo(
"steamroller",
PlayerType.Human,
null,
"steamroller_id",
);
const secondPlayerInfo = new PlayerInfo(
"second_player",
PlayerType.Human,
null,
"second_id",
);
const nationInfo = new PlayerInfo(
"defender_nation",
PlayerType.Nation,
null,
"nation_id",
);
game.addPlayer(steamrollerInfo);
game.addPlayer(secondPlayerInfo);
game.addPlayer(nationInfo);
// Skip spawn phase
while (game.inSpawnPhase()) {
game.executeNextTick();
}
const steamroller = game.player("steamroller_id");
const secondPlayer = game.player("second_id");
const nation = game.player("nation_id");
// Give nation a small territory and missile silo
for (let x = 45; x < 55; x++) {
for (let y = 45; y < 55; y++) {
const tile = game.ref(x, y);
if (game.map().isLand(tile) && !game.map().hasOwner(tile)) {
nation.conquer(tile);
}
}
}
const nationTile = Array.from(nation.tiles())[0];
if (nationTile) {
nation.buildUnit(UnitType.MissileSilo, nationTile, {});
}
// Give second player territory and cities (5 cities)
for (let x = 25; x < 45; x++) {
for (let y = 25; y < 45; y++) {
const tile = game.ref(x, y);
if (game.map().isLand(tile) && !game.map().hasOwner(tile)) {
secondPlayer.conquer(tile);
}
}
}
for (let i = 0; i < 5; i++) {
const secondPlayerTile = Array.from(secondPlayer.tiles())[0];
if (secondPlayerTile) {
secondPlayer.buildUnit(UnitType.City, secondPlayerTile, {});
}
}
// Give steamroller territory and cities
const minLeaderCities = 10;
for (let x = 5; x < 25; x++) {
for (let y = 5; y < 25; y++) {
const tile = game.ref(x, y);
if (game.map().isLand(tile) && !game.map().hasOwner(tile)) {
steamroller.conquer(tile);
}
}
}
for (let i = 0; i < minLeaderCities; i++) {
const steamrollerTile = Array.from(steamroller.tiles())[0];
if (steamrollerTile) {
steamroller.buildUnit(UnitType.City, steamrollerTile, {});
}
}
// Give all players enough gold for MIRVs
steamroller.addGold(100_000_000n);
secondPlayer.addGold(100_000_000n);
nation.addGold(100_000_000n);
// Verify preconditions
expect(nation.units(UnitType.MissileSilo)).toHaveLength(1);
expect(steamroller.unitCount(UnitType.City)).toBe(minLeaderCities);
expect(secondPlayer.unitCount(UnitType.City)).toBe(5);
expect(nation.units(UnitType.MIRV)).toHaveLength(0);
// Track MIRVs before nation considers steamroll stop
const mirvCountBefore = nation.units(UnitType.MIRV).length;
// Initialize nation with NationExecution to enable steamroll stop logic
const testExecutionNation = new Nation(new Cell(50, 50), nation.info());
// Try different game IDs to account for hesitation odds
const gameIds = Array.from({ length: 20 }, (_, i) => `game_${i}`);
let steamrollStopAttempted = false;
for (const gameId of gameIds) {
const testExecution = new NationExecution(gameId, testExecutionNation);
testExecution.init(game);
for (let tick = 0; tick < 200; tick++) {
testExecution.tick(tick);
game.executeNextTick();
}
// Check if any MIRVs were launched for steamroll stop
const nationMirvs = nation.units(UnitType.MIRV);
if (nationMirvs.length > mirvCountBefore) {
steamrollStopAttempted = true;
break;
}
}
// Assert that steamroll stop was NOT attempted
expect(steamrollStopAttempted).toBe(false);
});
test("nation launches MIRV to prevent team victory when team approaches victory denial threshold (targets biggest team member)", async () => {
// Setup game
const teamPlayer1Info = new PlayerInfo(
"[ALPHA]team_player_1",
PlayerType.Human,
null,
"team1_id",
);
const teamPlayer2Info = new PlayerInfo(
"[ALPHA]team_player_2",
PlayerType.Human,
null,
"team2_id",
);
const nationInfo = new PlayerInfo(
"defender_nation",
PlayerType.Nation,
null,
"nation_id",
);
const game = await setup(
"big_plains",
{
infiniteGold: true,
instantBuild: true,
gameMode: GameMode.Team,
playerTeams: 2,
},
[teamPlayer1Info, teamPlayer2Info, nationInfo],
);
// Players already added via setup() with Team mode and shared clan for humans
// Skip spawn phase
while (game.inSpawnPhase()) {
game.executeNextTick();
}
const teamPlayer1 = game.player("team1_id");
const teamPlayer2 = game.player("team2_id");
const nation = game.player("nation_id");
// Give nation a small territory and missile silo
for (let x = 45; x < 55; x++) {
for (let y = 45; y < 55; y++) {
const tile = game.ref(x, y);
if (game.map().isLand(tile) && !game.map().hasOwner(tile)) {
nation.conquer(tile);
}
}
}
const nationTile = Array.from(nation.tiles())[0];
if (nationTile) {
nation.buildUnit(UnitType.MissileSilo, nationTile, {});
// Register MissileSiloExecution so the silo can reload after firing
const silo = nation.units(UnitType.MissileSilo)[0];
if (silo) {
game.addExecution(new MissileSiloExecution(silo));
}
}
// Give team players a large amount of territory to exceed team threshold,
// but skew so teamPlayer1 is clearly the largest member
const totalLandTiles = game.map().numLandTiles();
const teamTargetTiles = Math.floor(totalLandTiles * 0.82);
let conqueredTiles = 0;
for (
let x = 0;
x < game.map().width() && conqueredTiles < teamTargetTiles;
x++
) {
for (
let y = 0;
y < game.map().height() && conqueredTiles < teamTargetTiles;
y++
) {
const tile = game.ref(x, y);
if (game.map().isLand(tile) && !game.map().hasOwner(tile)) {
// 3:1 bias towards teamPlayer1 to ensure largest-member targeting is well-defined
const teamPlayer =
conqueredTiles % 4 === 0 ? teamPlayer2 : teamPlayer1;
teamPlayer.conquer(tile);
conqueredTiles++;
}
}
}
// Give all players enough gold for MIRVs
teamPlayer1.addGold(100_000_000n);
teamPlayer2.addGold(100_000_000n);
nation.addGold(100_000_000n);
// Verify preconditions
expect(nation.units(UnitType.MissileSilo)).toHaveLength(1);
expect(nation.units(UnitType.MIRV)).toHaveLength(0);
expect(teamPlayer1.gold()).toBeGreaterThan(35_000_000n);
expect(teamPlayer2.gold()).toBeGreaterThan(35_000_000n);
expect(nation.gold()).toBeGreaterThan(35_000_000n);
expect(nation.isAlive()).toBe(true);
expect(nation.numTilesOwned()).toBeGreaterThan(0);
// Verify team has enough territory to trigger team victory denial
const teamTerritory =
teamPlayer1.numTilesOwned() + teamPlayer2.numTilesOwned();
const teamShare = teamTerritory / game.map().numLandTiles();
expect(teamShare).toBeGreaterThan(0.8); //
// Track MIRVs before nation considers team victory denial
const mirvCountBefore = nation.units(UnitType.MIRV).length;
// Initialize nation with NationExecution to enable team victory denial logic
const testExecutionNation = new Nation(new Cell(50, 50), nation.info());
// Try different game IDs to account for hesitation odds
const gameIds = Array.from({ length: 20 }, (_, i) => `game_${i}`);
let teamVictoryDenialSuccessful = false;
for (const gameId of gameIds) {
// Advance game to clear any silo cooldowns from previous iteration
executeTicks(game, 100);
const testExecution = new NationExecution(gameId, testExecutionNation);
testExecution.init(game);
for (let tick = 0; tick < 200; tick++) {
testExecution.tick(tick);
// Allow the game to process executions
if (tick % 10 === 0) {
game.executeNextTick();
}
if (nation.units(UnitType.MIRV).length > mirvCountBefore) {
teamVictoryDenialSuccessful = true;
break;
}
}
if (teamVictoryDenialSuccessful) break;
}
// Assert that team victory denial was successful
expect(teamVictoryDenialSuccessful).toBe(true);
// Process the team victory denial MIRV
executeTicks(game, 2);
// Assert: Nation launched a team victory denial MIRV
const mirvCountAfter = nation.units(UnitType.MIRV).length;
expect(mirvCountAfter).toBeGreaterThan(mirvCountBefore);
// Verify the team victory denial MIRV targets the largest member of the team
const nationMirvs = nation.units(UnitType.MIRV);
expect(nationMirvs.length).toBeGreaterThan(0);
const teamVictoryDenialMirv = nationMirvs[nationMirvs.length - 1];
const teamVictoryDenialTarget = teamVictoryDenialMirv.targetTile();
expect(teamVictoryDenialTarget).toBeDefined();
if (teamVictoryDenialTarget) {
const targetOwner = game.owner(teamVictoryDenialTarget);
// Should target the biggest member of the team
const biggest =
teamPlayer1.numTilesOwned() >= teamPlayer2.numTilesOwned()
? teamPlayer1
: teamPlayer2;
expect(targetOwner).toBe(biggest);
}
});
});