private ValidateMoveResult CanMove(ChessBoard board, string source, string dest, PlayerTypeEnum player, string lastMoveNotation, bool playerInCheck) { var result = new ValidateMoveResult(); var sourceTuple = NotationHelper.Translate(source); if (!BoardLogic.IsOnBoard(sourceTuple)) return result.SetMessage(source + " is not on board"); var destTuple = NotationHelper.Translate(dest); if (!BoardLogic.IsOnBoard(destTuple)) return result.SetMessage(dest + " is not on board"); if (source.Equals(dest, StringComparison.OrdinalIgnoreCase)) return result.SetMessage("source and dest are the same"); var sourcePiece = board.Get(sourceTuple); if (sourcePiece == null) return result.SetMessage("source piece is null"); if (sourcePiece.Player != player) return result.SetMessage("piece does not belong to player."); var destPiece = board.Get(destTuple); if (destPiece != null && sourcePiece.Player == destPiece.Player) return result.SetMessage("cannot move on top of own piece"); var relativeMoves = m_relativeMoveProviderFactory .GetProviderByPieceType(sourcePiece.Type) .GetRelativePaths(sourcePiece); var absoluteMoves = BoardLogic.ConvertToAbsolutePaths(sourceTuple, relativeMoves); bool destInPath = BoardLogic.IsInAtleastOnePath(destTuple, absoluteMoves); if (!destInPath) return result.SetMessage("destination not in available path"); string takenPiece = null; string additionalMove = null; switch (sourcePiece.Type) { case PieceTypeEnum.Pawn: { bool isRankOnlyMove = BoardLogic.IsRankOnlyMove(sourceTuple, destTuple); if (isRankOnlyMove) { var pathContaingDestination = BoardLogic.GetPathContaining(destTuple, absoluteMoves); if (!BoardLogic.IsPathClear(board, pathContaingDestination)) return result.SetMessage("path blocked"); var absDelta = BoardLogic.GetAbsoluteDelta(sourceTuple, destTuple); if (absDelta.Item1 == 2 && sourcePiece.HasMoved) return result.SetMessage("pawn has already moved, cannot jump 2 squares"); } else { // en-passant // > is piece in source *rank, destination *file an enemy pawn? // > if yes, was its last move two squares?e var pieceBehindTuple = new Tuple<int, int>( sourceTuple.Item1, destTuple.Item2); var pieceBehind = board.Get(pieceBehindTuple); bool targetWasLastMove = false; bool twoSquareLastMove = false; Tuple<string, string> lastMove = null; if (lastMoveNotation != null) { lastMove = NotationHelper.Breakdown(lastMoveNotation); var lastMoveSourceTuple = NotationHelper.Translate(lastMove.Item1); var lastMoveDestTuple = NotationHelper.Translate(lastMove.Item2); var lastMoveAbsDelta = BoardLogic.GetAbsoluteDelta(lastMoveSourceTuple, lastMoveDestTuple); twoSquareLastMove = lastMoveAbsDelta.Item1 == 2; targetWasLastMove = pieceBehindTuple.Item1 == lastMoveDestTuple.Item1 && pieceBehindTuple.Item2 == lastMoveDestTuple.Item2; } bool enPassant = pieceBehind != null && pieceBehind.Player != player && pieceBehind.Type == PieceTypeEnum.Pawn && twoSquareLastMove && targetWasLastMove ; if (enPassant) { takenPiece = lastMove.Item2; break; } if (destPiece == null) return result.SetMessage("enemy piece not present at destination"); } break; } case PieceTypeEnum.King: { // castle if (BoardLogic.IsFileOnlyMove(sourceTuple, destTuple) && BoardLogic.GetAbsoluteDelta(sourceTuple, destTuple).Item2 == 2) { if (sourcePiece.HasMoved) return result.SetMessage("king has already moved."); ChessPiece rook = null; Tuple<int, int> rookTuple = null; Tuple<int, int> toRookDelta = null; var rookNotations = board.GetPiecesOfType(player, PieceTypeEnum.Rook); if (rookNotations != null && rookNotations.Count > 0) { var moveDelta = BoardLogic.GetDelta(sourceTuple, destTuple); foreach (var rn in rookNotations) { rookTuple = NotationHelper.Translate(rn); toRookDelta = BoardLogic.GetDelta(sourceTuple, rookTuple); if (BoardLogic.IsFileOnlyMove(sourceTuple, rookTuple) && BoardLogic.AreDeltasInSameDirection(moveDelta.Item2, toRookDelta.Item2)) { rook = board.Get(rn); break; } } } if (rook == null) return result.SetMessage("no rook present on rank " + sourceTuple.Item1); if (rook.HasMoved) return result.SetMessage("rook has moved."); Tuple<int, int>[] kingToRookPath = BoardLogic.GetRankPath(sourceTuple, rookTuple); Tuple<int, int>[] trimmedPath = BoardLogic.TrimToBeforeDestination(rookTuple, kingToRookPath); bool isPathClear = BoardLogic.IsPathClear(board, trimmedPath); if (!isPathClear) return result.SetMessage("path blocked"); if (playerInCheck) return result.SetMessage("cannot castle out of check"); bool moveToLeft = toRookDelta.Item2 < 0; additionalMove = NotationHelper.Translate(rookTuple) + NotationHelper.Translate(new Tuple<int, int>(rookTuple.Item1, destTuple.Item2 + (moveToLeft ? 1 : -1))); } break; } case PieceTypeEnum.Knight: { break; } case PieceTypeEnum.Bishop: case PieceTypeEnum.Rook: case PieceTypeEnum.Queen: { // cases handled in general var pathContaingDestination = BoardLogic.GetPathContaining(destTuple, absoluteMoves); var trimmedPath = BoardLogic.TrimToBeforeDestination(destTuple, pathContaingDestination); if (!BoardLogic.IsPathClear(board, trimmedPath)) return result.SetMessage("path blocked"); break; } } if (destPiece != null) takenPiece = dest; result.Success = true; result.TakenPiece = takenPiece; result.Moves = new[] { source + dest, additionalMove }.Where(x => x != null).ToArray(); return result; }