public string ToFen() { var stringBuilder = new StringBuilder(); // Position var x = 0; var unoccupiedSquares = 0; for (var square = Square.A8; square < Square.Illegal; square++) { x++; if (x == 9) { stringBuilder.Append("/"); x = 1; } var piece = GetPiece(square); if (piece == Piece.None) { // Unoccupied Square unoccupiedSquares++; if (x == 8) { // Last File // Display count of unoccupied squares. stringBuilder.Append(unoccupiedSquares); unoccupiedSquares = 0; } } else { // Occupied Square if (unoccupiedSquares > 0) { // Display count of unoccupied squares. stringBuilder.Append(unoccupiedSquares); unoccupiedSquares = 0; } stringBuilder.Append(PieceHelper.GetChar(piece)); } } // Display side to move, castling rights, en passant square, ply, and full move number. stringBuilder.Append(" "); stringBuilder.Append(ColorToMove == Color.White ? "w" : "b"); stringBuilder.Append(" "); stringBuilder.Append(Game.Castling.ToString(Castling)); stringBuilder.Append(" "); stringBuilder.Append(EnPassantSquare == Square.Illegal ? "-" : Board.SquareLocations[(int)EnPassantSquare]); stringBuilder.Append(" "); stringBuilder.Append(PlySinceCaptureOrPawnMove); stringBuilder.Append(" "); stringBuilder.Append(FullMoveNumber); return(stringBuilder.ToString()); }
public static ulong ParseStandardAlgebraic(Board board, string standardAlgebraic) { var move = Null; // Remove check and checkmate symbols. var standardAlgebraicNoCheck = standardAlgebraic.TrimEnd("+#".ToCharArray()); // ReSharper disable once SwitchStatementMissingSomeCases switch (standardAlgebraicNoCheck) { case "O-O-O": case "0-0-0": // Castle Queenside SetFrom(ref move, Board.CastleFromSquares[(int)board.CurrentPosition.ColorToMove]); SetTo(ref move, Board.CastleToSquares[(int)board.CurrentPosition.ColorToMove][(int)BoardSide.Queen]); if (!board.ValidateMove(ref move)) { throw new Exception($"Move {standardAlgebraic} is illegal in position {board.CurrentPosition.ToFen()}."); } return(move); case "O-O": case "0-0": // Castle Kingside SetFrom(ref move, Board.CastleFromSquares[(int)board.CurrentPosition.ColorToMove]); SetTo(ref move, Board.CastleToSquares[(int)board.CurrentPosition.ColorToMove][(int)BoardSide.King]); if (!board.ValidateMove(ref move)) { throw new Exception($"Move {standardAlgebraic} is illegal in position {board.CurrentPosition.ToFen()}."); } return(move); } var length = standardAlgebraicNoCheck.Length; var fromFile = -1; var fromRank = -1; var promotedPiece = Game.Piece.None; Piece piece; Square toSquare; if (char.IsLower(standardAlgebraicNoCheck, 0)) { // Pawn Move piece = PieceHelper.GetPieceOfColor(ColorlessPiece.Pawn, board.CurrentPosition.ColorToMove); fromFile = Board.Files[(int)Board.GetSquare($"{standardAlgebraicNoCheck[0]}1")]; switch (length) { case 2: // Pawn Move toSquare = Board.GetSquare(standardAlgebraicNoCheck); break; case 4 when standardAlgebraicNoCheck[1] == 'x':
public override string ToString() { // Iterate over the piece array to construct an 8 x 8 text display of the chessboard. var stringBuilder = new StringBuilder(); stringBuilder.AppendLine(" +---+---+---+---+---+---+---+---+"); var square = Square.A8; for (var y = 8; y > 0; y--) { // Add rank. stringBuilder.Append(y); stringBuilder.Append(" "); for (var x = 1; x <= 8; x++) { stringBuilder.Append("| "); var piece = GetPiece(square); if (piece == Piece.None) { stringBuilder.Append(" "); } else { stringBuilder.Append(PieceHelper.GetChar(piece)); } stringBuilder.Append(" "); square++; } stringBuilder.AppendLine("|"); stringBuilder.AppendLine(" +---+---+---+---+---+---+---+---+"); } stringBuilder.Append(" "); for (var x = 1; x <= 8; x++) { // Add file. var chrFile = (char)(x + 96); stringBuilder.Append($" {chrFile} "); } stringBuilder.AppendLine(); stringBuilder.AppendLine(); // Display FEN, key, position count, and check. stringBuilder.AppendLine($"FEN: {ToFen()}"); stringBuilder.AppendLine($"Key: {Key:X16}"); stringBuilder.AppendLine($"Position Count: {_board.GetPositionCount()}"); stringBuilder.AppendLine($"King in Check: {(KingInCheck ? "Yes" : "No")}"); stringBuilder.AppendLine($"Static Score: {StaticScore}"); stringBuilder.AppendLine($"Played Move: {Move.ToString(PlayedMove)}"); return(stringBuilder.ToString()); }
public static ulong ParseLongAlgebraic(string longAlgebraic, Color colorToMove) { var fromSquare = Board.GetSquare(longAlgebraic.Substring(0, 2)); var toSquare = Board.GetSquare(longAlgebraic.Substring(2, 2)); // Set case of promoted piece character based on side to move. var promotedPiece = longAlgebraic.Length == 5 ? PieceHelper.ParseChar(colorToMove == Color.White ? char.ToUpper(longAlgebraic[4]) : char.ToLower(longAlgebraic[4])) : Game.Piece.None; var move = Null; SetFrom(ref move, fromSquare); SetTo(ref move, toSquare); SetPromotedPiece(ref move, promotedPiece); return(move); }
private void GeneratePieceMoves(MoveGeneration moveGeneration, ulong fromSquareMask, ulong toSquareMask) { for (var colorlessPiece = ColorlessPiece.Knight; colorlessPiece <= ColorlessPiece.Queen; colorlessPiece++) { var attacker = PieceHelper.GetPieceOfColor(colorlessPiece, ColorToMove); var pieces = PieceBitboards[(int)attacker] & fromSquareMask; if (pieces == 0) { continue; } var getPieceMovesMask = Board.PieceMoveMaskDelegates[(int)colorlessPiece]; var unOrEnemyOccupiedSquares = ~ColorOccupancy[(int)ColorToMove]; var enemyOccupiedSquares = ColorOccupancy[(int)ColorLastMoved]; Square fromSquare; while ((fromSquare = Bitwise.PopFirstSetSquare(ref pieces)) != Square.Illegal) { var pieceDestinations = moveGeneration switch { MoveGeneration.AllMoves => getPieceMovesMask(fromSquare, Occupancy) & unOrEnemyOccupiedSquares & toSquareMask, MoveGeneration.OnlyCaptures => getPieceMovesMask(fromSquare, Occupancy) & enemyOccupiedSquares & toSquareMask, MoveGeneration.OnlyNonCaptures => getPieceMovesMask(fromSquare, Occupancy) & ~Occupancy & toSquareMask, _ => throw new Exception($"{moveGeneration} move generation not supported.") }; Square toSquare; while ((toSquare = Bitwise.PopFirstSetSquare(ref pieceDestinations)) != Square.Illegal) { var victim = GetPiece(toSquare); var move = Move.Null; Move.SetPiece(ref move, attacker); Move.SetFrom(ref move, fromSquare); Move.SetTo(ref move, toSquare); if (victim != Piece.None) { Move.SetCaptureAttacker(ref move, attacker); } Move.SetCaptureVictim(ref move, victim); Moves[MoveIndex] = move; MoveIndex++; } } } }
public void FindMagicMultipliers(ColorlessPiece colorlessPiece, Delegates.WriteMessageLine writeMessageLine = null) { Direction[] directions; ulong[] unoccupiedMoveMasks; ulong[] relevantOccupancyMasks; ulong[] magicMultipliers; int[] shifts; ulong[][] moveMasks; // ReSharper disable SwitchStatementHandlesSomeKnownEnumValuesWithDefault switch (colorlessPiece) { case ColorlessPiece.Bishop: directions = new[] { Direction.NorthEast, Direction.SouthEast, Direction.SouthWest, Direction.NorthWest }; unoccupiedMoveMasks = Board.BishopMoveMasks; relevantOccupancyMasks = _bishopRelevantOccupancyMasks; magicMultipliers = _bishopMagicMultipliers; shifts = _bishopShifts; moveMasks = _bishopMoveMasks; break; case ColorlessPiece.Rook: directions = new[] { Direction.North, Direction.East, Direction.South, Direction.West }; unoccupiedMoveMasks = Board.RookMoveMasks; relevantOccupancyMasks = _rookRelevantOccupancyMasks; magicMultipliers = _rookMagicMultipliers; shifts = _rookShifts; moveMasks = _rookMoveMasks; break; default: throw new ArgumentException($"{colorlessPiece} piece not supported."); } // ReSharper restore SwitchStatementHandlesSomeKnownEnumValuesWithDefault // Generate moves mask on each square. var occupancyToMovesMask = new Dictionary <ulong, ulong>(); var uniqueMovesMasks = new HashSet <ulong>(); for (var square = Square.A8; square < Square.Illegal; square++) { occupancyToMovesMask.Clear(); uniqueMovesMasks.Clear(); var moveDestinations = unoccupiedMoveMasks[(int)square]; var relevantMoveDestinations = moveDestinations & relevantOccupancyMasks[(int)square]; var uniqueOccupancies = (int)Math.Pow(2, Bitwise.CountSetBits(relevantMoveDestinations)); occupancyToMovesMask.EnsureCapacity(uniqueOccupancies); // Generate moves mask for every permutation of occupancy of the relevant destination squares. var occupancyPermutations = Bitwise.GetAllPermutations(relevantMoveDestinations); for (var occupancyIndex = 0; occupancyIndex < occupancyPermutations.Count; occupancyIndex++) { var occupancy = occupancyPermutations[occupancyIndex]; var movesMask = Board.CreateMoveDestinationsMask(square, occupancy, directions); occupancyToMovesMask.Add(occupancy, movesMask); if (!uniqueMovesMasks.Contains(movesMask)) { uniqueMovesMasks.Add(movesMask); } } Debug.Assert(occupancyToMovesMask.Count == uniqueOccupancies); // Determine bit shift that produces number >= unique occupancies. // A stricter condition is number >= unique moves but this requires more computing time to find magic multipliers. var shift = 64 - (int)Math.Ceiling(Math.Log(uniqueOccupancies, 2d)); shifts[(int)square] = shift; var magicMultiplier = magicMultipliers[(int)square]; var knownMagicMultiplier = magicMultiplier == 0 ? null : (ulong?)magicMultiplier; (magicMultipliers[(int)square], moveMasks[(int)square]) = FindMagicMultiplier(occupancyToMovesMask, shift, knownMagicMultiplier); writeMessageLine?.Invoke($"{Board.SquareLocations[(int)square],6} {PieceHelper.GetName(colorlessPiece),6} {shift,5} {occupancyToMovesMask.Count,18} {uniqueMovesMasks.Count,12} {magicMultipliers[(int)square],16:X16}"); } }
public void HandlePieceDeselection(GameObject pieceGameObject) { IsSelected = false; PieceHelper.SetDefaultPieceMaterial(pieceGameObject); _onBoardMovementLogic.RemoveBacklightFromSquares(); }
private void GenerateKingMoves(MoveGeneration moveGeneration, ulong fromSquareMask, ulong toSquareMask) { var king = GetKing(ColorToMove) & fromSquareMask; if (king == 0) { return; } var unOrEnemyOccupiedSquares = ~ColorOccupancy[(int)ColorToMove]; var enemyOccupiedSquares = ColorOccupancy[(int)ColorLastMoved]; var attacker = PieceHelper.GetPieceOfColor(ColorlessPiece.King, ColorToMove); ulong move; var fromSquare = Bitwise.FirstSetSquare(king); if (fromSquare == Square.Illegal) { return; } var kingDestinations = moveGeneration switch { MoveGeneration.AllMoves => Board.KingMoveMasks[(int)fromSquare] & unOrEnemyOccupiedSquares & toSquareMask, MoveGeneration.OnlyCaptures => Board.KingMoveMasks[(int)fromSquare] & enemyOccupiedSquares & toSquareMask, MoveGeneration.OnlyNonCaptures => Board.KingMoveMasks[(int)fromSquare] & ~Occupancy & toSquareMask, _ => throw new Exception($"{moveGeneration} move generation not supported.") }; Square toSquare; while ((toSquare = Bitwise.PopFirstSetSquare(ref kingDestinations)) != Square.Illegal) { var victim = GetPiece(toSquare); move = Move.Null; Move.SetPiece(ref move, attacker); Move.SetFrom(ref move, fromSquare); Move.SetTo(ref move, toSquare); if (victim != Piece.None) { Move.SetCaptureAttacker(ref move, attacker); } Move.SetCaptureVictim(ref move, victim); Move.SetIsKingMove(ref move, true); Moves[MoveIndex] = move; MoveIndex++; } if (moveGeneration != MoveGeneration.OnlyCaptures) { for (var boardSide = BoardSide.Queen; boardSide <= BoardSide.King; boardSide++) { var castleEmptySquaresMask = Board.CastleEmptySquaresMask[(int)ColorToMove][(int)boardSide]; if (!KingInCheck && Game.Castling.Permitted(Castling, ColorToMove, boardSide) && ((Occupancy & castleEmptySquaresMask) == 0)) { // Castle toSquare = Board.CastleToSquares[(int)ColorToMove][(int)boardSide]; if ((Board.SquareMasks[(int)toSquare] & toSquareMask) > 0) { move = Move.Null; Move.SetPiece(ref move, attacker); Move.SetFrom(ref move, fromSquare); Move.SetTo(ref move, toSquare); Move.SetIsCastling(ref move, true); Move.SetIsKingMove(ref move, true); Moves[MoveIndex] = move; MoveIndex++; } } } } }
private void GeneratePawnMoves(MoveGeneration moveGeneration, ulong fromSquareMask, ulong toSquareMask) { var pawns = GetPawns(ColorToMove) & fromSquareMask; if (pawns == 0) { return; } var pawnMoveMasks = Board.PawnMoveMasks[(int)ColorToMove]; var pawnDoubleMoveMasks = Board.PawnDoubleMoveMasks[(int)ColorToMove]; var pawnAttackMasks = Board.PawnAttackMasks[(int)ColorToMove]; var enemyOccupiedSquares = ColorOccupancy[(int)ColorLastMoved]; var unoccupiedSquares = ~Occupancy; var ranks = Board.Ranks[(int)ColorToMove]; var attacker = PieceHelper.GetPieceOfColor(ColorlessPiece.Pawn, ColorToMove); var queen = PieceHelper.GetPieceOfColor(ColorlessPiece.Queen, ColorToMove); var knight = PieceHelper.GetPieceOfColor(ColorlessPiece.Knight, ColorToMove); var enPassantVictim = PieceHelper.GetPieceOfColor(ColorlessPiece.Pawn, ColorLastMoved); Square fromSquare; ulong move; if ((EnPassantSquare != Square.Illegal) && ((Board.SquareMasks[(int)EnPassantSquare] & toSquareMask) > 0) && (moveGeneration != MoveGeneration.OnlyNonCaptures)) { var enPassantAttackers = Board.EnPassantAttackerMasks[(int)EnPassantSquare] & pawns; while ((fromSquare = Bitwise.PopFirstSetSquare(ref enPassantAttackers)) != Square.Illegal) { // Capture pawn en passant. move = Move.Null; Move.SetPiece(ref move, attacker); Move.SetFrom(ref move, fromSquare); Move.SetTo(ref move, EnPassantSquare); Move.SetCaptureAttacker(ref move, attacker); Move.SetCaptureVictim(ref move, enPassantVictim); Move.SetIsEnPassantCapture(ref move, true); Move.SetIsPawnMove(ref move, true); Moves[MoveIndex] = move; MoveIndex++; } } while ((fromSquare = Bitwise.PopFirstSetSquare(ref pawns)) != Square.Illegal) { ulong pawnDestinations; Square toSquare; int toSquareRank; if (moveGeneration != MoveGeneration.OnlyCaptures) { // Pawns may move forward one square (or two if on initial square) if forward squares are unoccupied. pawnDestinations = pawnMoveMasks[(int)fromSquare] & unoccupiedSquares & toSquareMask; while ((toSquare = Bitwise.PopFirstSetSquare(ref pawnDestinations)) != Square.Illegal) { var doubleMove = Board.SquareDistances[(int)fromSquare][(int)toSquare] == 2; if (doubleMove && ((Occupancy & pawnDoubleMoveMasks[(int)fromSquare]) > 0)) { continue; // Double move is blocked. } toSquareRank = ranks[(int)toSquare]; if (toSquareRank < 7) { move = Move.Null; Move.SetPiece(ref move, attacker); Move.SetFrom(ref move, fromSquare); Move.SetTo(ref move, toSquare); Move.SetIsDoublePawnMove(ref move, doubleMove); Move.SetIsPawnMove(ref move, true); Moves[MoveIndex] = move; MoveIndex++; } else { for (var promotedPiece = queen; promotedPiece >= knight; promotedPiece--) { // Promote pawn. move = Move.Null; Move.SetPiece(ref move, attacker); Move.SetFrom(ref move, fromSquare); Move.SetTo(ref move, toSquare); Move.SetPromotedPiece(ref move, promotedPiece); Move.SetIsPawnMove(ref move, true); Moves[MoveIndex] = move; MoveIndex++; } } } } if (moveGeneration != MoveGeneration.OnlyNonCaptures) { // Pawns may attack diagonally forward one square if occupied by enemy. pawnDestinations = pawnAttackMasks[(int)fromSquare] & enemyOccupiedSquares & toSquareMask; while ((toSquare = Bitwise.PopFirstSetSquare(ref pawnDestinations)) != Square.Illegal) { toSquareRank = ranks[(int)toSquare]; var victim = GetPiece(toSquare); if (toSquareRank < 7) { move = Move.Null; Move.SetPiece(ref move, attacker); Move.SetFrom(ref move, fromSquare); Move.SetTo(ref move, toSquare); Move.SetCaptureAttacker(ref move, attacker); Move.SetCaptureVictim(ref move, victim); Move.SetIsPawnMove(ref move, true); Moves[MoveIndex] = move; MoveIndex++; } else { for (var promotedPiece = queen; promotedPiece >= knight; promotedPiece--) { // Promote pawn. move = Move.Null; Move.SetPiece(ref move, attacker); Move.SetFrom(ref move, fromSquare); Move.SetTo(ref move, toSquare); Move.SetCaptureAttacker(ref move, attacker); Move.SetPromotedPiece(ref move, promotedPiece); Move.SetCaptureVictim(ref move, victim); Move.SetIsPawnMove(ref move, true); Moves[MoveIndex] = move; MoveIndex++; } } } } } }