const { describe, it, beforeEach } = require('node:test'); const assert = require('node:assert'); const { HexMap, HexCell, CELL_TYPES, MAP_SIZE } = require('../public/map.js'); const { AIBot } = require('../public/ai-bot.js'); /** * Mock GameUI for testing AIBot * Provides minimal interface needed for AI bot to function */ class MockGameUI { constructor() { this.selectedCell = null; this.currentTarget = null; this.executedMoves = []; this.turnEnded = false; } endTurn() { this.turnEnded = true; } executeAttack() { this.executedMoves.push({ from: this.selectedCell, to: this.currentTarget }); } reset() { this.selectedCell = null; this.currentTarget = null; this.executedMoves = []; this.turnEnded = false; } } /** * Helper to create a clean map for testing */ function createTestMap(size = 5) { const map = new HexMap(size); // Clear all cells to empty for predictable testing map.cells.forEach(cell => { cell.type = CELL_TYPES.EMPTY; cell.dice = []; }); return map; } /** * Helper to set up player cells with specific strength */ function setupPlayerCell(map, q, r, playerId, strength) { const cellType = playerId === 1 ? CELL_TYPES.PLAYER1 : CELL_TYPES.PLAYER2; const cell = map.getCell(q, r); cell.type = cellType; cell.setStrength(strength); return cell; } describe('AIBot', () => { describe('Instantiation', () => { it('should create AIBot with playerId, map, and gameUI', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); assert.strictEqual(bot.playerId, 1); assert.strictEqual(bot.map, map); assert.strictEqual(bot.gameUI, gameUI); assert.strictEqual(bot.thinkingTime, 1000); }); it('should work with different player IDs', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot1 = new AIBot(1, map, gameUI); const bot2 = new AIBot(2, map, gameUI); assert.strictEqual(bot1.playerId, 1); assert.strictEqual(bot2.playerId, 2); }); it('should have default thinking time of 1000ms', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); assert.strictEqual(bot.thinkingTime, 1000); }); }); describe('findPossibleMoves', () => { it('should return empty array when player has no cells', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); const moves = bot.findPossibleMoves([]); assert.deepStrictEqual(moves, []); }); it('should return empty array when all player cells have strength <= 1', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 1); setupPlayerCell(map, 2, 3, 1, 0); const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); assert.deepStrictEqual(moves, []); }); it('should identify valid attack targets on adjacent enemy cells', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); // Player 1 at (2,2) with strength 8 setupPlayerCell(map, 2, 2, 1, 8); // Player 2 at adjacent (2,1) with strength 4 setupPlayerCell(map, 2, 1, 2, 4); const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); // Should find at least one attack move to the enemy cell assert.ok(moves.length >= 1, 'Should find at least one move'); // Find the attack move targeting the enemy const attackMove = moves.find(m => m.to.q === 2 && m.to.r === 1); assert.ok(attackMove, 'Should find attack move to enemy cell'); assert.strictEqual(attackMove.from.q, 2); assert.strictEqual(attackMove.from.r, 2); assert.strictEqual(attackMove.attackStrength, 7); // 8 - 1 assert.strictEqual(attackMove.defenseStrength, 4); assert.strictEqual(attackMove.type, 'attack'); }); it('should identify valid expansion targets on adjacent empty cells', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); // Player 1 at (2,2) with strength 6 setupPlayerCell(map, 2, 2, 1, 6); // Empty cell at adjacent (2,3) const emptyCell = map.getCell(2, 3); emptyCell.type = CELL_TYPES.EMPTY; const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); // Find expansion move to the specific empty cell const expansionMove = moves.find(m => m.to.q === 2 && m.to.r === 3); assert.ok(expansionMove, 'Should find expansion move to empty cell at (2,3)'); assert.strictEqual(expansionMove.attackStrength, 5); // 6 - 1 assert.strictEqual(expansionMove.type, 'expand'); }); it('should NOT include moves to own cells', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); // Player 1 owns two adjacent cells setupPlayerCell(map, 2, 2, 1, 8); setupPlayerCell(map, 2, 3, 1, 4); const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); // Should only have moves from each cell to neighbors that are NOT owned by player 1 moves.forEach(move => { assert.notStrictEqual(move.to.getOwner(), 1, 'Move target should not be own cell'); }); }); it('should skip cells with strength <= 1 as source', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); // Player 1 cell with strength 1 (cannot attack) setupPlayerCell(map, 2, 2, 1, 1); // Empty adjacent cell const emptyCell = map.getCell(2, 1); emptyCell.type = CELL_TYPES.EMPTY; const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); assert.strictEqual(moves.length, 0, 'Should not generate moves from cell with strength 1'); }); it('should find multiple moves from multiple player cells', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); // Two player 1 cells with strength > 1 setupPlayerCell(map, 1, 1, 1, 5); setupPlayerCell(map, 3, 3, 1, 6); // Enemy cell adjacent to first setupPlayerCell(map, 1, 0, 2, 3); // Empty cell adjacent to second const emptyCell = map.getCell(3, 2); emptyCell.type = CELL_TYPES.EMPTY; const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); assert.ok(moves.length >= 2, 'Should find moves from both cells'); }); it('should calculate correct attack and defense strengths', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 10); setupPlayerCell(map, 2, 1, 2, 6); const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); // Find the move targeting the enemy cell at (2,1) const attackMove = moves.find(m => m.to.q === 2 && m.to.r === 1); assert.ok(attackMove, 'Should find attack move'); assert.strictEqual(attackMove.attackStrength, 9); // 10 - 1 assert.strictEqual(attackMove.defenseStrength, 6); }); }); describe('movePriority', () => { it('should give higher priority to winning attacks', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); // Winning attack: attack 8 vs defense 3 const winningAttack = { type: 'attack', attackStrength: 8, defenseStrength: 3, from: { q: 0, r: 0 }, to: { q: 0, r: 1 } }; // Losing attack: attack 3 vs defense 8 const losingAttack = { type: 'attack', attackStrength: 3, defenseStrength: 8, from: { q: 0, r: 0 }, to: { q: 0, r: 1 } }; const winningPriority = bot.movePriority(winningAttack); const losingPriority = bot.movePriority(losingAttack); assert.ok(winningPriority > losingPriority, 'Winning attack should have higher priority'); assert.ok(winningPriority >= 100, 'Winning attack should have base priority of 100+'); }); it('should give positive priority to expansion moves', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); const expansionMove = { type: 'expand', attackStrength: 5, defenseStrength: 0, from: { q: 0, r: 0 }, to: { q: 0, r: 1 } }; const priority = bot.movePriority(expansionMove); assert.ok(priority >= 50, 'Expansion should have base priority of 50+'); }); it('should prefer attacks over expansion when attack is favorable', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); // Favorable attack: 10 vs 2 const attack = { type: 'attack', attackStrength: 10, defenseStrength: 2, from: { q: 0, r: 0 }, to: { q: 0, r: 1 } }; // Expansion with same strength const expansion = { type: 'expand', attackStrength: 10, defenseStrength: 0, from: { q: 0, r: 0 }, to: { q: 0, r: 1 } }; const attackPriority = bot.movePriority(attack); const expansionPriority = bot.movePriority(expansion); assert.ok(attackPriority > expansionPriority, 'Favorable attack should beat expansion'); }); it('should add bonus based on attack strength', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); const weakExpansion = { type: 'expand', attackStrength: 2, defenseStrength: 0, from: { q: 0, r: 0 }, to: { q: 0, r: 1 } }; const strongExpansion = { type: 'expand', attackStrength: 8, defenseStrength: 0, from: { q: 0, r: 0 }, to: { q: 0, r: 1 } }; const weakPriority = bot.movePriority(weakExpansion); const strongPriority = bot.movePriority(strongExpansion); assert.ok(strongPriority > weakPriority, 'Stronger expansion should have higher priority'); }); it('should penalize risky attacks', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); const riskyAttack = { type: 'attack', attackStrength: 3, defenseStrength: 7, from: { q: 0, r: 0 }, to: { q: 0, r: 1 } }; const priority = bot.movePriority(riskyAttack); assert.ok(priority < 50, 'Risky attack should have reduced priority'); }); it('should rank moves: strong attack > weak attack > expansion', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); const strongAttack = { type: 'attack', attackStrength: 10, defenseStrength: 2, from: { q: 0, r: 0 }, to: { q: 0, r: 1 } }; const weakAttack = { type: 'attack', attackStrength: 5, defenseStrength: 4, from: { q: 0, r: 0 }, to: { q: 0, r: 1 } }; const expansion = { type: 'expand', attackStrength: 5, defenseStrength: 0, from: { q: 0, r: 0 }, to: { q: 0, r: 1 } }; const strongPriority = bot.movePriority(strongAttack); const weakPriority = bot.movePriority(weakAttack); const expansionPriority = bot.movePriority(expansion); assert.ok(strongPriority > weakPriority, 'Strong attack should beat weak attack'); assert.ok(weakPriority > expansionPriority || strongPriority > expansionPriority, 'Attacks should generally be preferred over expansion'); }); }); describe('AI prefers attacking weak enemies', () => { it('should prefer attacking weak enemy over empty cell', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 8); // Weak enemy at (2,1) setupPlayerCell(map, 2, 1, 2, 2); // Empty cell at (2,3) const emptyCell = map.getCell(2, 3); emptyCell.type = CELL_TYPES.EMPTY; const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); // Sort by priority moves.sort((a, b) => bot.movePriority(b) - bot.movePriority(a)); assert.strictEqual(moves[0].type, 'attack', 'Should prefer attack on weak enemy'); assert.strictEqual(moves[0].to.getOwner(), 2, 'Should target enemy cell'); }); it('should prefer attacking very weak enemy (strength 1)', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 6); // Very weak enemy setupPlayerCell(map, 2, 1, 2, 1); // Empty cell const emptyCell = map.getCell(3, 2); emptyCell.type = CELL_TYPES.EMPTY; const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); moves.sort((a, b) => bot.movePriority(b) - bot.movePriority(a)); const attackMove = moves.find(m => m.type === 'attack'); const expandMove = moves.find(m => m.type === 'expand'); assert.ok(attackMove, 'Should have attack move'); assert.ok(expandMove, 'Should have expansion move'); assert.ok(bot.movePriority(attackMove) > bot.movePriority(expandMove), 'Attack on weak enemy should be preferred'); }); it('should calculate advantage correctly for attack prioritization', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); // Attack with +5 advantage const bigAdvantage = { type: 'attack', attackStrength: 10, defenseStrength: 5, from: { q: 0, r: 0 }, to: { q: 0, r: 1 } }; // Attack with +1 advantage const smallAdvantage = { type: 'attack', attackStrength: 6, defenseStrength: 5, from: { q: 0, r: 0 }, to: { q: 0, r: 1 } }; const bigPriority = bot.movePriority(bigAdvantage); const smallPriority = bot.movePriority(smallAdvantage); assert.ok(bigPriority > smallPriority, 'Bigger advantage should have higher priority'); }); }); describe('AI does not select moves with strength <= 1', () => { it('should not generate moves from cells with strength 1', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 1); const emptyCell = map.getCell(2, 1); emptyCell.type = CELL_TYPES.EMPTY; const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); assert.strictEqual(moves.length, 0, 'Should not generate any moves'); }); it('should not generate moves from cells with strength 0', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 0); const emptyCell = map.getCell(2, 1); emptyCell.type = CELL_TYPES.EMPTY; const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); assert.strictEqual(moves.length, 0, 'Should not generate any moves'); }); it('should generate moves from cells with strength 2', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 2); const emptyCell = map.getCell(2, 1); emptyCell.type = CELL_TYPES.EMPTY; const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); assert.ok(moves.length > 0, 'Should generate moves from cell with strength 2'); assert.strictEqual(moves[0].attackStrength, 1, 'Attack strength should be 1'); }); it('should filter out cells with strength <= 1 when multiple cells exist', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); // Strong cell setupPlayerCell(map, 2, 2, 1, 8); // Weak cell setupPlayerCell(map, 3, 3, 1, 1); // Empty cells adjacent to both map.getCell(2, 1).type = CELL_TYPES.EMPTY; map.getCell(3, 2).type = CELL_TYPES.EMPTY; const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); // All moves should come from the strong cell only moves.forEach(move => { assert.ok(move.from.getStrength() > 1, 'Move source should have strength > 1'); }); }); }); describe('AI respects map boundaries', () => { it('should not generate moves outside map boundaries', () => { const map = createTestMap(5); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); // Cell at corner setupPlayerCell(map, 0, 0, 1, 8); const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); // All moves should be within bounds moves.forEach(move => { assert.ok(move.to.q >= 0 && move.to.q < 5, 'Target q should be in bounds'); assert.ok(move.to.r >= 0 && move.to.r < 5, 'Target r should be in bounds'); }); }); it('should handle edge cells correctly', () => { const map = createTestMap(5); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); // Cell at edge setupPlayerCell(map, 0, 2, 1, 8); const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); // All moves should be within bounds moves.forEach(move => { assert.ok(move.to.q >= 0 && move.to.q < 5, 'Target q should be in bounds'); assert.ok(move.to.r >= 0 && move.to.r < 5, 'Target r should be in bounds'); }); }); it('should use map.getNeighbors which respects boundaries', () => { const map = createTestMap(5); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); // Corner cell should have only 2 neighbors setupPlayerCell(map, 0, 0, 1, 8); // Make adjacent cells passable 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); // Should only have moves to valid neighbors assert.ok(moves.length <= 2, 'Corner cell should have at most 2 moves'); }); it('should not include blocked cells as targets', () => { const map = createTestMap(5); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 8); // Block an adjacent cell map.getCell(2, 1).type = CELL_TYPES.BLOCKED; const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); // Should not have move to blocked cell const hasBlockedTarget = moves.some(m => m.to.type === CELL_TYPES.BLOCKED); assert.strictEqual(hasBlockedTarget, false, 'Should not target blocked cells'); }); }); describe('executeMove', () => { it('should set selectedCell and currentTarget on gameUI', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 8); setupPlayerCell(map, 2, 1, 2, 4); const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); bot.executeMove(moves[0]); assert.strictEqual(gameUI.selectedCell, moves[0].from); assert.strictEqual(gameUI.currentTarget, moves[0].to); }); it('should call executeAttack on gameUI', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 8); setupPlayerCell(map, 2, 1, 2, 4); const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); bot.executeMove(moves[0]); assert.strictEqual(gameUI.executedMoves.length, 1); }); }); describe('playTurn', () => { it('should end turn when no moves available (cells with strength <= 1)', async () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); // Player has cells but all with strength <= 1 (no valid moves) setupPlayerCell(map, 2, 2, 1, 1); bot.thinkingTime = 0; // Skip waiting for faster tests await bot.playTurn(); assert.strictEqual(gameUI.turnEnded, true); }); it('should execute best move when moves are available', async () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 8); setupPlayerCell(map, 2, 1, 2, 2); // Weak enemy bot.thinkingTime = 0; // Skip waiting await bot.playTurn(); assert.strictEqual(gameUI.executedMoves.length, 1); assert.strictEqual(gameUI.selectedCell.q, 2); assert.strictEqual(gameUI.selectedCell.r, 2); }); }); describe('Integration: Full AI decision making', () => { it('should choose attack over expansion when attack is favorable', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 10); // Weak enemy setupPlayerCell(map, 2, 1, 2, 3); // Empty cell map.getCell(3, 2).type = CELL_TYPES.EMPTY; const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); moves.sort((a, b) => bot.movePriority(b) - bot.movePriority(a)); assert.strictEqual(moves[0].type, 'attack', 'Should prefer favorable attack'); }); it('should handle multiple potential targets correctly', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 8); // Multiple enemies with different strengths setupPlayerCell(map, 2, 1, 2, 2); // Weak setupPlayerCell(map, 1, 2, 2, 6); // Strong // Empty cell map.getCell(3, 2).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, 2); }); it('should prefer expansion when no favorable attacks exist', () => { const map = createTestMap(); const gameUI = new MockGameUI(); const bot = new AIBot(1, map, gameUI); setupPlayerCell(map, 2, 2, 1, 5); // Strong enemy (risky attack) setupPlayerCell(map, 2, 1, 2, 8); // Empty cell map.getCell(3, 2).type = CELL_TYPES.EMPTY; const playerCells = map.getPlayerCells(1); const moves = bot.findPossibleMoves(playerCells); moves.sort((a, b) => bot.movePriority(b) - bot.movePriority(a)); // Expansion might be preferred over risky attack const bestMove = moves[0]; assert.ok( bestMove.type === 'expand' || bestMove.attackStrength > bestMove.defenseStrength, 'Should prefer safe moves' ); }); }); }); describe('GameUI.distributeSupply', () => { // We need to test distributeSupply which is a method on GameUI // Since GameUI has DOM dependencies, we'll create a minimal mock class MinimalGameUI { constructor(map, currentPlayer) { this.map = map; this.currentPlayer = currentPlayer; } // Copy the distributeSupply method logic distributeSupply(supply) { const playerCells = this.map.getPlayerCells(this.currentPlayer); const eligibleCells = playerCells.filter(cell => !cell.isMaxStrength()); if (eligibleCells.length === 0 || supply === 0) return; let remainingSupply = supply; while (remainingSupply > 0 && eligibleCells.length > 0) { const randomCell = eligibleCells[Math.floor(Math.random() * eligibleCells.length)]; const currentStrength = randomCell.getStrength(); if (currentStrength < 48) { randomCell.setStrength(currentStrength + 1); remainingSupply--; } if (randomCell.isMaxStrength()) { eligibleCells.splice(eligibleCells.indexOf(randomCell), 1); } } } } it('should add strength one-by-one to eligible cells', () => { const map = createTestMap(); const currentPlayer = 1; const gameUI = new MinimalGameUI(map, currentPlayer); setupPlayerCell(map, 2, 2, 1, 5); setupPlayerCell(map, 2, 3, 1, 3); const initialStrength = map.getPlayerCells(1) .reduce((sum, c) => sum + c.getStrength(), 0); gameUI.distributeSupply(3); const finalStrength = map.getPlayerCells(1) .reduce((sum, c) => sum + c.getStrength(), 0); assert.strictEqual(finalStrength, initialStrength + 3, 'Should add exactly 3 strength'); }); it('should only add to cells that are not at max strength', () => { const map = createTestMap(); const currentPlayer = 1; const gameUI = new MinimalGameUI(map, currentPlayer); // Cell at max strength (48) const maxCell = setupPlayerCell(map, 2, 2, 1, 48); // Cell below max setupPlayerCell(map, 2, 3, 1, 10); gameUI.distributeSupply(5); assert.strictEqual(maxCell.getStrength(), 48, 'Max cell should not increase'); }); it('should stop when supply is exhausted', () => { const map = createTestMap(); const currentPlayer = 1; const gameUI = new MinimalGameUI(map, currentPlayer); setupPlayerCell(map, 2, 2, 1, 5); gameUI.distributeSupply(3); const cell = map.getCell(2, 2); assert.strictEqual(cell.getStrength(), 8, 'Should add exactly 3 to strength'); }); it('should stop when all cells reach max strength', () => { const map = createTestMap(); const currentPlayer = 1; const gameUI = new MinimalGameUI(map, currentPlayer); // Cell near max setupPlayerCell(map, 2, 2, 1, 47); // Try to add more than can fit gameUI.distributeSupply(10); const cell = map.getCell(2, 2); assert.strictEqual(cell.getStrength(), 48, 'Should cap at max strength'); }); it('should do nothing when supply is 0', () => { const map = createTestMap(); const currentPlayer = 1; const gameUI = new MinimalGameUI(map, currentPlayer); setupPlayerCell(map, 2, 2, 1, 5); const initialStrength = map.getCell(2, 2).getStrength(); gameUI.distributeSupply(0); const finalStrength = map.getCell(2, 2).getStrength(); assert.strictEqual(finalStrength, initialStrength, 'Should not change with 0 supply'); }); it('should do nothing when no eligible cells exist', () => { const map = createTestMap(); const currentPlayer = 1; const gameUI = new MinimalGameUI(map, currentPlayer); // No player cells gameUI.distributeSupply(5); const playerCells = map.getPlayerCells(1); assert.strictEqual(playerCells.length, 0); }); it('should distribute to random eligible cells', () => { const map = createTestMap(); const currentPlayer = 1; const gameUI = new MinimalGameUI(map, currentPlayer); // Multiple eligible cells setupPlayerCell(map, 1, 1, 1, 5); setupPlayerCell(map, 2, 2, 1, 5); setupPlayerCell(map, 3, 3, 1, 5); gameUI.distributeSupply(6); const totalStrength = map.getPlayerCells(1) .reduce((sum, c) => sum + c.getStrength(), 0); assert.strictEqual(totalStrength, 15 + 6, 'Should distribute all supply'); // At least some cells should have received supply (random distribution) const cells = map.getPlayerCells(1); const receivedSupply = cells.filter(c => c.getStrength() > 5); assert.ok(receivedSupply.length > 0, 'Some cells should receive supply'); }); it('should handle multiple cells with different initial strengths', () => { const map = createTestMap(); const currentPlayer = 1; const gameUI = new MinimalGameUI(map, currentPlayer); setupPlayerCell(map, 1, 1, 1, 3); setupPlayerCell(map, 2, 2, 1, 6); setupPlayerCell(map, 3, 3, 1, 4); const initialTotal = map.getPlayerCells(1) .reduce((sum, c) => sum + c.getStrength(), 0); gameUI.distributeSupply(5); const finalTotal = map.getPlayerCells(1) .reduce((sum, c) => sum + c.getStrength(), 0); assert.strictEqual(finalTotal, initialTotal + 5, 'Should add exactly 5 total'); }); });