Friday, June 24, 2011

Designing The Chess Engine Using Object Oriented programming Java

The Chess Board class
view plaincopy to clipboardprint?

1. public class ChessBoard {
2. private final Squares squares;
3.
4. public ChessBoard() {
5. this (new Squares());
6. }
7.
8. public ChessBoard(Squares squares) {
9. this.squares = squares;
10. }
11.
12. public ChessBoard performMovement(Piece piece, Location locationFrom, Location locationTo) {
13. Squares copy = squares.copy();
14. copy.setContent(SquareContent.EMPTY_SQUARE, locationFrom.getCoordinateX(), locationFrom.getCoordinateY());
15. copy.setContent(piece, locationTo.getCoordinateX(), locationTo.getCoordinateY());
16. return new ChessBoard(copy);
17. }
18.
19. public ChessBoard performMovement(PieceOnLocation pieceInBoard, Location locationTo) {
20. return performMovement(pieceInBoard.getPiece(), pieceInBoard.getLocation(), locationTo);
21. }
22.
23. public ChessBoard performMovement(Movement movement) {
24. return performMovement(movement.getMovingPiece(), movement.getFrom(), movement.getTo());
25. }
26.
27. public SquareContent getSquareContent(Location location) {
28. return squares.getContent(location.getCoordinateX(), location.getCoordinateY());
29. }
30.
31. public ChessBoard addPiece(Piece piece, Location location) {
32. Squares copy = squares.copy();
33. copy.setContent (piece, location.getCoordinateX(), location.getCoordinateY());
34. return new ChessBoard(copy);
35. }
36.
37. public List getPieces(Color movingColor) {
38. List pieces = new ArrayList();
39. for (Location location : Location.values()) {
40. SquareContent content = squares.getContent(location.getCoordinateX(), location.getCoordinateY());
41. if (! content.isEmpty()){
42. Piece piece = (Piece) content;
43. if (piece.getColor() == movingColor) pieces.add(piece.on(location));
44. }
45. }
46. return pieces;
47. }
48.
49. public List find(Piece pieceToFind) {
50. List pieces = new ArrayList();
51. for (Location location : Location.values()) {
52. SquareContent content = squares.getContent(location.getCoordinateX(), location.getCoordinateY());
53. if (! content.isEmpty()){
54. Piece piece = (Piece) content;
55. if (piece == pieceToFind) pieces.add(piece.on(location));
56. }
57. }
58. return pieces;
59. }
60.
61. public PieceOnLocation getPieceOnLocation(Location location) {
62. return new PieceOnLocation(getSquareContent(location).asPiece(), location);
63. }
64.
65. public ChessBoard empty(Location location) {
66. Squares copy = squares.copy();
67. copy.setContent(SquareContent.EMPTY_SQUARE, location.getCoordinateX(), location.getCoordinateY());
68. return new ChessBoard(copy);
69. }
70. }

public class ChessBoard {
private final Squares squares;

public ChessBoard() {
this (new Squares());
}

public ChessBoard(Squares squares) {
this.squares = squares;
}

public ChessBoard performMovement(Piece piece, Location locationFrom, Location locationTo) {
Squares copy = squares.copy();
copy.setContent(SquareContent.EMPTY_SQUARE, locationFrom.getCoordinateX(), locationFrom.getCoordinateY());
copy.setContent(piece, locationTo.getCoordinateX(), locationTo.getCoordinateY());
return new ChessBoard(copy);
}

public ChessBoard performMovement(PieceOnLocation pieceInBoard, Location locationTo) {
return performMovement(pieceInBoard.getPiece(), pieceInBoard.getLocation(), locationTo);
}

public ChessBoard performMovement(Movement movement) {
return performMovement(movement.getMovingPiece(), movement.getFrom(), movement.getTo());
}

public SquareContent getSquareContent(Location location) {
return squares.getContent(location.getCoordinateX(), location.getCoordinateY());
}

public ChessBoard addPiece(Piece piece, Location location) {
Squares copy = squares.copy();
copy.setContent (piece, location.getCoordinateX(), location.getCoordinateY());
return new ChessBoard(copy);
}

public List getPieces(Color movingColor) {
List pieces = new ArrayList();
for (Location location : Location.values()) {
SquareContent content = squares.getContent(location.getCoordinateX(), location.getCoordinateY());
if (! content.isEmpty()){
Piece piece = (Piece) content;
if (piece.getColor() == movingColor) pieces.add(piece.on(location));
}
}
return pieces;
}

public List find(Piece pieceToFind) {
List pieces = new ArrayList();
for (Location location : Location.values()) {
SquareContent content = squares.getContent(location.getCoordinateX(), location.getCoordinateY());
if (! content.isEmpty()){
Piece piece = (Piece) content;
if (piece == pieceToFind) pieces.add(piece.on(location));
}
}
return pieces;
}

public PieceOnLocation getPieceOnLocation(Location location) {
return new PieceOnLocation(getSquareContent(location).asPiece(), location);
}

public ChessBoard empty(Location location) {
Squares copy = squares.copy();
copy.setContent(SquareContent.EMPTY_SQUARE, location.getCoordinateX(), location.getCoordinateY());
return new ChessBoard(copy);
}
}

About the Chess Board class is important to notice 3 things:

1. Is thread safe. All is member variables are private and final, and is immutable, all the “changing operations” create a copy of the object with the new state.
2. It delegates all the functionality to Squares. Squares is the main class, it contains all the logic for holding SquareContent objects (Pieces and empty squares). It is wrapped by the Board to achieve thread safeness.
3. It is easy to create and customise a board, we only have to create a new board, and call the method addPiece. (See example below).

Creating a new board with a few pieces.
view plaincopy to clipboardprint?

1. board = new ChessBoard().
2. addPiece(Piece.BLACK_PAWN, Location.C5).
3. addPiece(Piece.WHITE_PAWN, Location.D5).
4. addPiece(Piece.WHITE_KING, Location.D4);

board = new ChessBoard().
addPiece(Piece.BLACK_PAWN, Location.C5).
addPiece(Piece.WHITE_PAWN, Location.D5).
addPiece(Piece.WHITE_KING, Location.D4);

The Chess Analiser

This class contains the main entry points for all the main logic, in particular, we are interested in the method findReachableLocations and in the default implementation ChessAnaliserImpl.
view plaincopy to clipboardprint?

1. public interface ChessAnaliser {
2. [...]
3. public List findReachableLocations(PieceOnLocation pieceOnLocation, ChessBoard board, Movement previousMovement);
4. }

public interface ChessAnaliser {
[...]
public List findReachableLocations(PieceOnLocation pieceOnLocation, ChessBoard board, Movement previousMovement);
}

view plaincopy to clipboardprint?

1. public class ChessAnaliserImpl implements ChessAnaliser {
2. @Override
3. public List findReachableLocations(PieceOnLocation pieceOnLocation, ChessBoard board, Movement previousMovement) {
4. Piece piece = pieceOnLocation.getPiece();
5. List toReturn = new ArrayList();
6.
7. for (MovementLine movementLine : chessReader.findMovementLines(pieceOnLocation)) {
8. if (movementLine.isApplicable (board, previousMovement)){
9. List potentiallyReachablePositions = movementLine.filterPotentiallyReachablePositions (pieceOnLocation.getColor(), board);
10. for (Location location : potentiallyReachablePositions) {
11. ChessBoard nextPossibleBoard = nextBoard(board, movementLine.getType(), pieceOnLocation, location, previousMovement);
12. if (!isInCheck(nextPossibleBoard, piece.getColor())){
13. toReturn.add(location);
14. }
15. }
16. }
17. }
18.
19. return toReturn;
20. }
21. }

public class ChessAnaliserImpl implements ChessAnaliser {
@Override
public List findReachableLocations(PieceOnLocation pieceOnLocation, ChessBoard board, Movement previousMovement) {
Piece piece = pieceOnLocation.getPiece();
List toReturn = new ArrayList();

for (MovementLine movementLine : chessReader.findMovementLines(pieceOnLocation)) {
if (movementLine.isApplicable (board, previousMovement)){
List potentiallyReachablePositions = movementLine.filterPotentiallyReachablePositions (pieceOnLocation.getColor(), board);
for (Location location : potentiallyReachablePositions) {
ChessBoard nextPossibleBoard = nextBoard(board, movementLine.getType(), pieceOnLocation, location, previousMovement);
if (!isInCheck(nextPossibleBoard, piece.getColor())){
toReturn.add(location);
}
}
}
}

return toReturn;
}
}

Take into consideration:

1. [Line 07]. The base of any movement is the movement line. The MovementLine class defines the type of movement and the different locations for that movement type that a piece can potentially move to given a starting position. Movement lines don’t take into consideration other pieces in the board.
2. [Line 08]. The method isApplicable, in the movement line, checks if that movement line is applicable for a given board taking into consideration the previous movement. The clearest example here would be the movement line for a pawn to do an “en passant” movement, this method will check if all the conditions for an en passant movement are true.
3. [Line 09] The method filterPotentiallyReachablePositions checks from all the possible locations of a movement line, how many are reachable, so if there is a piece that is actually standing in the middle of the board for that movement line, it will filter out all the positions after that piece . (See examples below).
4. [Lines 11 and 12] For all the potential locations that given piece can move to, it finds what the next board would be, and then it checks wether if the movement would cause the pieces for that color to be in check. This would be an illegal movement, so the location would be discarded.

Tying everything together

The following are real examples to find what the possible locations for the given pieces are:
view plaincopy to clipboardprint?

1. public class ChessAnaliserImplIntegrationTest {
2. private ChessAnaliserImpl testObj;
3. private ChessReader chessReader = new ChessReaderImpl();
4. private ChessBoard board;
5.
6. @BeforeMethod (groups="integration")
7. public void setup (){
8. testObj = new ChessAnaliserImpl (chessReader);
9. }
10.
11. @Test (groups="integration")
12. public void testFindReachableLocations_forRook_withSameColorObstacles (){
13. //GIVEN
14. board = new ChessBoard().
15. addPiece(Piece.WHITE_ROOK, Location.A1).
16. addPiece(Piece.WHITE_QUEEN, Location.A5).
17. addPiece(Piece.WHITE_KNIGHT, Location.C1).
18. addPiece(Piece.WHITE_KING, Location.C8);
19.
20. //WHEN
21. List actualReachablePositions = testObj.findReachableLocations(board.getPieceOnLocation (Location.A1), board, null);
22.
23. //THEN
24. Assert.assertEquals(actualReachablePositions.size(), 4);
25. Assert.assertEquals(actualReachablePositions.get(0), Location.B1);
26. Assert.assertEquals(actualReachablePositions.get(1), Location.A2);
27. Assert.assertEquals(actualReachablePositions.get(2), Location.A3);
28. Assert.assertEquals(actualReachablePositions.get(3), Location.A4);
29. }
30.
31. @Test (groups="integration")
32. public void testFindReachableLocations_forRook_withDifferentColorObstacles (){
33. //GIVEN
34. board = new ChessBoard().
35. addPiece(Piece.WHITE_ROOK, Location.A1).
36. addPiece(Piece.BLACK_QUEEN, Location.A5).
37. addPiece(Piece.WHITE_KNIGHT, Location.C1).
38. addPiece(Piece.WHITE_KING, Location.C8);
39.
40. //WHEN
41. List actualReachablePositions = testObj.findReachableLocations(board.getPieceOnLocation (Location.A1), board, null);
42.
43. //THEN
44. Assert.assertEquals(actualReachablePositions.size(), 5);
45. Assert.assertEquals(actualReachablePositions.get(0), Location.B1);
46. Assert.assertEquals(actualReachablePositions.get(1), Location.A2);
47. Assert.assertEquals(actualReachablePositions.get(2), Location.A3);
48. Assert.assertEquals(actualReachablePositions.get(3), Location.A4);
49. Assert.assertEquals(actualReachablePositions.get(4), Location.A5);
50. }
51.
52. @Test (groups="integration")
53. public void testFindReachableLocations_forRook_whenCantMove (){
54. //GIVEN
55. board = new ChessBoard().
56. addPiece(Piece.WHITE_KING, Location.D1).
57. addPiece(Piece.WHITE_ROOK, Location.C2).
58. addPiece(Piece.BLACK_QUEEN, Location.A4).
59. addPiece(Piece.WHITE_KNIGHT, Location.C1);
60.
61. //WHEN
62. List actualReachablePositions = testObj.findReachableLocations(board.getPieceOnLocation (Location.C2), board, null);
63.
64. //THEN
65. Assert.assertEquals(actualReachablePositions.size(), 0);
66. }
67.
68. @Test (groups="integration")
69. public void testFindReachableLocations_forPawn_whenInFirstRow (){
70. //GIVEN
71. board = new ChessBoard().
72. addPiece(Piece.BLACK_PAWN, Location.A7).
73. addPiece(Piece.BLACK_KING, Location.D4);
74.
75. //WHEN
76. List actualReachablePositions = testObj.findReachableLocations(board.getPieceOnLocation (Location.A7), board, null);
77.
78. //THEN
79. Assert.assertEquals(actualReachablePositions.size(), 2);
80. Assert.assertEquals(actualReachablePositions.get(0), Location.A6);
81. Assert.assertEquals(actualReachablePositions.get(1), Location.A5);
82. }
83.
84. @Test (groups="integration")
85. public void testFindReachableLocations_forPawn_enPassant (){
86. //GIVEN
87. board = new ChessBoard().
88. addPiece(Piece.BLACK_PAWN, Location.C5).
89. addPiece(Piece.WHITE_PAWN, Location.D5).
90. addPiece(Piece.WHITE_KING, Location.D4);
91.
92. Movement enPassantEnabler = new Movement(Piece.BLACK_PAWN, Location.C7, Location.C5);
93. //WHEN
94. List actualReachablePositions = testObj.findReachableLocations(board.getPieceOnLocation (Location.D5), board, enPassantEnabler);
95.
96. //THEN
97. Assert.assertEquals(actualReachablePositions.size(), 1);
98. Assert.assertEquals(actualReachablePositions.get(0), Location.C6);
99. }
100. }

Source Code:http://subversion.assembla.com/svn/making-good-software/tags/mgsChess/blog20110515/src/com/mgs/chess/core/

No comments:

Post a Comment