Refined IO functionality, fixed FEN string serialization and deserialization #7
@ -7,6 +7,11 @@
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="src" output="bin_test" path="test_res">
|
||||
<attributes>
|
||||
<attribute name="test" value="true"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
|
||||
<classpathentry kind="output" path="bin"/>
|
||||
|
@ -1,9 +1,11 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -576,7 +578,7 @@ public class Board {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
final Piece piece = boardArr[j][i];
|
||||
if (piece == null) ++emptyCount;
|
||||
else { // TODO: rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b - - 1 2 error
|
||||
else {
|
||||
if (emptyCount != 0) {
|
||||
sb.append(emptyCount);
|
||||
emptyCount = 0;
|
||||
@ -616,6 +618,25 @@ public class Board {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.deepHashCode(boardArr);
|
||||
result = prime * result + Objects.hash(castlingRights, kingPos, log);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
Board other = (Board) obj;
|
||||
return Arrays.deepEquals(boardArr, other.boardArr) && Objects.equals(castlingRights, other.castlingRights)
|
||||
&& Objects.equals(kingPos, other.kingPos) && Objects.equals(log, other.log);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pos The position from which to return a piece
|
||||
* @return The piece at the position
|
||||
|
@ -1,7 +1,5 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@ -22,11 +20,10 @@ import dev.kske.chess.exception.ChessException;
|
||||
public class FENString {
|
||||
|
||||
private Board board;
|
||||
private Map<FENField, String> fields = new LinkedHashMap<>();
|
||||
|
||||
public static enum FENField {
|
||||
PIECE_PLACEMENT, ACTIVE_COLOR, CASTLING_AVAILABILITY, EN_PASSANT_TARGET_SQUARE, HALFMOVE_CLOCK, FULLMOVE_NUMBER
|
||||
}
|
||||
private String piecePlacement, castlingAvailability;
|
||||
private int halfmoveClock, fullmoveNumber;
|
||||
private Color activeColor;
|
||||
private Position enPassantTargetSquare;
|
||||
|
||||
/**
|
||||
* Constructs a {@link FENString} representing the starting position
|
||||
@ -34,12 +31,11 @@ public class FENString {
|
||||
*/
|
||||
public FENString() {
|
||||
board = new Board();
|
||||
fields.put(FENField.PIECE_PLACEMENT, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR");
|
||||
fields.put(FENField.ACTIVE_COLOR, "w");
|
||||
fields.put(FENField.CASTLING_AVAILABILITY, "KQkq");
|
||||
fields.put(FENField.EN_PASSANT_TARGET_SQUARE, "-");
|
||||
fields.put(FENField.HALFMOVE_CLOCK, "0");
|
||||
fields.put(FENField.FULLMOVE_NUMBER, "1");
|
||||
piecePlacement = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
|
||||
activeColor = Color.WHITE;
|
||||
castlingAvailability = "KQkq";
|
||||
halfmoveClock = 0;
|
||||
fullmoveNumber = 1;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -51,11 +47,18 @@ public class FENString {
|
||||
public FENString(String fen) throws ChessException {
|
||||
// Check fen string against regex
|
||||
Pattern fenPattern = Pattern.compile(
|
||||
"^(?<PIECE_PLACEMENT>(?:[1-8nbrqkpNBRQKP]{1,8}\\/){7}[1-8nbrqkpNBRQKP]{1,8}) (?<ACTIVE_COLOR>[wb]) (?<CASTLING_AVAILABILITY>-|[KQkq]{1,4}) (?<EN_PASSANT_TARGET_SQUARE>-|[a-h][1-8]) (?<HALFMOVE_CLOCK>\\d) (?<FULLMOVE_NUMBER>\\d)$");
|
||||
"^(?<piecePlacement>(?:[1-8nbrqkpNBRQKP]{1,8}\\/){7}[1-8nbrqkpNBRQKP]{1,8}) (?<activeColor>[wb]) (?<castlingAvailability>-|[KQkq]{1,4}) (?<enPassantTargetSquare>-|[a-h][1-8]) (?<halfmoveClock>\\d) (?<fullmoveNumber>\\d)$");
|
||||
Matcher matcher = fenPattern.matcher(fen);
|
||||
if (!matcher.find()) throw new ChessException("FEN string does not match pattern " + fenPattern.pattern());
|
||||
for (FENField field : FENField.values())
|
||||
fields.put(field, matcher.group(field.toString()));
|
||||
|
||||
// Initialize data fields
|
||||
piecePlacement = matcher.group("piecePlacement");
|
||||
activeColor = Color.fromFirstChar(matcher.group("activeColor").charAt(0));
|
||||
castlingAvailability = matcher.group("castlingAvailability");
|
||||
if (!matcher.group("enPassantTargetSquare").equals("-"))
|
||||
enPassantTargetSquare = Position.fromLAN(matcher.group("enPassantTargetSquare"));
|
||||
halfmoveClock = Integer.parseInt(matcher.group("halfmoveClock"));
|
||||
fullmoveNumber = Integer.parseInt(matcher.group("fullmoveNumber"));
|
||||
|
||||
// Initialize and clean board
|
||||
board = new Board();
|
||||
@ -66,7 +69,7 @@ public class FENString {
|
||||
// Parse individual fields
|
||||
|
||||
// Piece placement
|
||||
final String[] rows = fields.get(FENField.PIECE_PLACEMENT).split("/");
|
||||
final String[] rows = piecePlacement.split("/");
|
||||
if (rows.length != 8) throw new ChessException("FEN string contains invalid piece placement");
|
||||
for (int i = 0; i < 8; i++) {
|
||||
final char[] cols = rows[i].toCharArray();
|
||||
@ -80,22 +83,22 @@ public class FENString {
|
||||
Color color = Character.isUpperCase(c) ? Color.WHITE : Color.BLACK;
|
||||
switch (Character.toUpperCase(c)) {
|
||||
case 'K':
|
||||
board.getBoardArr()[i][j] = new King(color, board);
|
||||
board.getBoardArr()[j][i] = new King(color, board);
|
||||
break;
|
||||
case 'Q':
|
||||
board.getBoardArr()[i][j] = new Queen(color, board);
|
||||
board.getBoardArr()[j][i] = new Queen(color, board);
|
||||
break;
|
||||
case 'R':
|
||||
board.getBoardArr()[i][j] = new Rook(color, board);
|
||||
board.getBoardArr()[j][i] = new Rook(color, board);
|
||||
break;
|
||||
case 'N':
|
||||
board.getBoardArr()[i][j] = new Knight(color, board);
|
||||
board.getBoardArr()[j][i] = new Knight(color, board);
|
||||
break;
|
||||
case 'B':
|
||||
board.getBoardArr()[i][j] = new Bishop(color, board);
|
||||
board.getBoardArr()[j][i] = new Bishop(color, board);
|
||||
break;
|
||||
case 'P':
|
||||
board.getBoardArr()[i][j] = new Pawn(color, board);
|
||||
board.getBoardArr()[j][i] = new Pawn(color, board);
|
||||
break;
|
||||
}
|
||||
++j;
|
||||
@ -104,19 +107,18 @@ public class FENString {
|
||||
}
|
||||
|
||||
// Active color
|
||||
board.getLog().setActiveColor(Color.fromFirstChar(fields.get(FENField.ACTIVE_COLOR).charAt(0)));
|
||||
board.getLog().setActiveColor(activeColor);
|
||||
|
||||
// TODO: Castling availability
|
||||
|
||||
// En passant square
|
||||
if (!fields.get(FENField.EN_PASSANT_TARGET_SQUARE).equals("-"))
|
||||
board.getLog().setEnPassant(Position.fromLAN(fields.get(FENField.EN_PASSANT_TARGET_SQUARE)));
|
||||
board.getLog().setEnPassant(enPassantTargetSquare);
|
||||
|
||||
// Halfmove clock
|
||||
board.getLog().setHalfmoveClock(Integer.parseInt(fields.get(FENField.HALFMOVE_CLOCK)));
|
||||
board.getLog().setHalfmoveClock(halfmoveClock);
|
||||
|
||||
// Fullmove number
|
||||
board.getLog().setFullmoveNumber(Integer.parseInt(fields.get(FENField.FULLMOVE_NUMBER)));
|
||||
board.getLog().setFullmoveNumber(fullmoveNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,7 +136,7 @@ public class FENString {
|
||||
for (int i = 0; i < 8; i++) {
|
||||
int empty = 0;
|
||||
for (int j = 0; j < 8; j++) {
|
||||
final Piece piece = board.getBoardArr()[i][j];
|
||||
final Piece piece = board.getBoardArr()[j][i];
|
||||
|
||||
if (piece == null) ++empty;
|
||||
else {
|
||||
@ -159,22 +161,21 @@ public class FENString {
|
||||
|
||||
if (i < 7) sb.append('/');
|
||||
}
|
||||
fields.put(FENField.PIECE_PLACEMENT, sb.toString());
|
||||
piecePlacement = sb.toString();
|
||||
|
||||
// Active color
|
||||
fields.put(FENField.ACTIVE_COLOR, String.valueOf(board.getLog().getActiveColor().firstChar()));
|
||||
activeColor = board.getLog().getActiveColor();
|
||||
|
||||
// TODO: Castling availability
|
||||
|
||||
// En passant availability
|
||||
final Position enPassantPosition = board.getLog().getEnPassant();
|
||||
fields.put(FENField.EN_PASSANT_TARGET_SQUARE, enPassantPosition == null ? "-" : enPassantPosition.toLAN());
|
||||
enPassantTargetSquare = board.getLog().getEnPassant();
|
||||
|
||||
// Halfmove clock
|
||||
fields.put(FENField.HALFMOVE_CLOCK, String.valueOf(board.getLog().getHalfmoveClock()));
|
||||
halfmoveClock = board.getLog().getHalfmoveClock();
|
||||
|
||||
// Fullmove counter
|
||||
fields.put(FENField.FULLMOVE_NUMBER, String.valueOf(board.getLog().getFullmoveNumber()));
|
||||
fullmoveNumber = board.getLog().getFullmoveNumber();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -184,7 +185,13 @@ public class FENString {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.join(" ", fields.values());
|
||||
return String.format("%s %c %s %s %d %d",
|
||||
piecePlacement,
|
||||
activeColor.firstChar(),
|
||||
castlingAvailability,
|
||||
enPassantTargetSquare == null ? "-" : enPassantTargetSquare.toLAN(),
|
||||
halfmoveClock,
|
||||
fullmoveNumber);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,7 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Objects;
|
||||
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
|
||||
@ -160,6 +161,22 @@ public class Log implements Iterable<MoveNode> {
|
||||
halfmoveClock = current.halfmoveClock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(activeColor, current, enPassant, fullmoveNumber, halfmoveClock, root);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
Log other = (Log) obj;
|
||||
return activeColor == other.activeColor && Objects.equals(current, other.current)
|
||||
&& Objects.equals(enPassant, other.enPassant) && fullmoveNumber == other.fullmoveNumber
|
||||
&& halfmoveClock == other.halfmoveClock && Objects.equals(root, other.root);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The first logged move, or {@code null} if there is none
|
||||
*/
|
||||
|
@ -1,5 +1,7 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>Move.java</strong><br>
|
||||
@ -50,6 +52,21 @@ public class Move {
|
||||
return String.format("%s -> %s", pos, dest);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(dest, pos, type, xDist, xSign, yDist, ySign);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
Move other = (Move) obj;
|
||||
return Objects.equals(dest, other.dest) && Objects.equals(pos, other.pos) && type == other.type
|
||||
&& xDist == other.xDist && xSign == other.xSign && yDist == other.yDist && ySign == other.ySign;
|
||||
}
|
||||
|
||||
public static enum Type {
|
||||
NORMAL, PAWN_PROMOTION, CASTLING, EN_PASSANT, UNKNOWN
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package dev.kske.chess.board;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
|
||||
@ -93,4 +94,22 @@ public class MoveNode {
|
||||
public boolean hasParent() {
|
||||
return parent != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects
|
||||
.hash(activeColor, capturedPiece, enPassant, fullmoveCounter, halfmoveClock, move, parent, variations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
MoveNode other = (MoveNode) obj;
|
||||
return activeColor == other.activeColor && Objects.equals(capturedPiece, other.capturedPiece)
|
||||
&& Objects.equals(enPassant, other.enPassant) && fullmoveCounter == other.fullmoveCounter
|
||||
&& halfmoveClock == other.halfmoveClock && Objects.equals(move, other.move)
|
||||
&& Objects.equals(parent, other.parent) && Objects.equals(variations, other.variations);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package dev.kske.chess.board;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
@ -84,6 +85,20 @@ public abstract class Piece implements Cloneable {
|
||||
--moveCounter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(color, moveCounter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
Piece other = (Piece) obj;
|
||||
return color == other.color && moveCounter == other.moveCounter;
|
||||
}
|
||||
|
||||
public static enum Type {
|
||||
|
||||
KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN;
|
||||
|
72
test/dev/kske/chess/board/FENStringTest.java
Normal file
72
test/dev/kske/chess/board/FENStringTest.java
Normal file
@ -0,0 +1,72 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import dev.kske.chess.board.Piece.Color;
|
||||
import dev.kske.chess.exception.ChessException;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
* File: <strong>FENStringTest.java</strong><br>
|
||||
* Created: <strong>24 Oct 2019</strong><br>
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
class FENStringTest {
|
||||
|
||||
List<String> fenStrings = new ArrayList<>();
|
||||
List<Board> boards = new ArrayList<>();
|
||||
|
||||
void cleanBoard(Board board) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++)
|
||||
board.getBoardArr()[i][j] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws java.lang.Exception
|
||||
*/
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
fenStrings.addAll(Arrays.asList("rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b - - 1 2"));
|
||||
Board board = new Board();
|
||||
board.set(Position.fromLAN("c7"), null);
|
||||
board.set(Position.fromLAN("c5"), new Pawn(Color.BLACK, board));
|
||||
board.set(Position.fromLAN("e4"), new Pawn(Color.WHITE, board));
|
||||
board.set(Position.fromLAN("f3"), new Knight(Color.WHITE, board));
|
||||
board.set(Position.fromLAN("e2"), null);
|
||||
board.set(Position.fromLAN("g1"), null);
|
||||
|
||||
board.getLog().setActiveColor(Color.BLACK);
|
||||
board.getLog().setHalfmoveClock(1);
|
||||
board.getLog().setFullmoveNumber(2);
|
||||
boards.add(board);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.FENString#toString()}.
|
||||
*/
|
||||
@Test
|
||||
void testToString() {
|
||||
for (int i = 0; i < fenStrings.size(); i++)
|
||||
assertEquals(fenStrings.get(i), new FENString(boards.get(i)).toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.board.FENString#getBoard()}.
|
||||
*
|
||||
* @throws ChessException
|
||||
*/
|
||||
@Test
|
||||
void testGetBoard() throws ChessException {
|
||||
for (int i = 0; i < boards.size(); i++)
|
||||
assertEquals(boards.get(i), new FENString(fenStrings.get(i)).getBoard());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user