This repository has been archived on 2021-02-18. You can view files and clone it, but cannot push or open issues or pull requests.
chess/src/main/java/dev/kske/chess/board/FENString.java

239 lines
6.6 KiB
Java

package dev.kske.chess.board;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.exception.ChessException;
/**
* Project: <strong>Chess</strong><br>
* File: <strong>FENString.java</strong><br>
* Created: <strong>20 Oct 2019</strong><br>
* <br>
* Represents a FEN string and enables parsing an existing FEN string or
* serializing a {@link Board} to one.
*
* @since Chess v0.5-alpha
* @author Kai S. K. Engelbart
*/
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 if the FEN string contains invalid syntax
*/
public FENString(String fen) throws ChessException {
// Check fen string against regex
Pattern fenPattern = Pattern.compile(
"^(?<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()
);
// 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;
try {
Constructor<? extends Piece> pieceConstructor = Piece
.fromFirstChar(c)
.getDeclaredConstructor(Color.class, Board.class);
pieceConstructor.setAccessible(true);
board.getBoardArr()[j][i]
= pieceConstructor.newInstance(color, board);
} catch (
InstantiationException
| IllegalAccessException
| IllegalArgumentException
| InvocationTargetException
| NoSuchMethodException
| SecurityException e
) {
e.printStackTrace();
}
++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.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; }
}