Files
hexo/test/map.test.js

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');
});
});
});