namespace CheckersSpielBot { public class AiService { private const int MaxDepth = 7; private const int WinScore = 100000; private const int LoseScore = -100000; private readonly int _aiPlayer; private readonly int _humanPlayer; public AiService(int aiPlayer) { _aiPlayer = aiPlayer; _humanPlayer = aiPlayer == 1 ? 2 : 1; } public Move? GetBestMove(int[,] board) { Move? bestMove = null; int bestScore = int.MinValue; var allMoves = GetAllMovesForPlayer(board, _aiPlayer); if (allMoves.Count == 0) return null; foreach (var (originRow, originCol, move) in allMoves) { int[,] newBoard = ApplyMove(board, originRow, originCol, move); int score = Minimax(newBoard, MaxDepth - 1, int.MinValue, int.MaxValue, false, move.IsCapture, move.DestinationRow, move.DestinationCol); if (score > bestScore) { bestScore = score; bestMove = move; } } return bestMove; } // Minimax with Alpha-Beta Pruning private int Minimax(int[,] board, int depth, int alpha, int beta, bool isMaximizing, bool lastMoveWasCapture, int lastMoveRow, int lastMoveCol) { int currentPlayer = isMaximizing ? _aiPlayer : _humanPlayer; // Chain jump if (lastMoveWasCapture) { var chainJumps = new MoveGeneratorService(board).GetLegalMoves(lastMoveRow, lastMoveCol, capturesOnly: true); if (chainJumps.Count > 0) { // Same player continue jumping / don't switch turn if (isMaximizing) { int best = int.MinValue; foreach (var jump in chainJumps) { int[,] newBoard = ApplyMove(board, lastMoveRow, lastMoveCol, jump); int score = Minimax(newBoard, depth, alpha, beta, true, true, jump.DestinationRow, jump.DestinationCol); best = Math.Max(best, score); alpha = Math.Max(alpha, best); if (beta <= alpha) break; } return best; } else { int best = int.MaxValue; foreach (var jump in chainJumps) { int[,] newBoard = ApplyMove(board, lastMoveRow, lastMoveCol, jump); int score = Minimax(newBoard, depth, alpha, beta, false, true, jump.DestinationRow, jump.DestinationCol); best = Math.Min(best, score); beta = Math.Min(beta, best); if (beta <= alpha) break; } return best; } } } // Terminal / depth check var allMoves = GetAllMovesForPlayer(board, currentPlayer); if (allMoves.Count == 0) { return isMaximizing ? LoseScore : WinScore; } if (depth == 0) { return BoardEvaluator.Evaluate(board, _aiPlayer); } if (isMaximizing) { int best = int.MinValue; foreach (var (oRow, oCol, move) in allMoves) { int[,] newBoard = ApplyMove(board, oRow, oCol, move); int score = Minimax(newBoard, depth - 1, alpha, beta, false, move.IsCapture, move.DestinationRow, move.DestinationCol); best = Math.Max(best, score); alpha = Math.Max(alpha, best); if (beta <= alpha) { break; } } return best; } else { int best = int.MaxValue; foreach (var (oRow, oCol, move) in allMoves) { int[,] newBoard = ApplyMove(board, oRow, oCol, move); int score = Minimax(newBoard, depth - 1, alpha, beta, true, move.IsCapture, move.DestinationRow, move.DestinationCol); best = Math.Min(best, score); beta = Math.Min(beta, best); if (beta <= alpha) { break; } } return best; } } #region helpers private static List<(int, int, Move)> GetAllMovesForPlayer(int[,] board, int player) { var moveGen = new MoveGeneratorService(board); var captures = new List<(int, int, Move)>(); var normals = new List<(int, int, Move)>(); for (int r = 0; r < 8; r++) { for (int c = 0; c < 8; c++) { if (!BoardHelper.BelongsToPlayer(board[r, c], player)) continue; var jumps = moveGen.GetLegalMoves(r, c, capturesOnly: true); if (jumps.Count > 0) { foreach (var j in jumps) captures.Add((r, c, j)); continue; } var moves = moveGen.GetLegalMoves(r, c, capturesOnly: false); foreach (var m in moves) { normals.Add((r, c, m)); } } } return captures.Count > 0 ? captures : normals; } private static int[,] ApplyMove(int[,] board, int originRow, int originCol, Move move) { int[,] newBoard = (int[,])board.Clone(); int piece = newBoard[originRow, originCol]; newBoard[move.DestinationRow, move.DestinationCol] = piece; newBoard[originRow, originCol] = 0; if (move.IsCapture) { newBoard[move.CapturedPieceRow, move.CapturedPieceCol] = 0; } // Promotion if (newBoard[move.DestinationRow, move.DestinationCol] == 1 && move.DestinationRow == 0) { newBoard[move.DestinationRow, move.DestinationCol] = 3; } if (newBoard[move.DestinationRow, move.DestinationCol] == 2 && move.DestinationRow == 7) { newBoard[move.DestinationRow, move.DestinationCol] = 4; } return newBoard; } #endregion } }