logic
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user