Fix AI bot turn chain for multiple bots - prevent race conditions
This commit is contained in:
@@ -17,50 +17,63 @@ export class AIBot {
|
||||
* 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`);
|
||||
console.log(`[AI-BOT P${this.playerId}] === Turn started ===`);
|
||||
console.log(`[AI-BOT P${this.playerId}] Thinking...`);
|
||||
|
||||
if (playerCells.length === 0) {
|
||||
console.log(`AI Player ${this.playerId} has no cells, ending turn`);
|
||||
try {
|
||||
// Get all player cells
|
||||
const playerCells = this.map.getPlayerCells(this.playerId);
|
||||
|
||||
console.log(`[AI-BOT P${this.playerId}] Has ${playerCells.length} cells`);
|
||||
|
||||
if (playerCells.length === 0) {
|
||||
console.log(`[AI-BOT P${this.playerId}] No cells, ending turn`);
|
||||
await this.wait(this.thinkingTime);
|
||||
this.gameUI.endTurn();
|
||||
return;
|
||||
}
|
||||
|
||||
// Find all possible moves
|
||||
const moves = this.findPossibleMoves(playerCells);
|
||||
|
||||
console.log(`[AI-BOT P${this.playerId}] Found ${moves.length} possible moves`);
|
||||
|
||||
if (moves.length === 0) {
|
||||
// No moves available, end turn
|
||||
console.log(`[AI-BOT P${this.playerId}] No valid moves, ending turn`);
|
||||
await this.wait(this.thinkingTime);
|
||||
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-BOT P${this.playerId}] Selected move: from (${bestMove.from.q},${bestMove.from.r}) to (${bestMove.to.q},${bestMove.to.r}), type=${bestMove.type}, attackStr=${bestMove.attackStrength}, defStr=${bestMove.defenseStrength}`);
|
||||
|
||||
// Wait for thinking time
|
||||
await this.wait(this.thinkingTime);
|
||||
|
||||
// Execute the move
|
||||
this.gameUI.selectedCell = bestMove.from;
|
||||
this.gameUI.currentTarget = bestMove.to;
|
||||
this.gameUI.executeAttack();
|
||||
|
||||
console.log(`[AI-BOT P${this.playerId}] Move executed`);
|
||||
|
||||
// End turn after executing move
|
||||
console.log(`[AI-BOT P${this.playerId}] Calling endTurn()`);
|
||||
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`);
|
||||
|
||||
console.log(`[AI-BOT P${this.playerId}] === Turn completed ===`);
|
||||
} catch (error) {
|
||||
console.error(`[AI-BOT P${this.playerId}] Error during turn:`, error);
|
||||
// Still end turn on error to prevent game from getting stuck
|
||||
this.gameUI.endTurn();
|
||||
return;
|
||||
throw error; // Re-throw so caller knows there was an error
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
101
public/game.js
101
public/game.js
@@ -54,10 +54,11 @@ class GameUI {
|
||||
this.gamePhase = 'movement';
|
||||
this.hasMoved = false;
|
||||
this.isAIThinking = false;
|
||||
|
||||
this.isProcessingTurn = false; // Prevent re-entrancy during turn processing
|
||||
|
||||
this.offsetX = 0;
|
||||
this.offsetY = 0;
|
||||
|
||||
|
||||
this.init();
|
||||
}
|
||||
|
||||
@@ -115,7 +116,8 @@ class GameUI {
|
||||
this.gamePhase = 'movement';
|
||||
this.hasMoved = false;
|
||||
this.isAIThinking = false;
|
||||
|
||||
this.isProcessingTurn = false;
|
||||
|
||||
// Initialize AI bots AFTER map is created
|
||||
this.aiBots = {};
|
||||
for (let i = 1; i <= this.playerCount; i++) {
|
||||
@@ -123,16 +125,17 @@ class GameUI {
|
||||
this.aiBots[i] = new AIBot(i, this.map, this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Initialize starting positions for all players
|
||||
this.initializePlayers();
|
||||
|
||||
|
||||
this.centerMap();
|
||||
this.render();
|
||||
this.createPlayerCards();
|
||||
this.updateUI();
|
||||
this.log(`New game started with ${this.playerCount} players!`);
|
||||
|
||||
console.log(`[GAME] New game started with ${this.playerCount} players`);
|
||||
|
||||
// Start first player's turn (AI if needed)
|
||||
this.checkAndRunAITurn();
|
||||
}
|
||||
@@ -515,38 +518,82 @@ class GameUI {
|
||||
}
|
||||
|
||||
async endTurn() {
|
||||
if (this.gamePhase !== 'movement') return;
|
||||
// Remove isAIThinking check - AI needs to call endTurn() after its move
|
||||
if (this.gamePhase !== 'movement') {
|
||||
console.log(`[GAME] endTurn() called but gamePhase is '${this.gamePhase}', ignoring`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Apply supply
|
||||
const supply = this.map.calculateSupply(this.currentPlayer);
|
||||
this.distributeSupply(supply);
|
||||
this.log(`Player ${this.currentPlayer} received ${supply} supply`);
|
||||
|
||||
// Next player
|
||||
this.currentPlayer = (this.currentPlayer % this.playerCount) + 1;
|
||||
this.hasMoved = false;
|
||||
// Prevent re-entrancy - only one turn processing at a time
|
||||
if (this.isProcessingTurn) {
|
||||
console.log(`[GAME] endTurn() called but isProcessingTurn is true, ignoring`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.cancelSelection();
|
||||
this.updateUI();
|
||||
this.render();
|
||||
this.isProcessingTurn = true;
|
||||
console.log(`[GAME] endTurn() - Player ${this.currentPlayer} ending turn`);
|
||||
|
||||
this.log(`Player ${this.currentPlayer}'s turn`);
|
||||
|
||||
// Check if next player is AI
|
||||
this.checkAndRunAITurn();
|
||||
try {
|
||||
// Apply supply
|
||||
const supply = this.map.calculateSupply(this.currentPlayer);
|
||||
this.distributeSupply(supply);
|
||||
this.log(`Player ${this.currentPlayer} received ${supply} supply`);
|
||||
console.log(`[GAME] Player ${this.currentPlayer} received ${supply} supply`);
|
||||
|
||||
// Next player
|
||||
const previousPlayer = this.currentPlayer;
|
||||
this.currentPlayer = (this.currentPlayer % this.playerCount) + 1;
|
||||
this.hasMoved = false;
|
||||
|
||||
console.log(`[GAME] Turn transition: P${previousPlayer} -> P${this.currentPlayer}`);
|
||||
|
||||
this.cancelSelection();
|
||||
this.updateUI();
|
||||
this.render();
|
||||
|
||||
this.log(`Player ${this.currentPlayer}'s turn`);
|
||||
console.log(`[GAME] Player ${this.currentPlayer}'s turn started (${this.playerTypes[this.currentPlayer]})`);
|
||||
|
||||
// Check if next player is AI and await completion
|
||||
await this.checkAndRunAITurn();
|
||||
} catch (error) {
|
||||
console.error(`[GAME] Error in endTurn():`, error);
|
||||
this.log(`Error during turn transition: ${error.message}`, 'error');
|
||||
} finally {
|
||||
this.isProcessingTurn = false;
|
||||
console.log(`[GAME] endTurn() completed for Player ${this.currentPlayer}`);
|
||||
}
|
||||
}
|
||||
|
||||
checkAndRunAITurn() {
|
||||
/**
|
||||
* Check if current player is AI and run their turn
|
||||
* @returns {Promise<boolean>} - true if AI turn was run, false if human turn
|
||||
*/
|
||||
async checkAndRunAITurn() {
|
||||
if (this.playerTypes[this.currentPlayer] === 'ai' && this.aiBots[this.currentPlayer]) {
|
||||
console.log(`[AI] Player ${this.currentPlayer} is AI, starting turn`);
|
||||
this.isAIThinking = true;
|
||||
this.updateUI();
|
||||
|
||||
// Run AI turn and reset flag when done
|
||||
this.aiBots[this.currentPlayer].playTurn().then(() => {
|
||||
try {
|
||||
// Run AI turn and wait for it to complete
|
||||
await this.aiBots[this.currentPlayer].playTurn();
|
||||
console.log(`[AI] Player ${this.currentPlayer} AI turn completed`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error(`[AI] Error during Player ${this.currentPlayer} AI turn:`, error);
|
||||
this.log(`AI error: ${error.message}`, 'error');
|
||||
this.isAIThinking = false;
|
||||
});
|
||||
this.updateUI();
|
||||
// Still advance to next player even on error
|
||||
return true;
|
||||
} finally {
|
||||
// Always reset the flag when AI turn completes (success or error)
|
||||
this.isAIThinking = false;
|
||||
console.log(`[AI] Player ${this.currentPlayer} isAIThinking reset to false`);
|
||||
}
|
||||
}
|
||||
console.log(`[AI] Player ${this.currentPlayer} is human, no AI turn needed`);
|
||||
return false;
|
||||
}
|
||||
|
||||
distributeSupply(supply) {
|
||||
|
||||
Reference in New Issue
Block a user