Merge pull request #7 from CyB3RC0nN0R/feature/io

Refined IO functionality, fixed FEN string serialization and deserialization
This commit is contained in:
Kai S. K. Engelbart 2019-10-25 17:01:55 +02:00 committed by GitHub
commit 85a8bf817f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 644 additions and 476 deletions

View File

@ -7,6 +7,11 @@
<attribute name="test" value="true"/> <attribute name="test" value="true"/>
</attributes> </attributes>
</classpathentry> </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.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="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="output" path="bin"/> <classpathentry kind="output" path="bin"/>

View File

@ -1,9 +1,11 @@
package dev.kske.chess.board; package dev.kske.chess.board;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -18,63 +20,48 @@ import dev.kske.chess.board.Piece.Type;
*/ */
public class Board { public class Board {
private Piece[][] boardArr = new Piece[8][8]; private Piece[][] boardArr = new Piece[8][8];
private Map<Color, Position> kingPos = new HashMap<>(); private Map<Color, Position> kingPos = new HashMap<>();
private Map<Color, Map<Type, Boolean>> castlingRights = new HashMap<>(); private Log log = new Log();
private Log log = new Log();
private static final Map<Type, int[][]> positionScores; private static final Map<Type, int[][]> positionScores;
static { static {
positionScores = new HashMap<>(); positionScores = new HashMap<>();
positionScores.put(Type.KING, 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[] { -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[] { -2, -3, -3, -2, -2, -2, -2, -1 }, new int[] { -1, -2, -2, -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, 2, 0, 0, 0, 0, 2, 2 }, new int[] { 2, 3, 1, 0, 0, 1, 3, 2 } });
new int[] { 2, 3, 1, 0, 0, 1, 3, 2 } });
positionScores.put(Type.QUEEN, 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[][] { 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[] { -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[] { 0, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 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[] { -1, 0, 1, 0, 0, 0, 0, -1 }, new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } }); new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
positionScores.put(Type.ROOK, 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[][] { 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[] { -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 } }); new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { 0, 0, 0, 1, 1, 0, 0, 0 } });
positionScores.put(Type.KNIGHT, 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[][] { 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, 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, 0, 2, 2, 2, 2, 0, -1 }, new int[] { -3, 1, 1, 2, 2, 1, 1, -3 }, new int[] { -3, 1, 1, 2, 2, 1, 1, -3 }, new int[] { -4, -2, 0, 1, 1, 0, -2, -4 },
new int[] { -4, -2, 0, 1, 1, 0, -2, -4 }, new int[] { -5, -4, -3, -3, -3, -3, -4, -5 } }); new int[] { -5, -4, -3, -3, -3, -3, -4, -5 } });
positionScores.put(Type.BISHOP, 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[][] { 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, 0, 1, 1, 1, 1, 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, 1, 1, 1, 1, 1, 1, -1 }, new int[] { -1, 1, 0, 0, 0, 0, 1, -1 },
new int[] { -1, 1, 0, 0, 0, 0, 1, -1 }, new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } }); new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
positionScores.put(Type.PAWN, 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[][] { 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[] { 1, 1, 2, 3, 3, 2, 1, 1 }, new int[] { 0, 0, 1, 3, 3, 1, 0, 0 }, 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, 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 } }); 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. * Initializes the board with the default chess starting position.
*/ */
public Board() { public Board() { initDefaultPositions(); }
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);
}
/** /**
* Creates a copy of another {@link Board} instance.<br> * Creates a copy of another {@link Board} instance.<br>
@ -93,10 +80,6 @@ public class Board {
} }
kingPos.putAll(other.kingPos); 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); log = new Log(other.log, false);
} }
@ -156,8 +139,6 @@ public class Board {
: new Move(0, move.pos.y, 3, move.pos.y); // Queenside : new Move(0, move.pos.y, 3, move.pos.y); // Queenside
setDest(rookMove, getPos(rookMove)); setDest(rookMove, getPos(rookMove));
setPos(rookMove, null); setPos(rookMove, null);
getDest(rookMove).incMoveCounter();
break; break;
case UNKNOWN: case UNKNOWN:
System.err.printf("Move of unknown type %s found!%n", move); 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); System.err.printf("Move %s of unimplemented type found!%n", move);
} }
// Increment move counter // Update the king's position if the moved piece is the king
getDest(move).incMoveCounter();
// Update the king's position if the moved piece is the king and castling
// availability
if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest); if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest);
// Update log // Update log
log.add(move, capturePiece, piece.getType() == Type.PAWN); log.add(move, piece, capturePiece);
updateCastlingRights();
} }
/** /**
@ -193,9 +168,7 @@ public class Board {
Pattern.compile( Pattern.compile(
"^(?<pieceType>[NBRQK])(?:(?<fromFile>[a-h])|(?<fromRank>[1-8])|(?<fromSquare>[a-h][1-8]))?x?(?<toSquare>[a-h][1-8])(?:\\+{0,2}|\\#)$")); "^(?<pieceType>[NBRQK])(?:(?<fromFile>[a-h])|(?<fromRank>[1-8])|(?<fromSquare>[a-h][1-8]))?x?(?<toSquare>[a-h][1-8])(?:\\+{0,2}|\\#)$"));
patterns.put("pawnCapture", patterns.put("pawnCapture",
Pattern Pattern.compile("^(?<fromFile>[a-h])(?<fromRank>[1-8])?x(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQK])?(?:\\+{0,2}|\\#)?$"));
.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("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}|\\#)?$")); patterns.put("castling", Pattern.compile("^(?<queenside>O-O-O)|(?<kingside>O-O)(?:\\+{0,2}|\\#)?$"));
@ -226,8 +199,7 @@ public class Board {
case "pawnCapture": case "pawnCapture":
dest = Position.fromLAN(m.group("toSquare")); dest = Position.fromLAN(m.group("toSquare"));
char file = m.group("fromFile").charAt(0); char file = m.group("fromFile").charAt(0);
int rank = m.group("fromRank") == null ? get(Type.PAWN, file) int rank = m.group("fromRank") == null ? get(Type.PAWN, file) : Integer.parseInt(m.group("fromRank"));
: Integer.parseInt(m.group("fromRank"));
pos = Position.fromLAN(String.format("%c%d", file, rank)); pos = Position.fromLAN(String.format("%c%d", file, rank));
break; break;
case "pawnPush": case "pawnPush":
@ -280,8 +252,6 @@ public class Board {
: new Move(3, move.pos.y, 0, move.pos.y); // Queenside : new Move(3, move.pos.y, 0, move.pos.y); // Queenside
setDest(rookMove, getPos(rookMove)); setDest(rookMove, getPos(rookMove));
setPos(rookMove, null); setPos(rookMove, null);
getDest(rookMove).decMoveCounter();
break; break;
case UNKNOWN: case UNKNOWN:
System.err.printf("Move of unknown type %s found!%n", move); 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); 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 // 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); if (getPos(move).getType() == Type.KING) kingPos.put(getPos(move).getColor(), move.pos);
// Update log // Update log
log.removeLast(); 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<>(); List<Move> moves = new ArrayList<>();
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++) for (int j = 0; j < 8; j++)
if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) moves.addAll(boardArr[i][j].getMoves(new Position(i, j)));
moves.addAll(boardArr[i][j].getMoves(new Position(i, j)));
return moves; return moves;
} }
public List<Move> getMoves(Position pos) { public List<Move> getMoves(Position pos) { return get(pos).getMoves(pos); }
return get(pos).getMoves(pos);
}
/** /**
* Checks, if the king is in check. * Checks, if the king is in check.
@ -356,9 +296,7 @@ public class Board {
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++) { for (int j = 0; j < 8; j++) {
Position pos = new Position(i, j); Position pos = new Position(i, j);
if (get(pos) != null && get(pos).getColor() != color if (get(pos) != null && get(pos).getColor() != color && get(pos).isValidMove(new Move(pos, kingPos.get(color)))) return true;
&& get(pos).isValidMove(new Move(pos, kingPos.get(color))))
return true;
} }
return false; return false;
} }
@ -386,8 +324,7 @@ public class Board {
public GameState getGameEventType(Color color) { public GameState getGameEventType(Color color) {
return checkCheck(color) ? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK return checkCheck(color) ? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK
: getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? GameState.STALEMATE : getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? GameState.STALEMATE : GameState.NORMAL;
: GameState.NORMAL;
} }
/** /**
@ -469,160 +406,32 @@ public class Board {
for (int j = 2; j < 6; j++) for (int j = 2; j < 6; j++)
boardArr[i][j] = null; 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(); log.reset();
} }
/** @Override
* Initialized the board with a position specified in a FEN-encoded string. public int hashCode() {
* final int prime = 31;
* @param fen The FEN-encoded string representing target state of the board int result = 1;
*/ result = prime * result + Arrays.deepHashCode(boardArr);
public void initFromFEN(String fen) { result = prime * result + Objects.hash(kingPos, log);
String[] parts = fen.split(" "); return result;
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
* @return a FEN-encoded string representing the board public boolean equals(Object obj) {
*/ if (this == obj) return true;
public String toFEN() { if (obj == null) return false;
StringBuilder sb = new StringBuilder(); if (getClass() != obj.getClass()) return false;
Board other = (Board) obj;
// Piece placement (from white's perspective) return Arrays.deepEquals(boardArr, other.boardArr) && Objects.equals(kingPos, other.kingPos) && Objects.equals(log, other.log);
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();
} }
/** /**
* @param pos The position from which to return a piece * @param pos The position from which to return a piece
* @return The piece at the position * @return The piece at the position
*/ */
public Piece get(Position pos) { public Piece get(Position pos) { return boardArr[pos.x][pos.y]; }
return boardArr[pos.x][pos.y];
}
/** /**
* Searches for a {@link Piece} inside a file (A - H). * Searches for a {@link Piece} inside a file (A - H).
@ -635,9 +444,7 @@ public class Board {
public int get(Type type, char file) { public int get(Type type, char file) {
int x = file - 97; int x = file - 97;
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
if (boardArr[x][i] != null && boardArr[x][i].getType() == type if (boardArr[x][i] != null && boardArr[x][i].getType() == type && boardArr[x][i].getColor() == log.getActiveColor()) return 8 - i;
&& boardArr[x][i].getColor() == log.getActiveColor())
return 8 - i;
return -1; return -1;
} }
@ -652,8 +459,7 @@ public class Board {
public char get(Type type, int rank) { public char get(Type type, int rank) {
int y = rank - 1; int y = rank - 1;
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
if (boardArr[i][y] != null && boardArr[i][y].getType() == type if (boardArr[i][y] != null && boardArr[i][y].getType() == type && boardArr[i][y].getColor() == log.getActiveColor())
&& boardArr[i][y].getColor() == log.getActiveColor())
return (char) (i + 97); return (char) (i + 97);
return '-'; return '-';
} }
@ -668,8 +474,7 @@ public class Board {
public Position get(Type type, Position dest) { public Position get(Type type, Position dest) {
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++) for (int j = 0; j < 8; j++)
if (boardArr[i][j] != null && boardArr[i][j].getType() == type if (boardArr[i][j] != null && boardArr[i][j].getType() == type && boardArr[i][j].getColor() == log.getActiveColor()) {
&& boardArr[i][j].getColor() == log.getActiveColor()) {
Position pos = new Position(i, j); Position pos = new Position(i, j);
if (boardArr[i][j].isValidMove(new Move(pos, dest))) return pos; 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 pos The position to place the piece at
* @param piece The piece to place * @param piece The piece to place
*/ */
public void set(Position pos, Piece piece) { public void set(Position pos, Piece piece) { boardArr[pos.x][pos.y] = piece; }
boardArr[pos.x][pos.y] = piece;
}
/** /**
* @param move The move from which position to return a piece * @param move The move from which position to return a piece
* @return The piece at the position of the move * @return The piece at the position of the move
*/ */
public Piece getPos(Move move) { public Piece getPos(Move move) { return get(move.pos); }
return get(move.pos);
}
/** /**
* @param move The move from which destination to return a piece * @param move The move from which destination to return a piece
* @return The piece at the destination of the move * @return The piece at the destination of the move
*/ */
public Piece getDest(Move move) { public Piece getDest(Move move) { return get(move.dest); }
return get(move.dest);
}
/** /**
* Places a piece at the position of a move. * 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 move The move at which position to place the piece
* @param piece The piece to place * @param piece The piece to place
*/ */
public void setPos(Move move, Piece piece) { public void setPos(Move move, Piece piece) { set(move.pos, piece); }
set(move.pos, piece);
}
/** /**
* Places a piece at the destination of a move. * 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 move The move at which destination to place the piece
* @param piece The piece to place * @param piece The piece to place
*/ */
public void setDest(Move move, Piece piece) { public void setDest(Move move, Piece piece) { set(move.dest, piece); }
set(move.dest, piece);
}
/** /**
* @return The board array * @return The board array

View 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; }
}

View File

@ -11,9 +11,7 @@ import java.util.List;
*/ */
public class King extends Piece { public class King extends Piece {
public King(Color color, Board board) { public King(Color color, Board board) { super(color, board); }
super(color, board);
}
@Override @Override
public boolean isValidMove(Move move) { public boolean isValidMove(Move move) {
@ -33,22 +31,20 @@ public class King extends Piece {
} }
public boolean canCastleKingside() { 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; int y = getColor() == Color.WHITE ? 7 : 0;
Position rookPos = new Position(7, y); Position rookPos = new Position(7, y);
Piece rook = board.get(rookPos); Piece rook = board.get(rookPos);
return rook != null && rook.getType() == Type.ROOK && rook.getMoveCounter() == 0 return rook != null && rook.getType() == Type.ROOK && isFreePath(new Move(new Position(4, y), new Position(6, y)));
&& isFreePath(new Move(new Position(4, y), new Position(6, y)));
} else return false; } else return false;
} }
public boolean canCastleQueenside() { 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; int y = getColor() == Color.WHITE ? 7 : 0;
Position rookPos = new Position(0, y); Position rookPos = new Position(0, y);
Piece rook = board.get(rookPos); Piece rook = board.get(rookPos);
return rook != null && rook.getType() == Type.ROOK && rook.getMoveCounter() == 0 return rook != null && rook.getType() == Type.ROOK && isFreePath(new Move(new Position(4, y), new Position(1, y)));
&& isFreePath(new Move(new Position(4, y), new Position(1, y)));
} else return false; } else return false;
} }

View File

@ -1,8 +1,11 @@
package dev.kske.chess.board; package dev.kske.chess.board;
import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
import java.util.Objects;
import dev.kske.chess.board.Piece.Color; import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.board.Piece.Type;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
@ -14,13 +17,12 @@ public class Log implements Iterable<MoveNode> {
private MoveNode root, current; private MoveNode root, current;
private Position enPassant;
private Color activeColor; private Color activeColor;
private int fullmoveCounter, halfmoveClock; private boolean[] castlingRights;
private Position enPassant;
private int fullmoveNumber, halfmoveClock;
public Log() { public Log() { reset(); }
reset();
}
/** /**
* Creates a (partially deep) copy of another {@link Log} instance which begins * 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) { public Log(Log other, boolean copyVariations) {
enPassant = other.enPassant; enPassant = other.enPassant;
castlingRights = other.castlingRights.clone();
activeColor = other.activeColor; activeColor = other.activeColor;
fullmoveCounter = other.fullmoveCounter; fullmoveNumber = other.fullmoveNumber;
halfmoveClock = other.halfmoveClock; halfmoveClock = other.halfmoveClock;
// The new root is the current node of the copied instance // The new root is the current node of the copied instance
if (!other.isEmpty()) { if (!other.isEmpty()) {
root = new MoveNode(other.current, copyVariations); root = new MoveNode(other.current, copyVariations);
root.setParent(null); root.setParent(null);
current = root; current = root;
} }
} }
@ -53,9 +56,7 @@ public class Log implements Iterable<MoveNode> {
private boolean hasNext = true; private boolean hasNext = true;
@Override @Override
public boolean hasNext() { public boolean hasNext() { return hasNext; }
return hasNext;
}
@Override @Override
public MoveNode next() { 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. * Adds a move to the move history and adjusts the log to the new position.
* *
* @param move The move to log * @param move The move to log
* @param piece The piece that performed the move
* @param capturedPiece The piece captured with 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) { public void add(Move move, Piece piece, Piece capturedPiece) {
enPassant = pawnMove && move.yDist == 2 ? new Position(move.pos.x, move.pos.y + move.ySign) : null; enPassant = piece.getType() == Type.PAWN && move.yDist == 2 ? new Position(move.pos.x, move.pos.y + move.ySign) : null;
if (activeColor == Color.BLACK) ++fullmoveCounter; if (activeColor == Color.BLACK) ++fullmoveNumber;
if (pawnMove || capturedPiece != null) halfmoveClock = 0; if (piece.getType() == Type.PAWN || capturedPiece != null) halfmoveClock = 0;
else++halfmoveClock; else++halfmoveClock;
activeColor = activeColor.opposite(); 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()) { if (isEmpty()) {
root = leaf; root = leaf;
@ -105,9 +110,7 @@ public class Log implements Iterable<MoveNode> {
public boolean isEmpty() { return root == null; } public boolean isEmpty() { return root == null; }
public boolean hasParent() { public boolean hasParent() { return !isEmpty() && current.hasParent(); }
return !isEmpty() && current.hasParent();
}
/** /**
* Reverts the log to its initial state corresponding to the default board * 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() { public void reset() {
root = null; root = null;
current = null; current = null;
castlingRights = new boolean[] { true, true, true, true };
enPassant = null; enPassant = null;
activeColor = Color.WHITE; activeColor = Color.WHITE;
fullmoveCounter = 1; fullmoveNumber = 1;
halfmoveClock = 0; 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) { public void selectNextNode(int index) {
if (!isEmpty() && current.hasVariations() && index < current.getVariations().size()) { if (!isEmpty() && current.hasVariations() && index < current.getVariations().size()) {
@ -155,11 +160,41 @@ public class Log implements Iterable<MoveNode> {
private void update() { private void update() {
activeColor = current.activeColor; activeColor = current.activeColor;
castlingRights = current.castlingRights.clone();
enPassant = current.enPassant; enPassant = current.enPassant;
fullmoveCounter = current.fullmoveCounter; fullmoveNumber = current.fullmoveCounter;
halfmoveClock = current.halfmoveClock; 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 * @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 MoveNode getLast() { return current; }
public boolean[] getCastlingRights() { return castlingRights; }
public void setCastlingRights(boolean[] castlingRights) { this.castlingRights = castlingRights; }
public Position getEnPassant() { return enPassant; } public Position getEnPassant() { return enPassant; }
public void setEnPassant(Position enPassant) { this.enPassant = 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 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; } public int getHalfmoveClock() { return halfmoveClock; }

View File

@ -1,5 +1,7 @@
package dev.kske.chess.board; package dev.kske.chess.board;
import java.util.Objects;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
* File: <strong>Move.java</strong><br> * File: <strong>Move.java</strong><br>
@ -50,6 +52,21 @@ public class Move {
return String.format("%s -> %s", pos, dest); 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 { public static enum Type {
NORMAL, PAWN_PROMOTION, CASTLING, EN_PASSANT, UNKNOWN NORMAL, PAWN_PROMOTION, CASTLING, EN_PASSANT, UNKNOWN
} }

View File

@ -1,7 +1,9 @@
package dev.kske.chess.board; package dev.kske.chess.board;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects;
import dev.kske.chess.board.Piece.Color; import dev.kske.chess.board.Piece.Color;
@ -13,8 +15,11 @@ import dev.kske.chess.board.Piece.Color;
*/ */
public class MoveNode { 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 Move move;
public final Piece capturedPiece; public final Piece capturedPiece;
public final boolean[] castlingRights;
public final Position enPassant; public final Position enPassant;
public final Color activeColor; public final Color activeColor;
public final int fullmoveCounter, halfmoveClock; public final int fullmoveCounter, halfmoveClock;
@ -33,10 +38,11 @@ public class MoveNode {
* @param fullmoveCounter * @param fullmoveCounter
* @param halfmoveClock * @param halfmoveClock
*/ */
public MoveNode(Move move, Piece capturedPiece, Position enPassant, Color activeColor, int fullmoveCounter, public MoveNode(Move move, Piece capturedPiece, boolean castlingRights[], Position enPassant, Color activeColor,
int halfmoveClock) { int fullmoveCounter, int halfmoveClock) {
this.move = move; this.move = move;
this.capturedPiece = capturedPiece; this.capturedPiece = capturedPiece;
this.castlingRights = castlingRights;
this.enPassant = enPassant; this.enPassant = enPassant;
this.activeColor = activeColor; this.activeColor = activeColor;
this.fullmoveCounter = fullmoveCounter; this.fullmoveCounter = fullmoveCounter;
@ -52,8 +58,8 @@ public class MoveNode {
* considers subsequent variations * considers subsequent variations
*/ */
public MoveNode(MoveNode other, boolean copyVariations) { public MoveNode(MoveNode other, boolean copyVariations) {
this(other.move, other.capturedPiece, other.enPassant, other.activeColor, other.fullmoveCounter, this(other.move, other.capturedPiece, other.castlingRights.clone(), other.enPassant, other.activeColor,
other.halfmoveClock); other.fullmoveCounter, other.halfmoveClock);
if (copyVariations && other.variations != null) { if (copyVariations && other.variations != null) {
if (variations == null) variations = new ArrayList<>(); if (variations == null) variations = new ArrayList<>();
other.variations.forEach(variation -> { other.variations.forEach(variation -> {
@ -93,4 +99,26 @@ public class MoveNode {
public boolean hasParent() { public boolean hasParent() {
return parent != null; 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);
}
} }

View File

@ -2,6 +2,7 @@ package dev.kske.chess.board;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Objects;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
@ -13,7 +14,6 @@ public abstract class Piece implements Cloneable {
private final Color color; private final Color color;
protected Board board; protected Board board;
private int moveCounter;
public Piece(Color color, Board board) { public Piece(Color color, Board board) {
this.color = color; this.color = color;
@ -74,14 +74,18 @@ public abstract class Piece implements Cloneable {
public Color getColor() { return color; } public Color getColor() { return color; }
public int getMoveCounter() { return moveCounter; } @Override
public int hashCode() {
public void incMoveCounter() { return Objects.hash(color);
++moveCounter;
} }
public void decMoveCounter() { @Override
--moveCounter; 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 { public static enum Type {

View File

@ -12,10 +12,10 @@ import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.event.EventBus; import dev.kske.chess.event.EventBus;
import dev.kske.chess.event.MoveEvent; import dev.kske.chess.event.MoveEvent;
import dev.kske.chess.game.ai.AIPlayer; 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.BoardComponent;
import dev.kske.chess.ui.BoardPane; 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; import dev.kske.chess.ui.OverlayComponent;
/** /**

View File

@ -2,6 +2,7 @@ package dev.kske.chess.game;
import java.io.IOException; import java.io.IOException;
import dev.kske.chess.board.FENString;
import dev.kske.chess.board.Move; import dev.kske.chess.board.Move;
import dev.kske.chess.board.Piece.Color; import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.uci.UCIHandle; import dev.kske.chess.uci.UCIHandle;
@ -30,7 +31,7 @@ public class UCIPlayer extends Player implements UCIListener {
@Override @Override
public void requestMove() { public void requestMove() {
handle.positionFEN(board.toFEN()); handle.positionFEN(new FENString(board).toString());
handle.go(); handle.go();
} }

View File

@ -1,4 +1,4 @@
package dev.kske.chess.ui; package dev.kske.chess.io;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;

View File

@ -1,4 +1,4 @@
package dev.kske.chess.ui; package dev.kske.chess.io;
import java.awt.Image; import java.awt.Image;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;

View File

@ -7,6 +7,7 @@ import java.util.regex.MatchResult;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import dev.kske.chess.board.Board; import dev.kske.chess.board.Board;
import dev.kske.chess.board.FENString;
import dev.kske.chess.exception.ChessException; import dev.kske.chess.exception.ChessException;
/** /**
@ -26,8 +27,7 @@ public class PGNGame {
MatchResult matchResult; MatchResult matchResult;
Pattern tagPairPattern = Pattern.compile("\\[(\\w+) \"(.*)\"]"), Pattern tagPairPattern = Pattern.compile("\\[(\\w+) \"(.*)\"]"),
movePattern = Pattern.compile("\\d+\\.\\s+(?:(?:(\\S+)\\s+(\\S+))|(?:O-O-O)|(?:O-O))(?:\\+{0,2}|\\#)"), movePattern = Pattern.compile("\\d+\\.\\s+(?:(?:(\\S+)\\s+(\\S+))|(?:O-O-O)|(?:O-O))(?:\\+{0,2}|\\#)"),
nagPattern = Pattern.compile("(\\$\\d{1,3})*"), nagPattern = Pattern.compile("(\\$\\d{1,3})*"), terminationMarkerPattern = Pattern.compile("1-0|0-1|1\\/2-1\\/2|\\*");
terminationMarkerPattern = Pattern.compile("1-0|0-1|1\\/2-1\\/2|\\*");
// Parse tag pairs // Parse tag pairs
while (sc.findInLine(tagPairPattern) != null) { while (sc.findInLine(tagPairPattern) != null) {
@ -48,30 +48,23 @@ public class PGNGame {
matchResult = sc.match(); matchResult = sc.match();
if (matchResult.groupCount() > 0) for (int i = 1; i < matchResult.groupCount() + 1; i++) { if (matchResult.groupCount() > 0) for (int i = 1; i < matchResult.groupCount() + 1; i++) {
game.board.move(matchResult.group(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;
} else break; } else break;
} }
// Parse game termination marker // Parse game termination marker
if (sc.findWithinHorizon(terminationMarkerPattern, 20) == null) if (sc.findWithinHorizon(terminationMarkerPattern, 20) == null) System.err.println("Termination marker expected");
System.err.println("Termination marker expected");
return game; return game;
} }
public String getTag(String tagName) { public String getTag(String tagName) { return tagPairs.get(tagName); }
return tagPairs.get(tagName);
}
public boolean hasTag(String tagName) { public boolean hasTag(String tagName) { return tagPairs.containsKey(tagName); }
return tagPairs.containsKey(tagName);
}
public void setTag(String tagName, String tagValue) { public void setTag(String tagName, String tagValue) { tagPairs.put(tagName, tagValue); }
tagPairs.put(tagName, tagValue);
}
public Board getBoard() { return board; } public Board getBoard() { return board; }
} }

View File

@ -6,6 +6,7 @@ import java.awt.Graphics;
import javax.swing.JComponent; import javax.swing.JComponent;
import dev.kske.chess.board.Board; import dev.kske.chess.board.Board;
import dev.kske.chess.io.TextureUtil;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>

View File

@ -15,6 +15,9 @@ import javax.swing.JFileChooser;
import javax.swing.JLabel; import javax.swing.JLabel;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
import javax.swing.filechooser.FileFilter;
import dev.kske.chess.io.EngineUtil;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
@ -26,11 +29,25 @@ public class DialogUtil {
private 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(); JFileChooser fileChooser = new JFileChooser();
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home"))); fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) fileChooser.setAcceptAllFileFilterUsed(false);
action.accept(fileChooser.getSelectedFile()); 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) { public static void showGameConfigurationDialog(Component parent, BiConsumer<String, String> action) {

View File

@ -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();
}
}
}

View 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();
}
}
}

View File

@ -3,10 +3,23 @@ package dev.kske.chess.ui;
import java.awt.EventQueue; import java.awt.EventQueue;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.dnd.DropTarget; 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.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTabbedPane; 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> * Project: <strong>Chess</strong><br>
* File: <strong>MainWindow.java</strong><br> * File: <strong>MainWindow.java</strong><br>
@ -55,7 +68,7 @@ public class MainWindow extends JFrame {
getContentPane().add(tabbedPane); getContentPane().add(tabbedPane);
setJMenuBar(new MenuBar(this)); setJMenuBar(new MenuBar(this));
new DropTarget(this, new FENDropTarget(this)); new DropTarget(this, new GameDropTarget(this));
// Update position and dimensions // Update position and dimensions
pack(); pack();
@ -74,9 +87,7 @@ public class MainWindow extends JFrame {
* *
* @return The new {@link GamePane} * @return The new {@link GamePane}
*/ */
public GamePane addGamePane() { public GamePane addGamePane() { return addGamePane("Game " + (tabbedPane.getComponentCount() + 1)); }
return addGamePane("Game " + (tabbedPane.getComponentCount() + 1));
}
/** /**
* Creates a new {@link GamePane}, adds it to the tabbed pane and opens it. * 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; 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. * Removes a {@link GamePane} form the tabbed pane.
* *
* @param index The index of the {@link GamePane} to remove * @param index The index of the {@link GamePane} to remove
*/ */
public void removeGamePane(int index) { public void removeGamePane(int index) { tabbedPane.remove(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);
}
});
} }
} }

View File

@ -2,20 +2,16 @@ package dev.kske.chess.ui;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection; 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.JMenu;
import javax.swing.JMenuBar; import javax.swing.JMenuBar;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.JOptionPane; 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.game.Game;
import dev.kske.chess.pgn.PGNDatabase; import dev.kske.chess.io.EngineUtil;
import dev.kske.chess.pgn.PGNGame;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
@ -41,8 +37,7 @@ public class MenuBar extends JMenuBar {
JMenu gameMenu = new JMenu("Game"); JMenu gameMenu = new JMenu("Game");
JMenuItem newGameMenuItem = new JMenuItem("New Game"); JMenuItem newGameMenuItem = new JMenuItem("New Game");
newGameMenuItem newGameMenuItem.addActionListener((evt) -> DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
.addActionListener((evt) -> DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
GamePane gamePane = mainWindow.addGamePane(); GamePane gamePane = mainWindow.addGamePane();
Game game = new Game(gamePane.getBoardPane(), whiteName, blackName); Game game = new Game(gamePane.getBoardPane(), whiteName, blackName);
gamePane.setGame(game); gamePane.setGame(game);
@ -51,7 +46,7 @@ public class MenuBar extends JMenuBar {
gameMenu.add(newGameMenuItem); gameMenu.add(newGameMenuItem);
JMenuItem loadFileMenu = new JMenuItem("Load game file"); 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); gameMenu.add(loadFileMenu);
add(gameMenu); add(gameMenu);
@ -63,10 +58,8 @@ public class MenuBar extends JMenuBar {
JMenuItem addEngineMenuItem = new JMenuItem("Add engine"); JMenuItem addEngineMenuItem = new JMenuItem("Add engine");
addEngineMenuItem.addActionListener((evt) -> { addEngineMenuItem.addActionListener((evt) -> {
String enginePath = JOptionPane.showInputDialog(getParent(), String enginePath = JOptionPane
"Enter the path to a UCI-compatible chess engine:", .showInputDialog(getParent(), "Enter the path to a UCI-compatible chess engine:", "Engine selection", JOptionPane.QUESTION_MESSAGE);
"Engine selection",
JOptionPane.QUESTION_MESSAGE);
if (enginePath != null) EngineUtil.addEngine(enginePath); if (enginePath != null) EngineUtil.addEngine(enginePath);
}); });
@ -82,7 +75,7 @@ public class MenuBar extends JMenuBar {
JMenuItem exportFENMenuItem = new JMenuItem("Export board to FEN"); JMenuItem exportFENMenuItem = new JMenuItem("Export board to FEN");
exportFENMenuItem.addActionListener((evt) -> { 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); Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(fen), null);
JOptionPane.showMessageDialog(mainWindow, String.format("FEN-string copied to clipboard!%n%s", fen)); 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 GamePane gamePane = mainWindow.addGamePane();
final String fen = JOptionPane.showInputDialog("Enter a FEN string: "); final String fen = JOptionPane.showInputDialog("Enter a FEN string: ");
DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> { DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, new Board(fen)); Game game;
gamePane.setGame(game); try {
game.start(); 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); toolsMenu.add(loadFromFENMenuItem);
add(toolsMenu); 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) {
}
} }

View 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());
}
}

View File

@ -30,7 +30,7 @@ class LogTest {
assertNull(log.getRoot()); assertNull(log.getRoot());
assertEquals(log.getActiveColor(), Color.WHITE); assertEquals(log.getActiveColor(), Color.WHITE);
assertNull(log.getEnPassant()); assertNull(log.getEnPassant());
assertEquals(log.getFullmoveCounter(), 1); assertEquals(log.getFullmoveNumber(), 1);
assertEquals(log.getHalfmoveClock(), 0); assertEquals(log.getHalfmoveClock(), 0);
} }
@ -43,10 +43,10 @@ class LogTest {
log.setActiveColor(Color.WHITE); log.setActiveColor(Color.WHITE);
other.setActiveColor(Color.BLACK); other.setActiveColor(Color.BLACK);
assertNotEquals(log.getActiveColor(), other.getActiveColor()); assertNotEquals(log.getActiveColor(), other.getActiveColor());
log.add(Move.fromLAN("a2a4"), null, true); log.add(Move.fromLAN("a2a4"), new Pawn(Color.WHITE, null), null);
log.add(Move.fromLAN("a4a5"), null, true); log.add(Move.fromLAN("a4a5"), new Pawn(Color.WHITE, null), null);
other.add(Move.fromLAN("a2a4"), null, true); other.add(Move.fromLAN("a2a4"), new Pawn(Color.WHITE, null), null);
other.add(Move.fromLAN("a4a5"), null, true); other.add(Move.fromLAN("a4a5"), new Pawn(Color.WHITE, null), null);
assertNotEquals(log.getRoot(), other.getRoot()); assertNotEquals(log.getRoot(), other.getRoot());
assertNotEquals(log.getRoot().getVariations(), other.getRoot().getVariations()); 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 @Test
void testGetFullmoveCounter() { 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 @Test
void testSetFullmoveCounter() { void testSetFullmoveCounter() {