add testing infrastructure and example test (#276)

This commit is contained in:
evanpelle
2025-03-17 12:20:23 -07:00
committed by GitHub
parent bd91d7c00b
commit cd1f8b9586
15 changed files with 246 additions and 464 deletions
+16 -2
View File
@@ -1,6 +1,20 @@
module.exports = {
transform: { "^.+\\.ts?$": "ts-jest" },
export default {
testEnvironment: "node",
testRegex: "/tests/.*\\.(test|spec)?\\.(ts|tsx)$",
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
extensionsToTreatAsEsm: [".ts"],
moduleNameMapper: {
"^(\\.{1,2}/.*)\\.js$": "$1",
},
transform: {
"^.+\\.tsx?$": [
"ts-jest",
{
useESM: true,
tsconfig: "tsconfig.jest.json",
},
],
},
transformIgnorePatterns: ["node_modules/(?!(node:)/)"],
preset: "ts-jest/presets/default-esm",
};
+6 -6
View File
@@ -36,7 +36,7 @@
"jimp": "^0.22.12",
"lit": "^3.2.1",
"msgpack5": "^6.0.2",
"nanoid": "^5.0.9",
"nanoid": "^3.3.6",
"node-addon-api": "^8.1.0",
"node-gyp": "^10.2.0",
"obscenity": "^0.4.3",
@@ -14398,9 +14398,9 @@
}
},
"node_modules/nanoid": {
"version": "5.0.9",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.9.tgz",
"integrity": "sha512-Aooyr6MXU6HpvvWXKoVoXwKMs/KyVakWwg7xQfv5/S/RIgJMy0Ifa45H9qqYy7pTCszrHzP21Uk4PZq2HpEM8Q==",
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
"funding": [
{
"type": "github",
@@ -14409,10 +14409,10 @@
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.js"
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^18 || >=20"
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/natural-compare": {
+2 -2
View File
@@ -1,7 +1,7 @@
{
"name": "openfront-client",
"scripts": {
"build-map": "node --loader ts-node/esm --experimental-specifier-resolution=node src/scripts/TerrainMapGenerator.ts",
"build-map": "node --loader ts-node/esm --experimental-specifier-resolution=node src/scripts/generateTerrainMaps.ts",
"build-dev": "webpack --config webpack.config.js --mode development",
"build-prod": "webpack --config webpack.config.js --mode production",
"start:client": "webpack serve --open --node-env development",
@@ -103,7 +103,7 @@
"jimp": "^0.22.12",
"lit": "^3.2.1",
"msgpack5": "^6.0.2",
"nanoid": "^5.0.9",
"nanoid": "^3.3.6",
"node-addon-api": "^8.1.0",
"node-gyp": "^10.2.0",
"obscenity": "^0.4.3",
+8 -11
View File
@@ -9,9 +9,6 @@ const loadedMaps = new Map<
>();
export interface NationMap {
name: string;
width: number;
height: number;
nations: Nation[];
}
@@ -30,8 +27,8 @@ export async function loadTerrainMap(
}
const mapFiles = await terrainMapFileLoader.getMapData(map);
const gameMap = await loadTerrainFromFile(mapFiles.mapBin);
const miniGameMap = await loadTerrainFromFile(mapFiles.miniMapBin);
const gameMap = await genTerrainFromBin(mapFiles.mapBin);
const miniGameMap = await genTerrainFromBin(mapFiles.miniMapBin);
const result = {
nationMap: mapFiles.nationMap,
gameMap: gameMap,
@@ -41,13 +38,13 @@ export async function loadTerrainMap(
return result;
}
export async function loadTerrainFromFile(fileData: string): Promise<GameMap> {
const width = (fileData.charCodeAt(1) << 8) | fileData.charCodeAt(0);
const height = (fileData.charCodeAt(3) << 8) | fileData.charCodeAt(2);
export async function genTerrainFromBin(data: string): Promise<GameMap> {
const width = (data.charCodeAt(1) << 8) | data.charCodeAt(0);
const height = (data.charCodeAt(3) << 8) | data.charCodeAt(2);
if (fileData.length != width * height + 4) {
if (data.length != width * height + 4) {
throw new Error(
`Invalid data: buffer size ${fileData.length} incorrect for ${width}x${height} terrain plus 4 bytes for dimensions.`,
`Invalid data: buffer size ${data.length} incorrect for ${width}x${height} terrain plus 4 bytes for dimensions.`,
);
}
@@ -57,7 +54,7 @@ export async function loadTerrainFromFile(fileData: string): Promise<GameMap> {
// Copy data starting after the header
for (let i = 0; i < width * height; i++) {
const packedByte = fileData.charCodeAt(i + 4);
const packedByte = data.charCodeAt(i + 4);
rawData[i] = packedByte;
if (packedByte & 0b10000000) numLand++;
}
+19 -66
View File
@@ -2,23 +2,8 @@ import { decodePNGFromStream } from "pureimage";
import path from "path";
import fs from "fs/promises";
import { createReadStream } from "fs";
import { fileURLToPath } from "url";
import { Readable } from "stream";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const maps = [
"Africa",
"Asia",
"WorldMap",
"BlackSea",
"Europe",
"Mars",
"Mena",
"Oceania",
"NorthAmerica",
"SouthAmerica",
];
const min_island_size = 30;
interface Coord {
@@ -38,21 +23,14 @@ class Terrain {
constructor(public type: TerrainType) {}
}
async function loadTerrainMap(mapName: string): Promise<void> {
const imagePath = path.resolve(
__dirname,
"..",
"..",
"resources",
"maps",
mapName + ".png",
);
export async function generateMap(
imageBuffer: Buffer,
): Promise<{ map: Uint8Array; miniMap: Uint8Array }> {
const stream = Readable.from(imageBuffer);
const img = await decodePNGFromStream(stream);
const readStream = createReadStream(imagePath);
const img = await decodePNGFromStream(readStream);
console.log(`${mapName}: Image loaded successfully`);
console.log(`${mapName}: `, "Image dimensions:", img.width, "x", img.height);
console.log("Image loaded successfully");
console.log("Image dimensions:", img.width, "x", img.height);
const terrain: Terrain[][] = Array(img.width)
.fill(null)
@@ -79,34 +57,17 @@ async function loadTerrainMap(mapName: string): Promise<void> {
}
removeSmallIslands(terrain);
removeSmallLakes(mapName, terrain);
removeSmallLakes(terrain);
const shorelineWaters = processShore(terrain);
processDistToLand(shorelineWaters, terrain);
processOcean(terrain);
const outputPath = path.join(
__dirname,
"..",
"..",
"resources",
"maps",
mapName + ".bin",
);
fs.writeFile(outputPath, packTerrain(mapName, terrain));
const miniTerrain = await createMiniMap(terrain);
const miniOutputPath = path.join(
__dirname,
"..",
"..",
"resources",
"maps",
mapName + "Mini.bin",
);
fs.writeFile(miniOutputPath, packTerrain(mapName, miniTerrain));
}
export async function loadTerrainMaps() {
await Promise.all(maps.map((map) => loadTerrainMap(map)));
return {
map: packTerrain(terrain),
miniMap: packTerrain(miniTerrain),
};
}
export async function createMiniMap(tm: Terrain[][]): Promise<Terrain[][]> {
@@ -225,7 +186,7 @@ function processOcean(map: Terrain[][]) {
}
}
function packTerrain(mapName: string, map: Terrain[][]): Uint8Array {
function packTerrain(map: Terrain[][]): Uint8Array {
const width = map.length;
const height = map[0].length;
const packedData = new Uint8Array(4 + width * height);
@@ -262,7 +223,7 @@ function packTerrain(mapName: string, map: Terrain[][]): Uint8Array {
packedData[4 + y * width + x] = packedByte;
}
}
logBinaryAsBits(mapName, packedData);
logBinaryAsBits(packedData);
return packedData;
}
@@ -315,13 +276,11 @@ function removeSmallIslands(map: Terrain[][]) {
}
}
function removeSmallLakes(mapName: string, map: Terrain[][]) {
function removeSmallLakes(map: Terrain[][]) {
const visited = new Set<string>();
const min_lake_size = 200;
console.log(
`${mapName}: removing small lakes ${map.length}, ${map[0].length}`,
);
console.log(`removing small lakes ${map.length}, ${map[0].length}`);
for (let x = 0; x < map.length; x++) {
for (let y = 0; y < map[0].length; y++) {
@@ -341,15 +300,11 @@ function removeSmallLakes(mapName: string, map: Terrain[][]) {
}
}
function logBinaryAsBits(
mapName: string,
data: Uint8Array,
length: number = 8,
) {
function logBinaryAsBits(data: Uint8Array, length: number = 8) {
const bits = Array.from(data.slice(0, length))
.map((b) => b.toString(2).padStart(8, "0"))
.join(" ");
console.log(`${mapName}: Binary data (bits):`, bits);
console.log(`Binary data (bits):`, bits);
}
function getNeighborCoords(x: number, y: number, map: Terrain[][]): Coord[] {
@@ -368,5 +323,3 @@ function getNeighborCoords(x: number, y: number, map: Terrain[][]): Coord[] {
}
return coords;
}
await loadTerrainMaps();
+61
View File
@@ -0,0 +1,61 @@
import { generateMap } from "./TerrainMapGenerator.js";
import path from "path";
import fs from "fs/promises";
const maps = [
"Africa",
"Asia",
"WorldMap",
"BlackSea",
"Europe",
"Mars",
"Mena",
"Oceania",
"NorthAmerica",
"SouthAmerica",
];
async function loadTerrainMaps() {
await Promise.all(
maps.map(async (map) => {
const mapPath = path.resolve(
process.cwd(),
"resources",
"maps",
map + ".png",
);
const imageBuffer = await fs.readFile(mapPath);
const { map: mainMap, miniMap } = await generateMap(imageBuffer);
const outputPath = path.join(
process.cwd(),
"resources",
"maps",
map + ".bin",
);
const miniOutputPath = path.join(
process.cwd(),
"resources",
"maps",
map + "Mini.bin",
);
await Promise.all([
fs.writeFile(outputPath, mainMap),
fs.writeFile(miniOutputPath, miniMap),
]);
}),
);
}
async function main() {
try {
await loadTerrainMaps();
console.log("Terrain maps generated successfully");
} catch (error) {
console.error("Error generating terrain maps:", error);
process.exit(1);
}
}
main();
-182
View File
@@ -1,182 +0,0 @@
import { GameImpl, PlayerImpl } from "../src/core/GameImpl";
import { EventBus } from "../src/core/EventBus";
import {
Game,
Cell,
MutablePlayer,
PlayerInfo,
TerrainMap,
TerrainTypes,
Tile,
} from "../src/core/Game";
describe("borderTilesWith", () => {
let game: GameImpl;
let player1: PlayerImpl;
let player2: PlayerImpl;
let terrainMap: TerrainMap;
beforeEach(() => {
// Create a 5x5 terrain map
terrainMap = {
terrain: jest.fn().mockReturnValue(TerrainTypes.Land),
width: jest.fn().mockReturnValue(5),
height: jest.fn().mockReturnValue(5),
};
const eventBus = new EventBus();
game = new GameImpl(terrainMap, eventBus);
player1 = game.addPlayer(new PlayerInfo("Player 1", false)) as PlayerImpl;
player2 = game.addPlayer(new PlayerInfo("Player 2", false)) as PlayerImpl;
});
test("should return an empty set when players have no bordering tiles", () => {
const borderTiles = player1.borderTilesWith(player2);
expect(borderTiles.size).toBe(0);
});
test("should return correct border tiles when players are adjacent", () => {
game.conquer(player1, new Cell(0, 0));
game.conquer(player2, new Cell(1, 0));
const borderTilesP1 = player1.borderTilesWith(player2);
const borderTilesP2 = player2.borderTilesWith(player1);
expect(borderTilesP1.size).toBe(1);
expect(borderTilesP2.size).toBe(1);
const p1BorderTile = Array.from(borderTilesP1)[0];
const p2BorderTile = Array.from(borderTilesP2)[0];
expect(p1BorderTile.cell()).toEqual(new Cell(0, 0));
expect(p2BorderTile.cell()).toEqual(new Cell(1, 0));
});
test("should update border tiles when a new tile is conquered", () => {
game.conquer(player1, new Cell(0, 0));
game.conquer(player2, new Cell(2, 0));
expect(player1.borderTilesWith(player2).size).toBe(0);
game.conquer(player2, new Cell(1, 0));
const borderTiles = player1.borderTilesWith(player2);
expect(borderTiles.size).toBe(1);
expect(Array.from(borderTiles)[0].cell()).toEqual(new Cell(0, 0));
});
test("should handle multiple border tiles correctly", () => {
game.conquer(player1, new Cell(0, 0));
game.conquer(player1, new Cell(0, 1));
game.conquer(player2, new Cell(1, 0));
game.conquer(player2, new Cell(1, 1));
const borderTiles = player1.borderTilesWith(player2);
expect(borderTiles.size).toBe(2);
const borderCells = Array.from(borderTiles).map((tile) => tile.cell());
expect(borderCells).toEqual(
expect.arrayContaining([new Cell(0, 0), new Cell(0, 1)]),
);
});
test("should update border tiles when a tile changes ownership", () => {
game.conquer(player1, new Cell(0, 0));
game.conquer(player1, new Cell(1, 0));
game.conquer(player2, new Cell(2, 0));
expect(player1.borderTilesWith(player2).size).toBe(1);
game.conquer(player2, new Cell(1, 0));
const borderTilesP1 = player1.borderTilesWith(player2);
const borderTilesP2 = player2.borderTilesWith(player1);
expect(borderTilesP1.size).toBe(1);
expect(borderTilesP2.size).toBe(1);
expect(Array.from(borderTilesP1)[0].cell()).toEqual(new Cell(0, 0));
expect(Array.from(borderTilesP2).map((t) => t.cell())).toEqual(
expect.arrayContaining([new Cell(1, 0)]),
);
});
test("should handle border tiles with TerraNullius", () => {
game.conquer(player1, new Cell(0, 0));
const borderWithTerraNullius = player1.borderTilesWith(game.terraNullius());
expect(borderWithTerraNullius.size).toBe(1);
const borderCells = Array.from(borderWithTerraNullius).map((tile) =>
tile.cell(),
);
expect(borderCells).toEqual(expect.arrayContaining([new Cell(0, 0)]));
});
test("should not include diagonal tiles as borders", () => {
game.conquer(player1, new Cell(0, 0));
game.conquer(player2, new Cell(1, 1));
expect(player1.borderTilesWith(player2).size).toBe(0);
expect(player2.borderTilesWith(player1).size).toBe(0);
});
// test('should handle complex border scenarios', () => {
// // Create a more complex border scenario
// // 0 1 2 3 4
// // 0 1 1 2 2 2
// // 1 1 1 2 2 2
// // 2 1 1 1 2 2
// // 3 1 1 1 1 2
// // 4 1 1 1 1 1
// for (let y = 0; y < 5; y++) {
// for (let x = 0; x < 5; x++) {
// if (x + y < 6) {
// game.conquer(player1, new Cell(x, y));
// } else {
// game.conquer(player2, new Cell(x, y));
// }
// }
// }
// const borderTilesP1 = player1.borderTilesWith(player2);
// const borderTilesP2 = player2.borderTilesWith(player1);
// expect(borderTilesP1.size).toBe(5);
// expect(borderTilesP2.size).toBe(5);
// const expectedBorderP1 = [
// new Cell(2, 0),
// new Cell(2, 1),
// new Cell(3, 2),
// new Cell(3, 3),
// new Cell(4, 3)
// ];
// const expectedBorderP2 = [
// new Cell(2, 2),
// new Cell(3, 1),
// new Cell(3, 2),
// new Cell(4, 1),
// new Cell(4, 2)
// ];
// const actualBorderP1 = Array.from(borderTilesP1).map(t => t.cell());
// const actualBorderP2 = Array.from(borderTilesP2).map(t => t.cell());
// expect(actualBorderP1).toEqual(expect.arrayContaining(expectedBorderP1));
// expect(actualBorderP2).toEqual(expect.arrayContaining(expectedBorderP2));
// });
test("should handle border updates when a player loses all tiles", () => {
game.conquer(player1, new Cell(0, 0));
game.conquer(player2, new Cell(1, 0));
expect(player1.borderTilesWith(player2).size).toBe(1);
game.conquer(player1, new Cell(1, 0)); // Player 1 takes Player 2's only tile
expect(player1.borderTilesWith(player2).size).toBe(0);
expect(player2.borderTilesWith(player1).size).toBe(0);
});
});
-193
View File
@@ -1,193 +0,0 @@
// import {Game, Player, Tile, Cell, TerraNullius, PlayerInfo} from '../src/core/GameApi';
// import {placeName, calculateBoundingBox, createGrid, findLargestInscribedRectangle, largestRectangleInHistogram, calculateFontSize} from '../src/client/NameBoxCalculator';
// class MockPlayer implements Player {
// constructor(private playerTiles: [number, number][]) { }
// info(): PlayerInfo {
// return new PlayerInfo("TestPlayer", false);
// }
// id(): PlayerID {
// return 1;
// }
// troops(): number {
// return 0;
// }
// ownsTile(cell: Cell): boolean {
// return this.playerTiles.some(([x, y]) => x === cell.x && y === cell.y);
// }
// isAlive(): boolean {
// return true;
// }
// gameState(): Game {
// return {} as Game; // This should be properly implemented
// }
// executions(): ExecutionView[] {
// return [];
// }
// borderTilesWith(other: Player | TerraNullius): ReadonlySet<Tile> {
// return new Set();
// }
// isPlayer(): this is Player {
// return true;
// }
// neighbors(): (Player | TerraNullius)[] {
// return [];
// }
// }
// class MockGame implements Game {
// private tiles: Tile[][] = [];
// private mockPlayer: Player;
// constructor(width: number, height: number, playerTiles: [number, number][]) {
// this.tiles = Array(height).fill(null).map(() => Array(width).fill(null));
// this.mockPlayer = new MockPlayer(playerTiles);
// for (let y = 0; y < height; y++) {
// for (let x = 0; x < width; x++) {
// this.tiles[y][x] = {
// owner: () => playerTiles.some(([px, py]) => px === x && py === y) ? this.mockPlayer : this.terraNullius(),
// hasOwner: () => playerTiles.some(([px, py]) => px === x && py === y),
// isBorder: () => false,
// isInterior: () => false,
// cell: () => new Cell(x, y),
// terrain: () => ({expansionCost: 1, expansionTime: 1}),
// game: () => this,
// neighbors: () => []
// };
// }
// }
// }
// player(id: PlayerID): Player {return this.mockPlayer;}
// tile(cell: Cell): Tile {return this.tiles[cell.y][cell.x];}
// isOnMap(cell: Cell): boolean {return cell.x >= 0 && cell.x < this.width() && cell.y >= 0 && cell.y < this.height();}
// neighbors(cell: Cell): Cell[] {return [];}
// width(): number {return this.tiles[0].length;}
// height(): number {return this.tiles.length;}
// forEachTile(fn: (tile: Tile) => void): void {this.tiles.flat().forEach(fn);}
// executions(): ExecutionView[] {return [];}
// terraNullius(): TerraNullius {return {ownsTile: () => false, isPlayer: () => false};}
// tick() { }
// addExecution(...exec: Execution[]) { }
// }
// // Mock implementations
// class MockGame implements Game {
// private tiles: Tile[][] = [];
// private mockPlayer: Player;
// constructor(width: number, height: number, playerTiles: [number, number][]) {
// this.tiles = Array(height).fill(null).map(() => Array(width).fill(null));
// this.mockPlayer = {
// info: () => new PlayerInfo("TestPlayer", false),
// id: () => 1,
// troops: () => 0,
// ownsTile: (cell: Cell) => playerTiles.some(([x, y]) => x === cell.x && y === cell.y),
// isAlive: () => true,
// gameState: () => this,
// executions: () => [],
// borderTilesWith: () => new Set(),
// isPlayer: function (this: Player): this is Player {return true},
// neighbors: () => []
// };
// for (let y = 0; y < height; y++) {
// for (let x = 0; x < width; x++) {
// this.tiles[y][x] = {
// owner: () => playerTiles.some(([px, py]) => px === x && py === y) ? this.mockPlayer : this.terraNullius(),
// hasOwner: () => playerTiles.some(([px, py]) => px === x && py === y),
// isBorder: () => false,
// isInterior: () => false,
// cell: () => new Cell(x, y),
// terrain: () => ({expansionCost: 1, expansionTime: 1}),
// game: () => this,
// neighbors: () => []
// };
// }
// }
// }
// player(id: number): Player {return this.mockPlayer;}
// tile(cell: Cell): Tile {return this.tiles[cell.y][cell.x];}
// isOnMap(cell: Cell): boolean {return cell.x >= 0 && cell.x < this.width() && cell.y >= 0 && cell.y < this.height();}
// neighbors(cell: Cell): Cell[] {return [];}
// width(): number {return this.tiles[0].length;}
// height(): number {return this.tiles.length;}
// forEachTile(fn: (tile: Tile) => void): void {this.tiles.flat().forEach(fn);}
// executions(): any[] {return [];}
// terraNullius(): TerraNullius {return {ownsTile: () => false, isPlayer: () => false};}
// tick() { }
// addExecution(...exec: any[]) { }
// }
// describe('Territory Name Placement', () => {
// test('placeName should return a position and font size', () => {
// const game = new MockGame(5, 5, [[1, 1], [2, 1], [3, 1], [2, 2], [2, 3]]);
// const player = game.player(1);
// const result = placeName(game, player);
// expect(result).toHaveProperty('position');
// expect(result).toHaveProperty('fontSize');
// expect(result.position).toHaveProperty('x');
// expect(result.position).toHaveProperty('y');
// expect(typeof result.fontSize).toBe('number');
// });
// test('calculateBoundingBox should return correct bounding box', () => {
// const game = new MockGame(5, 5, [[1, 1], [3, 3]]);
// const player = game.player(1);
// const boundingBox = calculateBoundingBox(game, player);
// expect(boundingBox).toEqual({min: {x: 1, y: 1}, max: {x: 3, y: 3}});
// });
// test('createGrid should create correct boolean grid', () => {
// const game = new MockGame(3, 3, [[0, 0], [1, 1], [2, 2]]);
// const player = game.player(1);
// const boundingBox = {min: {x: 0, y: 0}, max: {x: 2, y: 2}};
// const grid = createGrid(game, player, boundingBox);
// expect(grid).toEqual([
// [true, false, false],
// [false, true, false],
// [false, false, true]
// ]);
// });
// test('findLargestInscribedRectangle should find correct rectangle', () => {
// const grid = [
// [true, true, true],
// [true, true, false],
// [true, true, false]
// ];
// const result = findLargestInscribedRectangle(grid);
// expect(result).toEqual({x: 0, y: 0, width: 2, height: 3});
// });
// test('largestRectangleInHistogram should find correct rectangle', () => {
// const heights = [2, 1, 5, 6, 2, 3];
// const result = largestRectangleInHistogram(heights);
// expect(result).toEqual({x: 2, y: 0, width: 2, height: 5});
// });
// test('calculateFontSize should return correct font size', () => {
// const rectangle = {x: 0, y: 0, width: 100, height: 50};
// const name = "TestPlayer";
// const fontSize = calculateFontSize(rectangle, name);
// expect(fontSize).toBe(25); // 50 / 2 = 25 (height constrained)
// });
// });
+25
View File
@@ -0,0 +1,25 @@
import { Player, PlayerInfo, PlayerType } from "../src/core/game/Game";
import { SpawnExecution } from "../src/core/execution/SpawnExecution";
import { setup } from "./util/Setup";
describe("Territory management", () => {
test("player owns the tile it spawns on", async () => {
const game = await setup("Plains");
game.addPlayer(
new PlayerInfo("us", "test_player", PlayerType.Human, null, "test_id"),
1000,
);
const spawnTile = game.map().ref(50, 50);
game.addExecution(
new SpawnExecution(game.player("test_id").info(), spawnTile),
);
// Init the execution
game.executeNextTick();
// Execute the execution.
game.executeNextTick();
const owner = game.owner(spawnTile);
expect(owner.isPlayer()).toBe(true);
expect((owner as Player).name()).toBe("test_player");
});
});
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

+38
View File
@@ -0,0 +1,38 @@
import { generateMap } from "../../src/scripts/TerrainMapGenerator";
import fs from "fs/promises";
import path from "path";
import { createGame } from "../../src/core/game/GameImpl";
import { genTerrainFromBin } from "../../src/core/game/TerrainMapLoader";
import { TestConfig } from "./TestConfig";
import { TestServerConfig } from "./TestServerConfig";
import { UserSettings } from "../../src/core/game/UserSettings";
import { Difficulty, GameType } from "../../src/core/game/Game";
export async function setup(mapName: string) {
// Load the specified map
const mapPath = path.join(__dirname, "..", "testdata", `${mapName}.png`);
const imageBuffer = await fs.readFile(mapPath);
const { map, miniMap } = await generateMap(imageBuffer);
const gameMap = await genTerrainFromBin(String.fromCharCode.apply(null, map));
const miniGameMap = await genTerrainFromBin(
String.fromCharCode.apply(null, miniMap),
);
const nationMap = { nations: [] };
// Configure the game
const serverConfig = new TestServerConfig();
const gameConfig = {
gameMap: null,
gameType: GameType.Singleplayer,
difficulty: Difficulty.Medium,
disableNPCs: false,
bots: 0,
infiniteGold: false,
infiniteTroops: false,
instantBuild: false,
};
const config = new TestConfig(serverConfig, gameConfig, new UserSettings());
// Create and return the game
return createGame(gameMap, miniGameMap, nationMap, config);
}
+3
View File
@@ -0,0 +1,3 @@
import { DefaultConfig } from "../../src/core/configuration/DefaultConfig";
export class TestConfig extends DefaultConfig {}
+57
View File
@@ -0,0 +1,57 @@
import { GameEnv, ServerConfig } from "../../src/core/configuration/Config";
import { GameMapType } from "../../src/core/game/Game";
import { GameID } from "../../src/core/Schemas";
export class TestServerConfig implements ServerConfig {
turnIntervalMs(): number {
throw new Error("Method not implemented.");
}
gameCreationRate(): number {
throw new Error("Method not implemented.");
}
lobbyMaxPlayers(map: GameMapType): number {
throw new Error("Method not implemented.");
}
discordRedirectURI(): string {
throw new Error("Method not implemented.");
}
numWorkers(): number {
throw new Error("Method not implemented.");
}
workerIndex(gameID: GameID): number {
throw new Error("Method not implemented.");
}
workerPath(gameID: GameID): string {
throw new Error("Method not implemented.");
}
workerPort(gameID: GameID): number {
throw new Error("Method not implemented.");
}
workerPortByIndex(workerID: number): number {
throw new Error("Method not implemented.");
}
env(): GameEnv {
throw new Error("Method not implemented.");
}
adminToken(): string {
throw new Error("Method not implemented.");
}
adminHeader(): string {
throw new Error("Method not implemented.");
}
gitCommit(): string {
throw new Error("Method not implemented.");
}
r2Bucket(): string {
throw new Error("Method not implemented.");
}
r2Endpoint(): string {
throw new Error("Method not implemented.");
}
r2AccessKey(): string {
throw new Error("Method not implemented.");
}
r2SecretKey(): string {
throw new Error("Method not implemented.");
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "node",
"types": ["jest", "node"]
},
"include": ["tests/**/*"]
}
+2 -2
View File
@@ -2,7 +2,7 @@
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"rootDir": "src",
"rootDir": ".",
"moduleResolution": "node",
"sourceMap": true,
"allowSyntheticDefaultImports": true,
@@ -14,7 +14,7 @@
"src/**/*",
"resources/**/*",
"generated/**/*",
"test/core/GameImpl.test.ts",
"tests/**/*",
"src/scripts"
],
"exclude": ["node_modules"]