1
\$\begingroup\$

Just curious if someone could review my design, and implementation of the following requirements below. I am a mid level dev, and this is for a new job. I am looking for ways to improve, and your overall thoughts if you were the interviewer.

Code Test Instructions:

Write a simple application/service that accepts two inputs: chess piece (one of king, queen, rook or bishop) and a starting position using standard chess notation (a1, b2, c3, etc). The service provides all possible positions on a standard chess board that the chess piece can move to from that position.

Assumptions: there are no other pieces on the board and there are no special considerations for a piece relative to another piece such as a king and rook moving together in a single turn under certain circumstances.

Reviewer will be looking for correct functionality, use of design patterns, use of unit testing, use of object oriented programming, code re-use, etc. Each stage may be represented with a new project in the single solution.

Stage 1:

Create a C# 7 .net core class library project with model classes for the chess pieces and the algorithms for determining the possible moves.

Create unit tests in a separate project with adequate coverage to test the algorithms

Other stages are not listed because I am on stage 1.

I did not use any design patterns because I thought it wasn't necessary, and would be overkill for this coding exercise. Should I add something like the strategy design pattern for the rules just for the sake of adding it? I thought I could explain that adding design patterns would add unnecessary complexity for these requirements.

Code Below - Note: all other chess pieces are similar to the Queen class.

public class Board
{

    private const int MAX_ROW_SIZE = 8;
    private const int MAX_COLUMN_SIZE = 8;

    public readonly Square[,] squares = new Square[MAX_ROW_SIZE,MAX_COLUMN_SIZE];


    public Board()
    {
        for (int i = 0; i < MAX_ROW_SIZE; i++)
        {
            for (int j = 0; j < MAX_COLUMN_SIZE; j++)
            {
                squares[i,j] = new Square(i, j);
            }
        }
    }


    public List<Square> GetAvailableMoves(Square startingSquare)
    {
        if (startingSquare.piece == null)
        {
            throw new Exception("Square must have a chess piece on it to get the available moves.");
        }

        List<Square> validPieceMoves = new List<Square>();

        for (int i = 0; i < MAX_ROW_SIZE; i++)
        {
            for (int j = 0; j < MAX_COLUMN_SIZE; j++)
            {
                int destColumn = j;
                int destRow = i;
                int sourceColumn = startingSquare.column;
                int sourceRow = startingSquare.row;

                bool isNewSquareValid = startingSquare.piece.IsValidMove(sourceColumn, sourceRow, destColumn, destRow);

                if (isNewSquareValid)
                {
                    Square possibleSquare = new Square(destRow, destColumn);
                    validPieceMoves.Add(possibleSquare);
                }
            }
        }

        return validPieceMoves;
    }
}



public abstract class PieceBase
{

    public readonly PieceType pieceType; 


    public PieceBase(PieceType pieceType)
    {
        this.pieceType = pieceType;
    }


    internal abstract bool IsValidMove(int sourceColumn, int sourceRow, int destColumn, int destRow);


    protected bool _IsDiagonalMove(int sourceColumn, int sourceRow, int destColumn, int destRow)
    {
        bool isDiagonalMove = Math.Abs(destColumn - sourceColumn) == Math.Abs(destRow - sourceRow);
        return isDiagonalMove;
    }

    protected bool _IsVerticalMove(int sourceColumn, int sourceRow, int destColumn, int destRow)
    {
        bool isVerticalMove = sourceColumn == destColumn && sourceRow != destRow;
        return isVerticalMove;
    }

    protected bool _IsHorizontalMove(int sourceColumn, int sourceRow, int destColumn, int destRow)
    {
        bool isHorizontalMove = sourceRow == destRow && sourceColumn != destColumn;
        return isHorizontalMove;
    }
}



public class Square
{

    public readonly int row;
    public readonly int column;

    public PieceBase piece;


    public Square(int row, int column)
    {
        this.row = row;
        this.column = column;
    }

    public Square(int row, int column, PieceBase piece)
    {
        this.row = row;
        this.column = column;
        this.piece = piece;
    }

    public string GetDisplayCoordinates()
    {
        // 0 + 65 is the start of ascii uppercase characters
        // 65 + 32 is the start of ascii lowercase characters
        char rowCoordinate = Convert.ToChar(row + 65 + 32);

        return rowCoordinate + column.ToString();
    }
}



public class Queen : PieceBase
{

    public Queen() : base(PieceType.QUEEN)
    {

    }

    internal override bool IsValidMove(int sourceColumn, int sourceRow, int destColumn, int destRow)
    {
        bool isDiagonalMove = this._IsDiagonalMove(sourceColumn, sourceRow, destColumn, destRow);
        bool isVerticalMove = this._IsVerticalMove(sourceColumn, sourceRow, destColumn, destRow);
        bool isHorizontalMove = this._IsHorizontalMove(sourceColumn, sourceRow, destColumn, destRow);

        return isDiagonalMove || isVerticalMove || isHorizontalMove;
    }
}

Below are the NUnit tests

[TestFixture]
public class BoardTests
{

    private Board chessBoard = new Board();


    [Test]
    public void AllAvailableBishopMovesAreValid()
    {
        Square bishopStartSquare = new Square(4, 4, new Bishop());

        List<Square> availableChessMoves = chessBoard.GetAvailableMoves(bishopStartSquare);

        foreach (Square availableMove in availableChessMoves)
        {
            bool isValidMove = this._IsDiagonalMove(bishopStartSquare, availableMove);

            Assert.IsTrue(isValidMove, "Move is not valid for Bishop from " + bishopStartSquare.GetDisplayCoordinates() + " to " + availableMove.GetDisplayCoordinates());
        }
    }

    [Test]
    public void AllAvailableRookMovesAreValid()
    {
        Square rookStartSquare = new Square(4, 4, new Rook());

        List<Square> availableChessMoves = chessBoard.GetAvailableMoves(rookStartSquare);

        foreach (Square availableMove in availableChessMoves)
        {
            bool isValidMove = this._IsVerticalMove(rookStartSquare, availableMove)
                               || this._IsHorizontalMove(rookStartSquare, availableMove);

            Assert.IsTrue(isValidMove, "Move is not valid for Rook from " + rookStartSquare.GetDisplayCoordinates() + " to " + availableMove.GetDisplayCoordinates());
        }
    }

    [Test]
    public void AllAvailableQueenMovesAreValid()
    {
        Square queenStartSquare = new Square(4, 4, new Queen());

        List<Square> availableChessMoves = chessBoard.GetAvailableMoves(queenStartSquare);

        foreach (Square availableMove in availableChessMoves)
        {
            bool isValidMove = this._IsVerticalMove(queenStartSquare, availableMove) 
                               || this._IsHorizontalMove(queenStartSquare, availableMove) 
                               || this._IsDiagonalMove(queenStartSquare, availableMove);

            Assert.IsTrue(isValidMove, "Move is not valid for Queen from " + queenStartSquare.GetDisplayCoordinates() + " to " + availableMove.GetDisplayCoordinates());
        }
    }

    [Test]
    public void AllAvailableKingMovesAreValid()
    {
        Square kingStartSquare = new Square(4, 4, new King());

        List<Square> availableChessMoves = chessBoard.GetAvailableMoves(kingStartSquare);

        foreach (Square availableMove in availableChessMoves)
        {
            bool isValidMove = this._IsVerticalMove(kingStartSquare, availableMove)
                               || this._IsHorizontalMove(kingStartSquare, availableMove)
                               || this._IsDiagonalMove(kingStartSquare, availableMove);

            isValidMove = isValidMove && this._HasMovedOnlyOneSquare(kingStartSquare, availableMove);

            Assert.IsTrue(isValidMove, "Move is not valid for King from " + kingStartSquare.GetDisplayCoordinates() + " to " + availableMove.GetDisplayCoordinates());
        }
    }

    [Test]
    public void StartingSquareHasNoChessPiece()
    {
        Square startingSquare = new Square(1, 0);

        Exception ex = Assert.Throws<Exception>(() => chessBoard.GetAvailableMoves(startingSquare));

        Assert.That(ex.Message == "Square must have a chess piece on it to get the available moves.");
    }


    #region utility methods

    private bool _IsDiagonalMove(Square sourceSquare, Square destSquare)
    {
        int destColumn = destSquare.column;
        int destRow = destSquare.row;

        int sourceColumn = sourceSquare.row;
        int sourceRow = sourceSquare.row;

        bool isDiagonalMove = Math.Abs(destColumn - sourceColumn) == Math.Abs(destRow - sourceRow);
        return isDiagonalMove;
    }

    private bool _IsVerticalMove(Square sourceSquare, Square destSquare)
    {
        int destColumn = destSquare.column;
        int destRow = destSquare.row;

        int sourceColumn = sourceSquare.row;
        int sourceRow = sourceSquare.row;

        bool isVerticalMove = sourceColumn == destColumn && sourceRow != destRow;
        return isVerticalMove;
    }

    private bool _IsHorizontalMove(Square sourceSquare, Square destSquare)
    {
        int destColumn = destSquare.column;
        int destRow = destSquare.row;

        int sourceColumn = sourceSquare.row;
        int sourceRow = sourceSquare.row;

        bool isHorizontalMove = sourceRow == destRow && sourceColumn != destColumn;
        return isHorizontalMove;
    }

    private bool _HasMovedOnlyOneSquare(Square sourceSquare, Square destSquare)
    {
        int destColumn = destSquare.column;
        int destRow = destSquare.row;

        int sourceColumn = sourceSquare.row;
        int sourceRow = sourceSquare.row;

        bool isRowMoveOne = sourceRow - destRow * 1 == 1;
        bool isColumnMoveOne = sourceColumn - destColumn * 1 == 1;

        return isRowMoveOne && isColumnMoveOne;
    }

    #endregion
}
\$\endgroup\$
  • \$\begingroup\$ Well I ended up getting the job :) \$\endgroup\$ – Grim Feb 23 at 7:01
  • \$\begingroup\$ Oh, yeah, interview for an actual job. Sorry, I wasn't trying to participate in that :) Congratulations! And yeah, I'm still implementing the chess game now and then, in between things. Castling and check validation are pretty tricky although it is now close to be done. Now for the computer chess player and the scoring system - I might not go that far. \$\endgroup\$ – Maarten Bodewes Feb 23 at 11:38
  • \$\begingroup\$ >B d7-d8 Invalid move because [a4] put the players king in check :) \$\endgroup\$ – Maarten Bodewes Feb 23 at 11:44
  • \$\begingroup\$ Yeah I realized the day before the interview, should have done an edit of the post. \$\endgroup\$ – Grim Feb 24 at 4:50
2
\$\begingroup\$

Design

This is stage 1 of a project and there will be further stages with (presumably) new requirements coming in later stages.

One of the things to think about when reviewing a design is how well it lends itself to extensions in the requirements.

So let's think about some possible additional requirements for this project

1) Add the knight and pawn pieces
2) Allow for other pieces on the board (which will block moves)

So how would we fit these requirements into the current design?

Currently, ascertaining the valid moves is split between two classes

  • The board, which asks the piece 'can you move here' for each location on the board.
  • The piece, which reports whether or not it can move there.

In the current implementation, the code for checking a particular kind of move (diagonal, horizontal, vertical) is in the base Piece class.

Add the Knight and Pawn
We add the two new sub classes. The pawn, especially, brings the current shape of checking each possible location and checking if it is valid into question. A pawn can move to one of three, maybe four places (one of two if we are ignoring other pieces). Checking every possible location to see if it is valid seems very wasteful. The knight has a relatively small number of possible valid moves and the check for these is unrelated to the existing diagonal, horizontal, vertical checks.

We could just put the check in the Knight sub-class, say, IsKnightMove(). This works but the irrelevant IsDiagonalMove(), IsVerticalMove(), IsHorizontalMove() routines in the base class sort of niggles.

As does the check every possible move to see if it is valid.

Say we have an initial chess board set up and want to find the valid moves for each piece. Of the back-line pieces, only the knight can move, but we check every possible location for each piece and there is no way of pruning the checks because the possible moves are piece dependent.

So how can we change the shape to fix some of the problems?

If we make each piece responsible for generating a list of places to which it can move (passing in the current board state - not used yet but we reckon, YAGNI aside, that it is going to be needed) We can use our knowledge of how the pieces move to generate the possible moves and, if later needed, to adjust the checks based upon other pieces on the board.

public interface IChessPiece
{
    BoardLocation CurrentLocation { get; set; }
    ChessPieceType Type { get; }
    IEnumerable<Move> GetValidMoves(ChessBoard board);
}

public class BoardLocation
{
    private const int BoardSize = 8;

    private static bool IsInRange(int pos)
    {
        return (pos >= 1) && (pos <= BoardSize);
    }

    public BoardLocation(int row, int col)
    {
        if (!IsInRange(row))
        {
            throw new ArgumentOutOfRangeException("row");
        }
        if (!IsInRange(col))
        {
            throw new ArgumentOutOfRangeException("col");
        }
        Row = row;
        Column = col;
    }
    public int Row { get; }
    public int Column { get; }
}

public class Move
{
    public Move(IChessPiece piece, BoardLocation endingLocation)
    {
        Piece = piece ?? throw new ArgumentNullException("piece");
        EndingLocation = endingLocation ?? throw new ArgumentNullException("endingLocation");
    }
    public IChessPiece Piece { get; }
    public BoardLocation EndingLocation { get; }
}

NOTES:
When writing code we can hit problems with how much is enough (YAGNI exists for a reason). In the above, the board location is just a simple container for the row and column positions. It could also be responsible for formatting the position for display e.g. e4 or we could leave this up to an external formatter so that we could use the older notation (KP4). It could also be responsible for parsing an input string, or we could leave this to another class. The Move class could be omitted (just return a list of BoardLocations) but we may want to know the piece/move combinations.

In a lot of cases it comes down to a Matter of Personal Preference (MoPP (tm)).

Finding valid moves
With this shape we can use knowledge of how each piece moves to generate the possible moves (optionally using the board state). A pawn on d2 can now move to d3 or d4 (if nothing is in the way) or possibly take on c3 or e3 if there is something there or is taking en passant

A knight can generate the 2 to 6 possible moves it can make

The King, Queen, Rook and Bishop can all generate their own possible moves in their respective classes.

Shared functionality? The code for generating moves / checking if a move is valid can be shared but doesn't need to be in the base class, it could be in a shared utility class (again a MoPP, I don't like cluttering up a base class with code that is not used by all the sub classes).

public abstract class ChessPiece : IChessPiece
{
    private BoardLocation _currentLocation;
    public BoardLocation CurrentLocation
    {
        get => _currentLocation;
        set => _currentLocation = value ?? throw new ArgumentNullException();
    }
    public abstract ChessPieceType Type { get; }
    public abstract IEnumerable<Move> GetValidMoves(ChessBoard board);
}

public class King : ChessPiece
{
    private readonly static int[][] MoveTemplates = new int[][]
    {
      new [] { 1, -1 },
      new [] { 1, 0 },
      new [] { 1, 1 },
      new [] { 0, -1 },
      new [] { 0, 1 },
      new [] { -1, -1 },
      new [] { -1, 0 },
      new [] { -1, 1 },

    };

    public override ChessPieceType Type => ChessPieceType.King;

    public override IEnumerable<Move> GetValidMoves(ChessBoard board)
    {
        return ChessMoveUtilities.GetMoves(board, this, 1, MoveTemplates);
    }
}

public class Queen : ChessPiece
{
    private readonly static int[][] MoveTemplates = new int[][]
    {
      new [] { 1, -1 },
      new [] { 1, 0 },
      new [] { 1, 1 },
      new [] { 0, -1 },
      new [] { 0, 1 },
      new [] { -1, -1 },
      new [] { -1, 0 },
      new [] { -1, 1 },

    };

    public override ChessPieceType Type => ChessPieceType.King;

    public override IEnumerable<Move> GetValidMoves(ChessBoard board)
    {
        return ChessMoveUtilities.GetMoves(board, this, board.Size, MoveTemplates);
    }
}

internal static class ChessMoveUtilities
{
    private static bool IsValid(ChessBoard board, BoardLocation current, int deltaRow, int deltaCol, out BoardLocation location)
    {
        location = null;
        var newRow = current.Row + deltaRow;
        if ((newRow <= 0) ||(newRow > board.Size)) return false;

        var newCol = current.Column + deltaCol;
        if ((newCol <=0) || (newCol > board.Size)) return false;

        location = new BoardLocation(newRow, newCol);
        return true;
    }

    internal static IEnumerable<Move> GetMoves(ChessBoard board, IChessPiece piece, int range, IEnumerable<int[]> mults)
    {
        if (board == null) throw new ArgumentNullException("board");
        if (piece == null) throw new ArgumentNullException("piece");
        if (range < 1) throw new ArgumentOutOfRangeException("range");
        if (mults == null || !mults.Any()) throw new ArgumentException("mults");

        var ret = new List<Move>();

        foreach( var mult in mults)
        {
            for (var radius = 1; radius <= range; radius++)
            {

                var deltaX = radius * mult[0];
                var deltaY = radius * mult[1];
                if(IsValid(board, piece.CurrentLocation, deltaX, deltaY, out BoardLocation newLocation))
                {
                    ret.Add(new Move (piece, newLocation));
                }
                else
                {
                    break;
                }
            }
        }
        return ret;

    }

}

Notes:
It is possible (at the moment) to replace the individual classes for King, Queen, Rook and Bishop with instances of a common base class which takes in the ChessPieceType and MoveTemplates as parameters, but knowing about chess and thinking ahead, if we add in Castling and 'not moving into check' rules then the King and Rook need to be separate classes and merging the Queen and Bishop seems like overkill.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Some quick remarks WRT style:

  • Public properties should have at least a { get; } (IMHO; I'm not a fan of public readonly and instead prefer { get; private set; }) and should be PascalCase.

  • Don't just throw any Exception, throw the correct one, e.g. ArgumentException.

  • Underscores should only be used to prefix class-wide private properties; certainly not methods.

  • Do not assign a value only to return it on the next line. Combine those two lines into one.

  • Do not pointlessly abbreviate: there's no need to use "dest" instead of "destination". It certainly doesn't make your code clearer.

  • What is the point of allocating destColumn, destRow, sourceColumn and sourceRow in your "utility" methods? Why not simply use destSquare.column etc.?

  • Use string interpolation instead of concatenation.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Reviewer will be looking for correct functionality, use of design patterns, use of unit testing, use of object oriented programming, code re-use, etc. Each stage may be represented with a new project in the single solution.

And

Create a C# 7 .net core class library project with model classes for the chess pieces and the algorithms for determining the possible moves.

Requirements :

  • Design (how much do you enforce this concept in your work(mainly OOP).
  • Functionality (how many bugs are there ? )
  • Code Reuse (how much do you apply this concept?)
  • Unit Test (how much do you use it ? ).
  • C# 7 .NET Core (Familiarity with C# 7 updates and .NET Core updates)
  • Model Classes (this is important)
  • algorithms for determining the possible moves. (this would show how do you handle code complicity).

At this stage :

You're missing some of the requirements (some headlines).

  • Naming convention (like Board, Square ..etc).
  • Models (no models)
  • Algorithms (more on that is below)
  • Missing some fundamental game logic (like pieces sets (white, black)).
  • Missing Code Flexibility (will be not feasible to add more requirements).
  • Comments (something you want to consider all along).

Currently, I believe most of code parts can be improved.

Chess Rules

We need to know the basic requirements :

  • Board is a grid of 8x8 squares (total of 64 square).
  • It has two groups (black/white) of (one king, queen, and two kights, rooks, bishops, and 8 pawns).
  • Game starts with 2 moves
  • Each piece set has its own movements rules
    • King : One Step on (Vertical,Horizontal,Diagonal) directions
    • Queen : Open-Steps on (Vertical,Horizontal,Diagonal) directions
    • Rook : Open-Steps on (Vertical,Horizontal) directions
    • Bishop : Open-Steps on (Diagonal) directions
    • Knight : 3 Steps in LShape directions
    • Pawn : One-Step (Vertical) and can go One-Step (Diagonal) to eliminate another piece.

So, based on that we need :

  • ChessBoard : To hold the layout, pieces, positions, and the default startup game settings.
  • ChessPiece : Model which will hold each piece information plus the location.
  • ChessPieceMove class to hold the movement logic and used on moving the pieces, you can also name it ChessPieceMovement.
  • IChessPieceType an inteface which would be implemented on each piece type.
  • ChessPieceKing, ChessPieceQueen, ChessPieceRook, ChessPieceKnight ..etc. a class for each piece, which inherits IChessPieceType.

All of these are just the base requirements, we still need to implement game rule handler to be easier to add new requirements in the upcoming stages.

For now, we will make ChessBoard the main class to initiate a new game.

public class ChessBoard
{
    // Initiate a private new ChessPiece array
    private ChessPiece[] pieces = new ChessPiece[64];


    public ChessBoard()
    {
        // callback the initiation process
        Initiate();
    }

    // initiation process (To initiate the ChessPiece compenents)
    private void Initiate() { ... }

    // Dummy : To get the on-board pieces (playable peices)
    public IEnumerable<ChessPiece> GetAvailablePieces() { ... }

    // Dummy : To get the open-squares that can accept new ChessPiece 
    public IEnumerable<int> GetAvailablePositions(){ ... }
}

// Model 
public class ChessPiece
{
    // Enum : To define a color for each piece.
    public ChessPieceColor Color { get; set; }

    // stores piece's position
    public int Position { get; set; }

    // stores piece type or kind (Rook, Bishop, Knight, Queen, King, or Pawn) 
    // Note : they're concrete classes sharing the same interface
    public IChessPieceType PieceType { get; set; }

    // a flag to distinguish the playable pieces from the elementated ones
    public bool IsDistroyed { get; set; }

    public ChessPiece() { }

    public ChessPiece(int position, ChessPieceColor color, IChessPieceType pieceType) 
    {
        Color = color;
        Position = position;
        PieceType = pieceType;
    }
}

When initiate a new board, it should initiate an array of ChessPiece with length of 64 elements. Which is the total number of board squares. While using ChessPiece it would make it easier to just add the model in each element and then loop over them.

This is simple enough. Now, we need to setup the startup pieces, but we need to visualize the grid and see which element index in the array should have a actual piece at startup, and which element should be null. Because we're dealing with a fixed rule here, which would be easy enough to just number the elements on-top of a chess grid. From top-left-corner to the bottom-left-corner it would holds elements from 0 to 63. Using these we can figure out which index should have an item.

So, in your board initiation process you should have something like :

private void Initiate()
{
    IChessPieceType chessPiece = null;
    ChessPieceColor color = ChessPieceColor.Undefined;

    for (int x = 0; x < pieces.Length; x++)
    {
        // colors 
        switch (x)
        {
            case 0:
            case 1:
            case 2:
            case 3:
            case 4:
            case 5:
            case 6:
            case 7:
                color = ChessPieceColor.White;
                break;
            case 56:
            case 57:
            case 58:
            case 59:
            case 60:
            case 61:
            case 62:
            case 63:
                color = ChessPieceColor.Black;
                break;
        }

        // pieces
        switch (x)
        {
            case 0:
            case 7:
            case 56:
            case 63:
                chessPiece = new ChessPieceRook(x);
                break;
            case 1:
            case 6:
            case 57:
            case 62:
                chessPiece = new ChessPieceKnight(x);
                break;
            case 2:
            case 5:
            case 58:
            case 61:
                chessPiece = new ChessPieceBishop(x);
                break;
            case 3:
            case 59:
                chessPiece = new ChessPieceQueen(x);
                break;
            case 4:
            case 60:
                chessPiece = new ChessPieceKing(x);
                break;
        }

        // Pawns
        if ((x >= 8 && x <= 15) || (x >= 48 && x <= 55))
        {
            color = x >= 8 && x <= 15 ? ChessPieceColor.White : ChessPieceColor.Black;
            chessPiece = new ChessPiecePawn(x);
        }

        pieces[x] = new ChessPiece(x, color, chessPiece);
    }
}

Doing that, would setup the board and its pieces. We can also get the available positions (empty squares) like this :

public IEnumerable<int> GetAvailablePositions()
{
    for (int x = 0; x < pieces.Length; x++)
    {
        if (pieces[x] == null) { yield return x; }
    }
}

From there, we can implement ChessPieceMove which will be used internally with the pieces. Since we are using one-dimensional array, it's a matter of simple math to know which and where the next move.

public class ChessPieceMove
{
    private const int MAX_ROW_SIZE = 8;

    private const int MAX_COLUMN_SIZE = 8;

    private int _position;

    private int _row;

    private int _column;

    public ChessPieceMove(int position)
    {
        Move(position, 0);
    }

    private void Move(int steps)
    {
        Move(_position, steps);
    }

    private void Move(int position, int steps)
    {
        _position = position + steps;
        _column = _position % MAX_COLUMN_SIZE;
        _row = (MAX_COLUMN_SIZE - _column + _position) / MAX_ROW_SIZE;
    }

    public ChessPieceMove Forward(int steps)
    {
        Move(steps * 8);
        return this;
    }

    public ChessPieceMove Backward(int steps)
    {
        Move(steps * 8 * -1);
        return this;
    }

    public ChessPieceMove Left(int steps)
    {
        Move(steps * -1);       
        return this;
    }

    public ChessPieceMove Right(int steps)
    {
        Move(steps);
        return this;
    }

    public ChessPieceMove Oppsite()
    {
        _position = ((MAX_ROW_SIZE - _row + 1) * MAX_ROW_SIZE) - (MAX_COLUMN_SIZE - _column);
        return this;
    }

    public int Save()
    {
        return _position;
    }

}

This class can be used to get the element index for next move like :

// Move a Knight example : 
var index = new ChessPieceMove(12).Forward(2).Right(1).Save();
// returns index 29

So, this ChessPieceMove simple API would be very helpful to retrieve the element index. You can get the opposite index which is the square index that faces the current from the opponent view. TO get that you can use Oppsite() like :

// Move a Knight example : 
var index = new ChessPieceMove(12).Forward(2).Right(1).Oppsite().Save();
// returns index 37

So, square number 29 from player 1 view is equal to square number 37 from player 2 view.

For the pieces, you would use an interface :

public interface IChessPieceType
{
    int Position { get; set; }
}

and implement the pieces classes like :

public class ChessPiecePawn : IChessPieceType
{

    public int Position { get; set; }

    public ChessPiecePawn(int position)
    {
        Position = position;
    }

    public int MoveForward()
    {   
        return new ChessPieceMove(Position).Forward(1).Save();
    }
    public int MoveDiagonalRightFoward()
    {
        return new ChessPieceMove(Position).Forward(1).Right(1).Save();
    }
    public int MoveDiagonalLeftFoward()
    {
        return new ChessPieceMove(Position).Forward(1).Left(1).Save();
    }
}

Now, you can use ChessPiecePawn for instance, on the board, and get the move index, validate it, and execute the process you need (e.g. change the current index to the new one, return an error since it's not valid ...etc).

I have intentionally left off the validation process in the above codes for demonstration purpose. So, you have something you can work on and make your own project.

A few notes on the above process :

  • You have fixed 64 elements, so do some validation on that
  • some elements on the board would be out-of-range movements (for instance index 7 cannot accept Right move as it's on the board's edge.
  • Make use of generic interfaces like IEnumerable and IDisposable this is a must use.
  • You can implement another interface or abstract class for the movements class.
  • using const at this stage is just for Readability purpose.

  • for validations, start with the standard validations, then go for game validation.

  • Keep in mind that you would need a wrapper class to wrap your project like ChessGame so it would be more appropriate to initiate the game instead of calling ChessBoard. This wrapper can also have new functionalities, like specifying players name or timing ..etc.
  • Keep it simple and open for new requirements.

  • MOST IMPORTANTLY TAKE NOTES OF OTHER ANSWERS AS EACH ANSWER COVERS SOME PART OF YOUR QUESTION . gathering all answers points would give you a full answer.

I hope this would benefit you, and I wish you a good luck on your upcoming job.

| improve this answer | |
\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.