mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-07-17 11:30:52 +00:00
Merge branch 'main' into patterned-territory
This commit is contained in:
@@ -50,6 +50,28 @@ describe("PlayerInfo", () => {
|
||||
expect(playerInfo.clan).toBe("ABCDE");
|
||||
});
|
||||
|
||||
test("should extract clan from name when format is [xxxxx]Name", () => {
|
||||
const playerInfo = new PlayerInfo(
|
||||
"fr",
|
||||
"[abcde]PlayerName",
|
||||
PlayerType.Human,
|
||||
null,
|
||||
"player_id",
|
||||
);
|
||||
expect(playerInfo.clan).toBe("abcde");
|
||||
});
|
||||
|
||||
test("should extract clan from name when format is [XxXxX]Name", () => {
|
||||
const playerInfo = new PlayerInfo(
|
||||
"fr",
|
||||
"[AbCdE]PlayerName",
|
||||
PlayerType.Human,
|
||||
null,
|
||||
"player_id",
|
||||
);
|
||||
expect(playerInfo.clan).toBe("AbCdE");
|
||||
});
|
||||
|
||||
test("should return null when name doesn't start with [", () => {
|
||||
const playerInfo = new PlayerInfo(
|
||||
undefined,
|
||||
@@ -86,11 +108,11 @@ describe("PlayerInfo", () => {
|
||||
expect(playerInfo.clan).toBeNull();
|
||||
});
|
||||
|
||||
test("should return null when clan tag contains non-uppercase letters", () => {
|
||||
test("should return null when clan tag contains non alphanumeric characters", () => {
|
||||
const playerInfo = new PlayerInfo(
|
||||
undefined,
|
||||
"fr",
|
||||
"[Abc]PlayerName",
|
||||
"[A1c]PlayerName",
|
||||
PlayerType.Human,
|
||||
null,
|
||||
"player_id",
|
||||
|
||||
+4
-4
@@ -65,10 +65,10 @@ describe("SAM", () => {
|
||||
const sam = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 1), {});
|
||||
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam));
|
||||
attacker.buildUnit(UnitType.AtomBomb, game.ref(2, 1), {
|
||||
detonationDst: game.ref(2, 1),
|
||||
targetTile: game.ref(2, 1),
|
||||
});
|
||||
attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 2), {
|
||||
detonationDst: game.ref(1, 2),
|
||||
targetTile: game.ref(1, 2),
|
||||
});
|
||||
expect(attacker.units(UnitType.AtomBomb)).toHaveLength(2);
|
||||
|
||||
@@ -82,7 +82,7 @@ describe("SAM", () => {
|
||||
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam));
|
||||
expect(sam.isInCooldown()).toBeFalsy();
|
||||
const nuke = attacker.buildUnit(UnitType.AtomBomb, game.ref(1, 2), {
|
||||
detonationDst: game.ref(1, 2),
|
||||
targetTile: game.ref(1, 2),
|
||||
});
|
||||
|
||||
executeTicks(game, 3);
|
||||
@@ -106,7 +106,7 @@ describe("SAM", () => {
|
||||
const sam2 = defender.buildUnit(UnitType.SAMLauncher, game.ref(1, 2), {});
|
||||
game.addExecution(new SAMLauncherExecution(defender.id(), null, sam2));
|
||||
const nuke = attacker.buildUnit(UnitType.AtomBomb, game.ref(2, 2), {
|
||||
detonationDst: game.ref(2, 2),
|
||||
targetTile: game.ref(2, 2),
|
||||
});
|
||||
|
||||
executeTicks(game, 3);
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
import {
|
||||
Game,
|
||||
Player,
|
||||
PlayerInfo,
|
||||
PlayerType,
|
||||
UnitType,
|
||||
} from "../src/core/game/Game";
|
||||
import { Stats } from "../src/core/game/Stats";
|
||||
import { StatsImpl } from "../src/core/game/StatsImpl";
|
||||
import { replacer } from "../src/core/Util";
|
||||
import { setup } from "./util/Setup";
|
||||
|
||||
let stats: Stats;
|
||||
let game: Game;
|
||||
let player1: Player;
|
||||
let player2: Player;
|
||||
|
||||
describe("Stats", () => {
|
||||
beforeEach(async () => {
|
||||
stats = new StatsImpl();
|
||||
game = await setup("half_land_half_ocean", {}, [
|
||||
new PlayerInfo(
|
||||
"us",
|
||||
"boat dude",
|
||||
PlayerType.Human,
|
||||
"client1",
|
||||
"player_1_id",
|
||||
),
|
||||
new PlayerInfo(
|
||||
"us",
|
||||
"boat dude",
|
||||
PlayerType.Human,
|
||||
"client2",
|
||||
"player_2_id",
|
||||
),
|
||||
]);
|
||||
|
||||
while (game.inSpawnPhase()) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
|
||||
player1 = game.player("player_1_id");
|
||||
player2 = game.player("player_2_id");
|
||||
});
|
||||
|
||||
test("attack", () => {
|
||||
stats.attack(player1, player2, 1);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: {
|
||||
attacks: [1n],
|
||||
},
|
||||
client2: {
|
||||
attacks: [0n, 1n],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("attackCancel", () => {
|
||||
stats.attackCancel(player1, player2, 1);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: {
|
||||
attacks: [-1n, 0n, 1n],
|
||||
},
|
||||
client2: {
|
||||
attacks: [0n, -1n],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("betray", () => {
|
||||
stats.betray(player1);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: {
|
||||
betrayals: 1n,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("boatSendTrade", () => {
|
||||
stats.boatSendTrade(player1, player2);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: {
|
||||
boats: {
|
||||
trade: [1n],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("boatArriveTrade", () => {
|
||||
stats.boatArriveTrade(player1, player2, 1);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: {
|
||||
boats: { trade: [0n, 1n] },
|
||||
gold: [0n, 0n, 1n],
|
||||
},
|
||||
client2: {
|
||||
gold: [0n, 0n, 1n],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("boatCapturedTrade", () => {
|
||||
stats.boatCapturedTrade(player1, player2, 1);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: {
|
||||
boats: { trade: [0n, 0n, 1n] },
|
||||
gold: [0n, 0n, 0n, 1n],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("boatDestroyTrade", () => {
|
||||
stats.boatDestroyTrade(player1, player2);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: {
|
||||
boats: { trade: [0n, 0n, 0n, 1n] },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("boatSendTroops", () => {
|
||||
stats.boatSendTroops(player1, player2, 1);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: {
|
||||
boats: {
|
||||
trans: [1n],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("boatArriveTroops", () => {
|
||||
stats.boatArriveTroops(player1, player2, 1);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: {
|
||||
boats: { trans: [0n, 1n] },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("boatDestroyTroops", () => {
|
||||
stats.boatDestroyTroops(player1, player2, 1);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: {
|
||||
boats: { trans: [0n, 0n, 0n, 1n] },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("bombLaunch", () => {
|
||||
stats.bombLaunch(player1, player2, UnitType.AtomBomb);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: { bombs: { abomb: [1n] } },
|
||||
});
|
||||
});
|
||||
|
||||
test("bombLand", () => {
|
||||
stats.bombLand(player1, player2, UnitType.HydrogenBomb);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: { bombs: { hbomb: [0n, 1n] } },
|
||||
});
|
||||
});
|
||||
|
||||
test("bombIntercept", () => {
|
||||
stats.bombIntercept(player1, player2, UnitType.MIRVWarhead);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: { bombs: { mirvw: [0n, 0n, 1n] } },
|
||||
});
|
||||
});
|
||||
|
||||
test("goldWar", () => {
|
||||
stats.goldWar(player1, player2, 1);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: { gold: [0n, 1n] },
|
||||
});
|
||||
});
|
||||
|
||||
test("goldWork", () => {
|
||||
stats.goldWork(player1, 1);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: { gold: [1n] },
|
||||
});
|
||||
});
|
||||
|
||||
test("unitBuild", () => {
|
||||
stats.unitBuild(player1, UnitType.City);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: { units: { city: [1n] } },
|
||||
});
|
||||
});
|
||||
|
||||
test("unitCapture", () => {
|
||||
stats.unitCapture(player1, UnitType.DefensePost);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: {
|
||||
units: {
|
||||
defp: [0n, 0n, 1n],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("unitDestroy", () => {
|
||||
stats.unitDestroy(player1, UnitType.MissileSilo);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: {
|
||||
units: {
|
||||
silo: [0n, 1n],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("unitLose", () => {
|
||||
stats.unitLose(player1, UnitType.Port);
|
||||
expect(stats.stats()).toStrictEqual({
|
||||
client1: {
|
||||
units: {
|
||||
port: [0n, 0n, 0n, 1n],
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("stringify", () => {
|
||||
stats.unitLose(player1, UnitType.Port);
|
||||
expect(JSON.stringify(stats.stats(), replacer)).toBe(
|
||||
'{"client1":{"units":{"port":["0","0","0","1"]}}}',
|
||||
);
|
||||
});
|
||||
});
|
||||
+180
-62
@@ -1,4 +1,5 @@
|
||||
import { SpawnExecution } from "../src/core/execution/SpawnExecution";
|
||||
import { MoveWarshipExecution } from "../src/core/execution/MoveWarshipExecution";
|
||||
import { WarshipExecution } from "../src/core/execution/WarshipExecution";
|
||||
import {
|
||||
Game,
|
||||
Player,
|
||||
@@ -7,7 +8,7 @@ import {
|
||||
UnitType,
|
||||
} from "../src/core/game/Game";
|
||||
import { setup } from "./util/Setup";
|
||||
import { constructionExecution } from "./util/utils";
|
||||
import { executeTicks } from "./util/utils";
|
||||
|
||||
const coastX = 7;
|
||||
let game: Game;
|
||||
@@ -16,46 +17,38 @@ let player2: Player;
|
||||
|
||||
describe("Warship", () => {
|
||||
beforeEach(async () => {
|
||||
game = await setup("half_land_half_ocean", {
|
||||
infiniteGold: true,
|
||||
instantBuild: true,
|
||||
});
|
||||
const player_1_info = new PlayerInfo(
|
||||
undefined,
|
||||
"us",
|
||||
"boat dude",
|
||||
PlayerType.Human,
|
||||
null,
|
||||
"player_1_id",
|
||||
);
|
||||
game.addPlayer(player_1_info);
|
||||
const player_2_info = new PlayerInfo(
|
||||
undefined,
|
||||
"us",
|
||||
"boat dude",
|
||||
PlayerType.Human,
|
||||
null,
|
||||
"player_2_id",
|
||||
);
|
||||
game.addPlayer(player_2_info);
|
||||
|
||||
game.addExecution(
|
||||
new SpawnExecution(
|
||||
game.player(player_1_info.id).info(),
|
||||
game.ref(coastX, 10),
|
||||
),
|
||||
new SpawnExecution(
|
||||
game.player(player_2_info.id).info(),
|
||||
game.ref(coastX, 15),
|
||||
),
|
||||
game = await setup(
|
||||
"half_land_half_ocean",
|
||||
{
|
||||
infiniteGold: true,
|
||||
instantBuild: true,
|
||||
},
|
||||
[
|
||||
new PlayerInfo(
|
||||
undefined,
|
||||
"us",
|
||||
"boat dude",
|
||||
PlayerType.Human,
|
||||
null,
|
||||
"player_1_id",
|
||||
),
|
||||
new PlayerInfo(
|
||||
undefined,
|
||||
"us",
|
||||
"boat dude",
|
||||
PlayerType.Human,
|
||||
null,
|
||||
"player_2_id",
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
while (game.inSpawnPhase()) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
|
||||
player1 = game.player(player_1_info.id);
|
||||
player2 = game.player(player_2_info.id);
|
||||
player1 = game.player("player_1_id");
|
||||
player2 = game.player("player_2_id");
|
||||
});
|
||||
|
||||
test("Warship heals only if player has port", async () => {
|
||||
@@ -69,8 +62,11 @@ describe("Warship", () => {
|
||||
const warship = player1.buildUnit(
|
||||
UnitType.Warship,
|
||||
game.ref(coastX + 1, 10),
|
||||
{},
|
||||
{
|
||||
patrolTile: game.ref(coastX + 1, 10),
|
||||
},
|
||||
);
|
||||
game.addExecution(new WarshipExecution(warship));
|
||||
|
||||
game.executeNextTick();
|
||||
|
||||
@@ -87,26 +83,21 @@ describe("Warship", () => {
|
||||
});
|
||||
|
||||
test("Warship captures trade if player has port", async () => {
|
||||
constructionExecution(game, player1.id(), coastX, 10, UnitType.Port);
|
||||
constructionExecution(game, player1.id(), coastX + 1, 10, UnitType.Warship);
|
||||
// Warship need one more tick (for warship exec to actually build warship)
|
||||
game.executeNextTick();
|
||||
expect(player1.units(UnitType.Warship)).toHaveLength(1);
|
||||
expect(player1.units(UnitType.Port)).toHaveLength(1);
|
||||
|
||||
const dstPort = player2.buildUnit(
|
||||
UnitType.Port,
|
||||
game.ref(coastX + 2, 10),
|
||||
{},
|
||||
const portTile = game.ref(coastX, 10);
|
||||
player1.buildUnit(UnitType.Port, portTile, {});
|
||||
game.addExecution(
|
||||
new WarshipExecution(
|
||||
player1.buildUnit(UnitType.Warship, portTile, {
|
||||
patrolTile: portTile,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
// Cannot buildExec with trade ship as it's not buildable (but
|
||||
// we can obviously directly add it to the player)
|
||||
const tradeShip = player2.buildUnit(
|
||||
UnitType.TradeShip,
|
||||
game.ref(coastX + 1, 7),
|
||||
{
|
||||
dstPort,
|
||||
targetUnit: player2.buildUnit(UnitType.Port, game.ref(coastX, 10), {}),
|
||||
},
|
||||
);
|
||||
|
||||
@@ -115,32 +106,159 @@ describe("Warship", () => {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
expect(tradeShip.owner().id()).toBe(player1.id());
|
||||
expect(tradeShip.owner()).toBe(player1);
|
||||
});
|
||||
|
||||
test("Warship do not capture trade if player has no port", async () => {
|
||||
constructionExecution(game, player1.id(), coastX, 10, UnitType.Port);
|
||||
constructionExecution(game, player1.id(), coastX + 1, 10, UnitType.Warship);
|
||||
expect(player1.units(UnitType.Warship)).toHaveLength(1);
|
||||
game.addExecution(
|
||||
new WarshipExecution(
|
||||
player1.buildUnit(UnitType.Warship, game.ref(coastX + 1, 11), {
|
||||
patrolTile: game.ref(coastX + 1, 11),
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
const [dstPort] = player1.units(UnitType.Port);
|
||||
|
||||
player1.units(UnitType.Port)[0].delete();
|
||||
// Cannot buildExec with trade ship as it's not buildable (but
|
||||
// we can obviously directly add it to the player)
|
||||
const tradeShip = player2.buildUnit(
|
||||
UnitType.TradeShip,
|
||||
game.ref(coastX + 1, 11),
|
||||
{
|
||||
dstPort,
|
||||
targetUnit: player1.buildUnit(UnitType.Port, game.ref(coastX, 11), {}),
|
||||
},
|
||||
);
|
||||
|
||||
expect(tradeShip.owner().id()).toBe(player2.id());
|
||||
// Let plenty of time for A* to execute
|
||||
// Let plenty of time for warship to potentially capture trade ship
|
||||
for (let i = 0; i < 10; i++) {
|
||||
game.executeNextTick();
|
||||
}
|
||||
|
||||
expect(tradeShip.owner().id()).toBe(player2.id());
|
||||
});
|
||||
|
||||
test("Warship does not target trade ships that are safe from pirates", async () => {
|
||||
// build port so warship can target trade ships
|
||||
player1.buildUnit(UnitType.Port, game.ref(coastX, 10), {});
|
||||
|
||||
const warship = player1.buildUnit(
|
||||
UnitType.Warship,
|
||||
game.ref(coastX + 1, 10),
|
||||
{
|
||||
patrolTile: game.ref(coastX + 1, 10),
|
||||
},
|
||||
);
|
||||
game.addExecution(new WarshipExecution(warship));
|
||||
|
||||
const tradeShip = player2.buildUnit(
|
||||
UnitType.TradeShip,
|
||||
game.ref(coastX + 1, 10),
|
||||
{
|
||||
targetUnit: player2.buildUnit(UnitType.Port, game.ref(coastX, 10), {}),
|
||||
},
|
||||
);
|
||||
|
||||
tradeShip.setSafeFromPirates();
|
||||
|
||||
executeTicks(game, 10);
|
||||
|
||||
expect(tradeShip.owner().id()).toBe(player2.id());
|
||||
});
|
||||
|
||||
test("Warship moves to new patrol tile", async () => {
|
||||
game.config().warshipTargettingRange = () => 1;
|
||||
|
||||
const warship = player1.buildUnit(
|
||||
UnitType.Warship,
|
||||
game.ref(coastX + 1, 10),
|
||||
{
|
||||
patrolTile: game.ref(coastX + 1, 10),
|
||||
},
|
||||
);
|
||||
|
||||
game.addExecution(new WarshipExecution(warship));
|
||||
|
||||
game.addExecution(
|
||||
new MoveWarshipExecution(player1, warship.id(), game.ref(coastX + 5, 15)),
|
||||
);
|
||||
|
||||
executeTicks(game, 10);
|
||||
|
||||
expect(warship.patrolTile()).toBe(game.ref(coastX + 5, 15));
|
||||
});
|
||||
|
||||
test("Warship does not not target trade ships outside of patrol range", async () => {
|
||||
game.config().warshipTargettingRange = () => 3;
|
||||
|
||||
// build port so warship can target trade ships
|
||||
player1.buildUnit(UnitType.Port, game.ref(coastX, 10), {});
|
||||
|
||||
const warship = player1.buildUnit(
|
||||
UnitType.Warship,
|
||||
game.ref(coastX + 1, 10),
|
||||
{
|
||||
patrolTile: game.ref(coastX + 1, 10),
|
||||
},
|
||||
);
|
||||
game.addExecution(new WarshipExecution(warship));
|
||||
|
||||
const tradeShip = player2.buildUnit(
|
||||
UnitType.TradeShip,
|
||||
game.ref(coastX + 1, 15),
|
||||
{
|
||||
targetUnit: player2.buildUnit(UnitType.Port, game.ref(coastX, 10), {}),
|
||||
},
|
||||
);
|
||||
|
||||
executeTicks(game, 10);
|
||||
|
||||
// Trade ship should not be captured
|
||||
expect(tradeShip.owner().id()).toBe(player2.id());
|
||||
});
|
||||
|
||||
test("MoveWarshipExecution fails if player is not the owner", async () => {
|
||||
const originalPatrolTile = game.ref(coastX + 1, 10);
|
||||
const warship = player1.buildUnit(
|
||||
UnitType.Warship,
|
||||
game.ref(coastX + 1, 5),
|
||||
{
|
||||
patrolTile: originalPatrolTile,
|
||||
},
|
||||
);
|
||||
new MoveWarshipExecution(
|
||||
player2,
|
||||
warship.id(),
|
||||
game.ref(coastX + 5, 15),
|
||||
).init(game, 0);
|
||||
expect(warship.patrolTile()).toBe(originalPatrolTile);
|
||||
});
|
||||
|
||||
test("MoveWarshipExecution fails if warship is not active", async () => {
|
||||
const originalPatrolTile = game.ref(coastX + 1, 10);
|
||||
const warship = player1.buildUnit(
|
||||
UnitType.Warship,
|
||||
game.ref(coastX + 1, 5),
|
||||
{
|
||||
patrolTile: originalPatrolTile,
|
||||
},
|
||||
);
|
||||
warship.delete();
|
||||
new MoveWarshipExecution(
|
||||
player1,
|
||||
warship.id(),
|
||||
game.ref(coastX + 5, 15),
|
||||
).init(game, 0);
|
||||
expect(warship.patrolTile()).toBe(originalPatrolTile);
|
||||
});
|
||||
|
||||
test("MoveWarshipExecution fails gracefully if warship not found", async () => {
|
||||
const exec = new MoveWarshipExecution(
|
||||
player1,
|
||||
123,
|
||||
game.ref(coastX + 5, 15),
|
||||
);
|
||||
|
||||
// Verify that no error is thrown.
|
||||
exec.init(game, 0);
|
||||
|
||||
expect(exec.isActive()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -55,7 +55,6 @@ export async function setup(
|
||||
false,
|
||||
);
|
||||
|
||||
// Create and return the game
|
||||
return createGame(humans, [], gameMap, miniGameMap, config);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user