Feature: Map size configuration

This commit is contained in:
sokol
2026-02-22 11:04:11 +03:00
parent f3be577a32
commit 3439d04a55
4 changed files with 391 additions and 44 deletions

View File

@@ -1222,9 +1222,246 @@ describe('Multiple AI Bots Turn Chain', () => {
// Check the pattern repeats correctly
for (let i = 0; i < 12; i++) {
const expectedPlayer = (i % 4) + 1;
assert.strictEqual(gameUI.aiTurnsCompleted[i], expectedPlayer,
assert.strictEqual(gameUI.aiTurnsCompleted[i], expectedPlayer,
`Turn ${i + 1} should be player ${expectedPlayer}`);
}
});
});
});
describe('AIBot - Different Map Sizes', () => {
describe('AI works on 10x10 map', () => {
it('should find valid moves on 10x10 map', () => {
const map = new HexMap(10);
const gameUI = new MockGameUI();
const bot = new AIBot(1, map, gameUI);
// Clear map
map.cells.forEach(cell => {
cell.type = CELL_TYPES.EMPTY;
});
setupPlayerCell(map, 2, 2, 1, 8);
setupPlayerCell(map, 2, 1, 2, 4);
const playerCells = map.getPlayerCells(1);
const moves = bot.findPossibleMoves(playerCells);
assert.ok(moves.length > 0, 'Should find moves on 10x10 map');
});
it('should respect boundaries on 10x10 map', () => {
const map = new HexMap(10);
const gameUI = new MockGameUI();
const bot = new AIBot(1, map, gameUI);
map.cells.forEach(cell => {
cell.type = CELL_TYPES.EMPTY;
});
setupPlayerCell(map, 0, 0, 1, 8);
map.getCell(0, 1).type = CELL_TYPES.EMPTY;
map.getCell(1, 0).type = CELL_TYPES.EMPTY;
const playerCells = map.getPlayerCells(1);
const moves = bot.findPossibleMoves(playerCells);
moves.forEach(move => {
assert.ok(move.to.q >= 0 && move.to.q < 10, 'Target q should be in bounds');
assert.ok(move.to.r >= 0 && move.to.r < 10, 'Target r should be in bounds');
});
});
});
describe('AI works on 15x15 map', () => {
it('should find valid moves on 15x15 map', () => {
const map = new HexMap(15);
const gameUI = new MockGameUI();
const bot = new AIBot(1, map, gameUI);
map.cells.forEach(cell => {
cell.type = CELL_TYPES.EMPTY;
});
setupPlayerCell(map, 5, 5, 1, 8);
setupPlayerCell(map, 5, 4, 2, 4);
const playerCells = map.getPlayerCells(1);
const moves = bot.findPossibleMoves(playerCells);
assert.ok(moves.length > 0, 'Should find moves on 15x15 map');
});
it('should respect boundaries on 15x15 map', () => {
const map = new HexMap(15);
const gameUI = new MockGameUI();
const bot = new AIBot(1, map, gameUI);
map.cells.forEach(cell => {
cell.type = CELL_TYPES.EMPTY;
});
setupPlayerCell(map, 14, 14, 1, 8);
map.getCell(13, 14).type = CELL_TYPES.EMPTY;
map.getCell(14, 13).type = CELL_TYPES.EMPTY;
const playerCells = map.getPlayerCells(1);
const moves = bot.findPossibleMoves(playerCells);
moves.forEach(move => {
assert.ok(move.to.q >= 0 && move.to.q < 15, 'Target q should be in bounds');
assert.ok(move.to.r >= 0 && move.to.r < 15, 'Target r should be in bounds');
});
});
});
describe('AI works on 20x20 map', () => {
it('should find valid moves on 20x20 map', () => {
const map = new HexMap(20);
const gameUI = new MockGameUI();
const bot = new AIBot(1, map, gameUI);
map.cells.forEach(cell => {
cell.type = CELL_TYPES.EMPTY;
});
setupPlayerCell(map, 10, 10, 1, 8);
setupPlayerCell(map, 10, 9, 2, 4);
const playerCells = map.getPlayerCells(1);
const moves = bot.findPossibleMoves(playerCells);
assert.ok(moves.length > 0, 'Should find moves on 20x20 map');
});
it('should respect boundaries on 20x20 map', () => {
const map = new HexMap(20);
const gameUI = new MockGameUI();
const bot = new AIBot(1, map, gameUI);
map.cells.forEach(cell => {
cell.type = CELL_TYPES.EMPTY;
});
setupPlayerCell(map, 19, 19, 1, 8);
map.getCell(18, 19).type = CELL_TYPES.EMPTY;
map.getCell(19, 18).type = CELL_TYPES.EMPTY;
const playerCells = map.getPlayerCells(1);
const moves = bot.findPossibleMoves(playerCells);
moves.forEach(move => {
assert.ok(move.to.q >= 0 && move.to.q < 20, 'Target q should be in bounds');
assert.ok(move.to.r >= 0 && move.to.r < 20, 'Target r should be in bounds');
});
});
});
describe('AI works on 25x25 map', () => {
it('should find valid moves on 25x25 map', () => {
const map = new HexMap(25);
const gameUI = new MockGameUI();
const bot = new AIBot(1, map, gameUI);
map.cells.forEach(cell => {
cell.type = CELL_TYPES.EMPTY;
});
setupPlayerCell(map, 12, 12, 1, 8);
setupPlayerCell(map, 12, 11, 2, 4);
const playerCells = map.getPlayerCells(1);
const moves = bot.findPossibleMoves(playerCells);
assert.ok(moves.length > 0, 'Should find moves on 25x25 map');
});
it('should respect boundaries on 25x25 map', () => {
const map = new HexMap(25);
const gameUI = new MockGameUI();
const bot = new AIBot(1, map, gameUI);
map.cells.forEach(cell => {
cell.type = CELL_TYPES.EMPTY;
});
setupPlayerCell(map, 24, 24, 1, 8);
map.getCell(23, 24).type = CELL_TYPES.EMPTY;
map.getCell(24, 23).type = CELL_TYPES.EMPTY;
const playerCells = map.getPlayerCells(1);
const moves = bot.findPossibleMoves(playerCells);
moves.forEach(move => {
assert.ok(move.to.q >= 0 && move.to.q < 25, 'Target q should be in bounds');
assert.ok(move.to.r >= 0 && move.to.r < 25, 'Target r should be in bounds');
});
});
it('should calculate move priorities correctly on large map', () => {
const map = new HexMap(25);
const gameUI = new MockGameUI();
const bot = new AIBot(1, map, gameUI);
map.cells.forEach(cell => {
cell.type = CELL_TYPES.EMPTY;
});
setupPlayerCell(map, 12, 12, 1, 10);
// Weak enemy
setupPlayerCell(map, 12, 11, 2, 3);
// Strong enemy
setupPlayerCell(map, 11, 12, 2, 9);
// Empty cell
map.getCell(13, 12).type = CELL_TYPES.EMPTY;
const playerCells = map.getPlayerCells(1);
const moves = bot.findPossibleMoves(playerCells);
moves.sort((a, b) => bot.movePriority(b) - bot.movePriority(a));
// Best move should be attack on weak enemy
assert.strictEqual(moves[0].type, 'attack');
assert.strictEqual(moves[0].defenseStrength, 3);
});
});
describe('AI playTurn works on different map sizes', () => {
it('should complete turn on 10x10 map', async () => {
const map = new HexMap(10);
const gameUI = new MockGameUI();
const bot = new AIBot(1, map, gameUI);
map.cells.forEach(cell => {
cell.type = CELL_TYPES.EMPTY;
});
setupPlayerCell(map, 2, 2, 1, 8);
setupPlayerCell(map, 2, 1, 2, 2);
bot.thinkingTime = 0;
await bot.playTurn();
assert.strictEqual(gameUI.turnEnded, true);
});
it('should complete turn on 25x25 map', async () => {
const map = new HexMap(25);
const gameUI = new MockGameUI();
const bot = new AIBot(1, map, gameUI);
map.cells.forEach(cell => {
cell.type = CELL_TYPES.EMPTY;
});
setupPlayerCell(map, 12, 12, 1, 8);
setupPlayerCell(map, 12, 11, 2, 2);
bot.thinkingTime = 0;
await bot.playTurn();
assert.strictEqual(gameUI.turnEnded, true);
});
});
});

View File

@@ -2,6 +2,95 @@ const { describe, it } = require('node:test');
const assert = require('node:assert');
const { HexMap, HexCell, CELL_TYPES, MAP_SIZE } = require('../public/map.js');
describe('HexMap - Dynamic Map Sizes', () => {
it('should create a 10x10 map', () => {
const map = new HexMap(10);
assert.strictEqual(map.size, 10);
assert.strictEqual(map.cells.size, 10 * 10);
});
it('should create a 15x15 map', () => {
const map = new HexMap(15);
assert.strictEqual(map.size, 15);
assert.strictEqual(map.cells.size, 15 * 15);
});
it('should create a 20x20 map', () => {
const map = new HexMap(20);
assert.strictEqual(map.size, 20);
assert.strictEqual(map.cells.size, 20 * 20);
});
it('should create a 25x25 map', () => {
const map = new HexMap(25);
assert.strictEqual(map.size, 25);
assert.strictEqual(map.cells.size, 25 * 25);
});
it('should generate cells with correct coordinates for all map sizes', () => {
const sizes = [10, 15, 20, 25];
for (const size of sizes) {
const map = new HexMap(size);
for (let q = 0; q < size; q++) {
for (let r = 0; r < size; r++) {
const cell = map.getCell(q, r);
assert.ok(cell, `Cell at ${q},${r} should exist for size ${size}`);
assert.strictEqual(cell.q, q);
assert.strictEqual(cell.r, r);
}
}
}
});
it('should have correct neighbor counts for different map sizes', () => {
const sizes = [10, 15, 20, 25];
for (const size of sizes) {
const map = new HexMap(size);
// Clear all blocks for predictable testing
map.cells.forEach(cell => {
if (cell.type === CELL_TYPES.BLOCKED) {
cell.type = CELL_TYPES.EMPTY;
}
});
// Center cell should have 6 neighbors
const centerQ = Math.floor(size / 2);
const centerR = Math.floor(size / 2);
const centerNeighbors = map.getNeighbors(centerQ, centerR);
assert.strictEqual(centerNeighbors.length, 6, `Center cell should have 6 neighbors for size ${size}`);
// Corner cell should have 2 neighbors
const cornerNeighbors = map.getNeighbors(0, 0);
assert.strictEqual(cornerNeighbors.length, 2, `Corner cell should have 2 neighbors for size ${size}`);
}
});
it('should calculate supply correctly for different map sizes', () => {
const sizes = [10, 15, 20, 25];
for (const size of sizes) {
const map = new HexMap(size);
// Clear any existing ownership
map.cells.forEach(cell => {
cell.type = CELL_TYPES.EMPTY;
});
// Create a connected territory of 5 cells
for (let i = 0; i < 5; i++) {
map.setOwner(i, 0, 1);
}
const supply = map.calculateSupply(1);
assert.strictEqual(supply, 5, `Supply should be 5 for connected territory in size ${size}`);
}
});
});
describe('HexCell', () => {
it('should create a cell with axial coordinates', () => {
const cell = new HexCell(3, 5);