diff --git a/.classpath b/.classpath
index e4beaea..8469573 100644
--- a/.classpath
+++ b/.classpath
@@ -7,6 +7,11 @@
+
+
+
+
+
diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java
index 435b371..fbab10d 100644
--- a/src/dev/kske/chess/board/Board.java
+++ b/src/dev/kske/chess/board/Board.java
@@ -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;
@@ -18,63 +20,48 @@ import dev.kske.chess.board.Piece.Type;
*/
public class Board {
- private Piece[][] boardArr = new Piece[8][8];
- private Map kingPos = new HashMap<>();
- private Map> castlingRights = new HashMap<>();
- private Log log = new Log();
+ private Piece[][] boardArr = new Piece[8][8];
+ private Map kingPos = new HashMap<>();
+ private Log log = new Log();
private static final Map positionScores;
static {
positionScores = new HashMap<>();
positionScores.put(Type.KING,
- new int[][] { new int[] { -3, -4, -4, -5, -5, -4, -4, -3 },
+ new int[][] { new int[] { -3, -4, -4, -5, -5, -4, -4, -3 }, new int[] { -3, -4, -4, -5, -4, -4, -4, -3 },
new int[] { -3, -4, -4, -5, -4, -4, -4, -3 }, new int[] { -3, -4, -4, -5, -4, -4, -4, -3 },
- new int[] { -3, -4, -4, -5, -4, -4, -4, -3 }, new int[] { -2, -3, -3, -2, -2, -2, -2, -1 },
- new int[] { -1, -2, -2, -2, -2, -2, -2, -1 }, new int[] { 2, 2, 0, 0, 0, 0, 2, 2 },
- new int[] { 2, 3, 1, 0, 0, 1, 3, 2 } });
+ new int[] { -2, -3, -3, -2, -2, -2, -2, -1 }, new int[] { -1, -2, -2, -2, -2, -2, -2, -1 },
+ new int[] { 2, 2, 0, 0, 0, 0, 2, 2 }, new int[] { 2, 3, 1, 0, 0, 1, 3, 2 } });
positionScores.put(Type.QUEEN,
new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, -2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
- new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 },
- new int[] { 0, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 0, -1 },
- new int[] { -1, 0, 1, 0, 0, 0, 0, -1 }, new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
+ new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { 0, 0, 1, 1, 1, 1, 0, -1 },
+ new int[] { -1, 1, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 0, 0, 0, 0, -1 },
+ new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
positionScores.put(Type.ROOK,
- new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 1, 1, 1, 1, 1, 1, 1, 1 },
- new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
- new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
+ new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 1, 1, 1, 1, 1, 1, 1, 1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
+ new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { 0, 0, 0, 1, 1, 0, 0, 0 } });
positionScores.put(Type.KNIGHT,
new int[][] { new int[] { -5, -4, -3, -3, -3, -3, -4, -5 }, new int[] { -4, -2, 0, 0, 0, 0, -2, -4 },
- new int[] { -3, 0, 1, 2, 2, 1, 0, -3 }, new int[] { -3, 1, 2, 2, 2, 2, 1, -3 },
- new int[] { -3, 0, 2, 2, 2, 2, 0, -1 }, new int[] { -3, 1, 1, 2, 2, 1, 1, -3 },
- new int[] { -4, -2, 0, 1, 1, 0, -2, -4 }, new int[] { -5, -4, -3, -3, -3, -3, -4, -5 } });
+ new int[] { -3, 0, 1, 2, 2, 1, 0, -3 }, new int[] { -3, 1, 2, 2, 2, 2, 1, -3 }, new int[] { -3, 0, 2, 2, 2, 2, 0, -1 },
+ new int[] { -3, 1, 1, 2, 2, 1, 1, -3 }, new int[] { -4, -2, 0, 1, 1, 0, -2, -4 },
+ new int[] { -5, -4, -3, -3, -3, -3, -4, -5 } });
positionScores.put(Type.BISHOP,
new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, 2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
- new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 1, -1 },
- new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 1, -1 },
- new int[] { -1, 1, 0, 0, 0, 0, 1, -1 }, new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
+ new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 1, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 },
+ new int[] { -1, 1, 1, 1, 1, 1, 1, -1 }, new int[] { -1, 1, 0, 0, 0, 0, 1, -1 },
+ new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
positionScores.put(Type.PAWN,
- new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 5, 5, 5, 5, 5, 5, 5, 5 },
- new int[] { 1, 1, 2, 3, 3, 2, 1, 1 }, new int[] { 0, 0, 1, 3, 3, 1, 0, 0 },
- new int[] { 0, 0, 0, 2, 2, 0, 0, 0 }, new int[] { 0, 0, -1, 0, 0, -1, 0, 0 },
+ new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 5, 5, 5, 5, 5, 5, 5, 5 }, new int[] { 1, 1, 2, 3, 3, 2, 1, 1 },
+ new int[] { 0, 0, 1, 3, 3, 1, 0, 0 }, new int[] { 0, 0, 0, 2, 2, 0, 0, 0 }, new int[] { 0, 0, -1, 0, 0, -1, 0, 0 },
new int[] { 0, 1, 1, -2, -2, 1, 1, 0 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0 } });
}
/**
* Initializes the board with the default chess starting position.
*/
- public Board() {
- initDefaultPositions();
- }
-
- /**
- * Initializes the board with data from a FEN-string.
- *
- * @param fen The FEN-string to initialize the board from
- */
- public Board(String fen) {
- initFromFEN(fen);
- }
+ public Board() { initDefaultPositions(); }
/**
* Creates a copy of another {@link Board} instance.
@@ -93,10 +80,6 @@ public class Board {
}
kingPos.putAll(other.kingPos);
- Map whiteCastling = new HashMap<>(other.castlingRights.get(Color.WHITE)),
- blackCastling = new HashMap<>(other.castlingRights.get(Color.BLACK));
- castlingRights.put(Color.WHITE, whiteCastling);
- castlingRights.put(Color.BLACK, blackCastling);
log = new Log(other.log, false);
}
@@ -156,8 +139,6 @@ public class Board {
: new Move(0, move.pos.y, 3, move.pos.y); // Queenside
setDest(rookMove, getPos(rookMove));
setPos(rookMove, null);
-
- getDest(rookMove).incMoveCounter();
break;
case UNKNOWN:
System.err.printf("Move of unknown type %s found!%n", move);
@@ -169,17 +150,11 @@ public class Board {
System.err.printf("Move %s of unimplemented type found!%n", move);
}
- // Increment move counter
- getDest(move).incMoveCounter();
-
- // Update the king's position if the moved piece is the king and castling
- // availability
+ // Update the king's position if the moved piece is the king
if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest);
// Update log
- log.add(move, capturePiece, piece.getType() == Type.PAWN);
-
- updateCastlingRights();
+ log.add(move, piece, capturePiece);
}
/**
@@ -193,9 +168,7 @@ public class Board {
Pattern.compile(
"^(?[NBRQK])(?:(?[a-h])|(?[1-8])|(?[a-h][1-8]))?x?(?[a-h][1-8])(?:\\+{0,2}|\\#)$"));
patterns.put("pawnCapture",
- Pattern
- .compile(
- "^(?[a-h])(?[1-8])?x(?[a-h][1-8])(?[NBRQK])?(?:\\+{0,2}|\\#)?$"));
+ Pattern.compile("^(?[a-h])(?[1-8])?x(?[a-h][1-8])(?[NBRQK])?(?:\\+{0,2}|\\#)?$"));
patterns.put("pawnPush", Pattern.compile("^(?[a-h][1-8])(?[NBRQK])?(?:\\+{0,2}|\\#)$"));
patterns.put("castling", Pattern.compile("^(?O-O-O)|(?O-O)(?:\\+{0,2}|\\#)?$"));
@@ -226,8 +199,7 @@ public class Board {
case "pawnCapture":
dest = Position.fromLAN(m.group("toSquare"));
char file = m.group("fromFile").charAt(0);
- int rank = m.group("fromRank") == null ? get(Type.PAWN, file)
- : Integer.parseInt(m.group("fromRank"));
+ int rank = m.group("fromRank") == null ? get(Type.PAWN, file) : Integer.parseInt(m.group("fromRank"));
pos = Position.fromLAN(String.format("%c%d", file, rank));
break;
case "pawnPush":
@@ -280,8 +252,6 @@ public class Board {
: new Move(3, move.pos.y, 0, move.pos.y); // Queenside
setDest(rookMove, getPos(rookMove));
setPos(rookMove, null);
-
- getDest(rookMove).decMoveCounter();
break;
case UNKNOWN:
System.err.printf("Move of unknown type %s found!%n", move);
@@ -293,38 +263,11 @@ public class Board {
System.err.printf("Move %s of unimplemented type found!%n", move);
}
- // Decrement move counter
- getPos(move).decMoveCounter();
-
// Update the king's position if the moved piece is the king
if (getPos(move).getType() == Type.KING) kingPos.put(getPos(move).getColor(), move.pos);
// Update log
log.removeLast();
-
- updateCastlingRights();
- }
-
- private void updateCastlingRights() {
- // White
- if (new Position(4, 7).equals(kingPos.get(Color.WHITE))) {
- final King king = (King) get(kingPos.get(Color.WHITE));
- castlingRights.get(Color.WHITE).put(Type.KING, king.canCastleKingside());
- castlingRights.get(Color.WHITE).put(Type.QUEEN, king.canCastleQueenside());
- } else {
- castlingRights.get(Color.WHITE).put(Type.KING, false);
- castlingRights.get(Color.WHITE).put(Type.QUEEN, false);
- }
-
- // Black
- if (new Position(4, 0).equals(kingPos.get(Color.BLACK))) {
- final King king = (King) get(kingPos.get(Color.BLACK));
- castlingRights.get(Color.BLACK).put(Type.KING, king.canCastleKingside());
- castlingRights.get(Color.BLACK).put(Type.QUEEN, king.canCastleQueenside());
- } else {
- castlingRights.get(Color.BLACK).put(Type.KING, false);
- castlingRights.get(Color.BLACK).put(Type.QUEEN, false);
- }
}
/**
@@ -337,14 +280,11 @@ public class Board {
List moves = new ArrayList<>();
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
- if (boardArr[i][j] != null && boardArr[i][j].getColor() == color)
- moves.addAll(boardArr[i][j].getMoves(new Position(i, j)));
+ if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) moves.addAll(boardArr[i][j].getMoves(new Position(i, j)));
return moves;
}
- public List getMoves(Position pos) {
- return get(pos).getMoves(pos);
- }
+ public List getMoves(Position pos) { return get(pos).getMoves(pos); }
/**
* Checks, if the king is in check.
@@ -356,9 +296,7 @@ public class Board {
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++) {
Position pos = new Position(i, j);
- if (get(pos) != null && get(pos).getColor() != color
- && get(pos).isValidMove(new Move(pos, kingPos.get(color))))
- return true;
+ if (get(pos) != null && get(pos).getColor() != color && get(pos).isValidMove(new Move(pos, kingPos.get(color)))) return true;
}
return false;
}
@@ -386,8 +324,7 @@ public class Board {
public GameState getGameEventType(Color color) {
return checkCheck(color) ? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK
- : getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? GameState.STALEMATE
- : GameState.NORMAL;
+ : getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? GameState.STALEMATE : GameState.NORMAL;
}
/**
@@ -469,160 +406,32 @@ public class Board {
for (int j = 2; j < 6; j++)
boardArr[i][j] = null;
- // Initialize castling rights
- Map whiteCastling = new HashMap<>(), blackCastling = new HashMap<>();
- whiteCastling.put(Type.KING, true);
- whiteCastling.put(Type.QUEEN, true);
- blackCastling.put(Type.KING, true);
- blackCastling.put(Type.QUEEN, true);
- castlingRights.put(Color.WHITE, whiteCastling);
- castlingRights.put(Color.BLACK, blackCastling);
-
log.reset();
}
- /**
- * Initialized the board with a position specified in a FEN-encoded string.
- *
- * @param fen The FEN-encoded string representing target state of the board
- */
- public void initFromFEN(String fen) {
- String[] parts = fen.split(" ");
- log.reset();
-
- // Piece placement (from white's perspective)
- String[] rows = parts[0].split("/");
- for (int i = 0; i < 8; i++) {
- char[] places = rows[i].toCharArray();
- for (int j = 0, k = 0; k < places.length; j++, k++) {
- if (Character.isDigit(places[k])) {
- for (int l = j; l < Character.digit(places[k], 10); l++, j++)
- boardArr[j][i] = null;
- --j;
- } else {
- Color color = Character.isUpperCase(places[k]) ? Color.WHITE : Color.BLACK;
- switch (Character.toLowerCase(places[k])) {
- case 'k':
- boardArr[j][i] = new King(color, this);
- kingPos.put(color, new Position(j, i));
- break;
- case 'q':
- boardArr[j][i] = new Queen(color, this);
- break;
- case 'r':
- boardArr[j][i] = new Rook(color, this);
- break;
- case 'n':
- boardArr[j][i] = new Knight(color, this);
- break;
- case 'b':
- boardArr[j][i] = new Bishop(color, this);
- break;
- case 'p':
- boardArr[j][i] = new Pawn(color, this);
- break;
- default:
- System.err.printf("Unknown character '%c' in board declaration of FEN string '%s'%n",
- places[k],
- fen);
- }
- }
- }
- }
-
- // Active color
- log.setActiveColor(Color.fromFirstChar(parts[1].charAt(0)));
-
- // Castling rights
- Map whiteCastling = new HashMap<>(), blackCastling = new HashMap<>();
- for (char c : parts[2].toCharArray())
- switch (c) {
- case 'K':
- whiteCastling.put(Type.KING, true);
- case 'Q':
- whiteCastling.put(Type.QUEEN, true);
- case 'k':
- blackCastling.put(Type.KING, true);
- case 'q':
- blackCastling.put(Type.QUEEN, true);
- case '-':
- break;
- default:
- System.err
- .printf("Unknown character '%c' in castling rights declaration of FEN string '%s'", c, fen);
- }
- castlingRights.put(Color.WHITE, whiteCastling);
- castlingRights.put(Color.BLACK, blackCastling);
-
- // En passant availability
- if (!parts[3].equals("-")) log.setEnPassant(Position.fromLAN(parts[3]));
-
- // Halfmove clock
- log.setHalfmoveClock(Integer.parseInt(parts[4]));
-
- // Fullmove counter
- log.setFullmoveCounter(Integer.parseInt(parts[5]));
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.deepHashCode(boardArr);
+ result = prime * result + Objects.hash(kingPos, log);
+ return result;
}
- /**
- * @return a FEN-encoded string representing the board
- */
- public String toFEN() {
- StringBuilder sb = new StringBuilder();
-
- // Piece placement (from white's perspective)
- for (int i = 0; i < 8; i++) {
- int emptyCount = 0;
- 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
- if (emptyCount != 0) {
- sb.append(emptyCount);
- emptyCount = 0;
- }
- char p = boardArr[j][i].getType().firstChar();
- sb.append(piece.getColor() == Color.WHITE ? Character.toUpperCase(p) : p);
- }
- }
- if (emptyCount != 0) sb.append(emptyCount);
- if (i < 7) sb.append('/');
- }
-
- // Active color
- sb.append(" " + log.getActiveColor().firstChar());
-
- // Castling Rights
- sb.append(' ');
- StringBuilder castlingSb = new StringBuilder();
- if (castlingRights.get(Color.WHITE).get(Type.KING)) castlingSb.append('K');
- if (castlingRights.get(Color.WHITE).get(Type.QUEEN)) castlingSb.append('Q');
- if (castlingRights.get(Color.BLACK).get(Type.KING)) castlingSb.append('k');
- if (castlingRights.get(Color.BLACK).get(Type.QUEEN)) castlingSb.append('q');
- if (castlingSb.length() == 0) sb.append("-");
- sb.append(castlingSb);
-
- final MoveNode lastMove = log.getLast();
-
- // En passant availability
- sb.append(" " + (lastMove == null || lastMove.enPassant == null ? "-" : lastMove.enPassant.toLAN()));
-
- // Halfmove clock
- sb.append(" " + String.valueOf(lastMove == null ? 0 : lastMove.halfmoveClock));
-
- // Fullmove counter
- sb.append(" " + String.valueOf(lastMove == null ? 1 : lastMove.fullmoveCounter));
-
- return sb.toString();
+ @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(kingPos, other.kingPos) && Objects.equals(log, other.log);
}
/**
* @param pos The position from which to return a piece
* @return The piece at the position
*/
- public Piece get(Position pos) {
- return boardArr[pos.x][pos.y];
- }
+ public Piece get(Position pos) { return boardArr[pos.x][pos.y]; }
/**
* Searches for a {@link Piece} inside a file (A - H).
@@ -635,9 +444,7 @@ public class Board {
public int get(Type type, char file) {
int x = file - 97;
for (int i = 0; i < 8; i++)
- if (boardArr[x][i] != null && boardArr[x][i].getType() == type
- && boardArr[x][i].getColor() == log.getActiveColor())
- return 8 - i;
+ if (boardArr[x][i] != null && boardArr[x][i].getType() == type && boardArr[x][i].getColor() == log.getActiveColor()) return 8 - i;
return -1;
}
@@ -652,8 +459,7 @@ public class Board {
public char get(Type type, int rank) {
int y = rank - 1;
for (int i = 0; i < 8; i++)
- if (boardArr[i][y] != null && boardArr[i][y].getType() == type
- && boardArr[i][y].getColor() == log.getActiveColor())
+ if (boardArr[i][y] != null && boardArr[i][y].getType() == type && boardArr[i][y].getColor() == log.getActiveColor())
return (char) (i + 97);
return '-';
}
@@ -668,8 +474,7 @@ public class Board {
public Position get(Type type, Position dest) {
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
- if (boardArr[i][j] != null && boardArr[i][j].getType() == type
- && boardArr[i][j].getColor() == log.getActiveColor()) {
+ if (boardArr[i][j] != null && boardArr[i][j].getType() == type && boardArr[i][j].getColor() == log.getActiveColor()) {
Position pos = new Position(i, j);
if (boardArr[i][j].isValidMove(new Move(pos, dest))) return pos;
}
@@ -682,25 +487,19 @@ public class Board {
* @param pos The position to place the piece at
* @param piece The piece to place
*/
- public void set(Position pos, Piece piece) {
- boardArr[pos.x][pos.y] = piece;
- }
+ public void set(Position pos, Piece piece) { boardArr[pos.x][pos.y] = piece; }
/**
* @param move The move from which position to return a piece
* @return The piece at the position of the move
*/
- public Piece getPos(Move move) {
- return get(move.pos);
- }
+ public Piece getPos(Move move) { return get(move.pos); }
/**
* @param move The move from which destination to return a piece
* @return The piece at the destination of the move
*/
- public Piece getDest(Move move) {
- return get(move.dest);
- }
+ public Piece getDest(Move move) { return get(move.dest); }
/**
* Places a piece at the position of a move.
@@ -708,9 +507,7 @@ public class Board {
* @param move The move at which position to place the piece
* @param piece The piece to place
*/
- public void setPos(Move move, Piece piece) {
- set(move.pos, piece);
- }
+ public void setPos(Move move, Piece piece) { set(move.pos, piece); }
/**
* Places a piece at the destination of a move.
@@ -718,9 +515,7 @@ public class Board {
* @param move The move at which destination to place the piece
* @param piece The piece to place
*/
- public void setDest(Move move, Piece piece) {
- set(move.dest, piece);
- }
+ public void setDest(Move move, Piece piece) { set(move.dest, piece); }
/**
* @return The board array
diff --git a/src/dev/kske/chess/board/FENString.java b/src/dev/kske/chess/board/FENString.java
new file mode 100644
index 0000000..536cecf
--- /dev/null
+++ b/src/dev/kske/chess/board/FENString.java
@@ -0,0 +1,222 @@
+package dev.kske.chess.board;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import dev.kske.chess.board.Piece.Color;
+import dev.kske.chess.exception.ChessException;
+
+/**
+ * Project: Chess
+ * File: FENString.java
+ * Created: 20 Oct 2019
+ *
+ * Represents a FEN string and enables parsing an existing FEN string or
+ * serializing a {@link Board} to one.
+ *
+ * @author Kai S. K. Engelbart
+ * @since Chess v0.4-alpha
+ */
+public class FENString {
+
+ private Board board;
+ private String piecePlacement, castlingAvailability;
+ private int halfmoveClock, fullmoveNumber;
+ private Color activeColor;
+ private Position enPassantTargetSquare;
+
+ /**
+ * Constructs a {@link FENString} representing the starting position
+ * {@code rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1}.
+ */
+ public FENString() {
+ board = new Board();
+ piecePlacement = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
+ activeColor = Color.WHITE;
+ castlingAvailability = "KQkq";
+ halfmoveClock = 0;
+ fullmoveNumber = 1;
+ }
+
+ /**
+ * Constructs a {@link FENString} by parsing an existing string.
+ *
+ * @param fen the FEN string to parse
+ * @throws ChessException
+ */
+ public FENString(String fen) throws ChessException {
+ // Check fen string against regex
+ Pattern fenPattern = Pattern.compile(
+ "^(?(?:[1-8nbrqkpNBRQKP]{1,8}\\/){7}[1-8nbrqkpNBRQKP]{1,8}) (?[wb]) (?-|[KQkq]{1,4}) (?-|[a-h][1-8]) (?\\d+) (?\\d+)$");
+ Matcher matcher = fenPattern.matcher(fen);
+ if (!matcher.find()) throw new ChessException("FEN string does not match pattern " + fenPattern.pattern());
+
+ // 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();
+ for (int i = 0; i < 8; i++)
+ for (int j = 0; j < 8; j++)
+ board.getBoardArr()[i][j] = null;
+
+ // Parse individual fields
+
+ // Piece placement
+ 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();
+ int j = 0;
+ for (char c : cols) {
+
+ // Empty space
+ if (Character.isDigit(c)) {
+ j += Character.getNumericValue(c);
+ } else {
+ Color color = Character.isUpperCase(c) ? Color.WHITE : Color.BLACK;
+ switch (Character.toUpperCase(c)) {
+ case 'K':
+ board.getBoardArr()[j][i] = new King(color, board);
+ break;
+ case 'Q':
+ board.getBoardArr()[j][i] = new Queen(color, board);
+ break;
+ case 'R':
+ board.getBoardArr()[j][i] = new Rook(color, board);
+ break;
+ case 'N':
+ board.getBoardArr()[j][i] = new Knight(color, board);
+ break;
+ case 'B':
+ board.getBoardArr()[j][i] = new Bishop(color, board);
+ break;
+ case 'P':
+ board.getBoardArr()[j][i] = new Pawn(color, board);
+ break;
+ }
+ ++j;
+ }
+ }
+ }
+
+ // Active color
+ board.getLog().setActiveColor(activeColor);
+
+ // Castling availability
+ boolean castlingRights[] = new boolean[4];
+ for (char c : castlingAvailability.toCharArray())
+ switch (c) {
+ case 'K':
+ castlingRights[MoveNode.WHITE_KINGSIDE] = true;
+ break;
+ case 'Q':
+ castlingRights[MoveNode.WHITE_QUEENSIDE] = true;
+ break;
+ case 'k':
+ castlingRights[MoveNode.BLACK_KINGSIDE] = true;
+ break;
+ case 'q':
+ castlingRights[MoveNode.BLACK_QUEENSIDE] = true;
+ break;
+ }
+ board.getLog().setCastlingRights(castlingRights);
+
+ // En passant square
+ board.getLog().setEnPassant(enPassantTargetSquare);
+
+ // Halfmove clock
+ board.getLog().setHalfmoveClock(halfmoveClock);
+
+ // Fullmove number
+ board.getLog().setFullmoveNumber(fullmoveNumber);
+ }
+
+ /**
+ * Constructs a {@link FENString} form a {@link Board} object.
+ *
+ * @param board the {@link Board} object to encode in this {@link FENString}
+ */
+ public FENString(Board board) {
+ this.board = board;
+
+ // Serialize individual fields
+
+ // Piece placement
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 8; i++) {
+ int empty = 0;
+ for (int j = 0; j < 8; j++) {
+ final Piece piece = board.getBoardArr()[j][i];
+
+ if (piece == null) ++empty;
+ else {
+
+ // Write empty field count
+ if (empty > 0) {
+ sb.append(empty);
+ empty = 0;
+ }
+
+ // Write piece character
+ char p = piece.getType().firstChar();
+ sb.append(piece.getColor() == Color.WHITE ? Character.toUpperCase(p) : p);
+ }
+ }
+
+ // Write empty field count
+ if (empty > 0) {
+ sb.append(empty);
+ empty = 0;
+ }
+
+ if (i < 7) sb.append('/');
+ }
+ piecePlacement = sb.toString();
+
+ // Active color
+ activeColor = board.getLog().getActiveColor();
+
+ // Castling availability
+ castlingAvailability = "";
+ final char castlingRightsChars[] = new char[] { 'K', 'Q', 'k', 'q' };
+ for (int i = 0; i < 4; i++)
+ if (board.getLog().getCastlingRights()[i]) castlingAvailability += castlingRightsChars[i];
+ if (castlingAvailability.isEmpty()) castlingAvailability = "-";
+
+ // En passant availability
+ enPassantTargetSquare = board.getLog().getEnPassant();
+
+ // Halfmove clock
+ halfmoveClock = board.getLog().getHalfmoveClock();
+
+ // Fullmove counter
+ fullmoveNumber = board.getLog().getFullmoveNumber();
+ }
+
+ /**
+ * Exports this {@link FENString} object to a FEN string.
+ *
+ * @return a FEN string representing the board
+ */
+ @Override
+ public String toString() {
+ return String.format("%s %c %s %s %d %d",
+ piecePlacement,
+ activeColor.firstChar(),
+ castlingAvailability,
+ enPassantTargetSquare == null ? "-" : enPassantTargetSquare.toLAN(),
+ halfmoveClock,
+ fullmoveNumber);
+ }
+
+ /**
+ * @return a {@link Board} object corresponding to this {@link FENString}
+ */
+ public Board getBoard() { return board; }
+}
diff --git a/src/dev/kske/chess/board/King.java b/src/dev/kske/chess/board/King.java
index 78f672d..de90fbf 100644
--- a/src/dev/kske/chess/board/King.java
+++ b/src/dev/kske/chess/board/King.java
@@ -11,9 +11,7 @@ import java.util.List;
*/
public class King extends Piece {
- public King(Color color, Board board) {
- super(color, board);
- }
+ public King(Color color, Board board) { super(color, board); }
@Override
public boolean isValidMove(Move move) {
@@ -33,22 +31,20 @@ public class King extends Piece {
}
public boolean canCastleKingside() {
- if (getMoveCounter() == 0) {
+ if (board.getLog().getCastlingRights()[getColor() == Color.WHITE ? MoveNode.WHITE_KINGSIDE : MoveNode.BLACK_KINGSIDE]) {
int y = getColor() == Color.WHITE ? 7 : 0;
Position rookPos = new Position(7, y);
Piece rook = board.get(rookPos);
- return rook != null && rook.getType() == Type.ROOK && rook.getMoveCounter() == 0
- && isFreePath(new Move(new Position(4, y), new Position(6, y)));
+ return rook != null && rook.getType() == Type.ROOK && isFreePath(new Move(new Position(4, y), new Position(6, y)));
} else return false;
}
public boolean canCastleQueenside() {
- if (getMoveCounter() == 0) {
+ if (board.getLog().getCastlingRights()[getColor() == Color.WHITE ? MoveNode.WHITE_QUEENSIDE : MoveNode.BLACK_QUEENSIDE]) {
int y = getColor() == Color.WHITE ? 7 : 0;
Position rookPos = new Position(0, y);
Piece rook = board.get(rookPos);
- return rook != null && rook.getType() == Type.ROOK && rook.getMoveCounter() == 0
- && isFreePath(new Move(new Position(4, y), new Position(1, y)));
+ return rook != null && rook.getType() == Type.ROOK && isFreePath(new Move(new Position(4, y), new Position(1, y)));
} else return false;
}
diff --git a/src/dev/kske/chess/board/Log.java b/src/dev/kske/chess/board/Log.java
index afa5f00..3ae372e 100644
--- a/src/dev/kske/chess/board/Log.java
+++ b/src/dev/kske/chess/board/Log.java
@@ -1,8 +1,11 @@
package dev.kske.chess.board;
+import java.util.Arrays;
import java.util.Iterator;
+import java.util.Objects;
import dev.kske.chess.board.Piece.Color;
+import dev.kske.chess.board.Piece.Type;
/**
* Project: Chess
@@ -14,13 +17,12 @@ public class Log implements Iterable {
private MoveNode root, current;
- private Position enPassant;
private Color activeColor;
- private int fullmoveCounter, halfmoveClock;
+ private boolean[] castlingRights;
+ private Position enPassant;
+ private int fullmoveNumber, halfmoveClock;
- public Log() {
- reset();
- }
+ public Log() { reset(); }
/**
* Creates a (partially deep) copy of another {@link Log} instance which begins
@@ -33,15 +35,16 @@ public class Log implements Iterable {
*/
public Log(Log other, boolean copyVariations) {
enPassant = other.enPassant;
+ castlingRights = other.castlingRights.clone();
activeColor = other.activeColor;
- fullmoveCounter = other.fullmoveCounter;
+ fullmoveNumber = other.fullmoveNumber;
halfmoveClock = other.halfmoveClock;
// The new root is the current node of the copied instance
if (!other.isEmpty()) {
- root = new MoveNode(other.current, copyVariations);
+ root = new MoveNode(other.current, copyVariations);
root.setParent(null);
- current = root;
+ current = root;
}
}
@@ -53,9 +56,7 @@ public class Log implements Iterable {
private boolean hasNext = true;
@Override
- public boolean hasNext() {
- return hasNext;
- }
+ public boolean hasNext() { return hasNext; }
@Override
public MoveNode next() {
@@ -71,16 +72,20 @@ public class Log implements Iterable {
* Adds a move to the move history and adjusts the log to the new position.
*
* @param move The move to log
+ * @param piece The piece that performed the move
* @param capturedPiece The piece captured with the move
- * @param pawnMove {@code true} if the move was made by a pawn
*/
- public void add(Move move, Piece capturedPiece, boolean pawnMove) {
- enPassant = pawnMove && move.yDist == 2 ? new Position(move.pos.x, move.pos.y + move.ySign) : null;
- if (activeColor == Color.BLACK) ++fullmoveCounter;
- if (pawnMove || capturedPiece != null) halfmoveClock = 0;
+ public void add(Move move, Piece piece, Piece capturedPiece) {
+ enPassant = piece.getType() == Type.PAWN && move.yDist == 2 ? new Position(move.pos.x, move.pos.y + move.ySign) : null;
+ if (activeColor == Color.BLACK) ++fullmoveNumber;
+ if (piece.getType() == Type.PAWN || capturedPiece != null) halfmoveClock = 0;
else++halfmoveClock;
activeColor = activeColor.opposite();
- final MoveNode leaf = new MoveNode(move, capturedPiece, enPassant, activeColor, fullmoveCounter, halfmoveClock);
+
+ // Disable castling rights if a king or a rook has been moved
+ if (piece.getType() == Type.KING || piece.getType() == Type.ROOK) disableCastlingRights(piece, move.pos);
+
+ final MoveNode leaf = new MoveNode(move, capturedPiece, castlingRights.clone(), enPassant, activeColor, fullmoveNumber, halfmoveClock);
if (isEmpty()) {
root = leaf;
@@ -105,9 +110,7 @@ public class Log implements Iterable {
public boolean isEmpty() { return root == null; }
- public boolean hasParent() {
- return !isEmpty() && current.hasParent();
- }
+ public boolean hasParent() { return !isEmpty() && current.hasParent(); }
/**
* Reverts the log to its initial state corresponding to the default board
@@ -116,15 +119,17 @@ public class Log implements Iterable {
public void reset() {
root = null;
current = null;
+ castlingRights = new boolean[] { true, true, true, true };
enPassant = null;
activeColor = Color.WHITE;
- fullmoveCounter = 1;
+ fullmoveNumber = 1;
halfmoveClock = 0;
}
/**
+ * Changes the current node to one of its children (variations).
*
- * @param index
+ * @param index the index of the variation to select
*/
public void selectNextNode(int index) {
if (!isEmpty() && current.hasVariations() && index < current.getVariations().size()) {
@@ -155,11 +160,41 @@ public class Log implements Iterable {
private void update() {
activeColor = current.activeColor;
+ castlingRights = current.castlingRights.clone();
enPassant = current.enPassant;
- fullmoveCounter = current.fullmoveCounter;
+ fullmoveNumber = current.fullmoveCounter;
halfmoveClock = current.halfmoveClock;
}
+ private void disableCastlingRights(Piece piece, Position initialPosition) {
+ // Kingside
+ if (piece.getType() == Type.KING || piece.getType() == Type.ROOK && initialPosition.x == 7)
+ castlingRights[piece.getColor() == Color.WHITE ? MoveNode.WHITE_KINGSIDE : MoveNode.BLACK_KINGSIDE] = false;
+
+ // Queenside
+ if (piece.getType() == Type.KING || piece.getType() == Type.ROOK && initialPosition.x == 0)
+ castlingRights[piece.getColor() == Color.WHITE ? MoveNode.WHITE_QUEENSIDE : MoveNode.BLACK_QUEENSIDE] = false;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(castlingRights);
+ result = prime * result + Objects.hash(activeColor, current, enPassant, fullmoveNumber, halfmoveClock);
+ return result;
+ }
+
+ @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 && Arrays.equals(castlingRights, other.castlingRights) && Objects.equals(current, other.current)
+ && Objects.equals(enPassant, other.enPassant) && fullmoveNumber == other.fullmoveNumber && halfmoveClock == other.halfmoveClock;
+ }
+
/**
* @return The first logged move, or {@code null} if there is none
*/
@@ -170,6 +205,10 @@ public class Log implements Iterable {
*/
public MoveNode getLast() { return current; }
+ public boolean[] getCastlingRights() { return castlingRights; }
+
+ public void setCastlingRights(boolean[] castlingRights) { this.castlingRights = castlingRights; }
+
public Position getEnPassant() { return enPassant; }
public void setEnPassant(Position enPassant) { this.enPassant = enPassant; }
@@ -178,9 +217,9 @@ public class Log implements Iterable {
public void setActiveColor(Color activeColor) { this.activeColor = activeColor; }
- public int getFullmoveCounter() { return fullmoveCounter; }
+ public int getFullmoveNumber() { return fullmoveNumber; }
- public void setFullmoveCounter(int fullmoveCounter) { this.fullmoveCounter = fullmoveCounter; }
+ public void setFullmoveNumber(int fullmoveCounter) { this.fullmoveNumber = fullmoveCounter; }
public int getHalfmoveClock() { return halfmoveClock; }
diff --git a/src/dev/kske/chess/board/Move.java b/src/dev/kske/chess/board/Move.java
index 74f8cb7..34cacf9 100644
--- a/src/dev/kske/chess/board/Move.java
+++ b/src/dev/kske/chess/board/Move.java
@@ -1,5 +1,7 @@
package dev.kske.chess.board;
+import java.util.Objects;
+
/**
* Project: Chess
* File: Move.java
@@ -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
}
diff --git a/src/dev/kske/chess/board/MoveNode.java b/src/dev/kske/chess/board/MoveNode.java
index 6d4afad..600a0c9 100644
--- a/src/dev/kske/chess/board/MoveNode.java
+++ b/src/dev/kske/chess/board/MoveNode.java
@@ -1,7 +1,9 @@
package dev.kske.chess.board;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
+import java.util.Objects;
import dev.kske.chess.board.Piece.Color;
@@ -13,8 +15,11 @@ import dev.kske.chess.board.Piece.Color;
*/
public class MoveNode {
+ public static final int WHITE_KINGSIDE = 0, WHITE_QUEENSIDE = 1, BLACK_KINGSIDE = 2, BLACK_QUEENSIDE = 3;
+
public final Move move;
public final Piece capturedPiece;
+ public final boolean[] castlingRights;
public final Position enPassant;
public final Color activeColor;
public final int fullmoveCounter, halfmoveClock;
@@ -33,10 +38,11 @@ public class MoveNode {
* @param fullmoveCounter
* @param halfmoveClock
*/
- public MoveNode(Move move, Piece capturedPiece, Position enPassant, Color activeColor, int fullmoveCounter,
- int halfmoveClock) {
+ public MoveNode(Move move, Piece capturedPiece, boolean castlingRights[], Position enPassant, Color activeColor,
+ int fullmoveCounter, int halfmoveClock) {
this.move = move;
this.capturedPiece = capturedPiece;
+ this.castlingRights = castlingRights;
this.enPassant = enPassant;
this.activeColor = activeColor;
this.fullmoveCounter = fullmoveCounter;
@@ -52,8 +58,8 @@ public class MoveNode {
* considers subsequent variations
*/
public MoveNode(MoveNode other, boolean copyVariations) {
- this(other.move, other.capturedPiece, other.enPassant, other.activeColor, other.fullmoveCounter,
- other.halfmoveClock);
+ this(other.move, other.capturedPiece, other.castlingRights.clone(), other.enPassant, other.activeColor,
+ other.fullmoveCounter, other.halfmoveClock);
if (copyVariations && other.variations != null) {
if (variations == null) variations = new ArrayList<>();
other.variations.forEach(variation -> {
@@ -93,4 +99,26 @@ public class MoveNode {
public boolean hasParent() {
return parent != null;
}
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + Arrays.hashCode(castlingRights);
+ result = prime * result
+ + Objects.hash(activeColor, capturedPiece, enPassant, fullmoveCounter, halfmoveClock, move);
+ return result;
+ }
+
+ @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)
+ && Arrays.equals(castlingRights, other.castlingRights) && Objects.equals(enPassant, other.enPassant)
+ && fullmoveCounter == other.fullmoveCounter && halfmoveClock == other.halfmoveClock
+ && Objects.equals(move, other.move);
+ }
}
\ No newline at end of file
diff --git a/src/dev/kske/chess/board/Piece.java b/src/dev/kske/chess/board/Piece.java
index e9ca766..ef4ea90 100644
--- a/src/dev/kske/chess/board/Piece.java
+++ b/src/dev/kske/chess/board/Piece.java
@@ -2,6 +2,7 @@ package dev.kske.chess.board;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
/**
* Project: Chess
@@ -13,7 +14,6 @@ public abstract class Piece implements Cloneable {
private final Color color;
protected Board board;
- private int moveCounter;
public Piece(Color color, Board board) {
this.color = color;
@@ -74,14 +74,18 @@ public abstract class Piece implements Cloneable {
public Color getColor() { return color; }
- public int getMoveCounter() { return moveCounter; }
-
- public void incMoveCounter() {
- ++moveCounter;
+ @Override
+ public int hashCode() {
+ return Objects.hash(color);
}
- public void decMoveCounter() {
- --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;
}
public static enum Type {
diff --git a/src/dev/kske/chess/game/Game.java b/src/dev/kske/chess/game/Game.java
index 13cf92b..feb8781 100644
--- a/src/dev/kske/chess/game/Game.java
+++ b/src/dev/kske/chess/game/Game.java
@@ -12,10 +12,10 @@ import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.event.EventBus;
import dev.kske.chess.event.MoveEvent;
import dev.kske.chess.game.ai.AIPlayer;
+import dev.kske.chess.io.EngineUtil;
+import dev.kske.chess.io.EngineUtil.EngineInfo;
import dev.kske.chess.ui.BoardComponent;
import dev.kske.chess.ui.BoardPane;
-import dev.kske.chess.ui.EngineUtil;
-import dev.kske.chess.ui.EngineUtil.EngineInfo;
import dev.kske.chess.ui.OverlayComponent;
/**
diff --git a/src/dev/kske/chess/game/UCIPlayer.java b/src/dev/kske/chess/game/UCIPlayer.java
index 12b73d3..369c16d 100644
--- a/src/dev/kske/chess/game/UCIPlayer.java
+++ b/src/dev/kske/chess/game/UCIPlayer.java
@@ -2,6 +2,7 @@ package dev.kske.chess.game;
import java.io.IOException;
+import dev.kske.chess.board.FENString;
import dev.kske.chess.board.Move;
import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.uci.UCIHandle;
@@ -30,7 +31,7 @@ public class UCIPlayer extends Player implements UCIListener {
@Override
public void requestMove() {
- handle.positionFEN(board.toFEN());
+ handle.positionFEN(new FENString(board).toString());
handle.go();
}
diff --git a/src/dev/kske/chess/ui/EngineUtil.java b/src/dev/kske/chess/io/EngineUtil.java
similarity index 95%
rename from src/dev/kske/chess/ui/EngineUtil.java
rename to src/dev/kske/chess/io/EngineUtil.java
index b56c416..608006e 100644
--- a/src/dev/kske/chess/ui/EngineUtil.java
+++ b/src/dev/kske/chess/io/EngineUtil.java
@@ -1,4 +1,4 @@
-package dev.kske.chess.ui;
+package dev.kske.chess.io;
import java.io.FileInputStream;
import java.io.FileOutputStream;
diff --git a/src/dev/kske/chess/ui/TextureUtil.java b/src/dev/kske/chess/io/TextureUtil.java
similarity index 95%
rename from src/dev/kske/chess/ui/TextureUtil.java
rename to src/dev/kske/chess/io/TextureUtil.java
index d7e6910..58fc621 100644
--- a/src/dev/kske/chess/ui/TextureUtil.java
+++ b/src/dev/kske/chess/io/TextureUtil.java
@@ -1,4 +1,4 @@
-package dev.kske.chess.ui;
+package dev.kske.chess.io;
import java.awt.Image;
import java.awt.image.BufferedImage;
diff --git a/src/dev/kske/chess/pgn/PGNGame.java b/src/dev/kske/chess/pgn/PGNGame.java
index 9b56b8f..6293976 100644
--- a/src/dev/kske/chess/pgn/PGNGame.java
+++ b/src/dev/kske/chess/pgn/PGNGame.java
@@ -7,6 +7,7 @@ import java.util.regex.MatchResult;
import java.util.regex.Pattern;
import dev.kske.chess.board.Board;
+import dev.kske.chess.board.FENString;
import dev.kske.chess.exception.ChessException;
/**
@@ -26,8 +27,7 @@ public class PGNGame {
MatchResult matchResult;
Pattern tagPairPattern = Pattern.compile("\\[(\\w+) \"(.*)\"]"),
movePattern = Pattern.compile("\\d+\\.\\s+(?:(?:(\\S+)\\s+(\\S+))|(?:O-O-O)|(?:O-O))(?:\\+{0,2}|\\#)"),
- nagPattern = Pattern.compile("(\\$\\d{1,3})*"),
- terminationMarkerPattern = Pattern.compile("1-0|0-1|1\\/2-1\\/2|\\*");
+ nagPattern = Pattern.compile("(\\$\\d{1,3})*"), terminationMarkerPattern = Pattern.compile("1-0|0-1|1\\/2-1\\/2|\\*");
// Parse tag pairs
while (sc.findInLine(tagPairPattern) != null) {
@@ -48,30 +48,23 @@ public class PGNGame {
matchResult = sc.match();
if (matchResult.groupCount() > 0) for (int i = 1; i < matchResult.groupCount() + 1; i++) {
game.board.move(matchResult.group(i));
- System.out.println(game.getBoard().getLog().getLast().move.toLAN() + ": " + game.board.toFEN());
+ System.out.println(game.getBoard().getLog().getLast().move.toLAN() + ": " + new FENString(game.board).toString());
}
else break;
} else break;
}
// Parse game termination marker
- if (sc.findWithinHorizon(terminationMarkerPattern, 20) == null)
- System.err.println("Termination marker expected");
+ if (sc.findWithinHorizon(terminationMarkerPattern, 20) == null) System.err.println("Termination marker expected");
return game;
}
- public String getTag(String tagName) {
- return tagPairs.get(tagName);
- }
+ public String getTag(String tagName) { return tagPairs.get(tagName); }
- public boolean hasTag(String tagName) {
- return tagPairs.containsKey(tagName);
- }
+ public boolean hasTag(String tagName) { return tagPairs.containsKey(tagName); }
- public void setTag(String tagName, String tagValue) {
- tagPairs.put(tagName, tagValue);
- }
+ public void setTag(String tagName, String tagValue) { tagPairs.put(tagName, tagValue); }
public Board getBoard() { return board; }
}
diff --git a/src/dev/kske/chess/ui/BoardComponent.java b/src/dev/kske/chess/ui/BoardComponent.java
index 09b97d7..802af23 100644
--- a/src/dev/kske/chess/ui/BoardComponent.java
+++ b/src/dev/kske/chess/ui/BoardComponent.java
@@ -6,6 +6,7 @@ import java.awt.Graphics;
import javax.swing.JComponent;
import dev.kske.chess.board.Board;
+import dev.kske.chess.io.TextureUtil;
/**
* Project: Chess
diff --git a/src/dev/kske/chess/ui/DialogUtil.java b/src/dev/kske/chess/ui/DialogUtil.java
index a25711d..f591881 100644
--- a/src/dev/kske/chess/ui/DialogUtil.java
+++ b/src/dev/kske/chess/ui/DialogUtil.java
@@ -15,6 +15,9 @@ import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
+import javax.swing.filechooser.FileFilter;
+
+import dev.kske.chess.io.EngineUtil;
/**
* Project: Chess
@@ -26,11 +29,25 @@ public class DialogUtil {
private DialogUtil() {}
- public static void showFileSelectionDialog(Component parent, Consumer action) {
+ public static void showFileSelectionDialog(Component parent, Consumer> action) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
- if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION)
- action.accept(fileChooser.getSelectedFile());
+ fileChooser.setAcceptAllFileFilterUsed(false);
+ fileChooser.addChoosableFileFilter(new FileFilter() {
+
+ @Override
+ public boolean accept(File f) {
+ int dotIndex = f.getName().lastIndexOf('.');
+ if (dotIndex >= 0) {
+ String extension = f.getName().substring(dotIndex).toLowerCase();
+ return extension.equals(".fen") || extension.equals(".pgn");
+ } else return f.isDirectory();
+ }
+
+ @Override
+ public String getDescription() { return "FEN and PGN files"; }
+ });
+ if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) action.accept(Arrays.asList(fileChooser.getSelectedFile()));
}
public static void showGameConfigurationDialog(Component parent, BiConsumer action) {
diff --git a/src/dev/kske/chess/ui/FENDropTarget.java b/src/dev/kske/chess/ui/FENDropTarget.java
deleted file mode 100644
index b8ffc6d..0000000
--- a/src/dev/kske/chess/ui/FENDropTarget.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package dev.kske.chess.ui;
-
-import java.awt.datatransfer.DataFlavor;
-import java.awt.datatransfer.UnsupportedFlavorException;
-import java.awt.dnd.DnDConstants;
-import java.awt.dnd.DropTargetAdapter;
-import java.awt.dnd.DropTargetDropEvent;
-import java.io.BufferedReader;
-import java.io.File;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.List;
-
-import dev.kske.chess.board.Board;
-import dev.kske.chess.game.Game;
-
-/**
- * Project: Chess
- * File: FENDropTarget.java
- * Created: 13 Aug 2019
- * Author: Kai S. K. Engelbart
- */
-public class FENDropTarget extends DropTargetAdapter {
-
- private MainWindow mainWindow;
-
- public FENDropTarget(MainWindow mainWindow) {
- this.mainWindow = mainWindow;
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public void drop(DropTargetDropEvent evt) {
- try {
- evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
- ((List) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor)).forEach(file -> {
- try (BufferedReader br = new BufferedReader(new FileReader(file))) {
- final GamePane gamePane = mainWindow.addGamePane();
- final String fen = br.readLine();
- DialogUtil.showGameConfigurationDialog(null, (whiteName, blackName) -> {
- final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, new Board(fen));
- gamePane.setGame(game);
- game.start();
- });
- evt.dropComplete(true);
- } catch (IOException e) {
- e.printStackTrace();
- evt.rejectDrop();
- }
- });
- } catch (UnsupportedFlavorException | IOException ex) {
- ex.printStackTrace();
- evt.rejectDrop();
- }
- }
-}
diff --git a/src/dev/kske/chess/ui/GameDropTarget.java b/src/dev/kske/chess/ui/GameDropTarget.java
new file mode 100644
index 0000000..c6da82b
--- /dev/null
+++ b/src/dev/kske/chess/ui/GameDropTarget.java
@@ -0,0 +1,35 @@
+package dev.kske.chess.ui;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DropTargetAdapter;
+import java.awt.dnd.DropTargetDropEvent;
+import java.io.File;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Project: Chess
+ * File: GameDropTarget.java
+ * Created: 13 Aug 2019
+ * Author: Kai S. K. Engelbart
+ */
+public class GameDropTarget extends DropTargetAdapter {
+
+ private MainWindow mainWindow;
+
+ public GameDropTarget(MainWindow mainWindow) { this.mainWindow = mainWindow; }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void drop(DropTargetDropEvent evt) {
+ try {
+ evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
+ mainWindow.loadFiles((List) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor));
+ } catch (UnsupportedFlavorException | IOException ex) {
+ ex.printStackTrace();
+ evt.rejectDrop();
+ }
+ }
+}
diff --git a/src/dev/kske/chess/ui/MainWindow.java b/src/dev/kske/chess/ui/MainWindow.java
index edaa113..cb6271f 100644
--- a/src/dev/kske/chess/ui/MainWindow.java
+++ b/src/dev/kske/chess/ui/MainWindow.java
@@ -3,10 +3,23 @@ package dev.kske.chess.ui;
import java.awt.EventQueue;
import java.awt.Toolkit;
import java.awt.dnd.DropTarget;
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.util.List;
+import javax.swing.JComboBox;
import javax.swing.JFrame;
+import javax.swing.JOptionPane;
import javax.swing.JTabbedPane;
+import dev.kske.chess.board.Board;
+import dev.kske.chess.board.FENString;
+import dev.kske.chess.exception.ChessException;
+import dev.kske.chess.game.Game;
+import dev.kske.chess.pgn.PGNDatabase;
+import dev.kske.chess.pgn.PGNGame;
+
/**
* Project: Chess
* File: MainWindow.java
@@ -55,7 +68,7 @@ public class MainWindow extends JFrame {
getContentPane().add(tabbedPane);
setJMenuBar(new MenuBar(this));
- new DropTarget(this, new FENDropTarget(this));
+ new DropTarget(this, new GameDropTarget(this));
// Update position and dimensions
pack();
@@ -74,9 +87,7 @@ public class MainWindow extends JFrame {
*
* @return The new {@link GamePane}
*/
- public GamePane addGamePane() {
- return addGamePane("Game " + (tabbedPane.getComponentCount() + 1));
- }
+ public GamePane addGamePane() { return addGamePane("Game " + (tabbedPane.getComponentCount() + 1)); }
/**
* Creates a new {@link GamePane}, adds it to the tabbed pane and opens it.
@@ -91,12 +102,64 @@ public class MainWindow extends JFrame {
return gamePane;
}
+ public GamePane addGamePane(String title, Board board) {
+ GamePane gamePane = addGamePane(title);
+ DialogUtil.showGameConfigurationDialog(this,
+ (whiteName, blackName) -> {
+ Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, board);
+ gamePane.setGame(game);
+ game.start();
+ });
+ return gamePane;
+ }
+
/**
* Removes a {@link GamePane} form the tabbed pane.
*
* @param index The index of the {@link GamePane} to remove
*/
- public void removeGamePane(int index) {
- tabbedPane.remove(index);
+ public void removeGamePane(int index) { tabbedPane.remove(index); }
+
+ /**
+ * Loads a game file (FEN or PGN) and adds it to a new {@link GamePane}.
+ *
+ * @param files the files to load the game from
+ */
+ public void loadFiles(List files) {
+ files.forEach(file -> {
+ final String name = file.getName().substring(0, file.getName().lastIndexOf('.'));
+ final String extension = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase();
+ try {
+ Board board;
+ switch (extension) {
+ case ".fen":
+ board = new FENString(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)).getBoard();
+ break;
+ case ".pgn":
+ PGNDatabase pgnDB = new PGNDatabase();
+ pgnDB.load(file);
+ if (pgnDB.getGames().size() > 0) {
+ String[] gameNames = new String[pgnDB.getGames().size()];
+ for (int i = 0; i < gameNames.length; i++) {
+ final PGNGame game = pgnDB.getGames().get(i);
+ gameNames[i] = String.format("%s vs %s: %s", game.getTag("White"), game.getTag("Black"), game.getTag("Result"));
+ }
+ JComboBox comboBox = new JComboBox<>(gameNames);
+ JOptionPane.showInputDialog(this, comboBox, "Select a game", JOptionPane.QUESTION_MESSAGE);
+ board = pgnDB.getGames().get(comboBox.getSelectedIndex()).getBoard();
+ } else throw new ChessException("The PGN database '" + name + "' is empty!");
+ break;
+ default:
+ throw new ChessException("The file extension '" + extension + "' is not supported!");
+ }
+ addGamePane(name, board);
+ } catch (Exception e) {
+ e.printStackTrace();
+ JOptionPane.showMessageDialog(this,
+ "Failed to load the file " + file.getName() + ": " + e.toString(),
+ "File loading error",
+ JOptionPane.ERROR_MESSAGE);
+ }
+ });
}
}
diff --git a/src/dev/kske/chess/ui/MenuBar.java b/src/dev/kske/chess/ui/MenuBar.java
index c7c74d0..a224268 100644
--- a/src/dev/kske/chess/ui/MenuBar.java
+++ b/src/dev/kske/chess/ui/MenuBar.java
@@ -2,20 +2,16 @@ package dev.kske.chess.ui;
import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection;
-import java.io.File;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import javax.swing.JComboBox;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
-import dev.kske.chess.board.Board;
+import dev.kske.chess.board.FENString;
+import dev.kske.chess.exception.ChessException;
import dev.kske.chess.game.Game;
-import dev.kske.chess.pgn.PGNDatabase;
-import dev.kske.chess.pgn.PGNGame;
+import dev.kske.chess.io.EngineUtil;
/**
* Project: Chess
@@ -41,8 +37,7 @@ public class MenuBar extends JMenuBar {
JMenu gameMenu = new JMenu("Game");
JMenuItem newGameMenuItem = new JMenuItem("New Game");
- newGameMenuItem
- .addActionListener((evt) -> DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
+ newGameMenuItem.addActionListener((evt) -> DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
GamePane gamePane = mainWindow.addGamePane();
Game game = new Game(gamePane.getBoardPane(), whiteName, blackName);
gamePane.setGame(game);
@@ -51,7 +46,7 @@ public class MenuBar extends JMenuBar {
gameMenu.add(newGameMenuItem);
JMenuItem loadFileMenu = new JMenuItem("Load game file");
- loadFileMenu.addActionListener((evt) -> DialogUtil.showFileSelectionDialog(mainWindow, this::loadFile));
+ loadFileMenu.addActionListener((evt) -> DialogUtil.showFileSelectionDialog(mainWindow, mainWindow::loadFiles));
gameMenu.add(loadFileMenu);
add(gameMenu);
@@ -63,10 +58,8 @@ public class MenuBar extends JMenuBar {
JMenuItem addEngineMenuItem = new JMenuItem("Add engine");
addEngineMenuItem.addActionListener((evt) -> {
- String enginePath = JOptionPane.showInputDialog(getParent(),
- "Enter the path to a UCI-compatible chess engine:",
- "Engine selection",
- JOptionPane.QUESTION_MESSAGE);
+ String enginePath = JOptionPane
+ .showInputDialog(getParent(), "Enter the path to a UCI-compatible chess engine:", "Engine selection", JOptionPane.QUESTION_MESSAGE);
if (enginePath != null) EngineUtil.addEngine(enginePath);
});
@@ -82,7 +75,7 @@ public class MenuBar extends JMenuBar {
JMenuItem exportFENMenuItem = new JMenuItem("Export board to FEN");
exportFENMenuItem.addActionListener((evt) -> {
- final String fen = mainWindow.getSelectedGamePane().getGame().getBoard().toFEN();
+ final String fen = new FENString(mainWindow.getSelectedGamePane().getGame().getBoard()).toString();
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(fen), null);
JOptionPane.showMessageDialog(mainWindow, String.format("FEN-string copied to clipboard!%n%s", fen));
});
@@ -93,77 +86,20 @@ public class MenuBar extends JMenuBar {
final GamePane gamePane = mainWindow.addGamePane();
final String fen = JOptionPane.showInputDialog("Enter a FEN string: ");
DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
- final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, new Board(fen));
- gamePane.setGame(game);
- game.start();
+ Game game;
+ try {
+ game = new Game(gamePane.getBoardPane(), whiteName, blackName, new FENString(fen).getBoard());
+ gamePane.setGame(game);
+ game.start();
+ } catch (ChessException e) {
+ e.printStackTrace();
+ JOptionPane
+ .showMessageDialog(mainWindow, "Failed to load FEN string: " + e.toString(), "FEN loading error", JOptionPane.ERROR_MESSAGE);
+ }
});
});
toolsMenu.add(loadFromFENMenuItem);
add(toolsMenu);
}
-
- private void loadFile(File file) {
- final String name = file.getName().substring(0, file.getName().lastIndexOf('.'));
- final String extension = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase();
- switch (extension) {
- case ".fen":
- try {
- final GamePane gamePane = mainWindow.addGamePane(name);
- final String fen = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
- DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
- final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, new Board(fen));
- gamePane.setGame(game);
- game.start();
- });
- } catch (Exception e) {
- e.printStackTrace();
- JOptionPane.showMessageDialog(mainWindow,
- "Failed to load the file " + file.getName() + ": " + e.toString(),
- "File loading error",
- JOptionPane.ERROR_MESSAGE);
- }
- break;
- case ".pgn":
- try {
- final GamePane gamePane = mainWindow.addGamePane(name);
- PGNDatabase pgnDB = new PGNDatabase();
- pgnDB.load(file);
- if (pgnDB.getGames().size() > 0) {
- String[] gameNames = new String[pgnDB.getGames().size()];
- for (int i = 0; i < gameNames.length; i++) {
- final PGNGame game = pgnDB.getGames().get(i);
- gameNames[i] = String.format("%s vs %s: %s",
- game.getTag("White"),
- game.getTag("Black"),
- game.getTag("Result"));
- }
- JComboBox comboBox = new JComboBox<>(gameNames);
- JOptionPane
- .showInputDialog(mainWindow, comboBox, "Select a game", JOptionPane.QUESTION_MESSAGE);
- DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
- final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName,
- pgnDB.getGames().get(comboBox.getSelectedIndex()).getBoard());
- game.start();
- });
- }
- } catch (Exception e) {
- e.printStackTrace();
- JOptionPane.showMessageDialog(mainWindow,
- "Failed to load the file " + file.getName() + ": " + e.toString(),
- "File loading error",
- JOptionPane.ERROR_MESSAGE);
- }
- break;
- default:
- JOptionPane.showMessageDialog(mainWindow,
- "The file extension '" + extension + "' is not supported!",
- "File loading error",
- JOptionPane.ERROR_MESSAGE);
- }
- }
-
- public void loadFENFile(File file) {
-
- }
}
diff --git a/test/dev/kske/chess/board/FENStringTest.java b/test/dev/kske/chess/board/FENStringTest.java
new file mode 100644
index 0000000..b2c1e43
--- /dev/null
+++ b/test/dev/kske/chess/board/FENStringTest.java
@@ -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: Chess
+ * File: FENStringTest.java
+ * Created: 24 Oct 2019
+ *
+ * @author Kai S. K. Engelbart
+ */
+class FENStringTest {
+
+ List fenStrings = new ArrayList<>();
+ List 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 KQkq - 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());
+ }
+}
diff --git a/test/dev/kske/chess/board/LogTest.java b/test/dev/kske/chess/board/LogTest.java
index 4e682d4..fafc6dc 100644
--- a/test/dev/kske/chess/board/LogTest.java
+++ b/test/dev/kske/chess/board/LogTest.java
@@ -30,7 +30,7 @@ class LogTest {
assertNull(log.getRoot());
assertEquals(log.getActiveColor(), Color.WHITE);
assertNull(log.getEnPassant());
- assertEquals(log.getFullmoveCounter(), 1);
+ assertEquals(log.getFullmoveNumber(), 1);
assertEquals(log.getHalfmoveClock(), 0);
}
@@ -43,10 +43,10 @@ class LogTest {
log.setActiveColor(Color.WHITE);
other.setActiveColor(Color.BLACK);
assertNotEquals(log.getActiveColor(), other.getActiveColor());
- log.add(Move.fromLAN("a2a4"), null, true);
- log.add(Move.fromLAN("a4a5"), null, true);
- other.add(Move.fromLAN("a2a4"), null, true);
- other.add(Move.fromLAN("a4a5"), null, true);
+ log.add(Move.fromLAN("a2a4"), new Pawn(Color.WHITE, null), null);
+ log.add(Move.fromLAN("a4a5"), new Pawn(Color.WHITE, null), null);
+ other.add(Move.fromLAN("a2a4"), new Pawn(Color.WHITE, null), null);
+ other.add(Move.fromLAN("a4a5"), new Pawn(Color.WHITE, null), null);
assertNotEquals(log.getRoot(), other.getRoot());
assertNotEquals(log.getRoot().getVariations(), other.getRoot().getVariations());
}
@@ -132,7 +132,7 @@ class LogTest {
}
/**
- * Test method for {@link dev.kske.chess.board.Log#getFullmoveCounter()}.
+ * Test method for {@link dev.kske.chess.board.Log#getFullmoveNumber()}.
*/
@Test
void testGetFullmoveCounter() {
@@ -140,7 +140,7 @@ class LogTest {
}
/**
- * Test method for {@link dev.kske.chess.board.Log#setFullmoveCounter(int)}.
+ * Test method for {@link dev.kske.chess.board.Log#setFullmoveNumber(int)}.
*/
@Test
void testSetFullmoveCounter() {