2
\$\begingroup\$

The next post in my Checker series: The C# wrapper for my F# library to make it cleaner for these methods to be called from C# code.

GeneralTypes.cs

public enum Player
{
    White, Black
}

public enum PieceType
{
    Checker,
    King
}

public static class Extensions
{
    public static Player Convert(this Checkers.Types.Player value) =>
        Equals(value, Checkers.Types.Player.Black) ? Player.Black : Player.White;

    public static Checkers.Types.Player ConvertBack(this Player value) =>
        value == Player.Black ? Checkers.Types.Player.Black : Checkers.Types.Player.White;

    public static PieceType Convert(this Checkers.Types.PieceType value) =>
        Equals(value, Checkers.Types.PieceType.Checker) ? PieceType.Checker : PieceType.King;

    public static Checkers.Types.PieceType ConvertBack(this PieceType value) =>
        value == PieceType.Checker ? Checkers.Types.PieceType.Checker : Checkers.Types.PieceType.King;

    public static Piece Convert(this FSharpOption<Checkers.Piece.Piece> piece)
    {
        if (Equals(piece, Checkers.Piece.whiteChecker))
        {
            return Piece.WhiteChecker;
        }
        if (Equals(piece, Checkers.Piece.whiteKing))
        {
            return Piece.WhiteKing;
        }
        if (Equals(piece, Checkers.Piece.blackChecker))
        {
            return Piece.BlackChecker;
        }
        if (Equals(piece, Checkers.Piece.blackKing))
        {
            return Piece.BlackKing;
        }

        return null;
    }

    public static FSharpOption<Checkers.Piece.Piece> ConvertBack(this Piece piece)
    {
        if (Equals(piece, Piece.WhiteChecker))
        {
            return Checkers.Piece.whiteChecker;
        }
        if (Equals(piece, Piece.WhiteKing))
        {
            return Checkers.Piece.whiteKing;
        }
        if (Equals(piece, Piece.BlackChecker))
        {
            return Checkers.Piece.blackChecker;
        }
        if (Equals(piece, Piece.BlackKing))
        {
            return Checkers.Piece.blackKing;
        }

        return null;
    }
}

Coord.cs

public class Coord
{
    public int Row { get; }
    public int Column { get; }

    public Coord(int row, int column)
    {
        Row = row;
        Column = column;
    }

    public static Coord operator +(Coord coord1, Coord cooord2)
    {
        return new Coord(coord1.Row + cooord2.Row, coord1.Column + cooord2.Column);
    }

    public static implicit operator Coord(Checkers.Types.Coord coord)
    {
        return new Coord(coord.Row, coord.Column);
    }

    public static implicit operator Checkers.Types.Coord(Coord coord)
    {
        return new Checkers.Types.Coord(coord.Row, coord.Column);
    }

    public static implicit operator Coord(FSharpOption<Checkers.Types.Coord> coord)
    {
        return Equals(coord, FSharpOption<Checkers.Types.Coord>.None)
            ? null
            : new Coord(coord.Value.Row, coord.Value.Column);
    }

    public static implicit operator FSharpOption<Checkers.Types.Coord>(Coord coord)
    {
        return coord == null
            ? FSharpOption<Checkers.Types.Coord>.None
            : FSharpOption<Checkers.Types.Coord>.Some(new Checkers.Types.Coord(coord.Row, coord.Column));
    }
}

Piece.cs

public class Piece
{
    public Player Player { get; }
    public PieceType PieceType { get; }

    public Piece(Player player, PieceType pieceType)
    {
        Player = player;
        PieceType = pieceType;
    }

    public static Piece WhiteChecker =>
        new Piece(Player.White, PieceType.Checker);

    public static Piece WhiteKing =>
        new Piece(Player.White, PieceType.King);

    public static Piece BlackChecker =>
        new Piece(Player.Black, PieceType.Checker);

    public static Piece BlackKing =>
        new Piece(Player.Black, PieceType.King);

    public override bool Equals(object obj)
    {
        if (obj == null || typeof(Piece) != obj.GetType())
        {
            return false;
        }

        var value = (Piece)obj;
        return Player.Equals(value.Player) &&
               PieceType.Equals(value.PieceType);
    }

    public override int GetHashCode() => Player.GetHashCode() ^ PieceType.GetHashCode();
}

Board.cs

public class Board
{
    public List<List<Piece>> GameBoard { get; }

    public Board(IEnumerable<IEnumerable<FSharpOption<Checkers.Piece.Piece>>> board)
    {
        GameBoard = board.Select(row => row.Select(piece => piece.Convert()).ToList()).ToList();
    }

    public Board() : this(Checkers.Board.defaultBoard) { }

    public Piece this[Coord coord] => GameBoard[coord.Row][coord.Column];

    public static implicit operator Board(FSharpList<FSharpList<FSharpOption<Checkers.Piece.Piece>>> value)
    {
        return new Board(value);
    }

    public static implicit operator FSharpList<FSharpList<FSharpOption<Checkers.Piece.Piece>>>(Board value)
    {
        return Checkers.Board.listFromSeq(value.GameBoard.Select(row => row.Select(piece => piece.ConvertBack())));
    }
}

GameController.cs

public class GameController : INotifyPropertyChanged
{
    public GameController(Board board, Player currentPlayer, Coord currentCoord = null)
    {
        Board = board;
        CurrentPlayer = currentPlayer;
        CurrentCoord = currentCoord;
    }

    public Player CurrentPlayer { get; }
    public Coord CurrentCoord { get; }

    private Board _board;
    public Board Board
    {
        get { return _board; }
        set
        {
            Debug.Assert(value.GameBoard != null);
            _board = value;
            OnPropertyChanged();
        }
    }

    public GameController(Checkers.GameController.GameController gameController)
        :this(gameController.Board, gameController.CurrentPlayer.Convert(), gameController.CurrentCoord) { }

    public GameController()
        : this(new Board(), Player.Black) { }

    public GameController Move(Coord startCoord, Coord endCoord) =>
        Checkers.PublicAPI.movePiece(startCoord, endCoord, this);

    public GameController Move(IEnumerable<Coord> moves) =>
        Checkers.PublicAPI.move(moves.Select(item => (Checkers.Types.Coord)item), this);

    public bool IsValidMove(Coord startCoord, Coord endCoord) =>
        Checkers.PublicAPI.isValidMove(startCoord, endCoord, this);

    public IEnumerable<Coord> GetMove(int searchDepth) =>
        Checkers.PublicAPI.getMove(searchDepth, this).Select(coord => (Coord)coord);

    public Player? GetWinningPlayer()
    {
        var player = Checkers.PublicAPI.isWon(this);
        return Equals(player, FSharpOption<Checkers.Types.Player>.None) ? new Player?() : player.Value.Convert();
    }

    public static implicit operator GameController(Checkers.GameController.GameController controller)
    {
        return new GameController(controller);
    }

    public static implicit operator Checkers.GameController.GameController(GameController controller)
    {
        return new Checkers.GameController.GameController(controller.Board, controller.CurrentPlayer.ConvertBack(),
            controller.CurrentCoord);
    }

    public static implicit operator GameController(FSharpOption<Checkers.GameController.GameController> controller)
    {
        return Equals(controller, FSharpOption<Checkers.GameController.GameController>.None)
            ? null
            : new GameController(controller.Value);
    }

    public static implicit operator FSharpOption<Checkers.GameController.GameController>(GameController controller)
    {
        return controller == null
            ? FSharpOption<Checkers.GameController.GameController>.None
            : FSharpOption<Checkers.GameController.GameController>.Some(
                new Checkers.GameController.GameController(controller.Board, controller.CurrentPlayer.ConvertBack(),
                    controller.CurrentCoord));
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
\$\endgroup\$
2
\$\begingroup\$

Piece.cs

Why do you need to create a new instance of piece and return it each time when public static properties of Piece class are retrieved?
I'd prefer to declare them as static readonly fields or static get-only properties as follows:

public static Piece WhiteChecker { get; } =
    new Piece(Player.White, PieceType.Checker);

public static Piece WhiteKing { get; } =
    new Piece(Player.White, PieceType.King);

public static Piece BlackChecker { get; } =
    new Piece(Player.Black, PieceType.Checker);

public static Piece BlackKing { get; } =
    new Piece(Player.Black, PieceType.King);

Consider to make your classes sealed if they don't intended for inheritance.
In your current implementation it's (hypothetically) possible that the following condition returns true for Piece object:

if (obj == null || typeof(Piece) != obj.GetType())

Reason: for some object of type that derived from Piece, obj.GetType() returns a type not equal to the Piece.

Another way is to compare types taking into account inheritance:

if (!typeof(Piece).IsInstanceOfType(obj))

Why not implement the IEquatable<Piece> interface in the Piece class? It will eliminate unnecessary boxing when you call the Equals method to compare two Pieces:

public class Piece : IEquatable<Piece>
{
    ... 

    public override bool Equals(object obj)
    {
        return Equals(obj as Piece);
    }

    public bool Equals(Piece other)
    {
        return other != null
            && Player.Equals(other.Player)
            && PieceType.Equals(other.PieceType);
    }

    ...
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy