/// <summary> /// Moves a piece with the specified color and source/target positions and promotes to the new /// piece with the specified type. /// </summary> /// <param name="color">The piece color.</param> /// <param name="from">The source piece position.</param> /// <param name="to">The target piece position.</param> /// <param name="promotionPieceType">The promotion piece type.</param> public void Move(Color color, Position from, Position to, PieceType promotionPieceType) { Bitboard.Calculate(GeneratorMode.CalculateMoves | GeneratorMode.CalculateAttacks, false); CheckBitboardIntegrity(); UpdateMovesCount(color); if (CheckIfGameHasEnded()) { return; } var possibleMovesToApply = Bitboard.Moves .OfType <PromotionMove>() .FirstOrDefault(p => p.From == from && p.To == to && p.PromotionPiece == promotionPieceType); // Temporary but I think that's not necessary if (possibleMovesToApply == null) { Console.WriteLine($"{from} {to} not found"); return; } Bitboard = Bitboard.Move(possibleMovesToApply); _history.Add(possibleMovesToApply); }
/// <summary> /// Applies friendly board to the bitboard and updates the visual board. /// </summary> /// <param name="friendlyBoard">The friendly board to apply.</param> /// <param name="whiteMode">The white generator mode.</param> /// <param name="blackMode">The black generator mode.</param> /// <param name="quiescenceSearch">If true, only quiescence moves (mainly captures) will be generated.</param> protected void CalculateBitboard(FriendlyBoard friendlyBoard, GeneratorMode whiteMode, GeneratorMode blackMode, bool quiescenceSearch) { Bitboard = new Bitboard(friendlyBoard); Bitboard.Calculate(whiteMode, blackMode, quiescenceSearch); VisualBoard.FriendlyBoard = new FriendlyBoard(Bitboard); }
public int Do(Color color, Bitboard bitboard, int alpha, int beta, AIStats stats) { var enemyColor = ColorOperations.Invert(color); var colorSign = ColorOperations.ToSign(color); stats.QuiescenceTotalNodes++; var whiteGeneratorMode = GetGeneratorMode(color, Color.White); var blackGeneratorMode = GetGeneratorMode(color, Color.Black); bitboard.Calculate(whiteGeneratorMode, blackGeneratorMode, true); if (bitboard.IsCheck(enemyColor)) { stats.QuiescenceEndNodes++; return(AIConstants.MateValue); } var evaluation = colorSign * bitboard.GetEvaluation(); if (evaluation >= beta) { stats.QuiescenceEndNodes++; return(beta); } if (evaluation > alpha) { alpha = evaluation; } var sortedMoves = SortMoves(color, bitboard, bitboard.Moves); foreach (var move in sortedMoves) { var bitboardAfterMove = bitboard.Move(move); var nodeValue = -Do(enemyColor, bitboardAfterMove, -beta, -alpha, stats); if (nodeValue >= beta) { return(beta); } if (nodeValue > alpha) { alpha = nodeValue; } } if (!sortedMoves.Any()) { stats.QuiescenceEndNodes++; } return(alpha); }
/// <summary> /// Recursive method for calculating bitboard. If depth is equal or less than zero, then /// current node is the last and next CalculateBitboard call will not be executed. /// </summary> /// <param name="color">Color of the current player.</param> /// <param name="bitboard">Currently calculated bitboard</param> /// <param name="depth">Current depth</param> /// <param name="calculateEndNodes">If true, every end node will calculate attacks and evaluation function.</param> /// <param name="verifyIntegrity">If true, every board will be checked whether incremental-updating parameters are correctly calculated.</param> /// <param name="testData">Container for test data which will be returned when test is done.</param> private void CalculateBitboard(Color color, Bitboard bitboard, int depth, bool calculateEndNodes, bool verifyIntegrity, MovesTestData testData) { if (verifyIntegrity) { bitboard.Calculate(GeneratorMode.CalculateAttacks, false); if (!bitboard.VerifyIntegrity()) { testData.Integrity = false; var boardWriter = new BoardWriter(); boardWriter.Write("Boards/error.board", new FriendlyBoard(bitboard)); } } if (depth <= 0) { if (calculateEndNodes) { bitboard.Calculate(GeneratorMode.CalculateAttacks, false); bitboard.GetEvaluation(); } testData.EndNodes++; } else { var enemyColor = ColorOperations.Invert(color); var whiteMode = GetGeneratorMode(color); var blackMode = GetGeneratorMode(enemyColor); bitboard.Calculate(whiteMode, blackMode, false); foreach (var move in bitboard.Moves) { var bitboardAfterMove = bitboard.Move(move); CalculateBitboard(enemyColor, bitboardAfterMove, depth - 1, calculateEndNodes, verifyIntegrity, testData); } } testData.TotalNodes++; }
/// <summary> /// Runs AI and does best found move. /// </summary> /// <param name="color">The engine color.</param> /// <returns>The AI result.</returns> public AIResult MoveAI(Color color) { Bitboard.Calculate(GeneratorMode.CalculateMoves | GeneratorMode.CalculateAttacks, false); CheckBitboardIntegrity(); UpdateMovesCount(color); if (CheckIfGameHasEnded()) { return(null); } var openingBookMove = _openingBook.GetMoveFromBook(_history); if (openingBookMove != null) { var moveToApply = Bitboard.Moves.First(p => p.From == openingBookMove.From && p.To == openingBookMove.To); Bitboard = Bitboard.Move(moveToApply); _history.Add(moveToApply); return(new AIResult() { PVNodes = new PVNodesList() { moveToApply } }); } else { var preferredTime = _preferredTimeCalculator.Calculate(MovesCount, _remainingTime[(int)color]); var aiResult = _aiCore.Calculate(color, Bitboard, preferredTime); // Temporary if (aiResult.PVNodes.Count < 1) { Console.WriteLine($"Move not found"); return(null); } Bitboard = Bitboard.Move(aiResult.PVNodes[0]); _history.Add(aiResult.PVNodes[0]); return(aiResult); } }
/// <summary> /// Moves a piece with the specified color and source/target positions. /// </summary> /// <param name="color">The piece color.</param> /// <param name="from">The source piece position.</param> /// <param name="to">The target piece position.</param> public void Move(Color color, Position from, Position to) { Bitboard.Calculate(GeneratorMode.CalculateMoves | GeneratorMode.CalculateAttacks, false); CheckBitboardIntegrity(); UpdateMovesCount(color); CheckIfGameHasEnded(); if (CheckIfGameHasEnded()) { return; } var moveToApply = Bitboard.Moves.FirstOrDefault(p => p.From == from && p.To == to); Bitboard = Bitboard.Move(moveToApply); _history.Add(moveToApply); }
public List <Move> ParseMoves(List <string> moves) { var parsedMoves = new List <Move>(); var bitboard = new Bitboard(new DefaultFriendlyBoard()); var currentColor = Color.White; foreach (var move in moves) { bitboard.Calculate(GeneratorMode.CalculateMoves, false); var parsedMove = GetMove(bitboard, currentColor, move); bitboard = bitboard.Move(parsedMove); parsedMoves.Add(parsedMove); currentColor = ColorOperations.Invert(currentColor); } return(parsedMoves); }
/// <summary> /// Moves a piece with the specified color and source/target positions and promotes to the new /// piece with the specified type. /// </summary> /// <param name="color">The piece color.</param> /// <param name="from">The source piece position.</param> /// <param name="to">The target piece position.</param> /// <param name="promotionPieceType">The promotion piece type.</param> public void Move(Color color, Position from, Position to, PieceType promotionPieceType) { Bitboard.Calculate(GeneratorMode.CalculateMoves | GeneratorMode.CalculateAttacks, false); CheckBitboardIntegrity(); UpdateMovesCount(color); if (CheckIfGameHasEnded()) { return; } var possibleMovesToApply = Bitboard.Moves .OfType <PromotionMove>() .FirstOrDefault(p => p.From == from && p.To == to && p.PromotionPiece == promotionPieceType); Bitboard = Bitboard.Move(possibleMovesToApply); _history.Add(possibleMovesToApply); }
/// <summary> /// Moves a piece with the specified color and source/target positions. /// </summary> /// <param name="color">The piece color.</param> /// <param name="from">The source piece position.</param> /// <param name="to">The target piece position.</param> public void Move(Color color, Position from, Position to) { Bitboard.Calculate(GeneratorMode.CalculateMoves | GeneratorMode.CalculateAttacks, false); CheckBitboardIntegrity(); UpdateMovesCount(color); CheckIfGameHasEnded(); if (CheckIfGameHasEnded()) { return; } // Temporary but I think that's not necessary var moveToApply = Bitboard.Moves.FirstOrDefault(p => p.From == from && p.To == to); if (moveToApply == null) { Console.WriteLine($"{from} {to} not found"); return; } Bitboard = Bitboard.Move(moveToApply); _history.Add(moveToApply); }
/// <summary> /// Temporary method to calculating best move. /// </summary> /// <param name="color">The player color.</param> /// <param name="bitboard">The bitboard.</param> /// <param name="depth">The current depth.</param> /// <param name="bestMove">The best possible move from nested nodes.</param> /// <param name="stats">The AI stats.</param> /// <returns>The evaluation score of best move.</returns> public int Do(Color color, Bitboard bitboard, int depth, int alpha, int beta, AIStats stats) { var bestValue = AIConstants.InitialAlphaValue; var enemyColor = ColorOperations.Invert(color); var boardHash = bitboard.GetHashForColor(color); var originalAlpha = alpha; stats.TotalNodes++; if (bitboard.IsThreefoldRepetition()) { stats.EndNodes++; return(0); } if (_transpositionTable.Exists(boardHash)) { var transpositionNode = _transpositionTable.Get(boardHash); if (transpositionNode.Depth >= depth) { stats.TranspositionTableHits++; switch (transpositionNode.Type) { case ScoreType.Exact: { return(transpositionNode.Score); } case ScoreType.LowerBound: { alpha = Math.Max(alpha, transpositionNode.Score); break; } case ScoreType.UpperBound: { beta = Math.Min(beta, transpositionNode.Score); break; } } if (alpha >= beta) { return(transpositionNode.Score); } } } if (depth <= 0) { stats.EndNodes++; return(_quiescenceSearch.Do(color, bitboard, alpha, beta, stats)); } var whiteGeneratorMode = GetGeneratorMode(color, Color.White); var blackGeneratorMode = GetGeneratorMode(color, Color.Black); bitboard.Calculate(whiteGeneratorMode, blackGeneratorMode, false); if (bitboard.IsCheck(enemyColor)) { stats.EndNodes++; return(AIConstants.MateValue + depth); } Move bestMove = null; var availableMoves = SortMoves(color, bitboard, bitboard.Moves); var firstMove = true; foreach (var move in availableMoves) { var bitboardAfterMove = bitboard.Move(move); var nodeValue = 0; if (firstMove) { nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -beta, -alpha, stats); firstMove = false; } else { nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -alpha - 1, -alpha, stats); if (nodeValue > alpha && nodeValue < beta) { bitboardAfterMove = bitboard.Move(move); nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -beta, -alpha, stats); } } if (nodeValue > bestValue) { bestValue = nodeValue; bestMove = move; } alpha = Math.Max(nodeValue, alpha); if (alpha >= beta) { stats.AlphaBetaCutoffs++; break; } } if (bestValue == -(AIConstants.MateValue + depth - 1) && !bitboard.IsCheck(color)) { stats.EndNodes++; return(0); } var updateTranspositionNode = new TranspositionNode(); updateTranspositionNode.Score = bestValue; updateTranspositionNode.Depth = depth; updateTranspositionNode.BestMove = bestMove; if (bestValue <= originalAlpha) { updateTranspositionNode.Type = ScoreType.UpperBound; } else if (bestValue >= beta) { updateTranspositionNode.Type = ScoreType.LowerBound; } else { updateTranspositionNode.Type = ScoreType.Exact; } _transpositionTable.AddOrUpdate(boardHash, updateTranspositionNode); return(bestValue); }
/// <summary> /// Regular search, the core of AI algorithms. /// </summary> /// <param name="color">The player color.</param> /// <param name="bitboard">The bitboard.</param> /// <param name="depth">The current depth.</param> /// <param name="alpha">The alpha value.</param> /// <param name="beta">The beta value.</param> /// <param name="deadline">The deadline (time after which search is immediately terminated).</param> /// <param name="helper">The flag indicating whether the search is an helper or not.</param> /// <param name="stats">The AI stats.</param> /// <returns>The evaluation score of best move.</returns> public int Do(Color color, Bitboard bitboard, int depth, int alpha, int beta, long deadline, bool helper, AIStats stats) { var root = stats.TotalNodes == 0; var bestValue = AIConstants.InitialAlphaValue; var enemyColor = ColorOperations.Invert(color); var boardHash = bitboard.GetHashForColor(color); var originalAlpha = alpha; stats.TotalNodes++; if (bitboard.IsThreefoldRepetition()) { stats.EndNodes++; return(0); } #if TRANSPOSITION_TABLE if (_transpositionTable.Exists(boardHash)) { var transpositionNode = _transpositionTable.Get(boardHash); if (transpositionNode.Depth >= depth) { stats.TranspositionTableHits++; switch (transpositionNode.Type) { case ScoreType.Exact: { return(transpositionNode.Score); } case ScoreType.LowerBound: { alpha = Math.Max(alpha, transpositionNode.Score); break; } case ScoreType.UpperBound: { beta = Math.Min(beta, transpositionNode.Score); break; } } if (alpha >= beta) { return(transpositionNode.Score); } } } #endif if (depth <= 0) { stats.EndNodes++; #if QUIESCENCE_SEARCH return(_quiescenceSearch.Do(color, bitboard, alpha, beta, stats)); #else bitboard.Calculate(GeneratorMode.CalculateAttacks, false); return(bitboard.GetEvaluation()); #endif } var whiteGeneratorMode = GetGeneratorMode(color, Color.White); var blackGeneratorMode = GetGeneratorMode(color, Color.Black); bitboard.Calculate(whiteGeneratorMode, blackGeneratorMode, false); if (bitboard.IsCheck(enemyColor)) { stats.EndNodes++; return(AIConstants.MateValue + depth); } Move bestMove = null; var availableMoves = SortMoves(color, depth, bitboard, bitboard.Moves, helper); var firstMove = true; foreach (var move in availableMoves) { if (DateTime.Now.Ticks >= deadline) { break; } if (root) { if (_patternsDetector.IsPattern(bitboard, move)) { continue; } } var bitboardAfterMove = bitboard.Move(move); var nodeValue = 0; if (firstMove) { nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -beta, -alpha, deadline, helper, stats); #if NEGASCOUT firstMove = false; #endif } else { nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -alpha - 1, -alpha, deadline, helper, stats); if (nodeValue > alpha && nodeValue < beta) { bitboardAfterMove = bitboard.Move(move); nodeValue = -Do(enemyColor, bitboardAfterMove, depth - 1, -beta, -alpha, deadline, helper, stats); } } if (nodeValue > bestValue) { bestValue = nodeValue; bestMove = move; } alpha = Math.Max(nodeValue, alpha); if (alpha >= beta) { if (move is QuietMove) { _historyTable.AddKiller(color, depth, bestMove); _killerTable.AddKiller(depth, move); } #if ALPHABETA_PRUNNING stats.AlphaBetaCutoffs++; break; #endif } } if (bestValue == -(AIConstants.MateValue + depth - 1) && !bitboard.IsCheck(color)) { stats.EndNodes++; return(0); } var updateTranspositionNode = new TranspositionNode { Score = bestValue, Depth = depth, BestMove = bestMove, Type = GetTranspositionNodeType(originalAlpha, beta, bestValue) }; _transpositionTable.AddOrUpdate(boardHash, updateTranspositionNode); return(bestValue); }
public async Task Chess(CommandContext ctx, [Description("`new` aby rozpocząć grę lub ruch (np. `e2e4`)")] string action = null) { if (action == null) { await ctx.RespondAsync("Użycie: `!szachy new` aby rozpocząć nową grę lub `!szachy e2e4` aby wykonać ruch."); } else if (action == "new") { _selectedPositions.Clear(); _messageIds.Clear(); CreateSession(); var boardMessage = await ctx.RespondAsync(new DiscordMessageBuilder().WithContent("**Nowa gra utworzona:**").WithFile("board.png", GetBoardImage())); _messageIds.Add(boardMessage.Id); } else { _gameSession.UpdateRemainingTime(Color.White, 200); _gameSession.UpdateRemainingTime(Color.Black, 200); Position fromPosition, toPosition; try { var from = action.Substring(0, 2); var to = action.Substring(2, 2); fromPosition = PositionConverter.ToPosition(from); toPosition = PositionConverter.ToPosition(to); } catch (Exception e) { await ctx.RespondAsync("Nieprawidłowy ruch"); return; } var moveValidationBitboard = new Bitboard(_gameSession.Bitboard); moveValidationBitboard.Calculate(GeneratorMode.CalculateAttacks | GeneratorMode.CalculateMoves, false); var move = moveValidationBitboard.Moves.FirstOrDefault(p => p.From == fromPosition && p.To == toPosition); if (move == null) { await ctx.RespondAsync("Nieprawidłowy ruch"); return; } var validationBitboardAfterMove = moveValidationBitboard.Move(move); validationBitboardAfterMove.Calculate(false); if (validationBitboardAfterMove.IsCheck(Color.White)) { await ctx.RespondAsync("Invalid move"); return; } foreach (var msgIdToDelete in _messageIds) { var messageToDelete = await ctx.Channel.GetMessageAsync(msgIdToDelete); await ctx.Channel.DeleteMessageAsync(messageToDelete); } _messageIds.Clear(); _gameSession.Move(Color.White, fromPosition, toPosition); _selectedPositions.Clear(); _selectedPositions.Add(fromPosition); _selectedPositions.Add(toPosition); var playerBoardMsg = await ctx.RespondAsync(new DiscordMessageBuilder().WithContent("**Ruch gracza:**").WithFile("board.png", GetBoardImage())); _messageIds.Add(playerBoardMsg.Id); var thinkingMessage = await ctx.RespondAsync("Myślę..."); var aiMove = _gameSession.MoveAI(Color.Black); _selectedPositions.Clear(); _selectedPositions.Add(aiMove.PVNodes[0].From); _selectedPositions.Add(aiMove.PVNodes[0].To); await thinkingMessage.DeleteAsync(); var aiBoardMsg = await ctx.RespondAsync(new DiscordMessageBuilder().WithContent("**Ruch AI:**").WithFile("board.png", GetBoardImage())); _messageIds.Add(aiBoardMsg.Id); } }