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;
|
||||
|
||||
@ -18,63 +20,48 @@ import dev.kske.chess.board.Piece.Type;
|
||||
*/
|
||||
public class Board {
|
||||
|
||||
private Piece[][] boardArr = new Piece[8][8];
|
||||
private Map<Color, Position> kingPos = new HashMap<>();
|
||||
private Map<Color, Map<Type, Boolean>> castlingRights = new HashMap<>();
|
||||
private Log log = new Log();
|
||||
private Piece[][] boardArr = new Piece[8][8];
|
||||
private Map<Color, Position> kingPos = new HashMap<>();
|
||||
private Log log = new Log();
|
||||
|
||||
private static final Map<Type, int[][]> 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.<br>
|
||||
@ -93,10 +80,6 @@ public class Board {
|
||||
}
|
||||
|
||||
kingPos.putAll(other.kingPos);
|
||||
Map<Type, Boolean> 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(
|
||||
"^(?<pieceType>[NBRQK])(?:(?<fromFile>[a-h])|(?<fromRank>[1-8])|(?<fromSquare>[a-h][1-8]))?x?(?<toSquare>[a-h][1-8])(?:\\+{0,2}|\\#)$"));
|
||||
patterns.put("pawnCapture",
|
||||
Pattern
|
||||
.compile(
|
||||
"^(?<fromFile>[a-h])(?<fromRank>[1-8])?x(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQK])?(?:\\+{0,2}|\\#)?$"));
|
||||
Pattern.compile("^(?<fromFile>[a-h])(?<fromRank>[1-8])?x(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQK])?(?:\\+{0,2}|\\#)?$"));
|
||||
patterns.put("pawnPush", Pattern.compile("^(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQK])?(?:\\+{0,2}|\\#)$"));
|
||||
patterns.put("castling", Pattern.compile("^(?<queenside>O-O-O)|(?<kingside>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<Move> 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<Move> getMoves(Position pos) {
|
||||
return get(pos).getMoves(pos);
|
||||
}
|
||||
public List<Move> 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<Type, Boolean> 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<Type, Boolean> 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
|
||||
|
222
src/dev/kske/chess/board/FENString.java
Normal file
222
src/dev/kske/chess/board/FENString.java
Normal file
@ -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: <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 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(
|
||||
"^(?<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;
|
||||
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; }
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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: <strong>Chess</strong><br>
|
||||
@ -14,13 +17,12 @@ public class Log implements Iterable<MoveNode> {
|
||||
|
||||
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<MoveNode> {
|
||||
*/
|
||||
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<MoveNode> {
|
||||
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<MoveNode> {
|
||||
* 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<MoveNode> {
|
||||
|
||||
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<MoveNode> {
|
||||
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<MoveNode> {
|
||||
|
||||
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<MoveNode> {
|
||||
*/
|
||||
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<MoveNode> {
|
||||
|
||||
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; }
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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>
|
||||
@ -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 {
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
package dev.kske.chess.ui;
|
||||
package dev.kske.chess.io;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
@ -1,4 +1,4 @@
|
||||
package dev.kske.chess.ui;
|
||||
package dev.kske.chess.io;
|
||||
|
||||
import java.awt.Image;
|
||||
import java.awt.image.BufferedImage;
|
@ -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; }
|
||||
}
|
||||
|
@ -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: <strong>Chess</strong><br>
|
||||
|
@ -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: <strong>Chess</strong><br>
|
||||
@ -26,11 +29,25 @@ public class DialogUtil {
|
||||
|
||||
private DialogUtil() {}
|
||||
|
||||
public static void showFileSelectionDialog(Component parent, Consumer<File> action) {
|
||||
public static void showFileSelectionDialog(Component parent, Consumer<List<File>> 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<String, String> action) {
|
||||
|
@ -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: <strong>Chess</strong><br>
|
||||
* File: <strong>FENDropTarget.java</strong><br>
|
||||
* Created: <strong>13 Aug 2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*/
|
||||
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<File>) 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();
|
||||
}
|
||||
}
|
||||
}
|
35
src/dev/kske/chess/ui/GameDropTarget.java
Normal file
35
src/dev/kske/chess/ui/GameDropTarget.java
Normal file
@ -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: <strong>Chess</strong><br>
|
||||
* File: <strong>GameDropTarget.java</strong><br>
|
||||
* Created: <strong>13 Aug 2019</strong><br>
|
||||
* Author: <strong>Kai S. K. Engelbart</strong>
|
||||
*/
|
||||
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<File>) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor));
|
||||
} catch (UnsupportedFlavorException | IOException ex) {
|
||||
ex.printStackTrace();
|
||||
evt.rejectDrop();
|
||||
}
|
||||
}
|
||||
}
|
@ -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: <strong>Chess</strong><br>
|
||||
* File: <strong>MainWindow.java</strong><br>
|
||||
@ -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<File> 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<String> 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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: <strong>Chess</strong><br>
|
||||
@ -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<String> 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) {
|
||||
|
||||
}
|
||||
}
|
||||
|
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 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());
|
||||
}
|
||||
}
|
@ -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() {
|
||||
|
Reference in New Issue
Block a user