401 lines
12 KiB
JavaScript
401 lines
12 KiB
JavaScript
const { describe, it } = require('node:test');
|
|
const assert = require('node:assert');
|
|
const { HexMap, HexCell, CELL_TYPES, MAP_SIZE } = require('../public/map.js');
|
|
|
|
describe('HexCell', () => {
|
|
it('should create a cell with axial coordinates', () => {
|
|
const cell = new HexCell(3, 5);
|
|
assert.strictEqual(cell.q, 3);
|
|
assert.strictEqual(cell.r, 5);
|
|
assert.strictEqual(cell.type, CELL_TYPES.EMPTY);
|
|
assert.strictEqual(cell.dice.length, 0);
|
|
});
|
|
|
|
it('should calculate strength correctly', () => {
|
|
const cell = new HexCell(0, 0);
|
|
|
|
// Empty cell has 0 strength
|
|
assert.strictEqual(cell.getStrength(), 0);
|
|
|
|
// Single die with value 4 = strength 4
|
|
cell.dice = [4];
|
|
assert.strictEqual(cell.getStrength(), 4);
|
|
|
|
// Two dice: [6, 3] = (2-1)*6 + 3 = 9
|
|
cell.dice = [6, 3];
|
|
assert.strictEqual(cell.getStrength(), 9);
|
|
|
|
// Three dice: [6, 6, 2] = (3-1)*6 + 2 = 14
|
|
cell.dice = [6, 6, 2];
|
|
assert.strictEqual(cell.getStrength(), 14);
|
|
});
|
|
|
|
it('should detect max strength', () => {
|
|
const cell = new HexCell(0, 0);
|
|
cell.dice = [6, 6, 6, 6, 6, 6, 6, 6];
|
|
assert.strictEqual(cell.isMaxStrength(), true);
|
|
|
|
cell.dice = [6, 6, 6, 6, 6, 6, 6, 5];
|
|
assert.strictEqual(cell.isMaxStrength(), false);
|
|
});
|
|
|
|
it('should add dice up to limit', () => {
|
|
const cell = new HexCell(0, 0);
|
|
|
|
for (let i = 0; i < 8; i++) {
|
|
assert.strictEqual(cell.addDie(6), true);
|
|
}
|
|
|
|
// 9th die should fail
|
|
assert.strictEqual(cell.addDie(6), false);
|
|
assert.strictEqual(cell.dice.length, 8);
|
|
});
|
|
|
|
it('should set strength correctly', () => {
|
|
const cell = new HexCell(0, 0);
|
|
|
|
cell.setStrength(1);
|
|
assert.deepStrictEqual(cell.dice, [1]);
|
|
|
|
cell.setStrength(6);
|
|
assert.deepStrictEqual(cell.dice, [6]);
|
|
|
|
cell.setStrength(7);
|
|
assert.deepStrictEqual(cell.dice, [6, 1]);
|
|
|
|
cell.setStrength(14);
|
|
assert.deepStrictEqual(cell.dice, [6, 6, 2]);
|
|
|
|
cell.setStrength(0);
|
|
assert.deepStrictEqual(cell.dice, []);
|
|
});
|
|
|
|
it('should check passability and ownership', () => {
|
|
const emptyCell = new HexCell(0, 0, CELL_TYPES.EMPTY);
|
|
const blockedCell = new HexCell(0, 0, CELL_TYPES.BLOCKED);
|
|
const player1Cell = new HexCell(0, 0, CELL_TYPES.PLAYER1);
|
|
|
|
assert.strictEqual(emptyCell.isPassable(), true);
|
|
assert.strictEqual(blockedCell.isPassable(), false);
|
|
assert.strictEqual(emptyCell.isOwned(), false);
|
|
assert.strictEqual(player1Cell.isOwned(), true);
|
|
assert.strictEqual(emptyCell.getOwner(), 0);
|
|
assert.strictEqual(player1Cell.getOwner(), 1);
|
|
});
|
|
});
|
|
|
|
describe('HexMap', () => {
|
|
it('should create a 20x20 map by default', () => {
|
|
const map = new HexMap();
|
|
assert.strictEqual(map.size, MAP_SIZE);
|
|
assert.strictEqual(map.cells.size, MAP_SIZE * MAP_SIZE);
|
|
});
|
|
|
|
it('should generate cells with correct coordinates', () => {
|
|
const map = new HexMap(5);
|
|
|
|
for (let q = 0; q < 5; q++) {
|
|
for (let r = 0; r < 5; r++) {
|
|
const cell = map.getCell(q, r);
|
|
assert.ok(cell, `Cell at ${q},${r} should exist`);
|
|
assert.strictEqual(cell.q, q);
|
|
assert.strictEqual(cell.r, r);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('should have some blocked cells', () => {
|
|
const map = new HexMap();
|
|
const blockedCells = Array.from(map.cells.values()).filter(
|
|
cell => cell.type === CELL_TYPES.BLOCKED
|
|
);
|
|
|
|
// Should have some blocked cells (roughly 15%)
|
|
assert.ok(blockedCells.length > 0, 'Should have at least one blocked cell');
|
|
assert.ok(blockedCells.length < map.cells.size / 2, 'Should not have more than 50% blocked');
|
|
});
|
|
|
|
it('should return passable cells', () => {
|
|
const map = new HexMap(5);
|
|
const passableCells = map.getPassableCells();
|
|
|
|
assert.ok(passableCells.length > 0);
|
|
passableCells.forEach(cell => {
|
|
assert.strictEqual(cell.isPassable(), true);
|
|
});
|
|
});
|
|
|
|
it('should return neighbors correctly', () => {
|
|
const map = new HexMap(5);
|
|
|
|
// 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 centerNeighbors = map.getNeighbors(2, 2);
|
|
assert.strictEqual(centerNeighbors.length, 6);
|
|
|
|
// Corner cell should have 2 neighbors
|
|
const cornerNeighbors = map.getNeighbors(0, 0);
|
|
assert.strictEqual(cornerNeighbors.length, 2);
|
|
|
|
// Edge cell should have 3-4 neighbors
|
|
const edgeNeighbors = map.getNeighbors(0, 2);
|
|
assert.ok(edgeNeighbors.length >= 3 && edgeNeighbors.length <= 4);
|
|
});
|
|
|
|
it('should set and clear ownership', () => {
|
|
const map = new HexMap(5);
|
|
|
|
// Clear the map first
|
|
map.cells.forEach(cell => {
|
|
cell.type = CELL_TYPES.EMPTY;
|
|
});
|
|
|
|
// Set ownership on cells that exist
|
|
assert.strictEqual(map.setOwner(2, 2, 1), true);
|
|
const cell = map.getCell(2, 2);
|
|
assert.strictEqual(cell.type, CELL_TYPES.PLAYER1);
|
|
assert.strictEqual(cell.getOwner(), 1);
|
|
|
|
// Set player 2 ownership on adjacent cell (2,3 is a neighbor of 2,2)
|
|
assert.strictEqual(map.setOwner(2, 3, 2), true);
|
|
const cell2 = map.getCell(2, 3);
|
|
assert.strictEqual(cell2.type, CELL_TYPES.PLAYER2);
|
|
assert.strictEqual(cell2.getOwner(), 2);
|
|
|
|
// Clear ownership
|
|
assert.strictEqual(map.clearOwner(2, 2), true);
|
|
const clearedCell = map.getCell(2, 2);
|
|
assert.strictEqual(clearedCell.type, CELL_TYPES.EMPTY);
|
|
assert.strictEqual(clearedCell.getOwner(), 0);
|
|
});
|
|
|
|
it('should return player cells', () => {
|
|
const map = new HexMap(5);
|
|
|
|
// First clear any existing ownership
|
|
map.cells.forEach(cell => {
|
|
cell.type = CELL_TYPES.EMPTY;
|
|
});
|
|
|
|
map.setOwner(0, 0, 1);
|
|
map.setOwner(0, 1, 1);
|
|
map.setOwner(1, 0, 2);
|
|
|
|
const player1Cells = map.getPlayerCells(1);
|
|
const player2Cells = map.getPlayerCells(2);
|
|
|
|
assert.strictEqual(player1Cells.length, 2);
|
|
assert.strictEqual(player2Cells.length, 1);
|
|
});
|
|
|
|
it('should calculate supply correctly', () => {
|
|
const map = new HexMap(5);
|
|
|
|
// First clear any existing ownership
|
|
map.cells.forEach(cell => {
|
|
cell.type = CELL_TYPES.EMPTY;
|
|
});
|
|
|
|
map.setOwner(0, 0, 1);
|
|
map.setOwner(0, 1, 1);
|
|
map.setOwner(1, 0, 1);
|
|
|
|
const supply = map.calculateSupply(1);
|
|
assert.strictEqual(supply, 3); // 3 cells = 3 supply
|
|
});
|
|
|
|
it('should not allow setting owner on blocked cell', () => {
|
|
const map = new HexMap(5);
|
|
|
|
// Find a blocked cell
|
|
let blockedCell = null;
|
|
map.cells.forEach(cell => {
|
|
if (cell.type === CELL_TYPES.BLOCKED) {
|
|
blockedCell = cell;
|
|
}
|
|
});
|
|
|
|
if (blockedCell) {
|
|
assert.strictEqual(map.setOwner(blockedCell.q, blockedCell.r, 1), false);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Target Cell Selection Logic', () => {
|
|
it('should allow attacking adjacent enemy cells', () => {
|
|
const map = new HexMap(5);
|
|
|
|
// Clear map
|
|
map.cells.forEach(cell => {
|
|
cell.type = CELL_TYPES.EMPTY;
|
|
});
|
|
|
|
// Set up: player 1 at (2,2), player 2 at adjacent (2,1)
|
|
// Neighbors of (2,2) in axial coords: (3,2), (3,1), (2,1), (1,2), (1,3), (2,3)
|
|
map.setOwner(2, 2, 1);
|
|
map.setOwner(2, 1, 2); // (2,1) is a neighbor of (2,2)
|
|
|
|
const p1Cell = map.getCell(2, 2);
|
|
p1Cell.setStrength(8);
|
|
|
|
const p2Cell = map.getCell(2, 1);
|
|
p2Cell.setStrength(6);
|
|
|
|
// Get neighbors of player 1 cell
|
|
const neighbors = map.getNeighbors(2, 2);
|
|
|
|
// Player 2 cell should be in neighbors
|
|
const hasP2Cell = neighbors.some(n => n.q === 2 && n.r === 1);
|
|
assert.strictEqual(hasP2Cell, true, 'Enemy cell should be adjacent');
|
|
|
|
// Valid target: not blocked and not own
|
|
// All 6 neighbors are valid because none of them are owned by player 1
|
|
// (only the center cell 2,2 is owned by player 1, but it's not a neighbor of itself)
|
|
const validTargets = neighbors.filter(n =>
|
|
n.type !== CELL_TYPES.BLOCKED && n.getOwner() !== 1
|
|
);
|
|
|
|
// Should have 6 valid targets (one is enemy at 2,1, rest are empty)
|
|
assert.strictEqual(validTargets.length, 6, 'Should have 6 valid targets (enemy + 5 empty)');
|
|
|
|
// Verify enemy cell is in valid targets
|
|
const enemyInTargets = validTargets.some(n => n.q === 2 && n.r === 1);
|
|
assert.strictEqual(enemyInTargets, true);
|
|
});
|
|
|
|
it('should allow capturing adjacent empty cells', () => {
|
|
const map = new HexMap(5);
|
|
|
|
// Clear map
|
|
map.cells.forEach(cell => {
|
|
cell.type = CELL_TYPES.EMPTY;
|
|
});
|
|
|
|
// Set up: player 1 at (2,2), empty at adjacent (2,3)
|
|
map.setOwner(2, 2, 1);
|
|
|
|
const p1Cell = map.getCell(2, 2);
|
|
p1Cell.setStrength(8);
|
|
|
|
const emptyCell = map.getCell(2, 3);
|
|
assert.strictEqual(emptyCell.type, CELL_TYPES.EMPTY);
|
|
|
|
// Get neighbors of player 1 cell
|
|
const neighbors = map.getNeighbors(2, 2);
|
|
|
|
// Empty cell should be in neighbors and be a valid target
|
|
const validTargets = neighbors.filter(n =>
|
|
n.type !== CELL_TYPES.BLOCKED && n.getOwner() !== 1
|
|
);
|
|
|
|
const hasEmptyCell = validTargets.some(n => n.q === 2 && n.r === 3);
|
|
assert.strictEqual(hasEmptyCell, true, 'Empty cell should be a valid target');
|
|
});
|
|
|
|
it('should NOT allow attacking own cells', () => {
|
|
const map = new HexMap(5);
|
|
|
|
// Clear map
|
|
map.cells.forEach(cell => {
|
|
cell.type = CELL_TYPES.EMPTY;
|
|
});
|
|
|
|
// Set up: player 1 owns two adjacent cells
|
|
map.setOwner(2, 2, 1);
|
|
map.setOwner(2, 3, 1);
|
|
|
|
const p1Cell1 = map.getCell(2, 2);
|
|
p1Cell1.setStrength(8);
|
|
|
|
const p1Cell2 = map.getCell(2, 3);
|
|
p1Cell2.setStrength(6);
|
|
|
|
// Get neighbors of first player 1 cell
|
|
const neighbors = map.getNeighbors(2, 2);
|
|
|
|
// Valid targets should NOT include own cells
|
|
const validTargets = neighbors.filter(n =>
|
|
n.type !== CELL_TYPES.BLOCKED && n.getOwner() !== 1
|
|
);
|
|
|
|
const hasOwnCell = validTargets.some(n => n.getOwner() === 1);
|
|
assert.strictEqual(hasOwnCell, false, 'Own cells should not be valid targets');
|
|
});
|
|
|
|
it('should NOT allow attacking through blocked cells', () => {
|
|
const map = new HexMap(5);
|
|
|
|
// Clear map
|
|
map.cells.forEach(cell => {
|
|
cell.type = CELL_TYPES.EMPTY;
|
|
});
|
|
|
|
// Set up: player 1 at (2,2), blocked at adjacent (2,3)
|
|
map.setOwner(2, 2, 1);
|
|
map.getCell(2, 3).type = CELL_TYPES.BLOCKED;
|
|
|
|
const p1Cell = map.getCell(2, 2);
|
|
p1Cell.setStrength(8);
|
|
|
|
// Get neighbors of player 1 cell
|
|
const neighbors = map.getNeighbors(2, 2);
|
|
|
|
// Blocked cell should NOT be in passable neighbors
|
|
const hasBlocked = neighbors.some(n => n.type === CELL_TYPES.BLOCKED);
|
|
assert.strictEqual(hasBlocked, false, 'Blocked cells should not be neighbors');
|
|
});
|
|
|
|
it('should correctly identify all valid targets from center position', () => {
|
|
const map = new HexMap(5);
|
|
|
|
// Clear map
|
|
map.cells.forEach(cell => {
|
|
cell.type = CELL_TYPES.EMPTY;
|
|
});
|
|
|
|
// Set up: player 1 at center (2,2)
|
|
map.setOwner(2, 2, 1);
|
|
const centerCell = map.getCell(2, 2);
|
|
centerCell.setStrength(8);
|
|
|
|
// Surround with different cell types
|
|
// (2,1) - empty
|
|
// (3,1) - enemy (player 2)
|
|
// (3,2) - blocked
|
|
// (2,3) - empty
|
|
// (1,3) - enemy (player 2)
|
|
// (1,2) - own (player 1)
|
|
map.setOwner(3, 1, 2);
|
|
map.getCell(3, 2).type = CELL_TYPES.BLOCKED;
|
|
map.setOwner(1, 3, 2);
|
|
map.setOwner(1, 2, 1);
|
|
|
|
const neighbors = map.getNeighbors(2, 2);
|
|
|
|
// Valid targets: empty cells and enemy cells, not blocked, not own
|
|
const validTargets = neighbors.filter(n =>
|
|
n.type !== CELL_TYPES.BLOCKED && n.getOwner() !== 1
|
|
);
|
|
|
|
// Should have 3 valid targets: (2,1) empty, (3,1) enemy, (2,3) empty
|
|
// (1,3) enemy, but need to check if it's actually a neighbor
|
|
assert.ok(validTargets.length >= 2, 'Should have at least 2 valid targets');
|
|
|
|
// Verify no own cells in valid targets
|
|
validTargets.forEach(t => {
|
|
assert.notStrictEqual(t.getOwner(), 1, 'Valid target should not be own cell');
|
|
});
|
|
|
|
// Verify no blocked cells in valid targets
|
|
validTargets.forEach(t => {
|
|
assert.notStrictEqual(t.type, CELL_TYPES.BLOCKED, 'Valid target should not be blocked');
|
|
});
|
|
});
|
|
});
|