Implemented navigation of the move log, added Javadoc #19
@ -35,37 +35,54 @@ public class Bishop extends Piece {
|
||||
// Diagonal moves to the lower right
|
||||
for (int i = pos.x + 1, j = pos.y + 1; i < 8 && j < 8; i++, j++) {
|
||||
Move move = new Move(pos, new Position(i, j));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Diagonal moves to the lower left
|
||||
for (int i = pos.x - 1, j = pos.y + 1; i >= 0 && j < 8; i--, j++) {
|
||||
Move move = new Move(pos, new Position(i, j));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Diagonal moves to the upper right
|
||||
for (int i = pos.x + 1, j = pos.y - 1; i < 8 && j >= 0; i++, j--) {
|
||||
Move move = new Move(pos, new Position(i, j));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Diagonal moves to the upper left
|
||||
for (int i = pos.x - 1, j = pos.y - 1; i >= 0 && j >= 0; i--, j--) {
|
||||
Move move = new Move(pos, new Position(i, j));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
return moves;
|
||||
}
|
||||
|
@ -23,7 +23,9 @@ public class Board {
|
||||
/**
|
||||
* Initializes the board with the default chess starting position.
|
||||
*/
|
||||
public Board() { initDefaultPositions(); }
|
||||
public Board() {
|
||||
initDefaultPositions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a copy of another {@link Board} instance.<br>
|
||||
@ -31,14 +33,16 @@ public class Board {
|
||||
* history of the Board to copy.
|
||||
*
|
||||
* @param other The Board instance to copy
|
||||
* @param copyVariations if set to {@code true}, the {@link Log} object of the
|
||||
* @param copyVariations if set to {@code true}, the {@link Log} object of
|
||||
* the
|
||||
* other Board instance is copied with its entire move
|
||||
* history
|
||||
*/
|
||||
public Board(Board other, boolean copyVariations) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (other.boardArr[i][j] == null) continue;
|
||||
if (other.boardArr[i][j] == null)
|
||||
continue;
|
||||
boardArr[i][j] = (Piece) other.boardArr[i][j].clone();
|
||||
boardArr[i][j].board = this;
|
||||
}
|
||||
@ -59,7 +63,8 @@ public class Board {
|
||||
*/
|
||||
public boolean attemptMove(Move move) {
|
||||
Piece piece = getPos(move);
|
||||
if (piece == null || !piece.isValidMove(move)) return false;
|
||||
if (piece == null || !piece.isValidMove(move))
|
||||
return false;
|
||||
|
||||
// Move piece
|
||||
move(move);
|
||||
@ -69,7 +74,6 @@ public class Board {
|
||||
revert();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -86,7 +90,8 @@ public class Board {
|
||||
move.execute(this);
|
||||
|
||||
// Update the king's position if the moved piece is the king
|
||||
if (piece instanceof King) kingPos.put(piece.getColor(), move.getDest());
|
||||
if (piece instanceof King)
|
||||
kingPos.put(piece.getColor(), move.getDest());
|
||||
|
||||
// Update log
|
||||
log.add(move, piece, capturePiece);
|
||||
@ -97,7 +102,9 @@ public class Board {
|
||||
*
|
||||
* @param sanMove The move to execute in SAN (Standard Algebraic Notation)
|
||||
*/
|
||||
public void move(String sanMove) { move(Move.fromSAN(sanMove, this)); }
|
||||
public void move(String sanMove) {
|
||||
move(Move.fromSAN(sanMove, this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the last move and removes it from the log.
|
||||
@ -110,7 +117,8 @@ public class Board {
|
||||
move.revert(this, moveNode.capturedPiece);
|
||||
|
||||
// Update the king's position if the moved piece is the king
|
||||
if (getPos(move) instanceof King) kingPos.put(getPos(move).getColor(), move.getPos());
|
||||
if (getPos(move) instanceof King)
|
||||
kingPos.put(getPos(move).getColor(), move.getPos());
|
||||
|
||||
// Update log
|
||||
log.removeLast();
|
||||
@ -118,7 +126,8 @@ public class Board {
|
||||
|
||||
/**
|
||||
* Reverts the last move without removing it from the log. After that, a
|
||||
* {@link MoveEvent} is dispatched containing the inverse of the reverted move.
|
||||
* {@link MoveEvent} is dispatched containing the inverse of the reverted
|
||||
* move.
|
||||
*/
|
||||
public void selectPreviousNode() {
|
||||
MoveNode moveNode = log.getLast();
|
||||
@ -131,11 +140,18 @@ public class Board {
|
||||
log.selectPreviousNode();
|
||||
|
||||
// Dispatch move event
|
||||
EventBus.getInstance().dispatch(new MoveEvent(move.invert(), getState(log.getActiveColor().opposite())));
|
||||
EventBus.getInstance()
|
||||
.dispatch(
|
||||
new MoveEvent(
|
||||
move.invert(),
|
||||
getState(log.getActiveColor().opposite())
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the next move stored in the log. After that, a {@link MoveEvent} is
|
||||
* Applies the next move stored in the log. After that, a {@link MoveEvent}
|
||||
* is
|
||||
* dispatched.
|
||||
*
|
||||
* @param index the variation index of the move to select
|
||||
@ -149,7 +165,10 @@ public class Board {
|
||||
move.execute(this);
|
||||
|
||||
// Dispatch move event
|
||||
EventBus.getInstance().dispatch(new MoveEvent(move, getState(log.getActiveColor().opposite())));
|
||||
EventBus.getInstance()
|
||||
.dispatch(
|
||||
new MoveEvent(move, getState(log.getActiveColor().opposite()))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -162,7 +181,10 @@ public class Board {
|
||||
List<Move> moves = new ArrayList<>();
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++)
|
||||
if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) moves.addAll(boardArr[i][j].getMoves(new Position(i, j)));
|
||||
if (
|
||||
boardArr[i][j] != null && boardArr[i][j].getColor() == color
|
||||
)
|
||||
moves.addAll(boardArr[i][j].getMoves(new Position(i, j)));
|
||||
return moves;
|
||||
}
|
||||
|
||||
@ -172,7 +194,9 @@ public class Board {
|
||||
* @param pos the position of the piece to invoke the method on
|
||||
* @return a list of legal moves generated for the piece
|
||||
*/
|
||||
public List<Move> getMoves(Position pos) { return get(pos).getMoves(pos); }
|
||||
public List<Move> getMoves(Position pos) {
|
||||
return get(pos).getMoves(pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if the king is in check.
|
||||
@ -180,7 +204,9 @@ public class Board {
|
||||
* @param color The color of the king to check
|
||||
* @return {@code true}, if the king is in check
|
||||
*/
|
||||
public boolean checkCheck(Color color) { return isAttacked(kingPos.get(color), color.opposite()); }
|
||||
public boolean checkCheck(Color color) {
|
||||
return isAttacked(kingPos.get(color), color.opposite());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks, if a field can be attacked by pieces of a certain color.
|
||||
@ -193,7 +219,11 @@ public class Board {
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++) {
|
||||
Position pos = new Position(i, j);
|
||||
if (get(pos) != null && get(pos).getColor() == color && get(pos).isValidMove(new Move(pos, dest))) return true;
|
||||
if (
|
||||
get(pos) != null && get(pos).getColor() == color
|
||||
&& get(pos).isValidMove(new Move(pos, dest))
|
||||
)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -207,13 +237,15 @@ public class Board {
|
||||
*/
|
||||
public boolean checkCheckmate(Color color) {
|
||||
// Return false immediately if the king can move
|
||||
if (!getMoves(kingPos.get(color)).isEmpty()) return false;
|
||||
if (!getMoves(kingPos.get(color)).isEmpty())
|
||||
return false;
|
||||
|
||||
for (Move move : getMoves(color)) {
|
||||
move(move);
|
||||
boolean check = checkCheck(color);
|
||||
revert();
|
||||
if (!check) return false;
|
||||
if (!check)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -226,7 +258,8 @@ public class Board {
|
||||
* @return the current {@link BoardState}
|
||||
*/
|
||||
public BoardState getState(Color color) {
|
||||
return checkCheck(color) ? checkCheckmate(color) ? BoardState.CHECKMATE : BoardState.CHECK
|
||||
return checkCheck(color) ? checkCheckmate(color) ? BoardState.CHECKMATE
|
||||
: BoardState.CHECK
|
||||
: getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? BoardState.STALEMATE : BoardState.NORMAL;
|
||||
}
|
||||
|
||||
@ -239,7 +272,6 @@ public class Board {
|
||||
boardArr[i][1] = new Pawn(Color.BLACK, this);
|
||||
boardArr[i][6] = new Pawn(Color.WHITE, this);
|
||||
}
|
||||
|
||||
// Initialize kings
|
||||
boardArr[4][0] = new King(Color.BLACK, this);
|
||||
boardArr[4][7] = new King(Color.WHITE, this);
|
||||
@ -289,18 +321,24 @@ public class Board {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Board other = (Board) obj;
|
||||
return Arrays.deepEquals(boardArr, other.boardArr) && Objects.equals(kingPos, other.kingPos) && Objects.equals(log, other.log);
|
||||
return Arrays.deepEquals(boardArr, other.boardArr) && Objects
|
||||
.equals(kingPos, other.kingPos) && Objects.equals(log, other.log);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param pos The position from which to return a piece
|
||||
* @return The piece at the position
|
||||
*/
|
||||
public Piece get(Position pos) { return boardArr[pos.x][pos.y]; }
|
||||
public Piece get(Position pos) {
|
||||
return boardArr[pos.x][pos.y];
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches for a {@link Piece} inside a file (A - H).
|
||||
@ -313,7 +351,12 @@ public class Board {
|
||||
public int get(Class<? extends Piece> pieceClass, char file) {
|
||||
int x = file - 97;
|
||||
for (int i = 0; i < 8; i++)
|
||||
if (boardArr[x][i] != null && boardArr[x][i].getClass() == pieceClass && boardArr[x][i].getColor() == log.getActiveColor()) return 8 - i;
|
||||
if (
|
||||
boardArr[x][i] != null
|
||||
&& boardArr[x][i].getClass() == pieceClass
|
||||
&& boardArr[x][i].getColor() == log.getActiveColor()
|
||||
)
|
||||
return 8 - i;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@ -328,7 +371,11 @@ public class Board {
|
||||
public char get(Class<? extends Piece> pieceClass, int rank) {
|
||||
int y = rank - 1;
|
||||
for (int i = 0; i < 8; i++)
|
||||
if (boardArr[i][y] != null && boardArr[i][y].getClass() == pieceClass && boardArr[i][y].getColor() == log.getActiveColor())
|
||||
if (
|
||||
boardArr[i][y] != null
|
||||
&& boardArr[i][y].getClass() == pieceClass
|
||||
&& boardArr[i][y].getColor() == log.getActiveColor()
|
||||
)
|
||||
return (char) (i + 97);
|
||||
return '-';
|
||||
}
|
||||
@ -338,14 +385,20 @@ public class Board {
|
||||
*
|
||||
* @param pieceClass The class of the piece to search for
|
||||
* @param dest The destination that the piece is required to reach
|
||||
* @return The position of a piece that can move to the specified destination
|
||||
* @return The position of a piece that can move to the specified
|
||||
* destination
|
||||
*/
|
||||
public Position get(Class<? extends Piece> pieceClass, Position dest) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++)
|
||||
if (boardArr[i][j] != null && boardArr[i][j].getClass() == pieceClass && boardArr[i][j].getColor() == log.getActiveColor()) {
|
||||
if (
|
||||
boardArr[i][j] != null
|
||||
&& boardArr[i][j].getClass() == pieceClass
|
||||
&& boardArr[i][j].getColor() == log.getActiveColor()
|
||||
) {
|
||||
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;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -356,19 +409,25 @@ public class Board {
|
||||
* @param pos The position to place the piece at
|
||||
* @param piece The piece to place
|
||||
*/
|
||||
public void set(Position pos, Piece piece) { boardArr[pos.x][pos.y] = piece; }
|
||||
public void set(Position pos, Piece piece) {
|
||||
boardArr[pos.x][pos.y] = piece;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param move The move from which position to return a piece
|
||||
* @return The piece at the position of the move
|
||||
*/
|
||||
public Piece getPos(Move move) { return get(move.getPos()); }
|
||||
public Piece getPos(Move move) {
|
||||
return get(move.getPos());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param move The move from which destination to return a piece
|
||||
* @return The piece at the destination of the move
|
||||
*/
|
||||
public Piece getDest(Move move) { return get(move.getDest()); }
|
||||
public Piece getDest(Move move) {
|
||||
return get(move.getDest());
|
||||
}
|
||||
|
||||
/**
|
||||
* Places a piece at the position of a move.
|
||||
@ -376,7 +435,9 @@ public class Board {
|
||||
* @param move The move at which position to place the piece
|
||||
* @param piece The piece to place
|
||||
*/
|
||||
public void setPos(Move move, Piece piece) { set(move.getPos(), piece); }
|
||||
public void setPos(Move move, Piece piece) {
|
||||
set(move.getPos(), piece);
|
||||
}
|
||||
|
||||
/**
|
||||
* Places a piece at the destination of a move.
|
||||
@ -384,7 +445,9 @@ public class Board {
|
||||
* @param move The move at which destination to place the piece
|
||||
* @param piece The piece to place
|
||||
*/
|
||||
public void setDest(Move move, Piece piece) { set(move.getDest(), piece); }
|
||||
public void setDest(Move move, Piece piece) {
|
||||
set(move.getDest(), piece);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The board array
|
||||
|
@ -20,7 +20,8 @@ public class Castling extends Move {
|
||||
*/
|
||||
public Castling(Position pos, Position dest) {
|
||||
super(pos, dest);
|
||||
rookMove = dest.x == 6 ? new Move(7, pos.y, 5, pos.y) : new Move(0, pos.y, 3, pos.y);
|
||||
rookMove = dest.x == 6 ? new Move(7, pos.y, 5, pos.y)
|
||||
: new Move(0, pos.y, 3, pos.y);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -31,7 +32,9 @@ public class Castling extends Move {
|
||||
* @param xDest the horizontal destination of this castling move
|
||||
* @param yDest the vertical destination of this castling move
|
||||
*/
|
||||
public Castling(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); }
|
||||
public Castling(int xPos, int yPos, int xDest, int yDest) {
|
||||
this(new Position(xPos, yPos), new Position(xDest, yDest));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Board board) {
|
||||
@ -48,7 +51,8 @@ public class Castling extends Move {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {@code O-O-O} for a queenside castling or {@code O-O} for a kingside
|
||||
* @return {@code O-O-O} for a queenside castling or {@code O-O} for a
|
||||
* kingside
|
||||
* castling
|
||||
*/
|
||||
@Override
|
||||
|
@ -31,7 +31,9 @@ public class EnPassant extends Move {
|
||||
* @param xDest the horizontal destination of this move
|
||||
* @param yDest the vertical destination of this move
|
||||
*/
|
||||
public EnPassant(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); }
|
||||
public EnPassant(int xPos, int yPos, int xDest, int yDest) {
|
||||
this(new Position(xPos, yPos), new Position(xDest, yDest));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Board board) {
|
||||
@ -42,7 +44,10 @@ public class EnPassant extends Move {
|
||||
@Override
|
||||
public void revert(Board board, Piece capturedPiece) {
|
||||
super.revert(board, capturedPiece);
|
||||
board.set(capturePos, new Pawn(board.get(pos).getColor().opposite(), board));
|
||||
board.set(
|
||||
capturePos,
|
||||
new Pawn(board.get(pos).getColor().opposite(), board)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,15 +49,22 @@ public class FENString {
|
||||
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+)$");
|
||||
"^(?<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());
|
||||
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));
|
||||
activeColor
|
||||
= Color.fromFirstChar(matcher.group("activeColor").charAt(0));
|
||||
castlingAvailability = matcher.group("castlingAvailability");
|
||||
if (!matcher.group("enPassantTargetSquare").equals("-")) enPassantTargetSquare = Position.fromLAN(matcher.group("enPassantTargetSquare"));
|
||||
if (!matcher.group("enPassantTargetSquare").equals("-"))
|
||||
enPassantTargetSquare
|
||||
= Position.fromLAN(matcher.group("enPassantTargetSquare"));
|
||||
halfmoveClock = Integer.parseInt(matcher.group("halfmoveClock"));
|
||||
fullmoveNumber = Integer.parseInt(matcher.group("fullmoveNumber"));
|
||||
|
||||
@ -71,30 +78,40 @@ public class FENString {
|
||||
|
||||
// Piece placement
|
||||
final String[] rows = piecePlacement.split("/");
|
||||
if (rows.length != 8) throw new ChessException("FEN string contains invalid piece placement");
|
||||
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) {
|
||||
|
||||
for (char c : cols)
|
||||
// Empty space
|
||||
if (Character.isDigit(c)) {
|
||||
if (Character.isDigit(c))
|
||||
j += Character.getNumericValue(c);
|
||||
} else {
|
||||
Color color = Character.isUpperCase(c) ? Color.WHITE : Color.BLACK;
|
||||
else {
|
||||
Color color
|
||||
= Character.isUpperCase(c) ? Color.WHITE : Color.BLACK;
|
||||
try {
|
||||
Constructor<? extends Piece> pieceConstructor = Piece.fromFirstChar(c).getDeclaredConstructor(Color.class, Board.class);
|
||||
Constructor<? extends Piece> pieceConstructor = Piece
|
||||
.fromFirstChar(c)
|
||||
.getDeclaredConstructor(Color.class, Board.class);
|
||||
pieceConstructor.setAccessible(true);
|
||||
board.getBoardArr()[j][i] = pieceConstructor.newInstance(color, board);
|
||||
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
|
||||
| NoSuchMethodException | SecurityException e) {
|
||||
board.getBoardArr()[j][i]
|
||||
= pieceConstructor.newInstance(color, board);
|
||||
} catch (
|
||||
InstantiationException
|
||||
| IllegalAccessException
|
||||
| IllegalArgumentException
|
||||
| InvocationTargetException
|
||||
| NoSuchMethodException
|
||||
| SecurityException e
|
||||
) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
++j;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Active color
|
||||
board.getLog().setActiveColor(activeColor);
|
||||
|
||||
@ -144,7 +161,8 @@ public class FENString {
|
||||
for (int j = 0; j < 8; j++) {
|
||||
final Piece piece = board.getBoardArr()[j][i];
|
||||
|
||||
if (piece == null) ++empty;
|
||||
if (piece == null)
|
||||
++empty;
|
||||
else {
|
||||
|
||||
// Write empty field count
|
||||
@ -152,20 +170,22 @@ public class FENString {
|
||||
sb.append(empty);
|
||||
empty = 0;
|
||||
}
|
||||
|
||||
// Write piece character
|
||||
char p = piece.firstChar();
|
||||
sb.append(piece.getColor() == Color.WHITE ? Character.toUpperCase(p) : p);
|
||||
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('/');
|
||||
if (i < 7)
|
||||
sb.append('/');
|
||||
}
|
||||
piecePlacement = sb.toString();
|
||||
|
||||
@ -174,10 +194,14 @@ public class FENString {
|
||||
|
||||
// Castling availability
|
||||
castlingAvailability = "";
|
||||
final char castlingRightsChars[] = new char[] { 'K', 'Q', 'k', 'q' };
|
||||
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 = "-";
|
||||
if (board.getLog().getCastlingRights()[i])
|
||||
castlingAvailability += castlingRightsChars[i];
|
||||
if (castlingAvailability.isEmpty())
|
||||
castlingAvailability = "-";
|
||||
|
||||
// En passant availability
|
||||
enPassantTargetSquare = board.getLog().getEnPassant();
|
||||
@ -196,13 +220,15 @@ public class FENString {
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("%s %c %s %s %d %d",
|
||||
return String.format(
|
||||
"%s %c %s %s %d %d",
|
||||
piecePlacement,
|
||||
activeColor.firstChar(),
|
||||
castlingAvailability,
|
||||
enPassantTargetSquare == null ? "-" : enPassantTargetSquare.toLAN(),
|
||||
halfmoveClock,
|
||||
fullmoveNumber);
|
||||
fullmoveNumber
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,34 +19,52 @@ public class King extends Piece {
|
||||
* @param color the color of this king
|
||||
* @param board the board on which this king will be placed
|
||||
*/
|
||||
public King(Color color, Board board) { super(color, board); }
|
||||
public King(Color color, Board board) {
|
||||
super(color, board);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidMove(Move move) {
|
||||
return (move.getxDist() == 2 && move.getyDist() == 0
|
||||
&& (move.getDest().x == 6 && canCastleKingside() || move.getDest().x == 2 && canCastleQueenside()))
|
||||
|| move.getxDist() <= 1 && move.getyDist() <= 1 && checkDestination(move);
|
||||
return move.getxDist() == 2 && move.getyDist() == 0
|
||||
&& (move.getDest().x == 6 && canCastleKingside()
|
||||
|| move.getDest().x == 2 && canCastleQueenside())
|
||||
|| move.getxDist() <= 1 && move.getyDist() <= 1
|
||||
&& checkDestination(move);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Move> getPseudolegalMoves(Position pos) {
|
||||
List<Move> moves = new ArrayList<>();
|
||||
for (int i = Math.max(0, pos.x - 1); i < Math.min(8, pos.x + 2); i++)
|
||||
for (int j = Math.max(0, pos.y - 1); j < Math.min(8, pos.y + 2); j++)
|
||||
for (
|
||||
int j = Math.max(0, pos.y - 1);
|
||||
j < Math.min(8, pos.y + 2);
|
||||
j++
|
||||
)
|
||||
if (i != pos.x || j != pos.y) {
|
||||
Move move = new Move(pos, new Position(i, j));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) moves.add(move);
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
)
|
||||
moves.add(move);
|
||||
}
|
||||
|
||||
// Castling
|
||||
if (canCastleKingside()) moves.add(new Castling(pos, new Position(6, pos.y)));
|
||||
if (canCastleQueenside()) moves.add(new Castling(pos, new Position(2, pos.y)));
|
||||
if (canCastleKingside())
|
||||
moves.add(new Castling(pos, new Position(6, pos.y)));
|
||||
if (canCastleQueenside())
|
||||
moves.add(new Castling(pos, new Position(2, pos.y)));
|
||||
|
||||
return moves;
|
||||
}
|
||||
|
||||
private boolean canCastleKingside() {
|
||||
if (board.getLog().getCastlingRights()[getColor() == Color.WHITE ? MoveNode.WHITE_KINGSIDE : MoveNode.BLACK_KINGSIDE]) {
|
||||
if (
|
||||
board.getLog().getCastlingRights()[getColor() == Color.WHITE
|
||||
? MoveNode.WHITE_KINGSIDE
|
||||
: MoveNode.BLACK_KINGSIDE]
|
||||
) {
|
||||
int y = getColor() == Color.WHITE ? 7 : 0;
|
||||
Position kingPos = new Position(4, y);
|
||||
Position jumpPos = new Position(5, y);
|
||||
@ -58,7 +76,11 @@ public class King extends Piece {
|
||||
}
|
||||
|
||||
private boolean canCastleQueenside() {
|
||||
if (board.getLog().getCastlingRights()[getColor() == Color.WHITE ? MoveNode.WHITE_QUEENSIDE : MoveNode.BLACK_QUEENSIDE]) {
|
||||
if (
|
||||
board.getLog().getCastlingRights()[getColor() == Color.WHITE
|
||||
? MoveNode.WHITE_QUEENSIDE
|
||||
: MoveNode.BLACK_QUEENSIDE]
|
||||
) {
|
||||
int y = getColor() == Color.WHITE ? 7 : 0;
|
||||
Position kingPos = new Position(4, y);
|
||||
Position jumpPos = new Position(3, y);
|
||||
@ -69,9 +91,13 @@ public class King extends Piece {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean canCastle(Position kingPos, Position freeDest, Position rookPos, Position jumpPos) {
|
||||
private boolean canCastle(
|
||||
Position kingPos, Position freeDest, Position rookPos, Position jumpPos
|
||||
) {
|
||||
Piece rook = board.get(rookPos);
|
||||
return rook != null && rook instanceof Rook && isFreePath(new Move(kingPos, freeDest)) && !board.isAttacked(kingPos, getColor().opposite())
|
||||
return rook != null && rook instanceof Rook && isFreePath(
|
||||
new Move(kingPos, freeDest)
|
||||
) && !board.isAttacked(kingPos, getColor().opposite())
|
||||
&& !board.isAttacked(jumpPos, getColor().opposite());
|
||||
}
|
||||
|
||||
|
@ -26,13 +26,22 @@ public class Knight extends Piece {
|
||||
@Override
|
||||
public boolean isValidMove(Move move) {
|
||||
return Math.abs(move.getxDist() - move.getyDist()) == 1
|
||||
&& (move.getxDist() == 1 && move.getyDist() == 2 || move.getxDist() == 2 && move.getyDist() == 1) && checkDestination(move);
|
||||
&& (move.getxDist() == 1 && move.getyDist() == 2
|
||||
|| move.getxDist() == 2 && move.getyDist() == 1)
|
||||
&& checkDestination(move);
|
||||
}
|
||||
|
||||
private void checkAndInsertMove(List<Move> moves, Position pos, int offsetX, int offsetY) {
|
||||
if (pos.x + offsetX >= 0 && pos.x + offsetX < 8 && pos.y + offsetY >= 0 && pos.y + offsetY < 8) {
|
||||
Move move = new Move(pos, new Position(pos.x + offsetX, pos.y + offsetY));
|
||||
if (checkDestination(move)) moves.add(move);
|
||||
private void checkAndInsertMove(
|
||||
List<Move> moves, Position pos, int offsetX, int offsetY
|
||||
) {
|
||||
if (
|
||||
pos.x + offsetX >= 0 && pos.x + offsetX < 8 && pos.y + offsetY >= 0
|
||||
&& pos.y + offsetY < 8
|
||||
) {
|
||||
Move move
|
||||
= new Move(pos, new Position(pos.x + offsetX, pos.y + offsetY));
|
||||
if (checkDestination(move))
|
||||
moves.add(move);
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,5 +63,7 @@ public class Knight extends Piece {
|
||||
public int getValue() { return 35; }
|
||||
|
||||
@Override
|
||||
public char firstChar() { return 'n'; }
|
||||
public char firstChar() {
|
||||
return 'n';
|
||||
}
|
||||
}
|
||||
|
@ -29,14 +29,18 @@ public class Log implements Iterable<MoveNode> {
|
||||
/**
|
||||
* Creates an instance of {@link Log} in the default state.
|
||||
*/
|
||||
public Log() { reset(); }
|
||||
public Log() {
|
||||
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
|
||||
* with the current {@link MoveNode}.
|
||||
*
|
||||
* @param other The {@link Log} instance to copy
|
||||
* @param copyVariations If set to {@code true}, subsequent variations of the
|
||||
* @param copyVariations If set to {@code true}, subsequent variations of
|
||||
* the
|
||||
* current {@link MoveNode} are copied with the
|
||||
* {@link Log}
|
||||
*/
|
||||
@ -68,13 +72,17 @@ public class Log implements Iterable<MoveNode> {
|
||||
private boolean hasNext = !isEmpty();
|
||||
|
||||
@Override
|
||||
public boolean hasNext() { return hasNext; }
|
||||
public boolean hasNext() {
|
||||
return hasNext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MoveNode next() {
|
||||
MoveNode result = current;
|
||||
if (current.hasVariations()) current = current.getVariations().get(0);
|
||||
else hasNext = false;
|
||||
if (current.hasVariations())
|
||||
current = current.getVariations().get(0);
|
||||
else
|
||||
hasNext = false;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
@ -88,16 +96,30 @@ public class Log implements Iterable<MoveNode> {
|
||||
* @param capturedPiece The piece captured with the move
|
||||
*/
|
||||
public void add(Move move, Piece piece, Piece capturedPiece) {
|
||||
enPassant = piece instanceof Pawn && move.getyDist() == 2 ? new Position(move.getPos().x, move.getPos().y + move.getySign()) : null;
|
||||
if (activeColor == Color.BLACK) ++fullmoveNumber;
|
||||
if (piece instanceof Pawn || capturedPiece != null) halfmoveClock = 0;
|
||||
else++halfmoveClock;
|
||||
enPassant = piece instanceof Pawn && move.getyDist() == 2
|
||||
? new Position(move.getPos().x, move.getPos().y + move.getySign())
|
||||
: null;
|
||||
if (activeColor == Color.BLACK)
|
||||
++fullmoveNumber;
|
||||
if (piece instanceof Pawn || capturedPiece != null)
|
||||
halfmoveClock = 0;
|
||||
else
|
||||
++halfmoveClock;
|
||||
activeColor = activeColor.opposite();
|
||||
|
||||
// Disable castling rights if a king or a rook has been moved
|
||||
if (piece instanceof King || piece instanceof Rook) disableCastlingRights(piece, move.getPos());
|
||||
if (piece instanceof King || piece instanceof Rook)
|
||||
disableCastlingRights(piece, move.getPos());
|
||||
|
||||
final MoveNode leaf = new MoveNode(move, capturedPiece, castlingRights.clone(), enPassant, activeColor, fullmoveNumber, halfmoveClock);
|
||||
final MoveNode leaf = new MoveNode(
|
||||
move,
|
||||
capturedPiece,
|
||||
castlingRights.clone(),
|
||||
enPassant,
|
||||
activeColor,
|
||||
fullmoveNumber,
|
||||
halfmoveClock
|
||||
);
|
||||
|
||||
if (isEmpty()) {
|
||||
root = leaf;
|
||||
@ -117,7 +139,8 @@ public class Log implements Iterable<MoveNode> {
|
||||
current.getParent().getVariations().remove(current);
|
||||
current = current.getParent();
|
||||
update();
|
||||
} else reset();
|
||||
} else
|
||||
reset();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -128,7 +151,9 @@ public class Log implements Iterable<MoveNode> {
|
||||
/**
|
||||
* @return {@code true} if the current node has a parent node
|
||||
*/
|
||||
public boolean hasParent() { return !isEmpty() && current.hasParent(); }
|
||||
public boolean hasParent() {
|
||||
return !isEmpty() && current.hasParent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the log to its initial state corresponding to the default board
|
||||
@ -137,7 +162,9 @@ public class Log implements Iterable<MoveNode> {
|
||||
public void reset() {
|
||||
root = null;
|
||||
current = null;
|
||||
castlingRights = new boolean[] { true, true, true, true };
|
||||
castlingRights = new boolean[] {
|
||||
true, true, true, true
|
||||
};
|
||||
enPassant = null;
|
||||
activeColor = Color.WHITE;
|
||||
fullmoveNumber = 1;
|
||||
@ -150,7 +177,10 @@ public class Log implements Iterable<MoveNode> {
|
||||
* @param index the index of the variation to select
|
||||
*/
|
||||
public void selectNextNode(int index) {
|
||||
if (!isEmpty() && current.hasVariations() && index < current.getVariations().size()) {
|
||||
if (
|
||||
!isEmpty() && current.hasVariations()
|
||||
&& index < current.getVariations().size()
|
||||
) {
|
||||
current = current.getVariations().get(index);
|
||||
update();
|
||||
}
|
||||
@ -177,7 +207,8 @@ public class Log implements Iterable<MoveNode> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the active color, castling rights, en passant target square, fullmove
|
||||
* Sets the active color, castling rights, en passant target square,
|
||||
* fullmove
|
||||
* number and halfmove clock to those of the current {@link MoveNode}.
|
||||
*/
|
||||
private void update() {
|
||||
@ -189,22 +220,35 @@ public class Log implements Iterable<MoveNode> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removed the castling rights bound to a rook or king for the rest of the game.
|
||||
* Removed the castling rights bound to a rook or king for the rest of the
|
||||
* game.
|
||||
* This method should be called once the piece has been moved, as a castling
|
||||
* move involving this piece is forbidden afterwards.
|
||||
*
|
||||
* @param piece the rook or king to disable the castling rights for
|
||||
* @param initialPosition the initial position of the piece during the start of
|
||||
* @param piece the rook or king to disable the castling rights
|
||||
* for
|
||||
* @param initialPosition the initial position of the piece during the start
|
||||
* of
|
||||
* the game
|
||||
*/
|
||||
private void disableCastlingRights(Piece piece, Position initialPosition) {
|
||||
// Kingside
|
||||
if (piece instanceof King || piece instanceof Rook && initialPosition.x == 7)
|
||||
castlingRights[piece.getColor() == Color.WHITE ? MoveNode.WHITE_KINGSIDE : MoveNode.BLACK_KINGSIDE] = false;
|
||||
if (
|
||||
piece instanceof King
|
||||
|| piece instanceof Rook && initialPosition.x == 7
|
||||
)
|
||||
castlingRights[piece.getColor() == Color.WHITE
|
||||
? MoveNode.WHITE_KINGSIDE
|
||||
: MoveNode.BLACK_KINGSIDE] = false;
|
||||
|
||||
// Queenside
|
||||
if (piece instanceof King || piece instanceof Rook && initialPosition.x == 0)
|
||||
castlingRights[piece.getColor() == Color.WHITE ? MoveNode.WHITE_QUEENSIDE : MoveNode.BLACK_QUEENSIDE] = false;
|
||||
if (
|
||||
piece instanceof King
|
||||
|| piece instanceof Rook && initialPosition.x == 0
|
||||
)
|
||||
castlingRights[piece.getColor() == Color.WHITE
|
||||
? MoveNode.WHITE_QUEENSIDE
|
||||
: MoveNode.BLACK_QUEENSIDE] = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -212,17 +256,28 @@ public class Log implements Iterable<MoveNode> {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(castlingRights);
|
||||
result = prime * result + Objects.hash(activeColor, current, enPassant, fullmoveNumber, halfmoveClock);
|
||||
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;
|
||||
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)
|
||||
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;
|
||||
}
|
||||
|
||||
@ -246,10 +301,13 @@ public class Log implements Iterable<MoveNode> {
|
||||
*
|
||||
* @param castlingRights the castling rights to set
|
||||
*/
|
||||
public void setCastlingRights(boolean[] castlingRights) { this.castlingRights = castlingRights; }
|
||||
public void setCastlingRights(boolean[] castlingRights) {
|
||||
this.castlingRights = castlingRights;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the en passant target position of the current move or {@code null} if
|
||||
* @return the en passant target position of the current move or
|
||||
* {@code null} if
|
||||
* the current move is not an en passant move.
|
||||
*/
|
||||
public Position getEnPassant() { return enPassant; }
|
||||
@ -259,7 +317,9 @@ public class Log implements Iterable<MoveNode> {
|
||||
*
|
||||
* @param enPassant the en passant target position to set
|
||||
*/
|
||||
public void setEnPassant(Position enPassant) { this.enPassant = enPassant; }
|
||||
public void setEnPassant(Position enPassant) {
|
||||
this.enPassant = enPassant;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the color active during the current move
|
||||
@ -271,7 +331,9 @@ public class Log implements Iterable<MoveNode> {
|
||||
*
|
||||
* @param activeColor the active color to set
|
||||
*/
|
||||
public void setActiveColor(Color activeColor) { this.activeColor = activeColor; }
|
||||
public void setActiveColor(Color activeColor) {
|
||||
this.activeColor = activeColor;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of moves made until the current move
|
||||
@ -283,7 +345,9 @@ public class Log implements Iterable<MoveNode> {
|
||||
*
|
||||
* @param fullmoveNumber the fullmove number to set
|
||||
*/
|
||||
public void setFullmoveNumber(int fullmoveNumber) { this.fullmoveNumber = fullmoveNumber; }
|
||||
public void setFullmoveNumber(int fullmoveNumber) {
|
||||
this.fullmoveNumber = fullmoveNumber;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the number of halfmoves since the last capture move or pawn move
|
||||
@ -295,5 +359,7 @@ public class Log implements Iterable<MoveNode> {
|
||||
*
|
||||
* @param halfmoveClock the halfmove clock to set
|
||||
*/
|
||||
public void setHalfmoveClock(int halfmoveClock) { this.halfmoveClock = halfmoveClock; }
|
||||
public void setHalfmoveClock(int halfmoveClock) {
|
||||
this.halfmoveClock = halfmoveClock;
|
||||
}
|
||||
}
|
@ -44,7 +44,9 @@ public class Move {
|
||||
* @param xDest the horizontal destination of this move
|
||||
* @param yDest the vertical destination of this move
|
||||
*/
|
||||
public Move(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); }
|
||||
public Move(int xPos, int yPos, int xDest, int yDest) {
|
||||
this(new Position(xPos, yPos), new Position(xDest, yDest));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executed this move on a board.
|
||||
@ -52,7 +54,8 @@ public class Move {
|
||||
* @param board the board to execute this move on.
|
||||
*/
|
||||
public void execute(Board board) {
|
||||
// Move the piece to the move's destination square and clean the old position
|
||||
// Move the piece to the move's destination square and clean the old
|
||||
// position
|
||||
board.set(dest, board.get(pos));
|
||||
board.set(pos, null);
|
||||
}
|
||||
@ -61,11 +64,13 @@ public class Move {
|
||||
* Reverts this move on a board.
|
||||
*
|
||||
* @param board the board to revert this move on
|
||||
* @param capturedPiece the piece to place at the destination of this move (used
|
||||
* @param capturedPiece the piece to place at the destination of this move
|
||||
* (used
|
||||
* for reinstating captured pieces)
|
||||
*/
|
||||
public void revert(Board board, Piece capturedPiece) {
|
||||
// Move the piece to the move's position square and clean the destination
|
||||
// Move the piece to the move's position square and clean the
|
||||
// destination
|
||||
board.set(pos, board.get(dest));
|
||||
board.set(dest, capturedPiece);
|
||||
}
|
||||
@ -74,7 +79,9 @@ public class Move {
|
||||
* @return a new move containing this move's destination as its position and
|
||||
* this move's position as its destination
|
||||
*/
|
||||
public Move invert() { return new Move(dest, pos); }
|
||||
public Move invert() {
|
||||
return new Move(dest, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a move from a string representation in Long Algebraic Notation
|
||||
@ -86,14 +93,17 @@ public class Move {
|
||||
public static Move fromLAN(String move) {
|
||||
Position pos = Position.fromLAN(move.substring(0, 2));
|
||||
Position dest = Position.fromLAN(move.substring(2));
|
||||
if (move.length() == 5) {
|
||||
if (move.length() == 5)
|
||||
try {
|
||||
return new PawnPromotion(pos, dest, Piece.fromFirstChar(move.charAt(4)));
|
||||
return new PawnPromotion(
|
||||
pos,
|
||||
dest,
|
||||
Piece.fromFirstChar(move.charAt(4))
|
||||
);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return new Move(pos, dest);
|
||||
}
|
||||
|
||||
@ -103,7 +113,9 @@ public class Move {
|
||||
*
|
||||
* @return the LAN string
|
||||
*/
|
||||
public String toLAN() { return getPos().toLAN() + getDest().toLAN(); }
|
||||
public String toLAN() {
|
||||
return getPos().toLAN() + getDest().toLAN();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a move string from standard algebraic notation to a {@link Move}
|
||||
@ -115,13 +127,30 @@ public class Move {
|
||||
*/
|
||||
public static Move fromSAN(String sanMove, Board board) {
|
||||
Map<String, Pattern> patterns = new HashMap<>();
|
||||
patterns.put("pieceMove",
|
||||
patterns.put(
|
||||
"pieceMove",
|
||||
Pattern.compile(
|
||||
"^(?<pieceType>[NBRQK])(?:(?<fromFile>[a-h])|(?<fromRank>[1-8])|(?<fromSquare>[a-h][1-8]))?x?(?<toSquare>[a-h][1-8])(?:\\+{0,2}|\\#)$"));
|
||||
patterns.put("pawnCapture",
|
||||
Pattern.compile("^(?<fromFile>[a-h])(?<fromRank>[1-8])?x(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQ])?(?:\\+{0,2}|\\#)?$"));
|
||||
patterns.put("pawnPush", Pattern.compile("^(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQ])?(?:\\+{0,2}|\\#)$"));
|
||||
patterns.put("castling", Pattern.compile("^(?<queenside>O-O-O)|(?<kingside>O-O)(?:\\+{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",
|
||||
Pattern.compile(
|
||||
"^(?<fromFile>[a-h])(?<fromRank>[1-8])?x(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQ])?(?:\\+{0,2}|\\#)?$"
|
||||
)
|
||||
);
|
||||
patterns.put(
|
||||
"pawnPush",
|
||||
Pattern.compile(
|
||||
"^(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQ])?(?:\\+{0,2}|\\#)$"
|
||||
)
|
||||
);
|
||||
patterns.put(
|
||||
"castling",
|
||||
Pattern.compile(
|
||||
"^(?<queenside>O-O-O)|(?<kingside>O-O)(?:\\+{0,2}|\\#)?$"
|
||||
)
|
||||
);
|
||||
|
||||
for (Map.Entry<String, Pattern> entry : patterns.entrySet()) {
|
||||
Matcher m = entry.getValue().matcher(sanMove);
|
||||
@ -131,59 +160,92 @@ public class Move {
|
||||
switch (entry.getKey()) {
|
||||
case "pieceMove":
|
||||
dest = Position.fromLAN(m.group("toSquare"));
|
||||
if (m.group("fromSquare") != null) pos = Position.fromLAN(m.group("fromSquare"));
|
||||
if (m.group("fromSquare") != null)
|
||||
pos = Position.fromLAN(m.group("fromSquare"));
|
||||
else {
|
||||
Class<? extends Piece> pieceClass = Piece.fromFirstChar(m.group("pieceType").charAt(0));
|
||||
Class<? extends Piece> pieceClass = Piece
|
||||
.fromFirstChar(m.group("pieceType").charAt(0));
|
||||
char file;
|
||||
int rank;
|
||||
if (m.group("fromFile") != null) {
|
||||
file = m.group("fromFile").charAt(0);
|
||||
rank = board.get(pieceClass, file);
|
||||
pos = Position.fromLAN(String.format("%c%d", file, rank));
|
||||
} else if (m.group("fromRank") != null) {
|
||||
rank = Integer.parseInt(m.group("fromRank").substring(0, 1));
|
||||
pos = Position
|
||||
.fromLAN(String.format("%c%d", file, rank));
|
||||
} else
|
||||
if (m.group("fromRank") != null) {
|
||||
rank = Integer.parseInt(
|
||||
m.group("fromRank").substring(0, 1)
|
||||
);
|
||||
file = board.get(pieceClass, rank);
|
||||
pos = Position.fromLAN(String.format("%c%d", file, rank));
|
||||
} else pos = board.get(pieceClass, dest);
|
||||
pos = Position.fromLAN(
|
||||
String.format("%c%d", file, rank)
|
||||
);
|
||||
} else
|
||||
pos = board.get(pieceClass, dest);
|
||||
}
|
||||
move = new Move(pos, dest);
|
||||
break;
|
||||
case "pawnCapture":
|
||||
char file = m.group("fromFile").charAt(0);
|
||||
int rank = m.group("fromRank") == null ? board.get(Pawn.class, file) : Integer.parseInt(m.group("fromRank"));
|
||||
int rank = m.group("fromRank") == null
|
||||
? board.get(Pawn.class, file)
|
||||
: Integer.parseInt(m.group("fromRank"));
|
||||
|
||||
dest = Position.fromLAN(m.group("toSquare"));
|
||||
pos = Position.fromLAN(String.format("%c%d", file, rank));
|
||||
pos = Position
|
||||
.fromLAN(String.format("%c%d", file, rank));
|
||||
|
||||
if (m.group("promotedTo") != null) {
|
||||
if (m.group("promotedTo") != null)
|
||||
try {
|
||||
move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0)));
|
||||
move = new PawnPromotion(
|
||||
pos,
|
||||
dest,
|
||||
Piece.fromFirstChar(m.group("promotedTo").charAt(0))
|
||||
);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else move = new Move(pos, dest);
|
||||
else
|
||||
move = new Move(pos, dest);
|
||||
break;
|
||||
case "pawnPush":
|
||||
dest = Position.fromLAN(m.group("toSquare"));
|
||||
int step = board.getLog().getActiveColor() == Color.WHITE ? 1 : -1;
|
||||
int step
|
||||
= board.getLog().getActiveColor() == Color.WHITE ? 1
|
||||
: -1;
|
||||
|
||||
// One step forward
|
||||
if (board.getBoardArr()[dest.x][dest.y + step] != null) pos = new Position(dest.x, dest.y + step);
|
||||
if (board.getBoardArr()[dest.x][dest.y + step] != null)
|
||||
pos = new Position(dest.x, dest.y + step);
|
||||
|
||||
// Double step forward
|
||||
else pos = new Position(dest.x, dest.y + 2 * step);
|
||||
else
|
||||
pos = new Position(dest.x, dest.y + 2 * step);
|
||||
|
||||
if (m.group("promotedTo") != null) {
|
||||
if (m.group("promotedTo") != null)
|
||||
try {
|
||||
move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0)));
|
||||
move = new PawnPromotion(
|
||||
pos,
|
||||
dest,
|
||||
Piece.fromFirstChar(m.group("promotedTo").charAt(0))
|
||||
);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else move = new Move(pos, dest);
|
||||
else
|
||||
move = new Move(pos, dest);
|
||||
break;
|
||||
case "castling":
|
||||
pos = new Position(4, board.getLog().getActiveColor() == Color.WHITE ? 7 : 0);
|
||||
dest = new Position(m.group("kingside") != null ? 6 : 2, pos.y);
|
||||
pos = new Position(
|
||||
4,
|
||||
board.getLog().getActiveColor() == Color.WHITE ? 7
|
||||
: 0
|
||||
);
|
||||
dest = new Position(
|
||||
m.group("kingside") != null ? 6 : 2,
|
||||
pos.y
|
||||
);
|
||||
move = new Castling(pos, dest);
|
||||
break;
|
||||
}
|
||||
@ -194,7 +256,8 @@ public class Move {
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a string representation of this move in Standard Algebraic Notation
|
||||
* Generates a string representation of this move in Standard Algebraic
|
||||
* Notation
|
||||
* (SAN).
|
||||
*
|
||||
* @param board the {@link Board} providing the context of this move
|
||||
@ -205,15 +268,18 @@ public class Move {
|
||||
StringBuilder sb = new StringBuilder(8);
|
||||
|
||||
// Piece symbol
|
||||
if (!(piece instanceof Pawn)) sb.append(Character.toUpperCase(piece.firstChar()));
|
||||
if (!(piece instanceof Pawn))
|
||||
sb.append(Character.toUpperCase(piece.firstChar()));
|
||||
|
||||
// Position
|
||||
// TODO: Deconstruct position into optional file or rank
|
||||
// Omit position if the move is a pawn push
|
||||
if (!(piece instanceof Pawn && xDist == 0)) sb.append(pos.toLAN());
|
||||
if (!(piece instanceof Pawn && xDist == 0))
|
||||
sb.append(pos.toLAN());
|
||||
|
||||
// Capture indicator
|
||||
if (board.get(dest) != null) sb.append('x');
|
||||
if (board.get(dest) != null)
|
||||
sb.append('x');
|
||||
|
||||
// Destination
|
||||
sb.append(dest.toLAN());
|
||||
@ -237,19 +303,35 @@ public class Move {
|
||||
public boolean isDiagonal() { return getxDist() == getyDist(); }
|
||||
|
||||
@Override
|
||||
public String toString() { return toLAN(); }
|
||||
public String toString() {
|
||||
return toLAN();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() { return Objects.hash(getDest(), getPos(), getxDist(), getxSign(), getyDist(), getySign()); }
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
getDest(),
|
||||
getPos(),
|
||||
getxDist(),
|
||||
getxSign(),
|
||||
getyDist(),
|
||||
getySign()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Move other = (Move) obj;
|
||||
return Objects.equals(getDest(), other.getDest()) && Objects.equals(getPos(), other.getPos()) && getxDist() == other.getxDist()
|
||||
&& getxSign() == other.getxSign() && getyDist() == other.getyDist() && getySign() == other.getySign();
|
||||
return Objects.equals(getDest(), other.getDest()) && Objects
|
||||
.equals(getPos(), other.getPos()) && getxDist() == other.getxDist()
|
||||
&& getxSign() == other.getxSign() && getyDist() == other.getyDist()
|
||||
&& getySign() == other.getySign();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -265,20 +347,28 @@ public class Move {
|
||||
/**
|
||||
* @return the x distance
|
||||
*/
|
||||
public int getxDist() { return xDist; }
|
||||
public int getxDist() {
|
||||
return xDist;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the y distance
|
||||
*/
|
||||
public int getyDist() { return yDist; }
|
||||
public int getyDist() {
|
||||
return yDist;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the sign of the x distance
|
||||
*/
|
||||
public int getxSign() { return xSign; }
|
||||
public int getxSign() {
|
||||
return xSign;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the sign of the y distance
|
||||
*/
|
||||
public int getySign() { return ySign; }
|
||||
public int getySign() {
|
||||
return ySign;
|
||||
}
|
||||
}
|
||||
|
@ -77,17 +77,24 @@ public class MoveNode {
|
||||
* Creates a new {@link MoveNode}.
|
||||
*
|
||||
* @param move the logged {@link Move}
|
||||
* @param capturedPiece the {@link Piece} captures by the logged {@link Move}
|
||||
* @param capturedPiece the {@link Piece} captures by the logged
|
||||
* {@link Move}
|
||||
* @param castlingRights the castling rights present during the move
|
||||
* @param enPassant the en passant {@link Position} valid after the logged
|
||||
* @param enPassant the en passant {@link Position} valid after the
|
||||
* logged
|
||||
* {@link Move}, or {@code null} if there is none
|
||||
* @param activeColor the {@link Color} active after the logged {@link Move}
|
||||
* @param activeColor the {@link Color} active after the logged
|
||||
* {@link Move}
|
||||
* @param fullmoveCounter the number of moves made until the current move
|
||||
* @param halfmoveClock the number of halfmoves since the last capture move or
|
||||
* @param halfmoveClock the number of halfmoves since the last capture
|
||||
* move or
|
||||
* pawn move
|
||||
*/
|
||||
public MoveNode(Move move, Piece capturedPiece, boolean castlingRights[], Position enPassant, Color activeColor, int fullmoveCounter,
|
||||
int halfmoveClock) {
|
||||
public MoveNode(
|
||||
Move move, Piece capturedPiece, boolean castlingRights[],
|
||||
Position enPassant, Color activeColor, int fullmoveCounter,
|
||||
int halfmoveClock
|
||||
) {
|
||||
this.move = move;
|
||||
this.capturedPiece = capturedPiece;
|
||||
this.castlingRights = castlingRights;
|
||||
@ -106,10 +113,18 @@ public class MoveNode {
|
||||
* considers subsequent variations
|
||||
*/
|
||||
public MoveNode(MoveNode other, boolean copyVariations) {
|
||||
this(other.move, other.capturedPiece, other.castlingRights.clone(), other.enPassant, other.activeColor, other.fullmoveCounter,
|
||||
other.halfmoveClock);
|
||||
this(
|
||||
other.move,
|
||||
other.capturedPiece,
|
||||
other.castlingRights.clone(),
|
||||
other.enPassant,
|
||||
other.activeColor,
|
||||
other.fullmoveCounter,
|
||||
other.halfmoveClock
|
||||
);
|
||||
if (copyVariations && other.variations != null) {
|
||||
if (variations == null) variations = new ArrayList<>();
|
||||
if (variations == null)
|
||||
variations = new ArrayList<>();
|
||||
for (MoveNode variation : other.variations) {
|
||||
MoveNode copy = new MoveNode(variation, true);
|
||||
copy.parent = this;
|
||||
@ -124,7 +139,8 @@ public class MoveNode {
|
||||
* @param variation The {@link MoveNode} to append to this {@link MoveNode}
|
||||
*/
|
||||
public void addVariation(MoveNode variation) {
|
||||
if (variations == null) variations = new ArrayList<>();
|
||||
if (variations == null)
|
||||
variations = new ArrayList<>();
|
||||
if (!variations.contains(variation)) {
|
||||
variations.add(variation);
|
||||
variation.parent = this;
|
||||
@ -139,7 +155,9 @@ public class MoveNode {
|
||||
/**
|
||||
* @return {@code true} if this move node has any variations
|
||||
*/
|
||||
public boolean hasVariations() { return variations != null && variations.size() > 0; }
|
||||
public boolean hasVariations() {
|
||||
return variations != null && variations.size() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the parent node of this move node
|
||||
@ -156,18 +174,22 @@ public class MoveNode {
|
||||
/**
|
||||
* @return {@code true} if this move node has a parent
|
||||
*/
|
||||
public boolean hasParent() { return parent != null; }
|
||||
public boolean hasParent() {
|
||||
return parent != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("MoveNode[move=%s,capturedPiece=%s,castlingRights=%s,enPassant=%s,activeColor=%s,fullmoveCounter=%d,halfmoveClock=%d]",
|
||||
return String.format(
|
||||
"MoveNode[move=%s,capturedPiece=%s,castlingRights=%s,enPassant=%s,activeColor=%s,fullmoveCounter=%d,halfmoveClock=%d]",
|
||||
move,
|
||||
capturedPiece,
|
||||
Arrays.toString(castlingRights),
|
||||
enPassant,
|
||||
activeColor,
|
||||
fullmoveCounter,
|
||||
halfmoveClock);
|
||||
halfmoveClock
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -175,18 +197,31 @@ public class MoveNode {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + Arrays.hashCode(castlingRights);
|
||||
result = prime * result + Objects.hash(activeColor, capturedPiece, enPassant, fullmoveCounter, halfmoveClock, move);
|
||||
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;
|
||||
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)
|
||||
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);
|
||||
&& fullmoveCounter == other.fullmoveCounter
|
||||
&& halfmoveClock == other.halfmoveClock
|
||||
&& Objects.equals(move, other.move);
|
||||
}
|
||||
}
|
@ -19,62 +19,101 @@ public class Pawn extends Piece {
|
||||
* @param color the color of this pawn
|
||||
* @param board the board on which this pawn will be placed
|
||||
*/
|
||||
public Pawn(Color color, Board board) { super(color, board); }
|
||||
public Pawn(Color color, Board board) {
|
||||
super(color, board);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidMove(Move move) {
|
||||
boolean step = move.isVertical() && move.getyDist() == 1;
|
||||
boolean doubleStep = move.isVertical() && move.getyDist() == 2;
|
||||
boolean strafe = move.isDiagonal() && move.getxDist() == 1;
|
||||
boolean enPassant = strafe && move.getDest().equals(board.getLog().getEnPassant());
|
||||
if (getColor() == Color.WHITE) doubleStep &= move.getPos().y == 6;
|
||||
else doubleStep &= move.getPos().y == 1;
|
||||
boolean enPassant
|
||||
= strafe && move.getDest().equals(board.getLog().getEnPassant());
|
||||
if (getColor() == Color.WHITE)
|
||||
doubleStep &= move.getPos().y == 6;
|
||||
else
|
||||
doubleStep &= move.getPos().y == 1;
|
||||
|
||||
return enPassant || (step ^ doubleStep ^ strafe) && move.getySign() == (getColor() == Color.WHITE ? -1 : 1) && isFreePath(move);
|
||||
return enPassant || step ^ doubleStep ^ strafe
|
||||
&& move.getySign() == (getColor() == Color.WHITE ? -1 : 1)
|
||||
&& isFreePath(move);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isFreePath(Move move) {
|
||||
// Two steps forward
|
||||
if (move.getyDist() == 2)
|
||||
return board.getBoardArr()[move.getPos().x][move.getDest().y - move.getySign()] == null && board.getDest(move) == null;
|
||||
return board.getBoardArr()[move.getPos().x][move.getDest().y
|
||||
- move.getySign()] == null && board.getDest(move) == null;
|
||||
// One step forward
|
||||
else if (move.getxDist() == 0) return board.getDest(move) == null;
|
||||
else
|
||||
if (move.getxDist() == 0)
|
||||
return board.getDest(move) == null;
|
||||
// Capture move
|
||||
else return board.getDest(move) != null && board.getDest(move).getColor() != getColor();
|
||||
else
|
||||
return board.getDest(move) != null
|
||||
&& board.getDest(move).getColor() != getColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Move> getPseudolegalMoves(Position pos) {
|
||||
List<Move> moves = new ArrayList<>();
|
||||
int sign = getColor() == Color.WHITE ? -1 : 1;
|
||||
boolean pawnPromotion = sign == 1 && pos.y == 6 || sign == -1 && pos.y == 1;
|
||||
boolean pawnPromotion
|
||||
= sign == 1 && pos.y == 6 || sign == -1 && pos.y == 1;
|
||||
|
||||
// Strafe left
|
||||
if (pos.x > 0) addMoveIfValid(moves, pos, new Position(pos.x - 1, pos.y + sign), pawnPromotion);
|
||||
if (pos.x > 0)
|
||||
addMoveIfValid(
|
||||
moves,
|
||||
pos,
|
||||
new Position(pos.x - 1, pos.y + sign),
|
||||
pawnPromotion
|
||||
);
|
||||
|
||||
// Strafe right
|
||||
if (pos.x < 7) addMoveIfValid(moves, pos, new Position(pos.x + 1, pos.y + sign), pawnPromotion);
|
||||
if (pos.x < 7)
|
||||
addMoveIfValid(
|
||||
moves,
|
||||
pos,
|
||||
new Position(pos.x + 1, pos.y + sign),
|
||||
pawnPromotion
|
||||
);
|
||||
|
||||
// Step forward
|
||||
if (sign == 1 && pos.y < 7 || sign == -1 && pos.y > 0) addMoveIfValid(moves, pos, new Position(pos.x, pos.y + sign), pawnPromotion);
|
||||
if (sign == 1 && pos.y < 7 || sign == -1 && pos.y > 0)
|
||||
addMoveIfValid(
|
||||
moves,
|
||||
pos,
|
||||
new Position(pos.x, pos.y + sign),
|
||||
pawnPromotion
|
||||
);
|
||||
|
||||
// Double step forward
|
||||
if (sign == 1 && pos.y == 1 || sign == -1 && pos.y == 6) addMoveIfValid(moves, pos, new Position(pos.x, pos.y + 2 * sign), pawnPromotion);
|
||||
if (sign == 1 && pos.y == 1 || sign == -1 && pos.y == 6)
|
||||
addMoveIfValid(
|
||||
moves,
|
||||
pos,
|
||||
new Position(pos.x, pos.y + 2 * sign),
|
||||
pawnPromotion
|
||||
);
|
||||
|
||||
// Add en passant move if necessary
|
||||
if (board.getLog().getEnPassant() != null) {
|
||||
Move move = new EnPassant(pos, board.getLog().getEnPassant());
|
||||
if (move.isDiagonal() && move.getxDist() == 1) moves.add(move);
|
||||
if (move.isDiagonal() && move.getxDist() == 1)
|
||||
moves.add(move);
|
||||
}
|
||||
|
||||
return moves;
|
||||
}
|
||||
|
||||
private void addMoveIfValid(List<Move> moves, Position pos, Position dest, boolean pawnPromotion) {
|
||||
private void addMoveIfValid(
|
||||
List<Move> moves, Position pos, Position dest, boolean pawnPromotion
|
||||
) {
|
||||
Move move = new Move(pos, dest);
|
||||
if (isFreePath(move)) {
|
||||
if (pawnPromotion) {
|
||||
if (isFreePath(move))
|
||||
if (pawnPromotion)
|
||||
try {
|
||||
moves.add(new PawnPromotion(pos, dest, Queen.class));
|
||||
moves.add(new PawnPromotion(pos, dest, Rook.class));
|
||||
@ -83,8 +122,8 @@ public class Pawn extends Piece {
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} else moves.add(move);
|
||||
}
|
||||
else
|
||||
moves.add(move);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -26,26 +26,28 @@ public class PawnPromotion extends Move {
|
||||
* @param dest the destination of this move
|
||||
* @param promotionPieceClass the class of the piece to which the pawn is
|
||||
* promoted
|
||||
*
|
||||
* @throws ReflectiveOperationException if the promotion piece could not be
|
||||
* instantiated
|
||||
* @throws RuntimeException if the promotion piece could not be
|
||||
* instantiated
|
||||
*/
|
||||
public PawnPromotion(Position pos, Position dest, Class<? extends Piece> promotionPieceClass)
|
||||
public PawnPromotion(
|
||||
Position pos, Position dest, Class<? extends Piece> promotionPieceClass
|
||||
)
|
||||
throws ReflectiveOperationException, RuntimeException {
|
||||
super(pos, dest);
|
||||
|
||||
// Cache piece constructor
|
||||
promotionPieceConstructor = promotionPieceClass.getDeclaredConstructor(Color.class, Board.class);
|
||||
promotionPieceConstructor = promotionPieceClass
|
||||
.getDeclaredConstructor(Color.class, Board.class);
|
||||
promotionPieceConstructor.setAccessible(true);
|
||||
|
||||
// Get piece char
|
||||
promotionPieceChar = (char) promotionPieceClass.getMethod("firstChar").invoke(promotionPieceConstructor.newInstance(null, null));
|
||||
promotionPieceChar = (char) promotionPieceClass.getMethod("firstChar")
|
||||
.invoke(promotionPieceConstructor.newInstance(null, null));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Creates an instance of {@link PawnPromotion}.
|
||||
*
|
||||
* @param xPos the horizontal position of this move
|
||||
@ -59,17 +61,33 @@ public class PawnPromotion extends Move {
|
||||
* @throws RuntimeException if the promotion piece could not be
|
||||
* instantiated
|
||||
*/
|
||||
public PawnPromotion(int xPos, int yPos, int xDest, int yDest, Class<? extends Piece> promotionPieceClass)
|
||||
public PawnPromotion(
|
||||
int xPos, int yPos, int xDest, int yDest,
|
||||
Class<? extends Piece> promotionPieceClass
|
||||
)
|
||||
throws ReflectiveOperationException, RuntimeException {
|
||||
this(new Position(xPos, yPos), new Position(xDest, yDest), promotionPieceClass);
|
||||
this(
|
||||
new Position(xPos, yPos),
|
||||
new Position(xDest, yDest),
|
||||
promotionPieceClass
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(Board board) {
|
||||
try {
|
||||
board.set(pos, promotionPieceConstructor.newInstance(board.get(pos).getColor(), board));
|
||||
board.set(
|
||||
pos,
|
||||
promotionPieceConstructor.newInstance(board.get(pos).getColor(), board)
|
||||
);
|
||||
super.execute(board);
|
||||
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException e) {
|
||||
} catch (
|
||||
InstantiationException
|
||||
| IllegalAccessException
|
||||
| IllegalArgumentException
|
||||
| InvocationTargetException
|
||||
| SecurityException e
|
||||
) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
@ -81,7 +99,9 @@ public class PawnPromotion extends Move {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toLAN() { return pos.toLAN() + dest.toLAN() + promotionPieceChar; }
|
||||
public String toLAN() {
|
||||
return pos.toLAN() + dest.toLAN() + promotionPieceChar;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toSAN(Board board) {
|
||||
@ -93,16 +113,21 @@ public class PawnPromotion extends Move {
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = super.hashCode();
|
||||
result = prime * result + Objects.hash(promotionPieceChar, promotionPieceConstructor);
|
||||
result = prime * result
|
||||
+ Objects.hash(promotionPieceChar, promotionPieceConstructor);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (!super.equals(obj)) return false;
|
||||
if (!(obj instanceof PawnPromotion)) return false;
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (!super.equals(obj))
|
||||
return false;
|
||||
if (!(obj instanceof PawnPromotion))
|
||||
return false;
|
||||
PawnPromotion other = (PawnPromotion) obj;
|
||||
return promotionPieceChar == other.promotionPieceChar && Objects.equals(promotionPieceConstructor, other.promotionPieceConstructor);
|
||||
return promotionPieceChar == other.promotionPieceChar && Objects
|
||||
.equals(promotionPieceConstructor, other.promotionPieceConstructor);
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,8 @@ public abstract class Piece implements Cloneable {
|
||||
for (Iterator<Move> iterator = moves.iterator(); iterator.hasNext();) {
|
||||
Move move = iterator.next();
|
||||
board.move(move);
|
||||
if (board.checkCheck(getColor())) iterator.remove();
|
||||
if (board.checkCheck(getColor()))
|
||||
iterator.remove();
|
||||
board.revert();
|
||||
}
|
||||
return moves;
|
||||
@ -64,16 +65,23 @@ public abstract class Piece implements Cloneable {
|
||||
public abstract boolean isValidMove(Move move);
|
||||
|
||||
/**
|
||||
* Checks, if the squares between the position and the destination of a move are
|
||||
* Checks, if the squares between the position and the destination of a move
|
||||
* are
|
||||
* free.
|
||||
*
|
||||
* @param move The move to check
|
||||
* @return {@true} if the path is free
|
||||
*/
|
||||
protected boolean isFreePath(Move move) {
|
||||
for (int i = move.getPos().x + move.getxSign(), j = move.getPos().y + move.getySign(); i != move.getDest().x
|
||||
|| j != move.getDest().y; i += move.getxSign(), j += move.getySign())
|
||||
if (board.getBoardArr()[i][j] != null) return false;
|
||||
for (
|
||||
int i = move.getPos().x + move.getxSign(), j
|
||||
= move.getPos().y + move.getySign();
|
||||
i != move.getDest().x
|
||||
|| j != move.getDest().y;
|
||||
i += move.getxSign(), j += move.getySign()
|
||||
)
|
||||
if (board.getBoardArr()[i][j] != null)
|
||||
return false;
|
||||
return checkDestination(move);
|
||||
}
|
||||
|
||||
@ -84,7 +92,10 @@ public abstract class Piece implements Cloneable {
|
||||
* @param move The move to check
|
||||
* @return {@code false} if the move's destination is from the same team
|
||||
*/
|
||||
protected final boolean checkDestination(Move move) { return board.getDest(move) == null || board.getDest(move).getColor() != getColor(); }
|
||||
protected final boolean checkDestination(Move move) {
|
||||
return board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object clone() {
|
||||
@ -98,35 +109,47 @@ public abstract class Piece implements Cloneable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return String.format("%s[color=%s]", getClass().getSimpleName(), color); }
|
||||
public String toString() {
|
||||
return String.format("%s[color=%s]", getClass().getSimpleName(), color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() { return Objects.hash(color); }
|
||||
public int hashCode() {
|
||||
return Objects.hash(color);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Piece other = (Piece) obj;
|
||||
return color == other.color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the standard value of this {@link Piece} that can be used for board
|
||||
* @return the standard value of this {@link Piece} that can be used for
|
||||
* board
|
||||
* evaluation
|
||||
*/
|
||||
public abstract int getValue();
|
||||
|
||||
/**
|
||||
* @return The first character of this {@link Piece} in algebraic notation and
|
||||
* @return The first character of this {@link Piece} in algebraic notation
|
||||
* and
|
||||
* lower case
|
||||
*/
|
||||
public char firstChar() { return Character.toLowerCase(getClass().getSimpleName().charAt(0)); }
|
||||
public char firstChar() {
|
||||
return Character.toLowerCase(getClass().getSimpleName().charAt(0));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param firstChar the first character of a piece's name
|
||||
* @return the class of the piece associated with that character or {@code null}
|
||||
* @return the class of the piece associated with that character or
|
||||
* {@code null}
|
||||
* if no piece is associated with the given character
|
||||
*/
|
||||
public static Class<? extends Piece> fromFirstChar(char firstChar) {
|
||||
@ -175,19 +198,26 @@ public abstract class Piece implements Cloneable {
|
||||
|
||||
/**
|
||||
* @param c the first character of a color's name
|
||||
* @return {@code WHITE} if the character is {@code w} or {@code W}, else
|
||||
* @return {@code WHITE} if the character is {@code w} or {@code W},
|
||||
* else
|
||||
* {@code BLACK}
|
||||
*/
|
||||
public static Color fromFirstChar(char c) { return Character.toLowerCase(c) == 'w' ? WHITE : BLACK; }
|
||||
public static Color fromFirstChar(char c) {
|
||||
return Character.toLowerCase(c) == 'w' ? WHITE : BLACK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the first character (lower case) of this color
|
||||
*/
|
||||
public char firstChar() { return this == WHITE ? 'w' : 'b'; }
|
||||
public char firstChar() {
|
||||
return this == WHITE ? 'w' : 'b';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the opposite of this color
|
||||
*/
|
||||
public Color opposite() { return this == WHITE ? BLACK : WHITE; }
|
||||
public Color opposite() {
|
||||
return this == WHITE ? BLACK : WHITE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,17 +37,26 @@ public class Position {
|
||||
* @param pos the LAN string to construct a position from
|
||||
* @return the position constructed from LAN
|
||||
*/
|
||||
public static Position fromLAN(String pos) { return new Position(pos.charAt(0) - 97, 8 - Character.getNumericValue(pos.charAt(1))); }
|
||||
public static Position fromLAN(String pos) {
|
||||
return new Position(
|
||||
pos.charAt(0) - 97,
|
||||
8 - Character.getNumericValue(pos.charAt(1))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this position to Long Algebraic Notation (LAN)
|
||||
*
|
||||
* @return a LAN string representing this position
|
||||
*/
|
||||
public String toLAN() { return String.valueOf((char) (x + 97)) + String.valueOf(8 - y); }
|
||||
public String toLAN() {
|
||||
return String.valueOf((char) (x + 97)) + String.valueOf(8 - y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return String.format("[%d, %d]", x, y); }
|
||||
public String toString() {
|
||||
return String.format("[%d, %d]", x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
@ -60,12 +69,17 @@ public class Position {
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
if (this == obj)
|
||||
return true;
|
||||
if (obj == null)
|
||||
return false;
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Position other = (Position) obj;
|
||||
if (x != other.x) return false;
|
||||
if (y != other.y) return false;
|
||||
if (x != other.x)
|
||||
return false;
|
||||
if (y != other.y)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -25,7 +25,8 @@ public class Queen extends Piece {
|
||||
|
||||
@Override
|
||||
public boolean isValidMove(Move move) {
|
||||
return ((move.isHorizontal() || move.isVertical()) || move.isDiagonal()) && isFreePath(move);
|
||||
return (move.isHorizontal() || move.isVertical() || move.isDiagonal())
|
||||
&& isFreePath(move);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -35,73 +36,106 @@ public class Queen extends Piece {
|
||||
// Horizontal moves to the right
|
||||
for (int i = pos.x + 1; i < 8; i++) {
|
||||
Move move = new Move(pos, new Position(i, pos.y));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Horizontal moves to the left
|
||||
for (int i = pos.x - 1; i >= 0; i--) {
|
||||
Move move = new Move(pos, new Position(i, pos.y));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Vertical moves to the top
|
||||
for (int i = pos.y - 1; i >= 0; i--) {
|
||||
Move move = new Move(pos, new Position(pos.x, i));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Vertical moves to the bottom
|
||||
for (int i = pos.y + 1; i < 8; i++) {
|
||||
Move move = new Move(pos, new Position(pos.x, i));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Diagonal moves to the lower right
|
||||
for (int i = pos.x + 1, j = pos.y + 1; i < 8 && j < 8; i++, j++) {
|
||||
Move move = new Move(pos, new Position(i, j));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Diagonal moves to the lower left
|
||||
for (int i = pos.x - 1, j = pos.y + 1; i >= 0 && j < 8; i--, j++) {
|
||||
Move move = new Move(pos, new Position(i, j));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Diagonal moves to the upper right
|
||||
for (int i = pos.x + 1, j = pos.y - 1; i < 8 && j >= 0; i++, j--) {
|
||||
Move move = new Move(pos, new Position(i, j));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Diagonal moves to the upper left
|
||||
for (int i = pos.x - 1, j = pos.y - 1; i >= 0 && j >= 0; i--, j--) {
|
||||
Move move = new Move(pos, new Position(i, j));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
return moves;
|
||||
}
|
||||
|
@ -35,37 +35,54 @@ public class Rook extends Piece {
|
||||
// Horizontal moves to the right
|
||||
for (int i = pos.x + 1; i < 8; i++) {
|
||||
Move move = new Move(pos, new Position(i, pos.y));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Horizontal moves to the left
|
||||
for (int i = pos.x - 1; i >= 0; i--) {
|
||||
Move move = new Move(pos, new Position(i, pos.y));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Vertical moves to the top
|
||||
for (int i = pos.y - 1; i >= 0; i--) {
|
||||
Move move = new Move(pos, new Position(pos.x, i));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
|
||||
// Vertical moves to the bottom
|
||||
for (int i = pos.y + 1; i < 8; i++) {
|
||||
Move move = new Move(pos, new Position(pos.x, i));
|
||||
if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) {
|
||||
if (
|
||||
board.getDest(move) == null
|
||||
|| board.getDest(move).getColor() != getColor()
|
||||
) {
|
||||
moves.add(move);
|
||||
if (board.getDest(move) != null) break;
|
||||
} else break;
|
||||
if (board.getDest(move) != null)
|
||||
break;
|
||||
} else
|
||||
break;
|
||||
}
|
||||
return moves;
|
||||
}
|
||||
|
@ -23,18 +23,23 @@ public class EventBus {
|
||||
* @return a singleton instance of {@link EventBus}
|
||||
*/
|
||||
public static EventBus getInstance() {
|
||||
if (instance == null) instance = new EventBus();
|
||||
if (instance == null)
|
||||
instance = new EventBus();
|
||||
return instance;
|
||||
}
|
||||
|
||||
private EventBus() { subscribers = new ArrayList<>(); }
|
||||
private EventBus() {
|
||||
subscribers = new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a subscriber to which future events will be dispatched.
|
||||
*
|
||||
* @param subscribable the subscriber to register
|
||||
*/
|
||||
public void register(Subscriber subscribable) { subscribers.add(subscribable); }
|
||||
public void register(Subscriber subscribable) {
|
||||
subscribers.add(subscribable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatches an event to all {@Subscriber}s registered at this event bus.
|
||||
@ -42,7 +47,9 @@ public class EventBus {
|
||||
* @param event the event to dispatch
|
||||
*/
|
||||
public void dispatch(Event<?> event) {
|
||||
subscribers.stream().filter(e -> e.supports().contains(event.getClass())).forEach(e -> e.handle(event));
|
||||
subscribers.stream()
|
||||
.filter(e -> e.supports().contains(event.getClass()))
|
||||
.forEach(e -> e.handle(event));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -19,7 +19,9 @@ public class GameStartEvent implements Event<Game> {
|
||||
*
|
||||
* @param source the game started
|
||||
*/
|
||||
public GameStartEvent(Game source) { game = source; }
|
||||
public GameStartEvent(Game source) {
|
||||
game = source;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Game getData() { return game; }
|
||||
|
@ -19,7 +19,8 @@ public interface Subscriber {
|
||||
/**
|
||||
* Consumes an event dispatched by an event bus.
|
||||
*
|
||||
* @param event The event dispatched by the event bus, only of supported type
|
||||
* @param event The event dispatched by the event bus, only of supported
|
||||
* type
|
||||
*/
|
||||
void handle(Event<?> event);
|
||||
|
||||
|
@ -37,7 +37,8 @@ public class Game {
|
||||
/**
|
||||
* Initializes game with a new {@link Board}.
|
||||
*
|
||||
* @param boardPane the board pane which will display the newly created board
|
||||
* @param boardPane the board pane which will display the newly created
|
||||
* board
|
||||
* @param whiteName the name of the player controlling the white pieces
|
||||
* @param blackName the name of the player controlling the black pieces
|
||||
*/
|
||||
@ -49,12 +50,15 @@ public class Game {
|
||||
/**
|
||||
* Initializes game with an existing {@link Board}.
|
||||
*
|
||||
* @param boardPane the board pane which will display the newly created board
|
||||
* @param boardPane the board pane which will display the newly created
|
||||
* board
|
||||
* @param whiteName the name of the player controlling the white pieces
|
||||
* @param blackName the name of the player controlling the black pieces
|
||||
* @param board the board on which the game will be played
|
||||
*/
|
||||
public Game(BoardPane boardPane, String whiteName, String blackName, Board board) {
|
||||
public Game(
|
||||
BoardPane boardPane, String whiteName, String blackName, Board board
|
||||
) {
|
||||
this.board = board;
|
||||
init(boardPane, whiteName, blackName);
|
||||
}
|
||||
@ -74,9 +78,11 @@ public class Game {
|
||||
/**
|
||||
* Initializes player subclass.
|
||||
*
|
||||
* @param name the name of the player. {@code Natural Player} will initialize a
|
||||
* @param name the name of the player. {@code Natural Player} will
|
||||
* initialize a
|
||||
* {@link NaturalPlayer}, {@code AI Player} will initialize an
|
||||
* {@link AIPlayer}. Everything else will attempt to load an engine
|
||||
* {@link AIPlayer}. Everything else will attempt to load an
|
||||
* engine
|
||||
* with that name
|
||||
* @param color the color of the player
|
||||
* @return the instantiated player or {@code null} if the name could not be
|
||||
@ -90,7 +96,8 @@ public class Game {
|
||||
return new AIPlayer(this, color, 4, -10);
|
||||
default:
|
||||
for (EngineInfo info : EngineUtil.getEngineInfos())
|
||||
if (info.name.equals(name)) return new UCIPlayer(this, color, info.path);
|
||||
if (info.name.equals(name))
|
||||
return new UCIPlayer(this, color, info.path);
|
||||
System.err.println("Invalid player name: " + name);
|
||||
return null;
|
||||
}
|
||||
@ -98,14 +105,18 @@ public class Game {
|
||||
|
||||
/**
|
||||
* Should be called once a player makes a move. Depending on the legality of
|
||||
* that move and the state of the game another move might be requested from one
|
||||
* that move and the state of the game another move might be requested from
|
||||
* one
|
||||
* of the players.
|
||||
*
|
||||
* @param player the player who generated the move
|
||||
* @param move the generated move
|
||||
*/
|
||||
public synchronized void onMove(Player player, Move move) {
|
||||
if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) {
|
||||
if (
|
||||
board.getPos(move).getColor() == player.color
|
||||
&& board.attemptMove(move)
|
||||
) {
|
||||
|
||||
// Redraw
|
||||
boardComponent.repaint();
|
||||
@ -114,25 +125,33 @@ public class Game {
|
||||
// Run garbage collection
|
||||
System.gc();
|
||||
|
||||
BoardState boardState = board.getState(board.getDest(move).getColor().opposite());
|
||||
BoardState boardState
|
||||
= board.getState(board.getDest(move).getColor().opposite());
|
||||
EventBus.getInstance().dispatch(new MoveEvent(move, boardState));
|
||||
switch (boardState) {
|
||||
case CHECKMATE:
|
||||
case STALEMATE:
|
||||
String result = String.format("%s in %s!%n", player.color.opposite(), boardState);
|
||||
String result = String.format(
|
||||
"%s in %s!%n",
|
||||
player.color.opposite(),
|
||||
boardState
|
||||
);
|
||||
System.out.print(result);
|
||||
JOptionPane.showMessageDialog(boardComponent, result);
|
||||
break;
|
||||
case CHECK:
|
||||
System.out.printf("%s in check!%n", player.color.opposite());
|
||||
System.out
|
||||
.printf("%s in check!%n", player.color.opposite());
|
||||
default:
|
||||
players.get(board.getLog().getActiveColor()).requestMove();
|
||||
}
|
||||
} else player.requestMove();
|
||||
} else
|
||||
player.requestMove();
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts the game by requesting a move from the player of the currently active
|
||||
* Starts the game by requesting a move from the player of the currently
|
||||
* active
|
||||
* color.
|
||||
*/
|
||||
public synchronized void start() {
|
||||
@ -141,7 +160,8 @@ public class Game {
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancels move calculations, initializes the default position and clears the
|
||||
* Cancels move calculations, initializes the default position and clears
|
||||
* the
|
||||
* {@link OverlayComponent}.
|
||||
*/
|
||||
public synchronized void reset() {
|
||||
@ -155,7 +175,9 @@ public class Game {
|
||||
/**
|
||||
* Stops the game by disconnecting its players from the UI.
|
||||
*/
|
||||
public synchronized void stop() { players.values().forEach(Player::disconnect); }
|
||||
public synchronized void stop() {
|
||||
players.values().forEach(Player::disconnect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Assigns the players their opposite colors.
|
||||
|
@ -39,10 +39,13 @@ public class NaturalPlayer extends Player implements MouseListener {
|
||||
*
|
||||
* @param game the game in which this player will be used
|
||||
* @param color the piece color this player will control
|
||||
* @param overlayComponent the overlay component that will be used to display
|
||||
* @param overlayComponent the overlay component that will be used to
|
||||
* display
|
||||
* possible moves to the user
|
||||
*/
|
||||
public NaturalPlayer(Game game, Color color, OverlayComponent overlayComponent) {
|
||||
public NaturalPlayer(
|
||||
Game game, Color color, OverlayComponent overlayComponent
|
||||
) {
|
||||
super(game, color);
|
||||
this.overlayComponent = overlayComponent;
|
||||
name = "Player";
|
||||
@ -52,41 +55,58 @@ public class NaturalPlayer extends Player implements MouseListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestMove() { moveRequested = true; }
|
||||
public void requestMove() {
|
||||
moveRequested = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelMove() { moveRequested = false; }
|
||||
public void cancelMove() {
|
||||
moveRequested = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() { overlayComponent.removeMouseListener(this); }
|
||||
public void disconnect() {
|
||||
overlayComponent.removeMouseListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mousePressed(MouseEvent evt) {
|
||||
if (!moveRequested) return;
|
||||
if (!moveRequested)
|
||||
return;
|
||||
if (selectedPiece == null) {
|
||||
|
||||
// Get selected Piece
|
||||
final Position pos = new Position(evt.getPoint().x / overlayComponent.getTileSize(), evt.getPoint().y / overlayComponent.getTileSize());
|
||||
final Position pos = new Position(
|
||||
evt.getPoint().x / overlayComponent.getTileSize(),
|
||||
evt.getPoint().y / overlayComponent.getTileSize()
|
||||
);
|
||||
selectedPiece = board.get(pos);
|
||||
|
||||
// Check if a piece was selected
|
||||
if (selectedPiece != null) {
|
||||
|
||||
if (selectedPiece != null)
|
||||
// Discard selection if the piece has the wrong color
|
||||
if (selectedPiece.getColor() == color.opposite()) selectedPiece = null;
|
||||
if (selectedPiece.getColor() == color.opposite())
|
||||
selectedPiece = null;
|
||||
else {
|
||||
|
||||
// Generate all moves possible with the selected piece and display their
|
||||
// Generate all moves possible with the selected piece and
|
||||
// display their
|
||||
// destinations
|
||||
possibleMoves = selectedPiece.getMoves(pos);
|
||||
overlayComponent.displayDots(possibleMoves.stream().map(move -> move.getDest()).collect(Collectors.toList()));
|
||||
}
|
||||
overlayComponent.displayDots(
|
||||
possibleMoves.stream().map(Move::getDest).collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
} else {
|
||||
Position dest = new Position(evt.getPoint().x / overlayComponent.getTileSize(), evt.getPoint().y / overlayComponent.getTileSize());
|
||||
Position dest = new Position(
|
||||
evt.getPoint().x / overlayComponent.getTileSize(),
|
||||
evt.getPoint().y / overlayComponent.getTileSize()
|
||||
);
|
||||
|
||||
// Get all moves leading to the specified destination
|
||||
List<Move> selectedMoves = possibleMoves.stream().filter(m -> m.getDest().equals(dest)).collect(Collectors.toList());
|
||||
List<Move> selectedMoves = possibleMoves.stream()
|
||||
.filter(m -> m.getDest().equals(dest))
|
||||
.collect(Collectors.toList());
|
||||
if (!selectedMoves.isEmpty()) {
|
||||
Move move;
|
||||
|
||||
@ -94,17 +114,22 @@ public class NaturalPlayer extends Player implements MouseListener {
|
||||
if (selectedMoves.size() > 1) {
|
||||
|
||||
// Let the user select a promotion piece
|
||||
JComboBox<Move> comboBox = new JComboBox<>(selectedMoves.toArray(new Move[0]));
|
||||
JOptionPane.showMessageDialog(overlayComponent, comboBox, "Select a promotion", JOptionPane.QUESTION_MESSAGE);
|
||||
JComboBox<Move> comboBox
|
||||
= new JComboBox<>(selectedMoves.toArray(new Move[0]));
|
||||
JOptionPane.showMessageDialog(
|
||||
overlayComponent,
|
||||
comboBox,
|
||||
"Select a promotion",
|
||||
JOptionPane.QUESTION_MESSAGE
|
||||
);
|
||||
|
||||
move = selectedMoves.get(comboBox.getSelectedIndex());
|
||||
} else move = selectedMoves.get(0);
|
||||
|
||||
} else
|
||||
move = selectedMoves.get(0);
|
||||
// Tell the game to execute the move
|
||||
moveRequested = false;
|
||||
game.onMove(NaturalPlayer.this, move);
|
||||
}
|
||||
|
||||
// Discard the selection
|
||||
overlayComponent.clearDots();
|
||||
selectedPiece = null;
|
||||
|
@ -49,13 +49,19 @@ public class UCIPlayer extends Player implements UCIListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancelMove() { handle.stop(); }
|
||||
public void cancelMove() {
|
||||
handle.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnect() { handle.quit(); }
|
||||
public void disconnect() {
|
||||
handle.quit();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIdName(String name) { this.name = name; }
|
||||
public void onIdName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBestMove(String move) {
|
||||
@ -64,23 +70,37 @@ public class UCIPlayer extends Player implements UCIListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBestMove(String move, Move ponderMove) { onBestMove(move); }
|
||||
|
||||
@Override
|
||||
public void onCopyProtectionChecking() { System.out.println("Copy protection checking..."); }
|
||||
|
||||
@Override
|
||||
public void onCopyProtectionOk() { System.out.println("Copy protection ok"); }
|
||||
|
||||
@Override
|
||||
public void onCopyProtectionError() { System.err.println("Copy protection error!"); }
|
||||
|
||||
@Override
|
||||
public void onRegistrationChecking() { System.out.println("Registration checking..."); }
|
||||
|
||||
@Override
|
||||
public void onRegistrationOk() { System.out.println("Registration ok"); }
|
||||
|
||||
@Override
|
||||
public void onRegistrationError() { System.err.println("Registration error!"); }
|
||||
public void onBestMove(String move, Move ponderMove) {
|
||||
onBestMove(move);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCopyProtectionChecking() {
|
||||
System.out.println("Copy protection checking...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCopyProtectionOk() {
|
||||
System.out.println("Copy protection ok");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCopyProtectionError() {
|
||||
System.err.println("Copy protection error!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegistrationChecking() {
|
||||
System.out.println("Registration checking...");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegistrationOk() {
|
||||
System.out.println("Registration ok");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRegistrationError() {
|
||||
System.err.println("Registration error!");
|
||||
}
|
||||
}
|
||||
|
@ -39,7 +39,9 @@ public class AIPlayer extends Player {
|
||||
* reached to continue searching the children of a
|
||||
* move
|
||||
*/
|
||||
public AIPlayer(Game game, Color color, int maxDepth, int alphaBetaThreshold) {
|
||||
public AIPlayer(
|
||||
Game game, Color color, int maxDepth, int alphaBetaThreshold
|
||||
) {
|
||||
super(game, color);
|
||||
name = "AIPlayer";
|
||||
availableProcessors = Runtime.getRuntime().availableProcessors();
|
||||
@ -51,7 +53,8 @@ public class AIPlayer extends Player {
|
||||
public void requestMove() {
|
||||
exitRequested = false;
|
||||
|
||||
// Define some processing threads, split the available moves between them and
|
||||
// Define some processing threads, split the available moves between
|
||||
// them and
|
||||
// retrieve the result after their execution.
|
||||
new Thread(() -> {
|
||||
|
||||
@ -59,24 +62,28 @@ public class AIPlayer extends Player {
|
||||
Board board = new Board(this.board, false);
|
||||
List<Move> moves = board.getMoves(color);
|
||||
|
||||
// Define move processors and split the available moves between them.
|
||||
// Define move processors and split the available moves between
|
||||
// them.
|
||||
int numThreads = Math.min(moves.size(), availableProcessors);
|
||||
List<MoveProcessor> processors = new ArrayList<>(numThreads);
|
||||
final int step = moves.size() / numThreads;
|
||||
int rem = moves.size() % numThreads;
|
||||
int beginIndex = 0, endIndex = 0;
|
||||
for (int i = 0; i < numThreads; i++) {
|
||||
if (rem-- > 0) ++endIndex;
|
||||
if (rem-- > 0)
|
||||
++endIndex;
|
||||
endIndex += step;
|
||||
processors.add(new MoveProcessor(new Board(board, false), moves.subList(beginIndex, endIndex), color, maxDepth, alphaBetaThreshold));
|
||||
processors
|
||||
.add(new MoveProcessor(new Board(board, false), moves.subList(beginIndex, endIndex), color, maxDepth, alphaBetaThreshold));
|
||||
beginIndex = endIndex;
|
||||
}
|
||||
|
||||
// Execute processors, get the best result and pass it back to the Game class
|
||||
// Execute processors, get the best result and pass it back to the
|
||||
// Game class
|
||||
executor = Executors.newFixedThreadPool(numThreads);
|
||||
List<ProcessingResult> results = new ArrayList<>(numThreads);
|
||||
try {
|
||||
List<Future<ProcessingResult>> futures = executor.invokeAll(processors);
|
||||
List<Future<ProcessingResult>> futures
|
||||
= executor.invokeAll(processors);
|
||||
for (Future<ProcessingResult> f : futures)
|
||||
results.add(f.get());
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
@ -85,7 +92,9 @@ public class AIPlayer extends Player {
|
||||
executor.shutdown();
|
||||
}
|
||||
results.sort((r1, r2) -> Integer.compare(r2.score, r1.score));
|
||||
if (!exitRequested) SwingUtilities.invokeLater(() -> game.onMove(this, results.get(0).move));
|
||||
if (!exitRequested)
|
||||
SwingUtilities
|
||||
.invokeLater(() -> game.onMove(this, results.get(0).move));
|
||||
}, "AIPlayer calculation setup").start();
|
||||
}
|
||||
|
||||
|
@ -32,34 +32,172 @@ public class MoveProcessor implements Callable<ProcessingResult> {
|
||||
|
||||
static {
|
||||
positionScores = new HashMap<>();
|
||||
positionScores.put(King.class,
|
||||
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[] { -2, -3, -3, -2, -2, -2, -2, -1 }, new int[] { -1, -2, -2, -2, -2, -2, -2, -1 },
|
||||
new int[] { 2, 2, 0, 0, 0, 0, 2, 2 }, new int[] { 2, 3, 1, 0, 0, 1, 3, 2 } });
|
||||
positionScores.put(Queen.class,
|
||||
new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, -2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
||||
new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { 0, 0, 1, 1, 1, 1, 0, -1 },
|
||||
new int[] { -1, 1, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 0, 0, 0, 0, -1 },
|
||||
new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
|
||||
positionScores.put(Rook.class,
|
||||
new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 1, 1, 1, 1, 1, 1, 1, 1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
||||
new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
||||
new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { 0, 0, 0, 1, 1, 0, 0, 0 } });
|
||||
positionScores.put(Knight.class,
|
||||
new int[][] { new int[] { -5, -4, -3, -3, -3, -3, -4, -5 }, new int[] { -4, -2, 0, 0, 0, 0, -2, -4 },
|
||||
new int[] { -3, 0, 1, 2, 2, 1, 0, -3 }, new int[] { -3, 1, 2, 2, 2, 2, 1, -3 }, new int[] { -3, 0, 2, 2, 2, 2, 0, -1 },
|
||||
new int[] { -3, 1, 1, 2, 2, 1, 1, -3 }, new int[] { -4, -2, 0, 1, 1, 0, -2, -4 },
|
||||
new int[] { -5, -4, -3, -3, -3, -3, -4, -5 } });
|
||||
positionScores.put(Bishop.class,
|
||||
new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, 2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
|
||||
new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 1, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 },
|
||||
new int[] { -1, 1, 1, 1, 1, 1, 1, -1 }, new int[] { -1, 1, 0, 0, 0, 0, 1, -1 },
|
||||
new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
|
||||
positionScores.put(Pawn.class,
|
||||
new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 5, 5, 5, 5, 5, 5, 5, 5 }, new int[] { 1, 1, 2, 3, 3, 2, 1, 1 },
|
||||
new int[] { 0, 0, 1, 3, 3, 1, 0, 0 }, new int[] { 0, 0, 0, 2, 2, 0, 0, 0 }, new int[] { 0, 0, -1, 0, 0, -1, 0, 0 },
|
||||
new int[] { 0, 1, 1, -2, -2, 1, 1, 0 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0 } });
|
||||
positionScores.put(
|
||||
King.class,
|
||||
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[] {
|
||||
-2, -3, -3, -2, -2, -2, -2, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, -2, -2, -2, -2, -2, -2, -1
|
||||
},
|
||||
new int[] {
|
||||
2, 2, 0, 0, 0, 0, 2, 2
|
||||
},
|
||||
new int[] {
|
||||
2, 3, 1, 0, 0, 1, 3, 2
|
||||
}
|
||||
}
|
||||
);
|
||||
positionScores.put(
|
||||
Queen.class,
|
||||
new int[][] {
|
||||
new int[] {
|
||||
-2, -1, -1, -1, -1, -1, -1, -2
|
||||
}, new int[] {
|
||||
-1, 0, 0, 0, 0, 0, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, 0, 1, 1, 1, 1, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, 0, 1, 1, 1, 1, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
0, 0, 1, 1, 1, 1, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, 1, 1, 1, 1, 1, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, 0, 1, 0, 0, 0, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
-2, -1, -1, -1, -1, -1, -1, -2
|
||||
}
|
||||
}
|
||||
);
|
||||
positionScores.put(
|
||||
Rook.class,
|
||||
new int[][] {
|
||||
new int[] {
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
}, new int[] {
|
||||
1, 1, 1, 1, 1, 1, 1, 1
|
||||
}, new int[] {
|
||||
-1, 0, 0, 0, 0, 0, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, 0, 0, 0, 0, 0, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, 0, 0, 0, 0, 0, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, 0, 0, 0, 0, 0, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, 0, 0, 0, 0, 0, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
0, 0, 0, 1, 1, 0, 0, 0
|
||||
}
|
||||
}
|
||||
);
|
||||
positionScores.put(
|
||||
Knight.class,
|
||||
new int[][] {
|
||||
new int[] {
|
||||
-5, -4, -3, -3, -3, -3, -4, -5
|
||||
}, new int[] {
|
||||
-4, -2, 0, 0, 0, 0, -2, -4
|
||||
},
|
||||
new int[] {
|
||||
-3, 0, 1, 2, 2, 1, 0, -3
|
||||
},
|
||||
new int[] {
|
||||
-3, 1, 2, 2, 2, 2, 1, -3
|
||||
},
|
||||
new int[] {
|
||||
-3, 0, 2, 2, 2, 2, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
-3, 1, 1, 2, 2, 1, 1, -3
|
||||
},
|
||||
new int[] {
|
||||
-4, -2, 0, 1, 1, 0, -2, -4
|
||||
},
|
||||
new int[] {
|
||||
-5, -4, -3, -3, -3, -3, -4, -5
|
||||
}
|
||||
}
|
||||
);
|
||||
positionScores.put(
|
||||
Bishop.class,
|
||||
new int[][] {
|
||||
new int[] {
|
||||
-2, -1, -1, -1, -1, -1, -1, 2
|
||||
}, new int[] {
|
||||
-1, 0, 0, 0, 0, 0, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, 0, 1, 1, 1, 1, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, 1, 1, 1, 1, 1, 1, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, 0, 1, 1, 1, 1, 0, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, 1, 1, 1, 1, 1, 1, -1
|
||||
},
|
||||
new int[] {
|
||||
-1, 1, 0, 0, 0, 0, 1, -1
|
||||
},
|
||||
new int[] {
|
||||
-2, -1, -1, -1, -1, -1, -1, -2
|
||||
}
|
||||
}
|
||||
);
|
||||
positionScores.put(
|
||||
Pawn.class,
|
||||
new int[][] {
|
||||
new int[] {
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
}, new int[] {
|
||||
5, 5, 5, 5, 5, 5, 5, 5
|
||||
}, new int[] {
|
||||
1, 1, 2, 3, 3, 2, 1, 1
|
||||
},
|
||||
new int[] {
|
||||
0, 0, 1, 3, 3, 1, 0, 0
|
||||
},
|
||||
new int[] {
|
||||
0, 0, 0, 2, 2, 0, 0, 0
|
||||
},
|
||||
new int[] {
|
||||
0, 0, -1, 0, 0, -1, 0, 0
|
||||
},
|
||||
new int[] {
|
||||
0, 1, 1, -2, -2, 1, 1, 0
|
||||
},
|
||||
new int[] {
|
||||
0, 0, 0, 0, 0, 0, 0, 0
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -69,10 +207,14 @@ public class MoveProcessor implements Callable<ProcessingResult> {
|
||||
* @param rootMoves the moves on which the search is based
|
||||
* @param color the color for which to search
|
||||
* @param maxDepth the maximal recursion depth to search to
|
||||
* @param alphaBetaThreshold the threshold necessary to continue a search for a
|
||||
* @param alphaBetaThreshold the threshold necessary to continue a search
|
||||
* for a
|
||||
* specific move
|
||||
*/
|
||||
public MoveProcessor(Board board, List<Move> rootMoves, Color color, int maxDepth, int alphaBetaThreshold) {
|
||||
public MoveProcessor(
|
||||
Board board, List<Move> rootMoves, Color color, int maxDepth,
|
||||
int alphaBetaThreshold
|
||||
) {
|
||||
this.board = board;
|
||||
this.rootMoves = rootMoves;
|
||||
this.color = color;
|
||||
@ -95,13 +237,18 @@ public class MoveProcessor implements Callable<ProcessingResult> {
|
||||
int valueChange = teamValue - enemyValue;
|
||||
|
||||
if (depth < maxDepth && valueChange >= alphaBetaThreshold)
|
||||
valueChange -= miniMax(board, board.getMoves(color.opposite()), color.opposite(), depth + 1);
|
||||
valueChange -= miniMax(
|
||||
board,
|
||||
board.getMoves(color.opposite()),
|
||||
color.opposite(),
|
||||
depth + 1
|
||||
);
|
||||
|
||||
if (valueChange > bestValue) {
|
||||
bestValue = valueChange;
|
||||
if (depth == 0) bestMove = move;
|
||||
if (depth == 0)
|
||||
bestMove = move;
|
||||
}
|
||||
|
||||
board.revert();
|
||||
}
|
||||
return bestValue;
|
||||
@ -118,10 +265,18 @@ public class MoveProcessor implements Callable<ProcessingResult> {
|
||||
int score = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++)
|
||||
if (board.getBoardArr()[i][j] != null && board.getBoardArr()[i][j].getColor() == color) {
|
||||
if (
|
||||
board.getBoardArr()[i][j] != null
|
||||
&& board.getBoardArr()[i][j].getColor() == color
|
||||
) {
|
||||
score += board.getBoardArr()[i][j].getValue();
|
||||
if (positionScores.containsKey(board.getBoardArr()[i][j].getClass()))
|
||||
score += positionScores.get(board.getBoardArr()[i][j].getClass())[i][color == Color.WHITE ? j : 7 - j];
|
||||
if (
|
||||
positionScores
|
||||
.containsKey(board.getBoardArr()[i][j].getClass())
|
||||
)
|
||||
score += positionScores.get(
|
||||
board.getBoardArr()[i][j].getClass()
|
||||
)[i][color == Color.WHITE ? j : 7 - j];
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
@ -36,5 +36,8 @@ public class ProcessingResult {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return String.format("ProcessingResult[Move = %s,Score = %d]", move, score); }
|
||||
public String toString() {
|
||||
return String
|
||||
.format("ProcessingResult[Move = %s,Score = %d]", move, score);
|
||||
}
|
||||
}
|
||||
|
@ -40,10 +40,14 @@ public class EngineUtil {
|
||||
handle.registerListener(new UCIListener() {
|
||||
|
||||
@Override
|
||||
public void onIdName(String name) { info.name = name; }
|
||||
public void onIdName(String name) {
|
||||
info.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIdAuthor(String author) { info.author = author; }
|
||||
public void onIdAuthor(String author) {
|
||||
info.author = author;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUCIOk() {
|
||||
@ -60,17 +64,25 @@ public class EngineUtil {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static void loadEngineInfos() {
|
||||
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(engineInfoFile))) {
|
||||
try (
|
||||
ObjectInputStream in
|
||||
= new ObjectInputStream(new FileInputStream(engineInfoFile))
|
||||
) {
|
||||
Object obj = in.readObject();
|
||||
if (obj instanceof ArrayList<?>) engineInfos = (ArrayList<EngineInfo>) obj;
|
||||
else throw new IOException("Serialized object has the wrong class.");
|
||||
if (obj instanceof ArrayList<?>)
|
||||
engineInfos = (ArrayList<EngineInfo>) obj;
|
||||
else
|
||||
throw new IOException("Serialized object has the wrong class.");
|
||||
} catch (ClassNotFoundException | IOException ex) {
|
||||
engineInfos = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
private static void saveEngineInfos() {
|
||||
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(engineInfoFile))) {
|
||||
try (
|
||||
ObjectOutputStream out
|
||||
= new ObjectOutputStream(new FileOutputStream(engineInfoFile))
|
||||
) {
|
||||
out.writeObject(engineInfos);
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
@ -112,10 +124,14 @@ public class EngineUtil {
|
||||
*
|
||||
* @param path the path of the engine executable
|
||||
*/
|
||||
public EngineInfo(String path) { this.path = path; }
|
||||
public EngineInfo(String path) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() { return name + " by " + author + " at " + path; }
|
||||
public String toString() {
|
||||
return name + " by " + author + " at " + path;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -21,7 +21,8 @@ import dev.kske.chess.board.Piece;
|
||||
*/
|
||||
public class TextureUtil {
|
||||
|
||||
private static Map<String, Image> textures = new HashMap<>(), scaledTextures = new HashMap<>();
|
||||
private static Map<String, Image> textures = new HashMap<>(),
|
||||
scaledTextures = new HashMap<>();
|
||||
|
||||
static {
|
||||
loadPieceTextures();
|
||||
@ -37,7 +38,8 @@ public class TextureUtil {
|
||||
* @return The fitting texture
|
||||
*/
|
||||
public static Image getPieceTexture(Piece piece) {
|
||||
String key = piece.getClass().getSimpleName().toLowerCase() + "_" + piece.getColor().toString().toLowerCase();
|
||||
String key = piece.getClass().getSimpleName().toLowerCase() + "_"
|
||||
+ piece.getColor().toString().toLowerCase();
|
||||
return scaledTextures.get(key);
|
||||
}
|
||||
|
||||
@ -48,7 +50,9 @@ public class TextureUtil {
|
||||
*/
|
||||
public static void scalePieceTextures(int tileSize) {
|
||||
scaledTextures.clear();
|
||||
textures.forEach((key, img) -> scaledTextures.put(key, img.getScaledInstance(tileSize, tileSize, Image.SCALE_SMOOTH)));
|
||||
textures.forEach(
|
||||
(key, img) -> scaledTextures.put(key, img.getScaledInstance(tileSize, tileSize, Image.SCALE_SMOOTH))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -73,7 +77,8 @@ public class TextureUtil {
|
||||
*/
|
||||
private static void loadPieceTextures() {
|
||||
Arrays
|
||||
.asList("king_white",
|
||||
.asList(
|
||||
"king_white",
|
||||
"king_black",
|
||||
"queen_white",
|
||||
"queen_black",
|
||||
@ -84,7 +89,10 @@ public class TextureUtil {
|
||||
"bishop_white",
|
||||
"bishop_black",
|
||||
"pawn_white",
|
||||
"pawn_black")
|
||||
.forEach(name -> textures.put(name, loadImage("/pieces/" + name + ".png")));
|
||||
"pawn_black"
|
||||
)
|
||||
.forEach(
|
||||
name -> textures.put(name, loadImage("/pieces/" + name + ".png"))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,8 @@ public class PGNDatabase {
|
||||
* @throws FileNotFoundException if the specified file is not found
|
||||
* @throws ChessException if an error occurs while parsing the file
|
||||
*/
|
||||
public void load(File pgnFile) throws FileNotFoundException, ChessException {
|
||||
public void load(File pgnFile)
|
||||
throws FileNotFoundException, ChessException {
|
||||
try (Scanner sc = new Scanner(pgnFile)) {
|
||||
while (sc.hasNext())
|
||||
games.add(PGNGame.parse(sc));
|
||||
|
@ -24,17 +24,22 @@ public class PGNGame {
|
||||
private final Board board;
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link PGNGame}. A new default {@link Board} will be
|
||||
* Creates an instance of {@link PGNGame}. A new default {@link Board} will
|
||||
* be
|
||||
* created.
|
||||
*/
|
||||
public PGNGame() { board = new Board(); }
|
||||
public PGNGame() {
|
||||
board = new Board();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link PGNGame}.
|
||||
*
|
||||
* @param board the board associated with the game
|
||||
*/
|
||||
public PGNGame(Board board) { this.board = board; }
|
||||
public PGNGame(Board board) {
|
||||
this.board = board;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a game in {@code PGN} format from a {@link Scanner} instance
|
||||
@ -48,17 +53,20 @@ public class PGNGame {
|
||||
|
||||
MatchResult matchResult;
|
||||
Pattern tagPairPattern = Pattern.compile("\\[(\\w+) \"(.*)\"]"),
|
||||
movePattern = Pattern.compile("\\d+\\.\\s+(?:(?:(\\S+)\\s+(\\S+))|(?:O-O-O)|(?:O-O))(?:\\+{0,2}|\\#)"),
|
||||
movePattern = Pattern.compile(
|
||||
"\\d+\\.\\s+(?:(?:(\\S+)\\s+(\\S+))|(?:O-O-O)|(?:O-O))(?:\\+{0,2}|\\#)"
|
||||
),
|
||||
nagPattern = Pattern.compile("(\\$\\d{1,3})*"), terminationMarkerPattern = Pattern.compile("1-0|0-1|1\\/2-1\\/2|\\*");
|
||||
|
||||
// Parse tag pairs
|
||||
while (sc.findInLine(tagPairPattern) != null) {
|
||||
matchResult = sc.match();
|
||||
if (matchResult.groupCount() == 2) game.setTag(matchResult.group(1), matchResult.group(2));
|
||||
else break;
|
||||
if (matchResult.groupCount() == 2)
|
||||
game.setTag(matchResult.group(1), matchResult.group(2));
|
||||
else
|
||||
break;
|
||||
sc.nextLine();
|
||||
}
|
||||
|
||||
// Parse movetext
|
||||
while (true) {
|
||||
// Skip NAG (Numeric Annotation Glyph)
|
||||
@ -68,16 +76,22 @@ public class PGNGame {
|
||||
|
||||
if (sc.findWithinHorizon(movePattern, 20) != null) {
|
||||
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));
|
||||
System.out.println(game.getBoard().getLog().getLast().move.toLAN() + ": " + new FENString(game.board).toString());
|
||||
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
|
||||
if (sc.findWithinHorizon(terminationMarkerPattern, 20) == null) System.err.println("Termination marker expected");
|
||||
if (sc.findWithinHorizon(terminationMarkerPattern, 20) == null)
|
||||
System.err.println("Termination marker expected");
|
||||
|
||||
return game;
|
||||
}
|
||||
@ -95,7 +109,8 @@ public class PGNGame {
|
||||
tagPairs.forEach((k, v) -> pw.printf("[%s \"%s\"]%n", k, v));
|
||||
|
||||
// Insert newline if tags were printed
|
||||
if (!tagPairs.isEmpty()) pw.println();
|
||||
if (!tagPairs.isEmpty())
|
||||
pw.println();
|
||||
|
||||
if (!board.getLog().isEmpty()) {
|
||||
// Collect SAN moves
|
||||
@ -106,7 +121,9 @@ public class PGNGame {
|
||||
Move move = clone.getLog().getLast().move;
|
||||
flag = clone.getLog().hasParent();
|
||||
clone.revert();
|
||||
String chunk = clone.getLog().getActiveColor() == Color.WHITE ? String.format(" %d. ", clone.getLog().getFullmoveNumber()) : " ";
|
||||
String chunk = clone.getLog().getActiveColor() == Color.WHITE
|
||||
? String.format(" %d. ", clone.getLog().getFullmoveNumber())
|
||||
: " ";
|
||||
chunk += move.toSAN(clone);
|
||||
chunks.add(chunk);
|
||||
}
|
||||
@ -115,12 +132,14 @@ public class PGNGame {
|
||||
// Write movetext
|
||||
String line = "";
|
||||
for (String chunk : chunks)
|
||||
if (line.length() + chunk.length() <= 80) line += chunk;
|
||||
if (line.length() + chunk.length() <= 80)
|
||||
line += chunk;
|
||||
else {
|
||||
pw.println(line);
|
||||
line = chunk;
|
||||
}
|
||||
if (!line.isEmpty()) pw.println(line);
|
||||
if (!line.isEmpty())
|
||||
pw.println(line);
|
||||
}
|
||||
// Write game termination marker
|
||||
pw.print(tagPairs.get("Result"));
|
||||
@ -130,13 +149,17 @@ public class PGNGame {
|
||||
* @param tagName the name of a game tag
|
||||
* @return the value of the game tag
|
||||
*/
|
||||
public String getTag(String tagName) { return tagPairs.get(tagName); }
|
||||
public String getTag(String tagName) {
|
||||
return tagPairs.get(tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tagName the name of a game tag
|
||||
* @return {@code true} if the tag is present
|
||||
*/
|
||||
public boolean hasTag(String tagName) { return tagPairs.containsKey(tagName); }
|
||||
public boolean hasTag(String tagName) {
|
||||
return tagPairs.containsKey(tagName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a game tag.
|
||||
@ -144,7 +167,9 @@ public class PGNGame {
|
||||
* @param tagName the name of the tag
|
||||
* @param tagValue the value of the tag
|
||||
*/
|
||||
public void setTag(String tagName, String tagValue) { tagPairs.put(tagName, tagValue); }
|
||||
public void setTag(String tagName, String tagValue) {
|
||||
tagPairs.put(tagName, tagValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the board associated with this game
|
||||
|
@ -22,7 +22,8 @@ public class UCIHandle {
|
||||
private final UCIReceiver receiver;
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link UCIHandle}. The engine process is started and
|
||||
* Creates an instance of {@link UCIHandle}. The engine process is started
|
||||
* and
|
||||
* passed to a new {@link UCIReceiver}.
|
||||
*
|
||||
* @param enginePath the path to the engine executable
|
||||
@ -45,26 +46,35 @@ public class UCIHandle {
|
||||
/**
|
||||
* Tells the engine to use UCI.
|
||||
*/
|
||||
public void uci() { out.println("uci"); }
|
||||
public void uci() {
|
||||
out.println("uci");
|
||||
}
|
||||
|
||||
/**
|
||||
* Switches the debug mode of the engine on or off.
|
||||
*
|
||||
* @param debug Enables debugging if set to {@code true}, disables it otherwise
|
||||
* @param debug Enables debugging if set to {@code true}, disables it
|
||||
* otherwise
|
||||
*/
|
||||
public void debug(boolean debug) { out.println("debug " + (debug ? "on" : "off")); }
|
||||
public void debug(boolean debug) {
|
||||
out.println("debug " + (debug ? "on" : "off"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronized the engine with the GUI
|
||||
*/
|
||||
public void isready() { out.println("isready"); }
|
||||
public void isready() {
|
||||
out.println("isready");
|
||||
}
|
||||
|
||||
/**
|
||||
* Signifies a button press to the engine.
|
||||
*
|
||||
* @param name The name of the button
|
||||
*/
|
||||
public void setOption(String name) { out.println("setoption name " + name); }
|
||||
public void setOption(String name) {
|
||||
out.println("setoption name " + name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes an internal parameter of the engine.
|
||||
@ -72,7 +82,9 @@ public class UCIHandle {
|
||||
* @param name The name of the parameter
|
||||
* @param value The value of the parameter
|
||||
*/
|
||||
public void setOption(String name, String value) { out.printf("setoption name %s value %s%n", name, value); }
|
||||
public void setOption(String name, String value) {
|
||||
out.printf("setoption name %s value %s%n", name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the engine
|
||||
@ -80,29 +92,39 @@ public class UCIHandle {
|
||||
* @param name The name the engine should be registered with
|
||||
* @param code The code the engine should be registered with
|
||||
*/
|
||||
public void register(String name, String code) { out.printf("register %s %s%n", name, code); }
|
||||
public void register(String name, String code) {
|
||||
out.printf("register %s %s%n", name, code);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the engine to postpone the registration.
|
||||
*/
|
||||
public void registerLater() { out.println("register later"); }
|
||||
public void registerLater() {
|
||||
out.println("register later");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the engine that the next search will be from a different game.
|
||||
*/
|
||||
public void uciNewGame() { out.println("ucinewgame"); }
|
||||
public void uciNewGame() {
|
||||
out.println("ucinewgame");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the position in its initial state.
|
||||
*/
|
||||
public void positionStartpos() { out.println("position startpos"); }
|
||||
public void positionStartpos() {
|
||||
out.println("position startpos");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the position described in the FEN string.
|
||||
*
|
||||
* @param fen FEN representation of the current board
|
||||
*/
|
||||
public void positionFEN(String fen) { out.println("position fen " + fen); }
|
||||
public void positionFEN(String fen) {
|
||||
out.println("position fen " + fen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up the position described by a list of moves.
|
||||
@ -119,12 +141,16 @@ public class UCIHandle {
|
||||
/**
|
||||
* Starts calculating on the current position.
|
||||
*/
|
||||
public void go() { out.println("go"); }
|
||||
public void go() {
|
||||
out.println("go");
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts calculating on the current position.
|
||||
* This command has multiple optional parameters which will only be included in
|
||||
* the call if they are not {@code null}, greater than zero or {@code true} for
|
||||
* This command has multiple optional parameters which will only be included
|
||||
* in
|
||||
* the call if they are not {@code null}, greater than zero or {@code true}
|
||||
* for
|
||||
* {@code searchMoves}, all integer parameters and all boolean parameters
|
||||
* respectively.
|
||||
*
|
||||
@ -141,8 +167,11 @@ public class UCIHandle {
|
||||
* @param moveTime the exact search time
|
||||
* @param infinite search until the {@code stop} command
|
||||
*/
|
||||
public void go(List<Move> searchMoves, boolean ponder, int wTime, int bTime, int wInc, int bInc, int movesToGo, int depth, int nodes, int mate,
|
||||
int moveTime, boolean infinite) {
|
||||
public void go(
|
||||
List<Move> searchMoves, boolean ponder, int wTime, int bTime, int wInc,
|
||||
int bInc, int movesToGo, int depth, int nodes, int mate,
|
||||
int moveTime, boolean infinite
|
||||
) {
|
||||
StringJoiner joiner = new StringJoiner(" ");
|
||||
joiner.add("go");
|
||||
|
||||
@ -150,7 +179,8 @@ public class UCIHandle {
|
||||
joiner.add("searchmoves");
|
||||
searchMoves.forEach(m -> joiner.add(m.toLAN()));
|
||||
}
|
||||
if (ponder) joiner.add("ponder");
|
||||
if (ponder)
|
||||
joiner.add("ponder");
|
||||
if (wTime > 0) {
|
||||
joiner.add("wtime");
|
||||
joiner.add(String.valueOf(wTime));
|
||||
@ -187,29 +217,38 @@ public class UCIHandle {
|
||||
joiner.add("movetime");
|
||||
joiner.add(String.valueOf(moveTime));
|
||||
}
|
||||
if (infinite) joiner.add("infinite");
|
||||
if (infinite)
|
||||
joiner.add("infinite");
|
||||
out.println(joiner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops calculation as soon as possible.
|
||||
*/
|
||||
public void stop() { out.println("stop"); }
|
||||
public void stop() {
|
||||
out.println("stop");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells the engine that the user has played the expected move.
|
||||
*/
|
||||
public void ponderHit() { out.println("ponderhit"); }
|
||||
public void ponderHit() {
|
||||
out.println("ponderhit");
|
||||
}
|
||||
|
||||
/**
|
||||
* Quits the engine process as soon as possible.
|
||||
*/
|
||||
public void quit() { out.println("quit"); }
|
||||
public void quit() {
|
||||
out.println("quit");
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a UCI listener.
|
||||
*
|
||||
* @param listener the UCI listener to register
|
||||
*/
|
||||
public void registerListener(UCIListener listener) { receiver.registerListener(listener); }
|
||||
public void registerListener(UCIListener listener) {
|
||||
receiver.registerListener(listener);
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,8 @@ import dev.kske.chess.board.Move;
|
||||
*/
|
||||
public class UCIInfo {
|
||||
|
||||
private int depth, seldepth, time, nodes, multipv, currmovenumber, hashfull, nps, tbhits, sbhits, cpuload, cpunr;
|
||||
private int depth, seldepth, time, nodes, multipv, currmovenumber, hashfull,
|
||||
nps, tbhits, sbhits, cpuload, cpunr;
|
||||
private List<Move> pv = new ArrayList<>(), refutation = new ArrayList<>();
|
||||
private Map<Integer, List<Move>> currline = new HashMap<>();
|
||||
private Move currmove;
|
||||
@ -25,7 +26,8 @@ public class UCIInfo {
|
||||
* Contains every parameter for the UCI info command. Helpful for parsing
|
||||
* multi-value parameters.
|
||||
*/
|
||||
private static final List<String> params = Arrays.asList("depth",
|
||||
private static final List<String> params = Arrays.asList(
|
||||
"depth",
|
||||
"seldepth",
|
||||
"time",
|
||||
"nodes",
|
||||
@ -41,10 +43,12 @@ public class UCIInfo {
|
||||
"score",
|
||||
"pv",
|
||||
"refutation",
|
||||
"currline");
|
||||
"currline"
|
||||
);
|
||||
|
||||
/**
|
||||
* Creates an instance of {@link UCIInfo} by parsing the argument list of a UCI
|
||||
* Creates an instance of {@link UCIInfo} by parsing the argument list of a
|
||||
* UCI
|
||||
* info command generated from an engine.
|
||||
*
|
||||
* @param line the UCI info argument list to parse
|
||||
@ -95,7 +99,8 @@ public class UCIInfo {
|
||||
displayString = tokens[++i];
|
||||
break;
|
||||
case "score":
|
||||
score = new Score(line.substring(line.indexOf("score") + tokens[i].length() + 1));
|
||||
score
|
||||
= new Score(line.substring(line.indexOf("score") + tokens[i].length() + 1));
|
||||
i += score.getLength() + 1;
|
||||
break;
|
||||
case "pv":
|
||||
@ -108,15 +113,22 @@ public class UCIInfo {
|
||||
break;
|
||||
case "currline":
|
||||
// A CPU number of 1 can be omitted
|
||||
final Integer cpu = tokens[i].matches("\\d+") ? Integer.parseInt(tokens[i++]) : 1;
|
||||
final Integer cpu = tokens[i].matches("\\d+")
|
||||
? Integer.parseInt(tokens[i++])
|
||||
: 1;
|
||||
final ArrayList<Move> moves = new ArrayList<>();
|
||||
while (i < tokens.length && !params.contains(tokens[i]))
|
||||
moves.add(Move.fromLAN(tokens[i++]));
|
||||
currline.put(cpu, moves);
|
||||
System.err.println("The parameter 'currline' for command 'info' is not yet implemented");
|
||||
System.err.println(
|
||||
"The parameter 'currline' for command 'info' is not yet implemented"
|
||||
);
|
||||
break;
|
||||
default:
|
||||
System.err.printf("Unknown parameter '%s' for command 'info' found!%n", tokens[i]);
|
||||
System.err.printf(
|
||||
"Unknown parameter '%s' for command 'info' found!%n",
|
||||
tokens[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,7 +178,8 @@ public class UCIInfo {
|
||||
String[] tokens = line.split(" ");
|
||||
int i = 0;
|
||||
for (; i < tokens.length; i++) {
|
||||
if (params.contains(tokens[i])) break;
|
||||
if (params.contains(tokens[i]))
|
||||
break;
|
||||
switch (tokens[i]) {
|
||||
case "cp":
|
||||
cp = Integer.parseInt(tokens[++i]);
|
||||
@ -181,7 +194,10 @@ public class UCIInfo {
|
||||
upperbound = true;
|
||||
break;
|
||||
default:
|
||||
System.err.printf("Unknown parameter '%s' for command 'score' found!%n", tokens[i]);
|
||||
System.err.printf(
|
||||
"Unknown parameter '%s' for command 'score' found!%n",
|
||||
tokens[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
length = i + 1;
|
||||
|
@ -1,9 +1,6 @@
|
||||
package dev.kske.chess.uci;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Project: <strong>Chess</strong><br>
|
||||
@ -27,7 +24,10 @@ public class UCIOption {
|
||||
switch (tokens[i]) {
|
||||
case "name":
|
||||
StringJoiner nameJoiner = new StringJoiner(" ");
|
||||
while (!Arrays.asList("type", "default", "min", "max", "var").contains(tokens[i + 1]))
|
||||
while (
|
||||
!Arrays.asList("type", "default", "min", "max", "var")
|
||||
.contains(tokens[i + 1])
|
||||
)
|
||||
nameJoiner.add(tokens[++i]);
|
||||
name = nameJoiner.toString();
|
||||
break;
|
||||
@ -48,7 +48,10 @@ public class UCIOption {
|
||||
varList.add(tokens[++i]);
|
||||
break;
|
||||
default:
|
||||
System.err.printf("Unknown parameter '%s' for command 'option' found!%n", tokens[i]);
|
||||
System.err.printf(
|
||||
"Unknown parameter '%s' for command 'option' found!%n",
|
||||
tokens[i]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,7 +67,7 @@ public class UCIOption {
|
||||
|
||||
public List<String> getVarList() { return varList; }
|
||||
|
||||
public static enum GUIType {
|
||||
public enum GUIType {
|
||||
CHECK, SPIN, COMBO, BUTTON, STRING
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,8 @@ public class UCIReceiver implements Runnable {
|
||||
String line;
|
||||
while (!Thread.currentThread().isInterrupted())
|
||||
try {
|
||||
if ((line = in.readLine()) != null && !line.isEmpty()) parse(line);
|
||||
if ((line = in.readLine()) != null && !line.isEmpty())
|
||||
parse(line);
|
||||
} catch (IndexOutOfBoundsException ex) {
|
||||
System.err.println("Too few arguments were provided!");
|
||||
ex.printStackTrace();
|
||||
@ -49,7 +50,8 @@ public class UCIReceiver implements Runnable {
|
||||
|
||||
private void parse(String line) {
|
||||
int spaceIndex = line.indexOf(' ');
|
||||
String command = spaceIndex == -1 ? line : line.substring(0, spaceIndex);
|
||||
String command
|
||||
= spaceIndex == -1 ? line : line.substring(0, spaceIndex);
|
||||
switch (command) {
|
||||
case "id":
|
||||
parseId(line.substring(command.length() + 1));
|
||||
@ -91,7 +93,10 @@ public class UCIReceiver implements Runnable {
|
||||
listeners.forEach(l -> l.onIdAuthor(arg));
|
||||
break;
|
||||
default:
|
||||
System.err.printf("Unknown parameter '%s' for command 'id' found!%n", param);
|
||||
System.err.printf(
|
||||
"Unknown parameter '%s' for command 'id' found!%n",
|
||||
param
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,8 +105,10 @@ public class UCIReceiver implements Runnable {
|
||||
String move = tokens[0];
|
||||
|
||||
// Ponder move
|
||||
if (tokens.length == 3) listeners.forEach(l -> l.onBestMove(move, Move.fromLAN(tokens[2])));
|
||||
else listeners.forEach(l -> l.onBestMove(move));
|
||||
if (tokens.length == 3)
|
||||
listeners.forEach(l -> l.onBestMove(move, Move.fromLAN(tokens[2])));
|
||||
else
|
||||
listeners.forEach(l -> l.onBestMove(move));
|
||||
}
|
||||
|
||||
private void parseCopyProtection(String line) {
|
||||
@ -116,7 +123,10 @@ public class UCIReceiver implements Runnable {
|
||||
listeners.forEach(UCIListener::onCopyProtectionError);
|
||||
break;
|
||||
default:
|
||||
System.err.printf("Unknown parameter '%s' for command 'copyprotection' found!%n", line);
|
||||
System.err.printf(
|
||||
"Unknown parameter '%s' for command 'copyprotection' found!%n",
|
||||
line
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,18 +142,27 @@ public class UCIReceiver implements Runnable {
|
||||
listeners.forEach(UCIListener::onRegistrationError);
|
||||
break;
|
||||
default:
|
||||
System.err.printf("Unknown parameter '%s' for command 'registration' found!%n", line);
|
||||
System.err.printf(
|
||||
"Unknown parameter '%s' for command 'registration' found!%n",
|
||||
line
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseInfo(String line) { listeners.forEach(l -> l.onInfo(new UCIInfo(line))); }
|
||||
private void parseInfo(String line) {
|
||||
listeners.forEach(l -> l.onInfo(new UCIInfo(line)));
|
||||
}
|
||||
|
||||
private void parseOption(String line) { listeners.forEach(l -> l.onOption(new UCIOption((line)))); }
|
||||
private void parseOption(String line) {
|
||||
listeners.forEach(l -> l.onOption(new UCIOption(line)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a UCI listener
|
||||
*
|
||||
* @param listener the UCI listener to register
|
||||
*/
|
||||
public void registerListener(UCIListener listener) { listeners.add(listener); }
|
||||
public void registerListener(UCIListener listener) {
|
||||
listeners.add(listener);
|
||||
}
|
||||
}
|
||||
|
@ -49,15 +49,25 @@ public class BoardComponent extends JComponent {
|
||||
g.setColor(Color.white);
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++) {
|
||||
if (j > 0) g.setColor(g.getColor().equals(Color.white) ? Color.lightGray : Color.white);
|
||||
if (j > 0)
|
||||
g.setColor(
|
||||
g.getColor().equals(Color.white) ? Color.lightGray : Color.white
|
||||
);
|
||||
g.fillRect(tileSize * i, tileSize * j, tileSize, tileSize);
|
||||
}
|
||||
|
||||
// Draw the pieces if a board is present
|
||||
if (board != null) for (int i = 0; i < 8; i++)
|
||||
if (board != null)
|
||||
for (int i = 0; i < 8; i++)
|
||||
for (int j = 0; j < 8; j++)
|
||||
if (board.getBoardArr()[i][j] != null)
|
||||
g.drawImage(TextureUtil.getPieceTexture(board.getBoardArr()[i][j]), i * tileSize, j * tileSize, this);
|
||||
g.drawImage(
|
||||
TextureUtil
|
||||
.getPieceTexture(board.getBoardArr()[i][j]),
|
||||
i * tileSize,
|
||||
j * tileSize,
|
||||
this
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -66,7 +76,8 @@ public class BoardComponent extends JComponent {
|
||||
public Board getBoard() { return board; }
|
||||
|
||||
/**
|
||||
* Sets the board rendered by this board component and repaints the component
|
||||
* Sets the board rendered by this board component and repaints the
|
||||
* component
|
||||
*
|
||||
* @param board the board rendered by this board component
|
||||
*/
|
||||
|
@ -51,7 +51,9 @@ public class BoardPane extends JLayeredPane {
|
||||
/**
|
||||
* @return overlay component contained inside this board pane
|
||||
*/
|
||||
public OverlayComponent getOverlayComponent() { return overlayComponent; }
|
||||
public OverlayComponent getOverlayComponent() {
|
||||
return overlayComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the size of an individual board tile in pixels
|
||||
|
@ -28,7 +28,8 @@ public class DialogUtil {
|
||||
/**
|
||||
* Saves the last accessed folder for loading and saving game files.
|
||||
*/
|
||||
private static Preferences preferences = Preferences.userNodeForPackage(DialogUtil.class);
|
||||
private static Preferences preferences
|
||||
= Preferences.userNodeForPackage(DialogUtil.class);
|
||||
|
||||
/**
|
||||
* Displays a parameterized file opening dialog.
|
||||
@ -37,7 +38,10 @@ public class DialogUtil {
|
||||
* @param action the action executed with the selected files a its argument
|
||||
* @param filters the file extension filters passed to the dialog
|
||||
*/
|
||||
public static void showFileSelectionDialog(Component parent, Consumer<List<File>> action, Collection<FileNameExtensionFilter> filters) {
|
||||
public static void showFileSelectionDialog(
|
||||
Component parent, Consumer<List<File>> action,
|
||||
Collection<FileNameExtensionFilter> filters
|
||||
) {
|
||||
JFileChooser fileChooser = createFileChooser(filters);
|
||||
if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) {
|
||||
action.accept(Arrays.asList(fileChooser.getSelectedFile()));
|
||||
@ -52,18 +56,29 @@ public class DialogUtil {
|
||||
* @param action the action executed with the selected file a its argument
|
||||
* @param filters the file extension filters passed to the dialog
|
||||
*/
|
||||
public static void showFileSaveDialog(Component parent, Consumer<File> action, Collection<FileNameExtensionFilter> filters) {
|
||||
public static void showFileSaveDialog(
|
||||
Component parent, Consumer<File> action,
|
||||
Collection<FileNameExtensionFilter> filters
|
||||
) {
|
||||
JFileChooser fileChooser = createFileChooser(filters);
|
||||
if (fileChooser.showSaveDialog(parent) == JFileChooser.APPROVE_OPTION) {
|
||||
action.accept(new File(fileChooser.getSelectedFile().getAbsolutePath() + "."
|
||||
+ ((FileNameExtensionFilter) fileChooser.getFileFilter()).getExtensions()[0]));
|
||||
action.accept(
|
||||
new File(
|
||||
fileChooser.getSelectedFile().getAbsolutePath() + "."
|
||||
+ ((FileNameExtensionFilter) fileChooser
|
||||
.getFileFilter()).getExtensions()[0]
|
||||
)
|
||||
);
|
||||
preferences.put("path", fileChooser.getSelectedFile().getParent());
|
||||
}
|
||||
}
|
||||
|
||||
private static JFileChooser createFileChooser(Collection<FileNameExtensionFilter> filters) {
|
||||
private static JFileChooser
|
||||
createFileChooser(Collection<FileNameExtensionFilter> filters) {
|
||||
JFileChooser fileChooser = new JFileChooser();
|
||||
fileChooser.setCurrentDirectory(new File(preferences.get("path", System.getProperty("user.home"))));
|
||||
fileChooser.setCurrentDirectory(
|
||||
new File(preferences.get("path", System.getProperty("user.home")))
|
||||
);
|
||||
fileChooser.setAcceptAllFileFilterUsed(false);
|
||||
filters.forEach(fileChooser::addChoosableFileFilter);
|
||||
return fileChooser;
|
||||
@ -73,16 +88,21 @@ public class DialogUtil {
|
||||
* Displays a dialog in which the user can select the player types for a
|
||||
* game.<br>
|
||||
* <br>
|
||||
* The dialog will always display {@code Natural Player} and {@code AIPlayer},
|
||||
* The dialog will always display {@code Natural Player} and
|
||||
* {@code AIPlayer},
|
||||
* as well as all engine names stored by {@link EngineUtil}.
|
||||
*
|
||||
* @param parent the parent component of the dialog
|
||||
* @param action the action executed with the two selected names as arguments
|
||||
* @param action the action executed with the two selected names as
|
||||
* arguments
|
||||
*/
|
||||
public static void showGameConfigurationDialog(Component parent, BiConsumer<String, String> action) {
|
||||
public static void showGameConfigurationDialog(
|
||||
Component parent, BiConsumer<String, String> action
|
||||
) {
|
||||
JPanel dialogPanel = new JPanel();
|
||||
|
||||
List<String> options = new ArrayList<>(Arrays.asList("Natural Player", "AI Player"));
|
||||
List<String> options
|
||||
= new ArrayList<>(Arrays.asList("Natural Player", "AI Player"));
|
||||
EngineUtil.getEngineInfos().forEach(info -> options.add(info.name));
|
||||
|
||||
JLabel lblWhite = new JLabel("White:");
|
||||
@ -105,7 +125,15 @@ public class DialogUtil {
|
||||
cbBlack.setBounds(98, 36, 159, 22);
|
||||
dialogPanel.add(cbBlack);
|
||||
|
||||
JOptionPane.showMessageDialog(parent, dialogPanel, "Game configuration", JOptionPane.QUESTION_MESSAGE);
|
||||
action.accept(options.get(cbWhite.getSelectedIndex()), options.get(cbBlack.getSelectedIndex()));
|
||||
JOptionPane.showMessageDialog(
|
||||
parent,
|
||||
dialogPanel,
|
||||
"Game configuration",
|
||||
JOptionPane.QUESTION_MESSAGE
|
||||
);
|
||||
action.accept(
|
||||
options.get(cbWhite.getSelectedIndex()),
|
||||
options.get(cbBlack.getSelectedIndex())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -30,14 +30,19 @@ public class GameDropTarget extends DropTargetAdapter {
|
||||
* @param mainWindow the {@link MainWindow} onto which {@code FEN} and
|
||||
* {@code PGN} files can be dropped
|
||||
*/
|
||||
public GameDropTarget(MainWindow mainWindow) { this.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));
|
||||
mainWindow.loadFiles(
|
||||
(List<File>) evt.getTransferable()
|
||||
.getTransferData(DataFlavor.javaFileListFlavor)
|
||||
);
|
||||
} catch (UnsupportedFlavorException | IOException ex) {
|
||||
ex.printStackTrace();
|
||||
evt.rejectDrop();
|
||||
|
@ -45,15 +45,23 @@ public class GamePane extends JComponent {
|
||||
activeColor = Color.WHITE;
|
||||
|
||||
GridBagLayout gridBagLayout = new GridBagLayout();
|
||||
gridBagLayout.columnWidths = new int[] { 450, 1, 0 };
|
||||
gridBagLayout.rowHeights = new int[] { 33, 267, 1, 0 };
|
||||
gridBagLayout.columnWeights = new double[] { 0.0, 1.0, 1.0 };
|
||||
gridBagLayout.rowWeights = new double[] { 1.0, 1.0, 1.0, Double.MIN_VALUE };
|
||||
gridBagLayout.columnWidths = new int[] {
|
||||
450, 1, 0
|
||||
};
|
||||
gridBagLayout.rowHeights = new int[] {
|
||||
33, 267, 1, 0
|
||||
};
|
||||
gridBagLayout.columnWeights = new double[] {
|
||||
0.0, 1.0, 1.0
|
||||
};
|
||||
gridBagLayout.rowWeights = new double[] {
|
||||
1.0, 1.0, 1.0, Double.MIN_VALUE
|
||||
};
|
||||
setLayout(gridBagLayout);
|
||||
|
||||
JPanel toolPanel = new JPanel();
|
||||
btnRestart = new JButton("Restart");
|
||||
btnRestart.addActionListener((evt) -> {
|
||||
btnRestart.addActionListener(evt -> {
|
||||
if (game != null) {
|
||||
game.reset();
|
||||
game.start();
|
||||
@ -61,9 +69,10 @@ public class GamePane extends JComponent {
|
||||
});
|
||||
|
||||
btnSwapColors = new JButton("Play as black");
|
||||
btnSwapColors.addActionListener((evt) -> {
|
||||
btnSwapColors.addActionListener(evt -> {
|
||||
game.swapColors();
|
||||
btnSwapColors.setText("Play as " + activeColor.toString().toLowerCase());
|
||||
btnSwapColors
|
||||
.setText("Play as " + activeColor.toString().toLowerCase());
|
||||
activeColor = activeColor.opposite();
|
||||
});
|
||||
|
||||
@ -90,7 +99,7 @@ public class GamePane extends JComponent {
|
||||
moveSelectionPanel.add(btnFirst);
|
||||
|
||||
btnPrevious = new JButton("Previous");
|
||||
btnPrevious.addActionListener((evt) -> {
|
||||
btnPrevious.addActionListener(evt -> {
|
||||
if (game != null) {
|
||||
game.getBoard().selectPreviousNode();
|
||||
getBoardPane().getOverlayComponent().clearArrow();
|
||||
@ -100,12 +109,19 @@ public class GamePane extends JComponent {
|
||||
moveSelectionPanel.add(btnPrevious);
|
||||
|
||||
btnNext = new JButton("Next");
|
||||
btnNext.addActionListener((evt) -> {
|
||||
btnNext.addActionListener(evt -> {
|
||||
if (game != null) {
|
||||
int numVariations = game.getBoard().getLog().getLast().getVariations().size();
|
||||
int numVariations
|
||||
= game.getBoard().getLog().getLast().getVariations().size();
|
||||
int index;
|
||||
if (numVariations == 1) index = 1;
|
||||
else index = Integer.parseInt(JOptionPane.showInputDialog("Enter the variation index."));
|
||||
if (numVariations == 1)
|
||||
index = 1;
|
||||
else
|
||||
index
|
||||
= Integer.parseInt(
|
||||
JOptionPane
|
||||
.showInputDialog("Enter the variation index.")
|
||||
);
|
||||
game.getBoard().selectNextNode(index);
|
||||
getBoardPane().getOverlayComponent().clearArrow();
|
||||
repaint();
|
||||
@ -147,7 +163,6 @@ public class GamePane extends JComponent {
|
||||
letterLabel.setHorizontalAlignment(SwingConstants.CENTER);
|
||||
letterPanel.add(letterLabel);
|
||||
}
|
||||
|
||||
JScrollPane scrollPane = new JScrollPane();
|
||||
GridBagConstraints gbc_scrollPane = new GridBagConstraints();
|
||||
gbc_scrollPane.fill = GridBagConstraints.BOTH;
|
||||
@ -162,19 +177,28 @@ public class GamePane extends JComponent {
|
||||
pgnList.setCellRenderer(new MoveNodeRenderer());
|
||||
scrollPane.setViewportView(pgnList);
|
||||
|
||||
// Listen to moves and game (re-)starts and update the move list or disable the
|
||||
// Listen to moves and game (re-)starts and update the move list or
|
||||
// disable the
|
||||
// color switching buttons if necessary
|
||||
EventBus.getInstance().register(new Subscriber() {
|
||||
|
||||
@Override
|
||||
public void handle(Event<?> event) {
|
||||
if (event instanceof MoveEvent && (((MoveEvent) event).getBoardState() == BoardState.CHECKMATE
|
||||
|| ((MoveEvent) event).getBoardState() == BoardState.STALEMATE))
|
||||
if (
|
||||
event instanceof MoveEvent && (((MoveEvent) event)
|
||||
.getBoardState() == BoardState.CHECKMATE
|
||||
|| ((MoveEvent) event)
|
||||
.getBoardState() == BoardState.STALEMATE)
|
||||
)
|
||||
btnSwapColors.setEnabled(false);
|
||||
else if (event instanceof GameStartEvent) btnSwapColors.setEnabled(
|
||||
game.getPlayers().get(Color.WHITE) instanceof NaturalPlayer ^ game.getPlayers().get(Color.BLACK) instanceof NaturalPlayer);
|
||||
else
|
||||
if (event instanceof GameStartEvent)
|
||||
btnSwapColors.setEnabled(
|
||||
game.getPlayers().get(Color.WHITE) instanceof NaturalPlayer ^ game.getPlayers().get(Color.BLACK) instanceof NaturalPlayer
|
||||
);
|
||||
|
||||
if (game.getBoard().getLog() == null) return;
|
||||
if (game.getBoard().getLog() == null)
|
||||
return;
|
||||
|
||||
DefaultListModel<MoveNode> model = new DefaultListModel<>();
|
||||
game.getBoard().getLog().forEach(model::addElement);
|
||||
@ -182,7 +206,11 @@ public class GamePane extends JComponent {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<?>> supports() { return new HashSet<>(Arrays.asList(MoveEvent.class, GameStartEvent.class)); }
|
||||
public Set<Class<?>> supports() {
|
||||
return new HashSet<>(
|
||||
Arrays.asList(MoveEvent.class, GameStartEvent.class)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -197,13 +225,15 @@ public class GamePane extends JComponent {
|
||||
public Game getGame() { return game; }
|
||||
|
||||
/**
|
||||
* Assigns a new {@link Game} instance to this game pane. If exactly one of the
|
||||
* Assigns a new {@link Game} instance to this game pane. If exactly one of
|
||||
* the
|
||||
* players is natural, color swapping functionality is enabled.
|
||||
*
|
||||
* @param game The {@link Game} to assign to this game pane.
|
||||
*/
|
||||
public void setGame(Game game) {
|
||||
if (this.game != null) this.game.stop();
|
||||
if (this.game != null)
|
||||
this.game.stop();
|
||||
this.game = game;
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,8 @@ public class GameTabComponent extends JPanel {
|
||||
*/
|
||||
public GameTabComponent(JTabbedPane tabbedPane) {
|
||||
super(new FlowLayout(FlowLayout.LEFT, 0, 0));
|
||||
if (tabbedPane == null) throw new NullPointerException("TabbedPane is null");
|
||||
if (tabbedPane == null)
|
||||
throw new NullPointerException("TabbedPane is null");
|
||||
this.tabbedPane = tabbedPane;
|
||||
|
||||
// Create title JLabel
|
||||
@ -68,13 +69,21 @@ public class GameTabComponent extends JPanel {
|
||||
addMouseListener(new MouseAdapter() {
|
||||
|
||||
@Override
|
||||
public void mouseEntered(MouseEvent evt) { setBorderPainted(true); }
|
||||
public void mouseEntered(MouseEvent evt) {
|
||||
setBorderPainted(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mouseExited(MouseEvent evt) { setBorderPainted(false); }
|
||||
public void mouseExited(MouseEvent evt) {
|
||||
setBorderPainted(false);
|
||||
}
|
||||
});
|
||||
setRolloverEnabled(true);
|
||||
addActionListener((evt) -> { int i = tabbedPane.indexOfTabComponent(GameTabComponent.this); if (i != -1) tabbedPane.remove(i); });
|
||||
addActionListener(evt -> {
|
||||
int i = tabbedPane.indexOfTabComponent(GameTabComponent.this);
|
||||
if (i != -1)
|
||||
tabbedPane.remove(i);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -85,13 +94,25 @@ public class GameTabComponent extends JPanel {
|
||||
super.paintComponent(g);
|
||||
Graphics2D g2 = (Graphics2D) g.create();
|
||||
// shift the image for pressed buttons
|
||||
if (getModel().isPressed()) { g2.translate(1, 1); }
|
||||
if (getModel().isPressed())
|
||||
g2.translate(1, 1);
|
||||
g2.setStroke(new BasicStroke(2));
|
||||
g2.setColor(Color.BLACK);
|
||||
if (getModel().isRollover()) { g2.setColor(Color.MAGENTA); }
|
||||
if (getModel().isRollover())
|
||||
g2.setColor(Color.MAGENTA);
|
||||
final int delta = 6;
|
||||
g2.drawLine(delta, delta, getWidth() - delta - 1, getHeight() - delta - 1);
|
||||
g2.drawLine(getWidth() - delta - 1, delta, delta, getHeight() - delta - 1);
|
||||
g2.drawLine(
|
||||
delta,
|
||||
delta,
|
||||
getWidth() - delta - 1,
|
||||
getHeight() - delta - 1
|
||||
);
|
||||
g2.drawLine(
|
||||
getWidth() - delta - 1,
|
||||
delta,
|
||||
delta,
|
||||
getHeight() - delta - 1
|
||||
);
|
||||
g2.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,9 @@ public class MainWindow extends JFrame {
|
||||
*
|
||||
* @param args command line arguments are ignored
|
||||
*/
|
||||
public static void main(String[] args) { SwingUtilities.invokeLater(MainWindow::new); }
|
||||
public static void main(String[] args) {
|
||||
SwingUtilities.invokeLater(MainWindow::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the application.
|
||||
@ -48,8 +50,11 @@ public class MainWindow extends JFrame {
|
||||
// Configure frame
|
||||
setResizable(false);
|
||||
setBounds(100, 100, 494, 565);
|
||||
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
||||
setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/pieces/queen_white.png")));
|
||||
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
|
||||
setIconImage(
|
||||
Toolkit.getDefaultToolkit()
|
||||
.getImage(getClass().getResource("/pieces/queen_white.png"))
|
||||
);
|
||||
|
||||
// Add tabbed pane, menu bar and drop target
|
||||
getContentPane().add(tabbedPane);
|
||||
@ -65,7 +70,9 @@ public class MainWindow extends JFrame {
|
||||
/**
|
||||
* @return the currently selected {@link GamePane} component
|
||||
*/
|
||||
public GamePane getSelectedGamePane() { return (GamePane) tabbedPane.getSelectedComponent(); }
|
||||
public GamePane getSelectedGamePane() {
|
||||
return (GamePane) tabbedPane.getSelectedComponent();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GamePane}, adds it to the tabbed pane and opens it.
|
||||
@ -73,7 +80,9 @@ public class MainWindow extends JFrame {
|
||||
*
|
||||
* @return the new {@link GamePane}
|
||||
*/
|
||||
public GamePane addGamePane() { return addGamePane("Game " + (tabbedPane.getTabCount() + 1)); }
|
||||
public GamePane addGamePane() {
|
||||
return addGamePane("Game " + (tabbedPane.getTabCount() + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GamePane}, adds it to the tabbed pane and opens it.
|
||||
@ -84,13 +93,17 @@ public class MainWindow extends JFrame {
|
||||
public GamePane addGamePane(String title) {
|
||||
GamePane gamePane = new GamePane();
|
||||
tabbedPane.add(title, gamePane);
|
||||
tabbedPane.setTabComponentAt(tabbedPane.getTabCount() - 1, new GameTabComponent(tabbedPane));
|
||||
tabbedPane.setTabComponentAt(
|
||||
tabbedPane.getTabCount() - 1,
|
||||
new GameTabComponent(tabbedPane)
|
||||
);
|
||||
tabbedPane.setSelectedIndex(tabbedPane.getTabCount() - 1);
|
||||
return gamePane;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link GamePane}, adds it to the tabbed pane and immediately
|
||||
* Creates a new {@link GamePane}, adds it to the tabbed pane and
|
||||
* immediately
|
||||
* displays a game configuration dialog for a new game on an existing
|
||||
* {@link Board}.
|
||||
*
|
||||
@ -100,12 +113,19 @@ public class MainWindow extends JFrame {
|
||||
*/
|
||||
public GamePane addGamePane(String title, Board board) {
|
||||
GamePane gamePane = addGamePane(title);
|
||||
DialogUtil.showGameConfigurationDialog(this,
|
||||
DialogUtil.showGameConfigurationDialog(
|
||||
this,
|
||||
(whiteName, blackName) -> {
|
||||
Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, board);
|
||||
Game game = new Game(
|
||||
gamePane.getBoardPane(),
|
||||
whiteName,
|
||||
blackName,
|
||||
board
|
||||
);
|
||||
gamePane.setGame(game);
|
||||
game.start();
|
||||
});
|
||||
}
|
||||
);
|
||||
return gamePane;
|
||||
}
|
||||
|
||||
@ -114,7 +134,9 @@ public class MainWindow extends JFrame {
|
||||
*
|
||||
* @param index The index of the {@link GamePane} to remove
|
||||
*/
|
||||
public void removeGamePane(int index) { tabbedPane.remove(index); }
|
||||
public void removeGamePane(int index) {
|
||||
tabbedPane.remove(index);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a game file (FEN or PGN) and adds it to a new {@link GamePane}.
|
||||
@ -123,38 +145,66 @@ public class MainWindow extends JFrame {
|
||||
*/
|
||||
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();
|
||||
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();
|
||||
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()];
|
||||
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"));
|
||||
gameNames[i] = String.format(
|
||||
"%s vs %s: %s",
|
||||
game.getTag("White"),
|
||||
game.getTag("Black"),
|
||||
game.getTag("Result")
|
||||
);
|
||||
}
|
||||
JComboBox<String> comboBox = new JComboBox<>(gameNames);
|
||||
JOptionPane.showMessageDialog(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!");
|
||||
JComboBox<String> comboBox
|
||||
= new JComboBox<>(gameNames);
|
||||
JOptionPane.showMessageDialog(
|
||||
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!");
|
||||
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(),
|
||||
JOptionPane.showMessageDialog(
|
||||
this,
|
||||
"Failed to load the file " + file.getName() + ": "
|
||||
+ e.toString(),
|
||||
"File loading error",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -167,27 +217,39 @@ public class MainWindow extends JFrame {
|
||||
*/
|
||||
public void saveFile(File file) {
|
||||
final int dotIndex = file.getName().lastIndexOf('.');
|
||||
final String extension = file.getName().substring(dotIndex).toLowerCase();
|
||||
final String extension
|
||||
= file.getName().substring(dotIndex).toLowerCase();
|
||||
|
||||
if (extension.equals(".pgn")) try {
|
||||
PGNGame pgnGame = new PGNGame(getSelectedGamePane().getGame().getBoard());
|
||||
pgnGame.setTag("Event", tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
|
||||
if (extension.equals(".pgn"))
|
||||
try {
|
||||
PGNGame pgnGame
|
||||
= new PGNGame(getSelectedGamePane().getGame().getBoard());
|
||||
pgnGame.setTag(
|
||||
"Event",
|
||||
tabbedPane.getTitleAt(tabbedPane.getSelectedIndex())
|
||||
);
|
||||
pgnGame.setTag("Result", "*");
|
||||
PGNDatabase pgnDB = new PGNDatabase();
|
||||
pgnDB.getGames().add(pgnGame);
|
||||
pgnDB.save(file);
|
||||
|
||||
if (JOptionPane.showConfirmDialog(this,
|
||||
if (
|
||||
JOptionPane.showConfirmDialog(
|
||||
this,
|
||||
"Game export finished. Do you want to view the created file?",
|
||||
"Game export finished",
|
||||
JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)
|
||||
JOptionPane.YES_NO_OPTION
|
||||
) == JOptionPane.YES_OPTION
|
||||
)
|
||||
Desktop.getDesktop().open(file);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
JOptionPane.showMessageDialog(this,
|
||||
JOptionPane.showMessageDialog(
|
||||
this,
|
||||
"Failed to save the file " + file.getName() + ": " + e,
|
||||
"File saving error",
|
||||
JOptionPane.ERROR_MESSAGE);
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,25 +43,52 @@ public class MenuBar extends JMenuBar {
|
||||
JMenu gameMenu = new JMenu("Game");
|
||||
|
||||
JMenuItem newGameMenuItem = new JMenuItem("New Game");
|
||||
newGameMenuItem.addActionListener((evt) -> DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
|
||||
newGameMenuItem.addActionListener(
|
||||
evt -> DialogUtil.showGameConfigurationDialog(
|
||||
mainWindow,
|
||||
(whiteName, blackName) -> {
|
||||
GamePane gamePane = mainWindow.addGamePane();
|
||||
Game game = new Game(gamePane.getBoardPane(), whiteName, blackName);
|
||||
Game game = new Game(
|
||||
gamePane.getBoardPane(),
|
||||
whiteName,
|
||||
blackName
|
||||
);
|
||||
gamePane.setGame(game);
|
||||
game.start();
|
||||
}));
|
||||
}
|
||||
)
|
||||
);
|
||||
gameMenu.add(newGameMenuItem);
|
||||
|
||||
JMenuItem loadFileMenu = new JMenuItem("Load game file");
|
||||
loadFileMenu.addActionListener((evt) -> DialogUtil
|
||||
.showFileSelectionDialog(mainWindow,
|
||||
loadFileMenu.addActionListener(
|
||||
evt -> DialogUtil
|
||||
.showFileSelectionDialog(
|
||||
mainWindow,
|
||||
mainWindow::loadFiles,
|
||||
Arrays.asList(new FileNameExtensionFilter("FEN and PGN files", "fen", "pgn"))));
|
||||
Arrays.asList(
|
||||
new FileNameExtensionFilter(
|
||||
"FEN and PGN files",
|
||||
"fen",
|
||||
"pgn"
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
gameMenu.add(loadFileMenu);
|
||||
|
||||
JMenuItem saveFileMenu = new JMenuItem("Save game file");
|
||||
saveFileMenu
|
||||
.addActionListener((evt) -> DialogUtil
|
||||
.showFileSaveDialog(mainWindow, mainWindow::saveFile, Arrays.asList(new FileNameExtensionFilter("PGN file", "pgn"))));
|
||||
.addActionListener(
|
||||
evt -> DialogUtil
|
||||
.showFileSaveDialog(
|
||||
mainWindow,
|
||||
mainWindow::saveFile,
|
||||
Arrays.asList(
|
||||
new FileNameExtensionFilter("PGN file", "pgn")
|
||||
)
|
||||
)
|
||||
);
|
||||
gameMenu.add(saveFileMenu);
|
||||
|
||||
add(gameMenu);
|
||||
@ -72,10 +99,16 @@ public class MenuBar extends JMenuBar {
|
||||
JMenu engineMenu = new JMenu("Engine");
|
||||
|
||||
JMenuItem addEngineMenuItem = new JMenuItem("Add engine");
|
||||
addEngineMenuItem.addActionListener((evt) -> {
|
||||
addEngineMenuItem.addActionListener(evt -> {
|
||||
String enginePath = JOptionPane
|
||||
.showInputDialog(getParent(), "Enter the path to a UCI-compatible chess engine:", "Engine selection", JOptionPane.QUESTION_MESSAGE);
|
||||
if (enginePath != null) EngineUtil.addEngine(enginePath);
|
||||
.showInputDialog(
|
||||
getParent(),
|
||||
"Enter the path to a UCI-compatible chess engine:",
|
||||
"Engine selection",
|
||||
JOptionPane.QUESTION_MESSAGE
|
||||
);
|
||||
if (enginePath != null)
|
||||
EngineUtil.addEngine(enginePath);
|
||||
});
|
||||
|
||||
JMenuItem showInfoMenuItem = new JMenuItem("Show engine info");
|
||||
@ -89,29 +122,50 @@ public class MenuBar extends JMenuBar {
|
||||
JMenu toolsMenu = new JMenu("Tools");
|
||||
|
||||
JMenuItem exportFENMenuItem = new JMenuItem("Export board to FEN");
|
||||
exportFENMenuItem.addActionListener((evt) -> {
|
||||
final String fen = new FENString(mainWindow.getSelectedGamePane().getGame().getBoard()).toString();
|
||||
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(fen), null);
|
||||
JOptionPane.showMessageDialog(mainWindow, String.format("FEN-string copied to clipboard!%n%s", fen));
|
||||
exportFENMenuItem.addActionListener(evt -> {
|
||||
final String fen = new FENString(
|
||||
mainWindow.getSelectedGamePane().getGame().getBoard()
|
||||
).toString();
|
||||
Toolkit.getDefaultToolkit()
|
||||
.getSystemClipboard()
|
||||
.setContents(new StringSelection(fen), null);
|
||||
JOptionPane.showMessageDialog(
|
||||
mainWindow,
|
||||
String.format("FEN-string copied to clipboard!%n%s", fen)
|
||||
);
|
||||
});
|
||||
toolsMenu.add(exportFENMenuItem);
|
||||
|
||||
JMenuItem loadFromFENMenuItem = new JMenuItem("Load board from FEN");
|
||||
loadFromFENMenuItem.addActionListener((evt) -> {
|
||||
loadFromFENMenuItem.addActionListener(evt -> {
|
||||
final GamePane gamePane = mainWindow.addGamePane();
|
||||
final String fen = JOptionPane.showInputDialog("Enter a FEN string: ");
|
||||
DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
|
||||
final String fen
|
||||
= JOptionPane.showInputDialog("Enter a FEN string: ");
|
||||
DialogUtil.showGameConfigurationDialog(
|
||||
mainWindow,
|
||||
(whiteName, blackName) -> {
|
||||
Game game;
|
||||
try {
|
||||
game = new Game(gamePane.getBoardPane(), whiteName, blackName, new FENString(fen).getBoard());
|
||||
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);
|
||||
.showMessageDialog(
|
||||
mainWindow,
|
||||
"Failed to load FEN string: " + e.toString(),
|
||||
"FEN loading error",
|
||||
JOptionPane.ERROR_MESSAGE
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
});
|
||||
toolsMenu.add(loadFromFENMenuItem);
|
||||
|
||||
|
@ -18,16 +18,20 @@ import dev.kske.chess.board.MoveNode;
|
||||
* @since Chess v0.5-alpha
|
||||
* @author Kai S. K. Engelbart
|
||||
*/
|
||||
public class MoveNodeRenderer extends JLabel implements ListCellRenderer<MoveNode> {
|
||||
public class MoveNodeRenderer extends JLabel
|
||||
implements ListCellRenderer<MoveNode> {
|
||||
|
||||
private static final long serialVersionUID = 5242015788752442446L;
|
||||
|
||||
@Override
|
||||
public Component getListCellRendererComponent(JList<? extends MoveNode> list, MoveNode node, int index,
|
||||
boolean isSelected, boolean cellHasFocus) {
|
||||
public Component getListCellRendererComponent(
|
||||
JList<? extends MoveNode> list, MoveNode node, int index,
|
||||
boolean isSelected, boolean cellHasFocus
|
||||
) {
|
||||
setBorder(new EmptyBorder(5, 5, 5, 5));
|
||||
|
||||
int numVariations = node.hasVariations() ? node.getVariations().size() : 0;
|
||||
int numVariations
|
||||
= node.hasVariations() ? node.getVariations().size() : 0;
|
||||
setText(String.format("%s (%d)", node.move.toLAN(), numVariations));
|
||||
|
||||
setBackground(isSelected ? Color.red : Color.white);
|
||||
|
@ -48,15 +48,31 @@ public class OverlayComponent extends JComponent {
|
||||
// Draw an arrow representing the last move and mark its position and
|
||||
// destination
|
||||
if (arrow != null) {
|
||||
Point pos = new Point(arrow.getPos().x * tileSize + tileSize / 2, arrow.getPos().y * tileSize + tileSize / 2);
|
||||
Point dest = new Point(arrow.getDest().x * tileSize + tileSize / 2, arrow.getDest().y * tileSize + tileSize / 2);
|
||||
Point pos = new Point(
|
||||
arrow.getPos().x * tileSize + tileSize / 2,
|
||||
arrow.getPos().y * tileSize + tileSize / 2
|
||||
);
|
||||
Point dest = new Point(
|
||||
arrow.getDest().x * tileSize + tileSize / 2,
|
||||
arrow.getDest().y * tileSize + tileSize / 2
|
||||
);
|
||||
|
||||
Graphics2D g2d = (Graphics2D) g;
|
||||
g2d.setStroke(new BasicStroke(3));
|
||||
|
||||
g2d.setColor(Color.yellow);
|
||||
g2d.drawRect(arrow.getPos().x * tileSize, arrow.getPos().y * tileSize, tileSize, tileSize);
|
||||
g2d.drawRect(arrow.getDest().x * tileSize, arrow.getDest().y * tileSize, tileSize, tileSize);
|
||||
g2d.drawRect(
|
||||
arrow.getPos().x * tileSize,
|
||||
arrow.getPos().y * tileSize,
|
||||
tileSize,
|
||||
tileSize
|
||||
);
|
||||
g2d.drawRect(
|
||||
arrow.getDest().x * tileSize,
|
||||
arrow.getDest().y * tileSize,
|
||||
tileSize,
|
||||
tileSize
|
||||
);
|
||||
|
||||
Shape arrowShape = createArrowShape(pos, dest);
|
||||
g.setColor(new Color(255, 0, 0, 127));
|
||||
@ -64,16 +80,17 @@ public class OverlayComponent extends JComponent {
|
||||
g2d.setColor(Color.black);
|
||||
g2d.draw(arrowShape);
|
||||
}
|
||||
|
||||
// Draw possible moves if a piece was selected
|
||||
if (!dots.isEmpty()) {
|
||||
g.setColor(Color.green);
|
||||
int radius = tileSize / 4;
|
||||
for (Position dot : dots)
|
||||
g.fillOval(dot.x * tileSize + tileSize / 2 - radius / 2,
|
||||
g.fillOval(
|
||||
dot.x * tileSize + tileSize / 2 - radius / 2,
|
||||
dot.y * tileSize + tileSize / 2 - radius / 2,
|
||||
radius,
|
||||
radius);
|
||||
radius
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +108,8 @@ public class OverlayComponent extends JComponent {
|
||||
|
||||
double rotate = Math.atan2(dest.y - pos.y, dest.x - pos.x);
|
||||
double ptDistance = pos.distance(dest);
|
||||
double scale = ptDistance / 12.0; // 12 because it's the length of the arrow
|
||||
double scale = ptDistance / 12.0; // 12 because it's the length of the
|
||||
// arrow
|
||||
// polygon.
|
||||
|
||||
AffineTransform transform = new AffineTransform();
|
||||
@ -104,7 +122,10 @@ public class OverlayComponent extends JComponent {
|
||||
}
|
||||
|
||||
private Point midpoint(Point p1, Point p2) {
|
||||
return new Point((int) ((p1.x + p2.x) / 2.0), (int) ((p1.y + p2.y) / 2.0));
|
||||
return new Point(
|
||||
(int) ((p1.x + p2.x) / 2.0),
|
||||
(int) ((p1.y + p2.y) / 2.0)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -37,6 +37,9 @@ class BoardTest {
|
||||
clone.getBoardArr()[0][0] = new Queen(Color.BLACK, clone);
|
||||
clone.move(new Move(1, 1, 1, 2));
|
||||
assertNotEquals(clone.getBoardArr()[0][0], board.getBoardArr()[0][0]);
|
||||
assertNotEquals(clone.getLog().getActiveColor(), board.getLog().getActiveColor());
|
||||
assertNotEquals(
|
||||
clone.getLog().getActiveColor(),
|
||||
board.getLog().getActiveColor()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,11 @@ class FENStringTest {
|
||||
*/
|
||||
@BeforeEach
|
||||
void setUp() throws Exception {
|
||||
fenStrings.addAll(Arrays.asList("rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"));
|
||||
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));
|
||||
@ -61,7 +65,10 @@ class FENStringTest {
|
||||
@Test
|
||||
void testToString() {
|
||||
for (int i = 0; i < fenStrings.size(); i++)
|
||||
assertEquals(fenStrings.get(i), new FENString(boards.get(i)).toString());
|
||||
assertEquals(
|
||||
fenStrings.get(i),
|
||||
new FENString(boards.get(i)).toString()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -72,6 +79,9 @@ class FENStringTest {
|
||||
@Test
|
||||
void testGetBoard() throws ChessException {
|
||||
for (int i = 0; i < boards.size(); i++)
|
||||
assertEquals(boards.get(i), new FENString(fenStrings.get(i)).getBoard());
|
||||
assertEquals(
|
||||
boards.get(i),
|
||||
new FENString(fenStrings.get(i)).getBoard()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -44,7 +44,10 @@ class LogTest {
|
||||
other.add(Move.fromLAN("a2a4"), new Pawn(Color.WHITE, null), null);
|
||||
other.add(Move.fromLAN("a4a5"), new Pawn(Color.WHITE, null), null);
|
||||
assertNotEquals(log.getRoot(), other.getRoot());
|
||||
assertNotEquals(log.getRoot().getVariations(), other.getRoot().getVariations());
|
||||
assertNotEquals(
|
||||
log.getRoot().getVariations(),
|
||||
other.getRoot().getVariations()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -120,7 +123,8 @@ class LogTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Test method for {@link Log#setActiveColor(dev.kske.chess.board.Piece.Color)}.
|
||||
* Test method for
|
||||
* {@link Log#setActiveColor(dev.kske.chess.board.Piece.Color)}.
|
||||
*/
|
||||
@Test
|
||||
void testSetActiveColor() {
|
||||
|
@ -13,9 +13,18 @@ import org.junit.jupiter.api.Test;
|
||||
class PositionTest {
|
||||
|
||||
private final int n = 4;
|
||||
private Position[] positions = new Position[] { new Position(0, 0), new Position(7, 7), new Position(0, 7), new Position(7, 0) };
|
||||
private String[] sans = new String[] { "a8", "h1", "a1", "h8" };
|
||||
private String[] strings = new String[] { "[0, 0]", "[7, 7]", "[0, 7]", "[7, 0]" };
|
||||
private Position[] positions = new Position[] {
|
||||
new Position(0, 0),
|
||||
new Position(7, 7),
|
||||
new Position(0, 7),
|
||||
new Position(7, 0)
|
||||
};
|
||||
private String[] sans = new String[] {
|
||||
"a8", "h1", "a1", "h8"
|
||||
};
|
||||
private String[] strings = new String[] {
|
||||
"[0, 0]", "[7, 7]", "[0, 7]", "[7, 0]"
|
||||
};
|
||||
|
||||
/**
|
||||
* Test method for
|
||||
|
@ -16,7 +16,8 @@ import dev.kske.chess.exception.ChessException;
|
||||
class PGNDatabaseTest {
|
||||
|
||||
/**
|
||||
* Test method for {@link dev.kske.chess.pgn.PGNDatabase#load(java.io.File)}.
|
||||
* Test method for
|
||||
* {@link dev.kske.chess.pgn.PGNDatabase#load(java.io.File)}.
|
||||
*
|
||||
* @throws ChessException if an error occurs while parsing the file
|
||||
* @throws FileNotFoundException if the test file {@code test.pgn} is not
|
||||
@ -25,6 +26,8 @@ class PGNDatabaseTest {
|
||||
@Test
|
||||
void testLoad() throws FileNotFoundException, ChessException {
|
||||
PGNDatabase db = new PGNDatabase();
|
||||
db.load(new File(getClass().getClassLoader().getResource("test.pgn").getFile()));
|
||||
db.load(
|
||||
new File(getClass().getClassLoader().getResource("test.pgn").getFile())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user