Files
hexo/public/ai-bot.js
2026-02-21 20:42:58 +03:00

141 lines
3.6 KiB
JavaScript

/**
* AI Bot for Hexo game
* Controls computer-controlled players
*/
import { CELL_TYPES } from './map.js';
export class AIBot {
constructor(playerId, map, gameUI) {
this.playerId = playerId;
this.map = map;
this.gameUI = gameUI;
this.thinkingTime = 1000; // ms delay between moves
}
/**
* Execute AI turn - makes ONE move then ends turn
*/
async playTurn() {
console.log(`AI Player ${this.playerId} thinking...`);
// Get all player cells
const playerCells = this.map.getPlayerCells(this.playerId);
console.log(`AI Player ${this.playerId} has ${playerCells.length} cells`);
if (playerCells.length === 0) {
console.log(`AI Player ${this.playerId} has no cells, ending turn`);
this.gameUI.endTurn();
return;
}
// Find all possible moves
const moves = this.findPossibleMoves(playerCells);
console.log(`AI Player ${this.playerId} found ${moves.length} possible moves`);
if (moves.length === 0) {
// No moves available, end turn
console.log(`AI Player ${this.playerId} has no moves, ending turn`);
this.gameUI.endTurn();
return;
}
// Sort moves by priority (attack > expand > reinforce)
moves.sort((a, b) => this.movePriority(b) - this.movePriority(a));
// Execute best move
const bestMove = moves[0];
console.log(`AI Player ${this.playerId} selected move: from (${bestMove.from.q},${bestMove.from.r}) to (${bestMove.to.q},${bestMove.to.r}), type=${bestMove.type}`);
// Wait for thinking time
await this.wait(this.thinkingTime);
// Execute the move directly without using executeMove helper
this.gameUI.selectedCell = bestMove.from;
this.gameUI.currentTarget = bestMove.to;
this.gameUI.executeAttack();
console.log(`AI Player ${this.playerId} executed move`);
// End turn after executing move
this.gameUI.endTurn();
}
/**
* Find all possible moves for AI
*/
findPossibleMoves(playerCells) {
const moves = [];
for (const cell of playerCells) {
if (cell.getStrength() <= 1) continue;
const neighbors = this.map.getNeighbors(cell.q, cell.r);
for (const neighbor of neighbors) {
// Skip own cells
if (neighbor.getOwner() === this.playerId) continue;
const attackStrength = cell.getStrength() - 1;
const defenseStrength = neighbor.getStrength();
moves.push({
from: cell,
to: neighbor,
attackStrength,
defenseStrength,
type: neighbor.type === CELL_TYPES.EMPTY ? 'expand' : 'attack'
});
}
}
return moves;
}
/**
* Calculate move priority (higher = better)
*/
movePriority(move) {
let priority = 0;
// Prefer attacks on weak enemies
if (move.type === 'attack') {
if (move.attackStrength > move.defenseStrength) {
priority += 100; // Likely to win
priority += move.attackStrength - move.defenseStrength;
} else {
priority -= 50; // Risky attack
}
}
// Prefer expanding to empty cells
if (move.type === 'expand') {
priority += 50;
priority += move.attackStrength; // Stronger placement = better
}
// Prefer moves that create strong positions
priority += move.attackStrength * 0.5;
return priority;
}
/**
* Execute a move
*/
executeMove(move) {
this.gameUI.selectedCell = move.from;
this.gameUI.currentTarget = move.to;
this.gameUI.executeAttack();
}
/**
* Wait for specified time
*/
wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}