Refined IO functionality, fixed FEN string serialization and deserialization #7
@ -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"/>
|
||||||
|
@ -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;
|
||||||
|
|
||||||
@ -20,7 +22,6 @@ 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;
|
||||||
@ -28,53 +29,39 @@ public class Board {
|
|||||||
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
|
@Override
|
||||||
log.setActiveColor(Color.fromFirstChar(parts[1].charAt(0)));
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) return true;
|
||||||
// Castling rights
|
if (obj == null) return false;
|
||||||
Map<Type, Boolean> whiteCastling = new HashMap<>(), blackCastling = new HashMap<>();
|
if (getClass() != obj.getClass()) return false;
|
||||||
for (char c : parts[2].toCharArray())
|
Board other = (Board) obj;
|
||||||
switch (c) {
|
return Arrays.deepEquals(boardArr, other.boardArr) && Objects.equals(kingPos, other.kingPos) && Objects.equals(log, other.log);
|
||||||
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]));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @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
|
||||||
|
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 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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,8 +35,9 @@ 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
|
||||||
@ -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; }
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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;
|
@ -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;
|
@ -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; }
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
@ -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) {
|
||||||
|
@ -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.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);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
try {
|
||||||
|
game = new Game(gamePane.getBoardPane(), whiteName, blackName, new FENString(fen).getBoard());
|
||||||
gamePane.setGame(game);
|
gamePane.setGame(game);
|
||||||
game.start();
|
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) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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());
|
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() {
|
||||||
|
Reference in New Issue
Block a user