153
lib/aiLogic.ts
Archivo normal
153
lib/aiLogic.ts
Archivo normal
@@ -0,0 +1,153 @@
|
||||
import { Player, GameState, GameMove, DominoTile, BoardEnd } from './types';
|
||||
import { getValidMoves, canPlaceTile } from './gameLogic';
|
||||
|
||||
// AI difficulty levels
|
||||
export type AIDifficulty = 'easy' | 'medium' | 'hard';
|
||||
|
||||
// Evaluate tile value for strategic play
|
||||
function evaluateTileValue(tile: DominoTile, boardEnds: BoardEnd[]): number {
|
||||
let value = 0;
|
||||
|
||||
// Prefer higher value tiles early in the game
|
||||
value += tile.left + tile.right;
|
||||
|
||||
// Doubles are slightly more valuable
|
||||
if (tile.isDouble) {
|
||||
value += 2;
|
||||
}
|
||||
|
||||
// Tiles that match both ends are very valuable
|
||||
const matchesLeft = boardEnds.some(end => end.side === 'left' && canPlaceTile(tile, end.value));
|
||||
const matchesRight = boardEnds.some(end => end.side === 'right' && canPlaceTile(tile, end.value));
|
||||
|
||||
if (matchesLeft && matchesRight) {
|
||||
value += 10;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Count remaining tiles with specific values
|
||||
function countRemainingTiles(value: number, allPlayerTiles: DominoTile[][]): number {
|
||||
let count = 0;
|
||||
allPlayerTiles.forEach(tiles => {
|
||||
tiles.forEach(tile => {
|
||||
if (tile.left === value || tile.right === value) {
|
||||
count++;
|
||||
}
|
||||
});
|
||||
});
|
||||
return count;
|
||||
}
|
||||
|
||||
// Choose the best move for AI based on difficulty
|
||||
export function chooseAIMove(
|
||||
gameState: GameState,
|
||||
aiPlayer: Player,
|
||||
difficulty: AIDifficulty = 'medium'
|
||||
): GameMove | null {
|
||||
const validMoves = getValidMoves(aiPlayer, gameState.boardEnds);
|
||||
|
||||
if (validMoves.length === 0) {
|
||||
// Try to draw from boneyard if possible
|
||||
if (gameState.boneyard.length > 0) {
|
||||
return null; // Will trigger draw action
|
||||
}
|
||||
// Pass if can't move and no tiles to draw
|
||||
return {
|
||||
playerId: aiPlayer.id,
|
||||
tile: aiPlayer.tiles[0], // Dummy tile
|
||||
side: 'left',
|
||||
pass: true,
|
||||
};
|
||||
}
|
||||
|
||||
// Easy: Random move
|
||||
if (difficulty === 'easy') {
|
||||
const randomMove = validMoves[Math.floor(Math.random() * validMoves.length)];
|
||||
return {
|
||||
playerId: aiPlayer.id,
|
||||
tile: randomMove.tile,
|
||||
side: randomMove.side,
|
||||
};
|
||||
}
|
||||
|
||||
// Medium: Prefer higher value tiles
|
||||
if (difficulty === 'medium') {
|
||||
let bestMove = validMoves[0];
|
||||
let bestValue = evaluateTileValue(bestMove.tile, gameState.boardEnds);
|
||||
|
||||
validMoves.forEach(move => {
|
||||
const value = evaluateTileValue(move.tile, gameState.boardEnds);
|
||||
if (value > bestValue) {
|
||||
bestValue = value;
|
||||
bestMove = move;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
playerId: aiPlayer.id,
|
||||
tile: bestMove.tile,
|
||||
side: bestMove.side,
|
||||
};
|
||||
}
|
||||
|
||||
// Hard: Strategic play
|
||||
// Consider opponent's possible tiles and blocking strategies
|
||||
let bestMove = validMoves[0];
|
||||
let bestScore = -Infinity;
|
||||
|
||||
validMoves.forEach(move => {
|
||||
let score = evaluateTileValue(move.tile, gameState.boardEnds);
|
||||
|
||||
// Prefer moves that limit opponent's options
|
||||
const resultingValue = move.tile.left === gameState.boardEnds.find(e => e.side === move.side)?.value
|
||||
? move.tile.right
|
||||
: move.tile.left;
|
||||
|
||||
// Check how common this value is among remaining tiles
|
||||
const allTiles = gameState.players.map(p => p.tiles);
|
||||
const commonality = countRemainingTiles(resultingValue, allTiles);
|
||||
|
||||
// Prefer less common values to block opponents
|
||||
score -= commonality * 3;
|
||||
|
||||
// Try to get rid of high-value tiles first (defensive play)
|
||||
const tileValue = move.tile.left + move.tile.right;
|
||||
score += tileValue * 0.5;
|
||||
|
||||
// Prefer doubles near the end of the game
|
||||
if (move.tile.isDouble && aiPlayer.tiles.length <= 3) {
|
||||
score += 5;
|
||||
}
|
||||
|
||||
if (score > bestScore) {
|
||||
bestScore = score;
|
||||
bestMove = move;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
playerId: aiPlayer.id,
|
||||
tile: bestMove.tile,
|
||||
side: bestMove.side,
|
||||
};
|
||||
}
|
||||
|
||||
// Simulate AI thinking delay
|
||||
export async function aiThinkingDelay(difficulty: AIDifficulty): Promise<void> {
|
||||
const delays = {
|
||||
easy: 500,
|
||||
medium: 1000,
|
||||
hard: 1500,
|
||||
};
|
||||
|
||||
const delay = delays[difficulty] + Math.random() * 500;
|
||||
return new Promise(resolve => setTimeout(resolve, delay));
|
||||
}
|
||||
|
||||
// Check if AI should draw a tile
|
||||
export function shouldAIDraw(gameState: GameState, aiPlayer: Player): boolean {
|
||||
const validMoves = getValidMoves(aiPlayer, gameState.boardEnds);
|
||||
return validMoves.length === 0 && gameState.boneyard.length > 0;
|
||||
}
|
||||
Referencia en una nueva incidencia
Block a user