Added FENString class
This commit is contained in:
parent
db8fe1c4c0
commit
7e8f75a008
194
src/dev/kske/chess/board/FENString.java
Normal file
194
src/dev/kske/chess/board/FENString.java
Normal file
@ -0,0 +1,194 @@
|
||||
package dev.kske.chess.board;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
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.
|
||||
*
|
||||
* @author Kai S. K. Engelbart
|
||||
* @since Chess v0.4-alpha
|
||||
*/
|
||||
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
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
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");
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
"^(?<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)$");
|
||||
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 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 = fields.get(FENField.PIECE_PLACEMENT).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()[i][j] = new King(color, board);
|
||||
break;
|
||||
case 'Q':
|
||||
board.getBoardArr()[i][j] = new Queen(color, board);
|
||||
break;
|
||||
case 'R':
|
||||
board.getBoardArr()[i][j] = new Rook(color, board);
|
||||
break;
|
||||
case 'N':
|
||||
board.getBoardArr()[i][j] = new Knight(color, board);
|
||||
break;
|
||||
case 'B':
|
||||
board.getBoardArr()[i][j] = new Bishop(color, board);
|
||||
break;
|
||||
case 'P':
|
||||
board.getBoardArr()[i][j] = new Pawn(color, board);
|
||||
break;
|
||||
}
|
||||
++j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Active color
|
||||
board.getLog().setActiveColor(Color.fromFirstChar(fields.get(FENField.ACTIVE_COLOR).charAt(0)));
|
||||
|
||||
// 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)));
|
||||
|
||||
// Halfmove clock
|
||||
board.getLog().setHalfmoveClock(Integer.parseInt(fields.get(FENField.HALFMOVE_CLOCK)));
|
||||
|
||||
// Fullmove number
|
||||
board.getLog().setFullmoveNumber(Integer.parseInt(fields.get(FENField.FULLMOVE_NUMBER)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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()[i][j];
|
||||
|
||||
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('/');
|
||||
}
|
||||
fields.put(FENField.PIECE_PLACEMENT, sb.toString());
|
||||
|
||||
// Active color
|
||||
fields.put(FENField.ACTIVE_COLOR, String.valueOf(board.getLog().getActiveColor().firstChar()));
|
||||
|
||||
// TODO: Castling availability
|
||||
|
||||
// En passant availability
|
||||
final Position enPassantPosition = board.getLog().getEnPassant();
|
||||
fields.put(FENField.EN_PASSANT_TARGET_SQUARE, enPassantPosition == null ? "-" : enPassantPosition.toLAN());
|
||||
|
||||
// Halfmove clock
|
||||
fields.put(FENField.HALFMOVE_CLOCK, String.valueOf(board.getLog().getHalfmoveClock()));
|
||||
|
||||
// Fullmove counter
|
||||
fields.put(FENField.FULLMOVE_NUMBER, String.valueOf(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.join(" ", fields.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a {@link Board} object corresponding to this {@link FENString}
|
||||
*/
|
||||
public Board getBoard() { return board; }
|
||||
}
|
Reference in New Issue
Block a user