public PositionDistance(Position position1, Position position2) { if (position1 == null) throw new ArgumentNullException("position1"); if (position2 == null) throw new ArgumentNullException("position2"); _distanceX = Math.Abs((int)position1.File - (int)position2.File); _distanceY = Math.Abs((int)position1.Rank - (int)position2.Rank); }
public async Task <ChessMatchStatus> Move(Stream stream, ulong channel, IUser player, string rawMove) { var moveInput = rawMove.Replace(" ", "").ToUpper(); if (!Regex.IsMatch(moveInput, "[A-H][1-8][A-H][1-8]")) { throw new ChessException("Error parsing move. Example move: a2a4"); } var match = _chessMatches.SingleOrDefault(x => x.Channel == channel && x.Players.Contains(player)); if (match == null) { throw new ChessException("You are not currently in a game"); } var whoseTurn = match.Game.WhoseTurn; var otherPlayer = whoseTurn == Player.White ? Player.Black : Player.White; if ((whoseTurn == Player.White && player != match.Challenger) || (whoseTurn == Player.Black && player != match.Challenged)) { throw new ChessException("It's not your turn."); } var sourceX = moveInput[0].ToString(); var sourceY = moveInput[1].ToString(); var destX = moveInput[2].ToString(); var destY = moveInput[3].ToString(); var positionEnumValues = (IEnumerable <ChessDotNet.File>)Enum.GetValues(typeof(ChessDotNet.File)); var sourcePositionX = positionEnumValues.Single(x => x.ToString("g") == sourceX); var destPositionX = positionEnumValues.Single(x => x.ToString("g") == destX); var originalPosition = new ChessDotNet.Position(sourcePositionX, int.Parse(sourceY)); var destPosition = new ChessDotNet.Position(destPositionX, int.Parse(destY)); var move = new Move(originalPosition, destPosition, whoseTurn); if (!match.Game.IsValidMove(move)) { throw new ChessException("Invalid move."); } var chessMove = new ChessMove { Move = move, MoveDate = DateTime.Now }; var board = match.Game.GetBoard(); for (var column = 0; column < board.Length; column++) { for (var row = 0; row < board[column].Length; row++) { chessMove.PreviousBoardState[column][row] = board[column][row]; } } match.Game.ApplyMove(move, true); match.History.Add(chessMove); match.UndoRequest = null; var checkMated = match.Game.IsCheckmated(otherPlayer); var isOver = checkMated || match.Game.IsStalemated(otherPlayer); var status = new ChessMatchStatus { IsOver = isOver, Winner = isOver && checkMated ? player : null, IsCheck = match.Game.IsInCheck(otherPlayer) }; await WriteBoard(channel, player, stream); if (isOver) { _chessMatches.Remove(match); } return(await Task.FromResult(status)); }
protected virtual bool IsInCheck(Player player) { List<Position> piecePositions = new List<Position>(); Position kingPos = new Position(File.None, Rank.None); for (int i = 0; i < Board.Length; i++) { for (int j = 0; j < Board[i].Length; j++) { ChessPiece curr = Board[i][j]; if (curr.Piece != Piece.None && curr.Player == Utilities.GetOpponentOf(player)) { piecePositions.Add(new Position((File)j, (Rank)i)); } else if (curr.Piece == Piece.King && curr.Player == player) { kingPos = new Position((File)j, (Rank)i); } } } if (kingPos.File == File.None) return false; ChessGame copy = new ChessGame(Board, Utilities.GetOpponentOf(player), false); for (int i = 0; i < piecePositions.Count; i++) { if (copy.IsValidMove(new Move(piecePositions[i], kingPos, Utilities.GetOpponentOf(player)), false)) { return true; } } return false; }
public virtual bool HasAnyValidMoves(Position from) { Utilities.ThrowIfNull(from, "from"); ReadOnlyCollection<Move> validMoves = GetValidMoves(from, true); return validMoves.Count > 0; }
protected virtual ReadOnlyCollection<Move> GetValidMoves(Position from, bool returnIfAny) { Utilities.ThrowIfNull(from, "from"); ChessPiece cp = GetPieceAt(from); if (cp.Player != WhoseTurn) return new ReadOnlyCollection<Move>(new List<Move>()); Piece piece = cp.Piece; switch (piece) { case Piece.King: return GetValidMovesKing(from, returnIfAny); case Piece.Pawn: return GetValidMovesPawn(from, returnIfAny); case Piece.Knight: return GetValidMovesKnight(from, returnIfAny); case Piece.Rook: return GetValidMovesRook(from, returnIfAny); case Piece.Bishop: return GetValidMovesBishop(from, returnIfAny); case Piece.Queen: return GetValidMovesQueen(from, returnIfAny); default: return new ReadOnlyCollection<Move>(new List<Move>()); } }
protected virtual ReadOnlyCollection<Move> GetValidMovesQueen(Position from, bool returnIfAny) { Utilities.ThrowIfNull(from, "from"); ReadOnlyCollection<Move> horizontalVerticalMoves = GetValidMovesRook(from, returnIfAny); if (returnIfAny && horizontalVerticalMoves.Count > 0) return horizontalVerticalMoves; ReadOnlyCollection<Move> diagonalMoves = GetValidMovesBishop(from, returnIfAny); return new ReadOnlyCollection<Move>(horizontalVerticalMoves.Concat(diagonalMoves).ToList()); }
protected virtual ReadOnlyCollection<Move> GetValidMovesBishop(Position from, bool returnIfAny) { Utilities.ThrowIfNull(from, "from"); List<Move> validMoves = new List<Move>(); ChessPiece cp = GetPieceAt(from); int l0 = Board.Length; int l1 = Board[0].Length; for (int i = -7; i < 8; i++) { if (i == 0) continue; if ((int)from.Rank + i > -1 && (int)from.Rank + i < l0 && (int)from.File + i > -1 && (int)from.File + i < l1) { Move move = new Move(from, new Position(from.File + i, from.Rank + i), cp.Player); if (IsValidMove(move)) { validMoves.Add(move); if (returnIfAny) return new ReadOnlyCollection<Move>(validMoves); } } if ((int)from.Rank - i > -1 && (int)from.Rank - i < l0 && (int)from.File + i > -1 && (int)from.File + i < l1) { Move move = new Move(from, new Position(from.File + i, from.Rank - i), cp.Player); if (IsValidMove(move)) { validMoves.Add(move); if (returnIfAny) return new ReadOnlyCollection<Move>(validMoves); } } } return new ReadOnlyCollection<Move>(validMoves); }
protected virtual ReadOnlyCollection<Move> GetValidMovesKnight(Position from, bool returnIfAny) { Utilities.ThrowIfNull(from, "from"); List<Move> validMoves = new List<Move>(); ChessPiece cp = GetPieceAt(from); int l0 = Board.Length; int l1 = Board[0].Length; int[][] directions = new int[][] { new int[] { 2, 1 }, new int[] { -2, -1 }, new int[] { 1, 2 }, new int[] { -1, -2 }, new int[] { 1, -2 }, new int[] { -1, 2 }, new int[] { 2, -1 }, new int[] { -2, 1 } }; foreach (int[] dir in directions) { if ((int)from.File + dir[0] < 0 || (int)from.File + dir[0] >= l1 || (int)from.Rank + dir[1] < 0 || (int)from.Rank + dir[1] >= l0) continue; Move move = new Move(from, new Position(from.File + dir[0], from.Rank + dir[1]), cp.Player); if (IsValidMove(move)) { validMoves.Add(move); if (returnIfAny) return new ReadOnlyCollection<Move>(validMoves); } } return new ReadOnlyCollection<Move>(validMoves); }
public ReadOnlyCollection<Move> GetValidMoves(Position from) { Utilities.ThrowIfNull(from, "from"); return GetValidMoves(from, false); }
public ChessPiece GetPieceAt(Position position) { Utilities.ThrowIfNull(position, "position"); return GetPieceAt(position.File, position.Rank); }
public abstract ReadOnlyCollection<Move> GetValidMoves(Position from, bool returnIfAny, ChessGame game, Func<Move, bool> gameMoveValidator);
public async Task <ChessMatchStatus> Move(Stream stream, ulong channel, ulong player, string rawMove) { return(await Task.Run(async() => { var moveInput = rawMove.Replace(" ", "").ToUpper(); if (!Regex.IsMatch(moveInput, "^[A-H][1-8][A-H][1-8][Q|N|B|R]?$")) { throw new ChessException("Error parsing move. Example move: a2a4"); } var match = _chessMatches.SingleOrDefault(x => x.Channel == channel && x.Players.Contains(player)); if (match == null) { throw new ChessException("You are not currently in a game"); } var whoseTurn = match.Game.WhoseTurn; var otherPlayer = whoseTurn == Player.White ? Player.Black : Player.White; if ((whoseTurn == Player.White && player != match.Challenger) || (whoseTurn == Player.Black && player != match.Challenged)) { throw new ChessException("It's not your turn."); } var sourceX = moveInput[0].ToString(); var sourceY = moveInput[1].ToString(); var destX = moveInput[2].ToString(); var destY = moveInput[3].ToString(); char?promotionChar = moveInput.Length > 4 ? moveInput[4].ToString().ToLower()[0] : 'q'; var positionEnumValues = (IEnumerable <ChessDotNet.File>)Enum.GetValues(typeof(ChessDotNet.File)); var sourcePositionX = positionEnumValues.Single(x => x.ToString("g") == sourceX); var destPositionX = positionEnumValues.Single(x => x.ToString("g") == destX); var originalPosition = new ChessDotNet.Position(sourcePositionX, int.Parse(sourceY)); var destPosition = new ChessDotNet.Position(destPositionX, int.Parse(destY)); var piece = match.Game.GetPieceAt(originalPosition); if (piece != null && !(piece is Pawn)) { promotionChar = null; } var move = new Move(originalPosition, destPosition, whoseTurn, promotionChar); if (!match.Game.IsValidMove(move)) { throw new ChessException("Invalid move."); } var chessMove = new ChessMove { NewRank = move.NewPosition.Rank, NewFile = move.NewPosition.File, OriginalFile = move.OriginalPosition.File, OriginalRank = move.OriginalPosition.Rank, MoveDate = DateTime.Now, PreviousWhoseTurn = match.Game.WhoseTurn }; var board = match.Game.GetBoard(); for (var column = 0; column < board.Length; column++) { for (var row = 0; row < board[column].Length; row++) { chessMove.PreviousBoardState[column][row] = board[column][row]; } } match.Game.ApplyMove(move, true); match.History.Add(chessMove); match.UndoRequest = null; var checkMated = match.Game.IsCheckmated(otherPlayer); var isOver = checkMated || match.Game.IsStalemated(otherPlayer); var winner = isOver && checkMated ? player : (ulong?)null; var status = new ChessMatchStatus { IsOver = isOver, Winner = winner, IsCheck = match.Game.IsInCheck(otherPlayer) }; await WriteBoard(channel, player, stream); if (isOver) { #pragma warning disable 4014 Task.Run(async() => { using (Db db = _services.GetService <Db>()) { await db.EndMatch(match, (long?)winner); db.SaveChanges(); } }); #pragma warning restore 4014 _chessMatches.Remove(match); } else { #pragma warning disable 4014 Task.Run(async() => { using (Db db = _services.GetService <Db>()) { await db.SaveOrUpdateMatch(match); db.SaveChanges(); } }); #pragma warning restore 4014 } return await Task.FromResult(status); })); }
public virtual bool IsInCheck(Player player) { if (player == Player.None) { throw new ArgumentException("IsInCheck: Player.None is an invalid argument."); } Cache<bool> cache = player == Player.White ? inCheckCacheWhite : inCheckCacheBlack; if (cache.CachedAt == Moves.Count) { return cache.Value; } Position kingPos = new Position(File.None, -1); for (int r = 1; r <= Board.Length; r++) { for (int f = 0; f < Board[8 - r].Length; f++) { Piece curr = GetPieceAt((File)f, r); if (curr is King && curr.Owner == player) { kingPos = new Position((File)f, r); break; } } if (kingPos != new Position(File.None, -1)) { break; } } if (kingPos.File == File.None) return cache.UpdateCache(false, Moves.Count); for (int r = 1; r <= Board.Length; r++) { for (int f = 0; f < Board[8 - r].Length; f++) { Piece curr = GetPieceAt((File)f, r); if (curr == null) continue; Player p = curr.Owner; Move move = new Move(new Position((File)f, r), kingPos, p); List<Move> moves = new List<Move>(); if (curr is Pawn && ((move.NewPosition.Rank == 8 && move.Player == Player.White) || (move.NewPosition.Rank == 1 && move.Player == Player.Black))) { moves.Add(new Move(move.OriginalPosition, move.NewPosition, move.Player, 'Q')); moves.Add(new Move(move.OriginalPosition, move.NewPosition, move.Player, 'R')); moves.Add(new Move(move.OriginalPosition, move.NewPosition, move.Player, 'B')); moves.Add(new Move(move.OriginalPosition, move.NewPosition, move.Player, 'N')); moves.Add(new Move(move.OriginalPosition, move.NewPosition, move.Player, 'K')); } else { moves.Add(move); } foreach (Move m in moves) { if (IsValidMove(m, false, false)) { return cache.UpdateCache(true, Moves.Count); } } } } return cache.UpdateCache(false, Moves.Count); }
protected virtual ReadOnlyCollection<Move> GetValidMoves(Position from, bool returnIfAny) { ChessUtilities.ThrowIfNull(from, "from"); Piece piece = GetPieceAt(from); if (piece == null || piece.Owner != WhoseTurn) return new ReadOnlyCollection<Move>(new List<Move>()); return piece.GetValidMoves(from, returnIfAny, this, IsValidMove); }
protected virtual GameCreationData FenStringToGameCreationData(string fen) { Dictionary<char, Piece> fenMappings = FenMappings; string[] parts = fen.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); if (!AllowedFenPartsLength.Contains(parts.Length)) throw new ArgumentException("The FEN string has too much, or too few, parts."); Piece[][] board = new Piece[8][]; string[] rows = parts[0].Split('/'); if (rows.Length != 8) throw new ArgumentException("The board in the FEN string does not have 8 rows."); GameCreationData data = new GameCreationData(); for (int i = 0; i < 8; i++) { string row = rows[i]; Piece[] currentRow = new Piece[8] { null, null, null, null, null, null, null, null }; int j = 0; foreach (char c in row) { if (char.IsDigit(c)) { j += (int)char.GetNumericValue(c); continue; } if (!fenMappings.ContainsKey(c)) throw new ArgumentException("The FEN string contains an unknown piece."); currentRow[j] = fenMappings[c]; j++; } if (j != 8) { throw new ArgumentException("Not enough pieces provided for a row in the FEN string."); } board[i] = currentRow; } data.Board = board; if (parts[1] == "w") { data.WhoseTurn = Player.White; } else if (parts[1] == "b") { data.WhoseTurn = Player.Black; } else { throw new ArgumentException("Expected `w` or `b` for the active player in the FEN string."); } if (parts[2].Contains('K')) data.CanWhiteCastleKingSide = true; else data.CanWhiteCastleKingSide = false; if (parts[2].Contains('Q')) data.CanWhiteCastleQueenSide = true; else data.CanWhiteCastleQueenSide = false; if (parts[2].Contains('k')) data.CanBlackCastleKingSide = true; else data.CanBlackCastleKingSide = false; if (parts[2].Contains('q')) data.CanBlackCastleQueenSide = true; else data.CanBlackCastleQueenSide = false; if (parts[3] == "-") data.EnPassant = null; else { Position ep = new Position(parts[3]); if ((data.WhoseTurn == Player.White && (ep.Rank != 6 || !(data.Board[3][(int)ep.File] is Pawn))) || (data.WhoseTurn == Player.Black && (ep.Rank != 3 || !(data.Board[4][(int)ep.File] is Pawn)))) { throw new ArgumentException("Invalid en passant field in FEN."); } data.EnPassant = ep; } int halfmoveClock; if (int.TryParse(parts[4], out halfmoveClock)) { data.HalfMoveClock = halfmoveClock; } else { throw new ArgumentException("Halfmove clock in FEN is invalid."); } int fullMoveNumber; if (int.TryParse(parts[5], out fullMoveNumber)) { data.FullMoveNumber = fullMoveNumber; } else { throw new ArgumentException("Fullmove number in FEN is invalid."); } return data; }