diff --git a/.classpath b/.classpath index 8469573..afa4751 100644 --- a/.classpath +++ b/.classpath @@ -1,18 +1,22 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dev/kske/chess/board/Bishop.java b/src/dev/kske/chess/board/Bishop.java index 9412a30..0321e80 100644 --- a/src/dev/kske/chess/board/Bishop.java +++ b/src/dev/kske/chess/board/Bishop.java @@ -7,12 +7,18 @@ import java.util.List; * Project: Chess
* File: Bishop.java
* Created: 01.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public class Bishop extends Piece { + /** + * Creates bishop {@link Piece}. + * + * @param color the color of this bishop + * @param board the board on which this bishop will be placed + */ public Bishop(Color color, Board board) { super(color, board); } @@ -29,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; } diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index 114f32e..a435c17 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -1,13 +1,10 @@ package dev.kske.chess.board; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import dev.kske.chess.board.Piece.Color; +import dev.kske.chess.event.EventBus; +import dev.kske.chess.event.MoveEvent; /** * Project: Chess
@@ -19,29 +16,35 @@ import dev.kske.chess.board.Piece.Color; */ public class Board { - private Piece[][] boardArr = new Piece[8][8]; - private Map kingPos = new HashMap<>(); - private Log log = new Log(); + private Piece[][] boardArr = new Piece[8][8]; + private Map kingPos = new EnumMap<>(Color.class); + private Log log = new Log(); /** * Initializes the board with the default chess starting position. */ - public Board() { initDefaultPositions(); } + public Board() { + initDefaultPositions(); + } /** * Creates a copy of another {@link Board} instance.
- * The created object is a deep copy, but does not contain any move history - * apart from the current {@link MoveNode}. + * The created object is a deep copy, and can optionally contain the move + * history of the Board to copy. * - * @param other The {@link Board} instance to copy - * @param copyVariations TODO + * @param other The Board instance to copy + * @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; - boardArr[i][j] = (Piece) other.boardArr[i][j].clone(); - boardArr[i][j].board = this; + if (other.boardArr[i][j] == null) + continue; + boardArr[i][j] = (Piece) other.boardArr[i][j].clone(); + boardArr[i][j].board = this; } kingPos.putAll(other.kingPos); @@ -60,19 +63,18 @@ public class Board { */ public boolean attemptMove(Move move) { Piece piece = getPos(move); - if (piece == null || !piece.isValidMove(move)) return false; - else { - // Move piece - move(move); + if (piece == null || !piece.isValidMove(move)) + return false; - // Revert move if it caused a check for its team - if (checkCheck(piece.getColor())) { - revert(); - return false; - } + // Move piece + move(move); - return true; + // Revert move if it caused a check for its team + if (checkCheck(piece.getColor())) { + revert(); + return false; } + return true; } /** @@ -81,14 +83,15 @@ public class Board { * @param move The move to execute */ public void move(Move move) { - Piece piece = getPos(move); - Piece capturePiece = getDest(move); + Piece piece = getPos(move); + Piece capturePiece = getDest(move); // Execute the move 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); @@ -107,19 +110,67 @@ public class Board { * Reverts the last move and removes it from the log. */ public void revert() { - MoveNode moveNode = log.getLast(); - Move move = moveNode.move; + MoveNode moveNode = log.getLast(); + Move move = moveNode.move; // Revert the move 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(); } + /** + * Reverts the last move without removing it from the log. After that, a + * {@link MoveEvent} is dispatched containing the inverse of the reverted + * move. + */ + public void selectPreviousNode() { + MoveNode moveNode = log.getLast(); + Move move = moveNode.move; + + // Revert the move + move.revert(this, moveNode.capturedPiece); + + // Select previous move node + log.selectPreviousNode(); + + // Dispatch move event + 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 + * dispatched. + * + * @param index the variation index of the move to select + */ + public void selectNextNode(int index) { + log.selectNextNode(index); + MoveNode moveNode = log.getLast(); + Move move = moveNode.move; + + // Execute the next move + move.execute(this); + + // Dispatch move event + EventBus.getInstance() + .dispatch( + new MoveEvent(move, getState(log.getActiveColor().opposite())) + ); + } + /** * Generated every legal move for one color * @@ -130,11 +181,22 @@ public class Board { List moves = new ArrayList<>(); for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) - if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) moves.addAll(boardArr[i][j].getMoves(new Position(i, j))); + if ( + boardArr[i][j] != null && boardArr[i][j].getColor() == color + ) + moves.addAll(boardArr[i][j].getMoves(new Position(i, j))); return moves; } - public List getMoves(Position pos) { return get(pos).getMoves(pos); } + /** + * Delegate method for {@link Piece#getMoves(Position)}. + * + * @param pos the position of the piece to invoke the method on + * @return a list of legal moves generated for the piece + */ + public List getMoves(Position pos) { + return get(pos).getMoves(pos); + } /** * Checks, if the king is in check. @@ -142,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. @@ -155,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; } @@ -169,21 +237,30 @@ public class Board { */ public boolean checkCheckmate(Color color) { // Return false immediately if the king can move - if (!getMoves(kingPos.get(color)).isEmpty()) return false; - else { - for (Move move : getMoves(color)) { - move(move); - boolean check = checkCheck(color); - revert(); - if (!check) return false; - } - return true; + if (!getMoves(kingPos.get(color)).isEmpty()) + return false; + + for (Move move : getMoves(color)) { + move(move); + boolean check = checkCheck(color); + revert(); + if (!check) + return false; } + return true; } - public BoardState getGameEventType(Color color) { - return checkCheck(color) ? checkCheckmate(color) ? BoardState.CHECKMATE : BoardState.CHECK - : getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? BoardState.STALEMATE : BoardState.NORMAL; + /** + * Checks whether the a check, checkmate, stalemate of none of the above is + * currently present. + * + * @param color the color to evaluate the board for + * @return the current {@link BoardState} + */ + public BoardState getState(Color color) { + return checkCheck(color) ? checkCheckmate(color) ? BoardState.CHECKMATE + : BoardState.CHECK + : getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? BoardState.STALEMATE : BoardState.NORMAL; } /** @@ -192,39 +269,38 @@ public class Board { public void initDefaultPositions() { // Initialize pawns for (int i = 0; i < 8; i++) { - boardArr[i][1] = new Pawn(Color.BLACK, this); - boardArr[i][6] = new Pawn(Color.WHITE, this); + 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); + boardArr[4][0] = new King(Color.BLACK, this); + boardArr[4][7] = new King(Color.WHITE, this); // Initialize king position objects kingPos.put(Color.BLACK, new Position(4, 0)); kingPos.put(Color.WHITE, new Position(4, 7)); // Initialize queens - boardArr[3][0] = new Queen(Color.BLACK, this); - boardArr[3][7] = new Queen(Color.WHITE, this); + boardArr[3][0] = new Queen(Color.BLACK, this); + boardArr[3][7] = new Queen(Color.WHITE, this); // Initialize rooks - boardArr[0][0] = new Rook(Color.BLACK, this); - boardArr[0][7] = new Rook(Color.WHITE, this); - boardArr[7][0] = new Rook(Color.BLACK, this); - boardArr[7][7] = new Rook(Color.WHITE, this); + boardArr[0][0] = new Rook(Color.BLACK, this); + boardArr[0][7] = new Rook(Color.WHITE, this); + boardArr[7][0] = new Rook(Color.BLACK, this); + boardArr[7][7] = new Rook(Color.WHITE, this); // Initialize knights - boardArr[1][0] = new Knight(Color.BLACK, this); - boardArr[1][7] = new Knight(Color.WHITE, this); - boardArr[6][0] = new Knight(Color.BLACK, this); - boardArr[6][7] = new Knight(Color.WHITE, this); + boardArr[1][0] = new Knight(Color.BLACK, this); + boardArr[1][7] = new Knight(Color.WHITE, this); + boardArr[6][0] = new Knight(Color.BLACK, this); + boardArr[6][7] = new Knight(Color.WHITE, this); // Initialize bishops - boardArr[2][0] = new Bishop(Color.BLACK, this); - boardArr[2][7] = new Bishop(Color.WHITE, this); - boardArr[5][0] = new Bishop(Color.BLACK, this); - boardArr[5][7] = new Bishop(Color.WHITE, this); + boardArr[2][0] = new Bishop(Color.BLACK, this); + boardArr[2][7] = new Bishop(Color.WHITE, this); + boardArr[5][0] = new Bishop(Color.BLACK, this); + boardArr[5][7] = new Bishop(Color.WHITE, this); // Clear all other tiles for (int i = 0; i < 8; i++) @@ -236,27 +312,33 @@ public class Board { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.deepHashCode(boardArr); - result = prime * result + Objects.hash(kingPos, log); + final int prime = 31; + int result = 1; + result = prime * result + Arrays.deepHashCode(boardArr); + result = prime * result + Objects.hash(kingPos, log); 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; 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). @@ -269,7 +351,12 @@ public class Board { public int get(Class 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; } @@ -284,7 +371,11 @@ public class Board { public char get(Class 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 '-'; } @@ -294,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 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; } @@ -312,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. @@ -332,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. @@ -340,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 diff --git a/src/dev/kske/chess/board/BoardState.java b/src/dev/kske/chess/board/BoardState.java index b8db707..4b27887 100644 --- a/src/dev/kske/chess/board/BoardState.java +++ b/src/dev/kske/chess/board/BoardState.java @@ -4,10 +4,11 @@ package dev.kske.chess.board; * Project: Chess
* File: BoardState.java
* Created: 07.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ +@SuppressWarnings("javadoc") public enum BoardState { CHECK, CHECKMATE, STALEMATE, NORMAL; } diff --git a/src/dev/kske/chess/board/Castling.java b/src/dev/kske/chess/board/Castling.java index 9f973ad..27b882e 100644 --- a/src/dev/kske/chess/board/Castling.java +++ b/src/dev/kske/chess/board/Castling.java @@ -12,12 +12,29 @@ public class Castling extends Move { private final Move rookMove; + /** + * Creates a castling move. + * + * @param pos the position of this castling move + * @param dest the destination of this castling 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); } - public Castling(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); } + /** + * Creates a castling move. + * + * @param xPos the horizontal position of this castling move + * @param yPos the vertical position of this castling 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)); + } @Override public void execute(Board board) { @@ -34,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 diff --git a/src/dev/kske/chess/board/EnPassant.java b/src/dev/kske/chess/board/EnPassant.java index 69c48d9..96c41de 100644 --- a/src/dev/kske/chess/board/EnPassant.java +++ b/src/dev/kske/chess/board/EnPassant.java @@ -4,7 +4,7 @@ package dev.kske.chess.board; * Project: Chess
* File: EnPassant.java
* Created: 2 Nov 2019
- * + * * @since Chess v0.5-alpha * @author Kai S. K. Engelbart */ @@ -12,12 +12,28 @@ public class EnPassant extends Move { private final Position capturePos; + /** + * Initializes an en passant move. + * + * @param pos the position of this move + * @param dest the destination of this move + */ public EnPassant(Position pos, Position dest) { super(pos, dest); capturePos = new Position(dest.x, dest.y - ySign); } - public EnPassant(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); } + /** + * Initializes an en passant move. + * + * @param xPos the horizontal position of this move + * @param yPos the vertical position of this 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)); + } @Override public void execute(Board board) { @@ -28,8 +44,14 @@ 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) + ); } + /** + * @return the position of the piece captures by this move + */ public Position getCapturePos() { return capturePos; } } diff --git a/src/dev/kske/chess/board/FENString.java b/src/dev/kske/chess/board/FENString.java index 28c890a..fd84df4 100644 --- a/src/dev/kske/chess/board/FENString.java +++ b/src/dev/kske/chess/board/FENString.java @@ -15,51 +15,58 @@ import dev.kske.chess.exception.ChessException; *
* Represents a FEN string and enables parsing an existing FEN string or * serializing a {@link Board} to one. - * + * * @since Chess v0.5-alpha * @author Kai S. K. Engelbart */ public class FENString { - private Board board; - private String piecePlacement, castlingAvailability; - private int halfmoveClock, fullmoveNumber; - private Color activeColor; - private Position enPassantTargetSquare; + private Board board; + private String piecePlacement, castlingAvailability; + private int halfmoveClock, fullmoveNumber; + private Color activeColor; + private Position enPassantTargetSquare; /** * Constructs a {@link FENString} representing the starting position * {@code rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1}. */ public FENString() { - board = new Board(); - piecePlacement = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"; - activeColor = Color.WHITE; - castlingAvailability = "KQkq"; - halfmoveClock = 0; - fullmoveNumber = 1; + board = new Board(); + piecePlacement = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"; + activeColor = Color.WHITE; + castlingAvailability = "KQkq"; + halfmoveClock = 0; + fullmoveNumber = 1; } /** * Constructs a {@link FENString} by parsing an existing string. - * + * * @param fen the FEN string to parse - * @throws ChessException + * @throws ChessException if the FEN string contains invalid syntax */ public FENString(String fen) throws ChessException { // Check fen string against regex - Pattern fenPattern = Pattern.compile( - "^(?(?:[1-8nbrqkpNBRQKP]{1,8}\\/){7}[1-8nbrqkpNBRQKP]{1,8}) (?[wb]) (?-|[KQkq]{1,4}) (?-|[a-h][1-8]) (?\\d+) (?\\d+)$"); - Matcher matcher = fenPattern.matcher(fen); - if (!matcher.find()) throw new ChessException("FEN string does not match pattern " + fenPattern.pattern()); + Pattern fenPattern = Pattern.compile( + "^(?(?:[1-8nbrqkpNBRQKP]{1,8}\\/){7}[1-8nbrqkpNBRQKP]{1,8}) (?[wb]) (?-|[KQkq]{1,4}) (?-|[a-h][1-8]) (?\\d+) (?\\d+)$" + ); + Matcher matcher = fenPattern.matcher(fen); + if (!matcher.find()) + throw new ChessException( + "FEN string does not match pattern " + fenPattern.pattern() + ); // Initialize data fields - piecePlacement = matcher.group("piecePlacement"); - activeColor = Color.fromFirstChar(matcher.group("activeColor").charAt(0)); - castlingAvailability = matcher.group("castlingAvailability"); - if (!matcher.group("enPassantTargetSquare").equals("-")) enPassantTargetSquare = Position.fromLAN(matcher.group("enPassantTargetSquare")); - halfmoveClock = Integer.parseInt(matcher.group("halfmoveClock")); - fullmoveNumber = Integer.parseInt(matcher.group("fullmoveNumber")); + piecePlacement = matcher.group("piecePlacement"); + activeColor + = Color.fromFirstChar(matcher.group("activeColor").charAt(0)); + castlingAvailability = matcher.group("castlingAvailability"); + if (!matcher.group("enPassantTargetSquare").equals("-")) + enPassantTargetSquare + = Position.fromLAN(matcher.group("enPassantTargetSquare")); + halfmoveClock = Integer.parseInt(matcher.group("halfmoveClock")); + fullmoveNumber = Integer.parseInt(matcher.group("fullmoveNumber")); // Initialize and clean board board = new Board(); @@ -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) { - + final char[] cols = rows[i].toCharArray(); + int j = 0; + 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 pieceConstructor = Piece.fromFirstChar(c).getDeclaredConstructor(Color.class, Board.class); + Constructor 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); @@ -129,7 +146,7 @@ public class FENString { /** * Constructs a {@link FENString} form a {@link Board} object. - * + * * @param board the {@link Board} object to encode in this {@link FENString} */ public FENString(Board board) { @@ -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(); @@ -191,18 +215,20 @@ public class FENString { /** * Exports this {@link FENString} object to a FEN string. - * + * * @return a FEN string representing the board */ @Override public String toString() { - return String.format("%s %c %s %s %d %d", - piecePlacement, - activeColor.firstChar(), - castlingAvailability, - enPassantTargetSquare == null ? "-" : enPassantTargetSquare.toLAN(), - halfmoveClock, - fullmoveNumber); + return String.format( + "%s %c %s %s %d %d", + piecePlacement, + activeColor.firstChar(), + castlingAvailability, + enPassantTargetSquare == null ? "-" : enPassantTargetSquare.toLAN(), + halfmoveClock, + fullmoveNumber + ); } /** diff --git a/src/dev/kske/chess/board/King.java b/src/dev/kske/chess/board/King.java index 6807bb3..8356ef3 100644 --- a/src/dev/kske/chess/board/King.java +++ b/src/dev/kske/chess/board/King.java @@ -7,66 +7,100 @@ import java.util.List; * Project: Chess
* File: King.java
* Created: 01.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public class King extends Piece { - public King(Color color, Board board) { super(color, board); } + /** + * Creates king {@link 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); + } @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 getPseudolegalMoves(Position pos) { List 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]) { - int y = getColor() == Color.WHITE ? 7 : 0; - Position kingPos = new Position(4, y); - Position jumpPos = new Position(5, y); - Position kingDest = new Position(6, y); - Position rookPos = new Position(7, y); + 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); + Position kingDest = new Position(6, y); + Position rookPos = new Position(7, y); return canCastle(kingPos, kingDest, rookPos, jumpPos); - } else return false; + } + return false; } private boolean canCastleQueenside() { - 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); - Position freeDest = new Position(1, y); - Position rookPos = new Position(0, y); + 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); + Position freeDest = new Position(1, y); + Position rookPos = new Position(0, y); return canCastle(kingPos, freeDest, rookPos, jumpPos); - } else return false; + } + 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()) - && !board.isAttacked(jumpPos, getColor().opposite()); + return rook != null && rook instanceof Rook && isFreePath( + new Move(kingPos, freeDest) + ) && !board.isAttacked(kingPos, getColor().opposite()) + && !board.isAttacked(jumpPos, getColor().opposite()); } @Override public int getValue() { return 0; } -} \ No newline at end of file +} diff --git a/src/dev/kske/chess/board/Knight.java b/src/dev/kske/chess/board/Knight.java index 9e54fd8..c3fe96e 100644 --- a/src/dev/kske/chess/board/Knight.java +++ b/src/dev/kske/chess/board/Knight.java @@ -7,12 +7,18 @@ import java.util.List; * Project: Chess
* File: Knight.java
* Created: 01.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public class Knight extends Piece { + /** + * Creates knight {@link Piece}. + * + * @param color the color of this knight + * @param board the board on which this knight will be placed + */ public Knight(Color color, Board board) { super(color, board); } @@ -20,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 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 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); } } @@ -48,5 +63,7 @@ public class Knight extends Piece { public int getValue() { return 35; } @Override - public char firstChar() { return 'n'; } + public char firstChar() { + return 'n'; + } } diff --git a/src/dev/kske/chess/board/Log.java b/src/dev/kske/chess/board/Log.java index ba4c509..2def86c 100644 --- a/src/dev/kske/chess/board/Log.java +++ b/src/dev/kske/chess/board/Log.java @@ -5,8 +5,11 @@ import java.util.Iterator; import java.util.Objects; import dev.kske.chess.board.Piece.Color; +import dev.kske.chess.game.Game; /** + * Manages the move history of a {@link Game}.
+ *
* Project: Chess
* File: Log.java
* Created: 09.07.2019
@@ -18,28 +21,35 @@ public class Log implements Iterable { private MoveNode root, current; - private Color activeColor; - private boolean[] castlingRights; - private Position enPassant; - private int fullmoveNumber, halfmoveClock; - - public Log() { reset(); } + private Color activeColor; + private boolean[] castlingRights; + private Position enPassant; + private int fullmoveNumber, halfmoveClock; /** - * Creates a (partially deep) copy of another {@link Log} instance which begins + * Creates an instance of {@link Log} in the default state. + */ + public Log() { + reset(); + } + + /** + * 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} */ public Log(Log other, boolean copyVariations) { - enPassant = other.enPassant; - castlingRights = other.castlingRights.clone(); - activeColor = other.activeColor; - fullmoveNumber = other.fullmoveNumber; - halfmoveClock = other.halfmoveClock; + enPassant = other.enPassant; + castlingRights = other.castlingRights.clone(); + activeColor = other.activeColor; + fullmoveNumber = other.fullmoveNumber; + halfmoveClock = other.halfmoveClock; // The new root is the current node of the copied instance if (!other.isEmpty()) { @@ -58,17 +68,21 @@ public class Log implements Iterable { public Iterator iterator() { return new Iterator() { - private MoveNode current = root; - private boolean hasNext = !isEmpty(); + private MoveNode current = root; + 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; } }; @@ -82,20 +96,34 @@ public class Log implements Iterable { * @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; - current = leaf; + root = leaf; + current = leaf; } else { current.addVariation(leaf); current = leaf; @@ -111,7 +139,8 @@ public class Log implements Iterable { current.getParent().getVariations().remove(current); current = current.getParent(); update(); - } else reset(); + } else + reset(); } /** @@ -122,20 +151,24 @@ public class Log implements Iterable { /** * @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 * position. */ public void reset() { - root = null; - current = null; - castlingRights = new boolean[] { true, true, true, true }; - enPassant = null; - activeColor = Color.WHITE; - fullmoveNumber = 1; - halfmoveClock = 0; + root = null; + current = null; + castlingRights = new boolean[] { + true, true, true, true + }; + enPassant = null; + activeColor = Color.WHITE; + fullmoveNumber = 1; + halfmoveClock = 0; } /** @@ -144,7 +177,10 @@ public class Log implements Iterable { * @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(); } @@ -171,53 +207,78 @@ public class Log implements Iterable { } /** - * 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() { - activeColor = current.activeColor; - castlingRights = current.castlingRights.clone(); - enPassant = current.enPassant; - fullmoveNumber = current.fullmoveCounter; - halfmoveClock = current.halfmoveClock; + activeColor = current.activeColor; + castlingRights = current.castlingRights.clone(); + enPassant = current.enPassant; + fullmoveNumber = current.fullmoveCounter; + halfmoveClock = current.halfmoveClock; } /** - * 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 public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(castlingRights); - result = prime * result + Objects.hash(activeColor, current, enPassant, fullmoveNumber, halfmoveClock); + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(castlingRights); + result = prime * result + Objects.hash( + activeColor, + current, + enPassant, + fullmoveNumber, + halfmoveClock + ); return result; } @Override public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; Log other = (Log) obj; - return activeColor == other.activeColor && Arrays.equals(castlingRights, other.castlingRights) && Objects.equals(current, other.current) - && Objects.equals(enPassant, other.enPassant) && fullmoveNumber == other.fullmoveNumber && halfmoveClock == other.halfmoveClock; + return activeColor == other.activeColor && Arrays + .equals(castlingRights, other.castlingRights) + && Objects.equals(current, other.current) + && Objects.equals(enPassant, other.enPassant) && fullmoveNumber == other.fullmoveNumber && halfmoveClock == other.halfmoveClock; } /** @@ -230,23 +291,75 @@ public class Log implements Iterable { */ public MoveNode getLast() { return current; } + /** + * @return the castling rights present during the current move + */ public boolean[] getCastlingRights() { return castlingRights; } - public void setCastlingRights(boolean[] castlingRights) { this.castlingRights = castlingRights; } + /** + * Sets the castling rights present during the current move. + * + * @param castlingRights the castling rights to set + */ + public void setCastlingRights(boolean[] castlingRights) { + this.castlingRights = castlingRights; + } + /** + * @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; } - public void setEnPassant(Position enPassant) { this.enPassant = enPassant; } + /** + * Sets the en passant target position. + * + * @param enPassant the en passant target position to set + */ + public void setEnPassant(Position enPassant) { + this.enPassant = enPassant; + } + /** + * @return the color active during the current move + */ public Color getActiveColor() { return activeColor; } - public void setActiveColor(Color activeColor) { this.activeColor = activeColor; } + /** + * Sets the color active during the current move. + * + * @param activeColor the active color to set + */ + public void setActiveColor(Color activeColor) { + this.activeColor = activeColor; + } + /** + * @return the number of moves made until the current move + */ public int getFullmoveNumber() { return fullmoveNumber; } - public void setFullmoveNumber(int fullmoveNumber) { this.fullmoveNumber = fullmoveNumber; } + /** + * Sets the number of moves made until the current move. + * + * @param fullmoveNumber the fullmove number to set + */ + public void setFullmoveNumber(int fullmoveNumber) { + this.fullmoveNumber = fullmoveNumber; + } + /** + * @return the number of halfmoves since the last capture move or pawn move + */ public int getHalfmoveClock() { return halfmoveClock; } - public void setHalfmoveClock(int halfmoveClock) { this.halfmoveClock = halfmoveClock; } -} \ No newline at end of file + /** + * Sets then number of halfmoves since the last capture move or pawn move + * + * @param halfmoveClock the halfmove clock to set + */ + public void setHalfmoveClock(int halfmoveClock) { + this.halfmoveClock = halfmoveClock; + } +} diff --git a/src/dev/kske/chess/board/Move.java b/src/dev/kske/chess/board/Move.java index 2ff63e2..e48ef56 100644 --- a/src/dev/kske/chess/board/Move.java +++ b/src/dev/kske/chess/board/Move.java @@ -18,46 +18,104 @@ import dev.kske.chess.board.Piece.Color; */ public class Move { - protected final Position pos, dest; - protected final int xDist, yDist, xSign, ySign; + protected final Position pos, dest; + protected final int xDist, yDist, xSign, ySign; + /** + * Creates an instance of {@link Move}. + * + * @param pos the position of this move + * @param dest the destination of this move + */ public Move(Position pos, Position dest) { - this.pos = pos; - this.dest = dest; - xDist = Math.abs(dest.x - pos.x); - yDist = Math.abs(dest.y - pos.y); - xSign = (int) Math.signum(dest.x - pos.x); - ySign = (int) Math.signum(dest.y - pos.y); + this.pos = pos; + this.dest = dest; + xDist = Math.abs(dest.x - pos.x); + yDist = Math.abs(dest.y - pos.y); + xSign = (int) Math.signum(dest.x - pos.x); + ySign = (int) Math.signum(dest.y - pos.y); } - public Move(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); } + /** + * Creates an instance of {@link Move}. + * + * @param xPos the horizontal position of this move + * @param yPos the vertical position of this 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)); + } + /** + * Executed this move on a board. + * + * @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); } + /** + * 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 + * 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); } + /** + * @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); + } + + /** + * Constructs a move from a string representation in Long Algebraic Notation + * (LAN). + * + * @param move the LAN string to construct the move from + * @return the constructed 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) { + Position pos = Position.fromLAN(move.substring(0, 2)); + Position dest = Position.fromLAN(move.substring(2)); + 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; } - } else return new Move(pos, dest); + return new Move(pos, dest); } - public String toLAN() { return getPos().toLAN() + getDest().toLAN(); } + /** + * Generates a string representation of this move in Long Algebraic Notation + * (LAN). + * + * @return the LAN string + */ + public String toLAN() { + return getPos().toLAN() + getDest().toLAN(); + } /** * Converts a move string from standard algebraic notation to a {@link Move} @@ -69,75 +127,125 @@ public class Move { */ public static Move fromSAN(String sanMove, Board board) { Map patterns = new HashMap<>(); - patterns.put("pieceMove", - Pattern.compile( - "^(?[NBRQK])(?:(?[a-h])|(?[1-8])|(?[a-h][1-8]))?x?(?[a-h][1-8])(?:\\+{0,2}|\\#)$")); - patterns.put("pawnCapture", - Pattern.compile("^(?[a-h])(?[1-8])?x(?[a-h][1-8])(?[NBRQ])?(?:\\+{0,2}|\\#)?$")); - patterns.put("pawnPush", Pattern.compile("^(?[a-h][1-8])(?[NBRQ])?(?:\\+{0,2}|\\#)$")); - patterns.put("castling", Pattern.compile("^(?O-O-O)|(?O-O)(?:\\+{0,2}|\\#)?$")); + patterns.put( + "pieceMove", + Pattern.compile( + "^(?[NBRQK])(?:(?[a-h])|(?[1-8])|(?[a-h][1-8]))?x?(?[a-h][1-8])(?:\\+{0,2}|\\#)$" + ) + ); + patterns.put( + "pawnCapture", + Pattern.compile( + "^(?[a-h])(?[1-8])?x(?[a-h][1-8])(?[NBRQ])?(?:\\+{0,2}|\\#)?$" + ) + ); + patterns.put( + "pawnPush", + Pattern.compile( + "^(?[a-h][1-8])(?[NBRQ])?(?:\\+{0,2}|\\#)$" + ) + ); + patterns.put( + "castling", + Pattern.compile( + "^(?O-O-O)|(?O-O)(?:\\+{0,2}|\\#)?$" + ) + ); for (Map.Entry entry : patterns.entrySet()) { Matcher m = entry.getValue().matcher(sanMove); if (m.find()) { - Position pos = null, dest = null; - Move move = null; + Position pos = null, dest = null; + Move move = null; 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 pieceClass = Piece.fromFirstChar(m.group("pieceType").charAt(0)); - char file; - int rank; + Class 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)); - file = board.get(pieceClass, rank); - pos = Position.fromLAN(String.format("%c%d", file, rank)); - } else pos = board.get(pieceClass, dest); + 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) + ); + file = board.get(pieceClass, rank); + 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; } @@ -147,21 +255,31 @@ public class Move { return null; } + /** + * Generates a string representation of this move in Standard Algebraic + * Notation + * (SAN). + * + * @param board the {@link Board} providing the context of this move + * @return the SAN string + */ public String toSAN(Board board) { - final Piece piece = board.get(pos); - StringBuilder sb = new StringBuilder(8); + final Piece piece = board.get(pos); + StringBuilder sb = new StringBuilder(8); // Piece symbol - if(!(piece instanceof Pawn)) + 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()); @@ -169,37 +287,88 @@ public class Move { return sb.toString(); } + /** + * @return {@code true} if the move is purely horizontal + */ public boolean isHorizontal() { return getyDist() == 0; } + /** + * @return {@code true} if the move is purely vertical + */ public boolean isVertical() { return getxDist() == 0; } + /** + * @return {@code true} if the move is diagonal + */ 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(); } + /** + * @return the position + */ public Position getPos() { return pos; } + /** + * @return the destination + */ public Position getDest() { return dest; } - public int getxDist() { return xDist; } + /** + * @return the x distance + */ + public int getxDist() { + return xDist; + } - public int getyDist() { return yDist; } + /** + * @return the y distance + */ + public int getyDist() { + return yDist; + } - public int getxSign() { return xSign; } + /** + * @return the sign of the x distance + */ + public int getxSign() { + return xSign; + } - public int getySign() { return ySign; } + /** + * @return the sign of the y distance + */ + public int getySign() { + return ySign; + } } diff --git a/src/dev/kske/chess/board/MoveNode.java b/src/dev/kske/chess/board/MoveNode.java index e618716..51b8f32 100644 --- a/src/dev/kske/chess/board/MoveNode.java +++ b/src/dev/kske/chess/board/MoveNode.java @@ -1,9 +1,6 @@ package dev.kske.chess.board; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Objects; +import java.util.*; import dev.kske.chess.board.Piece.Color; @@ -17,38 +14,94 @@ import dev.kske.chess.board.Piece.Color; */ public class MoveNode { - public static final int WHITE_KINGSIDE = 0, WHITE_QUEENSIDE = 1, BLACK_KINGSIDE = 2, BLACK_QUEENSIDE = 3; + /** + * The index of the white kingside casting in a casting rights array. + */ + public static final int WHITE_KINGSIDE = 0; - public final Move move; - public final Piece capturedPiece; - public final boolean[] castlingRights; - public final Position enPassant; - public final Color activeColor; - public final int fullmoveCounter, halfmoveClock; + /** + * The index of the white queenside castling in a castling rights array. + */ + public static final int WHITE_QUEENSIDE = 1; - private MoveNode parent; - private List variations; + /** + * The index of the white kingside casting in a casting rights array. + */ + public static final int BLACK_KINGSIDE = 2; + + /** + * The index of the white queenside castling in a castling rights array. + */ + public static final int BLACK_QUEENSIDE = 3; + + /** + * The move on the board associated with this move node. + */ + public final Move move; + + /** + * The piece captured by the move. + */ + public final Piece capturedPiece; + + /** + * The castling rights present during the move. + */ + public final boolean[] castlingRights; + + /** + * The en passant target position or {@code null} if the move is not an en + * passant move. + */ + public final Position enPassant; + + /** + * The color active during the move. + */ + public final Color activeColor; + + /** + * The number of moves performed since the beginning of the game. + */ + public final int fullmoveCounter; + + /** + * The halfmoves performed since the last capture move or pawn move. + */ + public final int halfmoveClock; + + private MoveNode parent; + private List variations; /** * Creates a new {@link MoveNode}. * - * @param move The logged {@link Move} - * @param capturedPiece The {@link Piece} captures by the logged {@link Move} - * @param enPassant The en passant {@link Position} valid after the logged + * @param move 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 * {@link Move}, or {@code null} if there is none - * @param activeColor The {@link Color} active after the logged {@link Move} - * @param fullmoveCounter - * @param halfmoveClock + * @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 + * pawn move */ - public MoveNode(Move move, Piece capturedPiece, boolean castlingRights[], Position enPassant, Color activeColor, - int fullmoveCounter, int halfmoveClock) { - this.move = move; - this.capturedPiece = capturedPiece; - this.castlingRights = castlingRights; - this.enPassant = enPassant; - this.activeColor = activeColor; - this.fullmoveCounter = fullmoveCounter; - this.halfmoveClock = halfmoveClock; + public MoveNode( + Move move, Piece capturedPiece, boolean castlingRights[], + Position enPassant, Color activeColor, int fullmoveCounter, + int halfmoveClock + ) { + this.move = move; + this.capturedPiece = capturedPiece; + this.castlingRights = castlingRights; + this.enPassant = enPassant; + this.activeColor = activeColor; + this.fullmoveCounter = fullmoveCounter; + this.halfmoveClock = halfmoveClock; } /** @@ -60,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; @@ -78,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; @@ -90,37 +152,76 @@ public class MoveNode { */ public List getVariations() { return variations; } + /** + * @return {@code true} if this move node has any variations + */ public boolean hasVariations() { return variations != null && variations.size() > 0; } + /** + * @return the parent node of this move node + */ public MoveNode getParent() { return parent; } + /** + * Sets the parent node of this move node + * + * @param parent the parent node to set + */ public void setParent(MoveNode parent) { this.parent = parent; } + /** + * @return {@code true} if this move node has a parent + */ 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]", + move, + capturedPiece, + Arrays.toString(castlingRights), + enPassant, + activeColor, + fullmoveCounter, + halfmoveClock + ); + } + @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + Arrays.hashCode(castlingRights); - result = prime * result - + Objects.hash(activeColor, capturedPiece, enPassant, fullmoveCounter, halfmoveClock, move); + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(castlingRights); + result = prime * result + Objects.hash( + activeColor, + capturedPiece, + enPassant, + fullmoveCounter, + halfmoveClock, + move + ); return result; } @Override public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; MoveNode other = (MoveNode) obj; - return activeColor == other.activeColor && Objects.equals(capturedPiece, other.capturedPiece) - && Arrays.equals(castlingRights, other.castlingRights) && Objects.equals(enPassant, other.enPassant) - && fullmoveCounter == other.fullmoveCounter && halfmoveClock == other.halfmoveClock - && Objects.equals(move, other.move); + 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); } -} \ No newline at end of file +} diff --git a/src/dev/kske/chess/board/Pawn.java b/src/dev/kske/chess/board/Pawn.java index 7b44b0a..c5c83e2 100644 --- a/src/dev/kske/chess/board/Pawn.java +++ b/src/dev/kske/chess/board/Pawn.java @@ -13,62 +13,107 @@ import java.util.List; */ public class Pawn extends Piece { - public Pawn(Color color, Board board) { super(color, board); } + /** + * Creates pawn {@link 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); + } @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 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; - 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; - // Capture move - else return board.getDest(move) != null && board.getDest(move).getColor() != getColor(); + else + if (move.getxDist() == 0) + return board.getDest(move) == null; + // Capture move + else + return board.getDest(move) != null + && board.getDest(move).getColor() != getColor(); } @Override protected List getPseudolegalMoves(Position pos) { - List moves = new ArrayList<>(); - int sign = getColor() == Color.WHITE ? -1 : 1; - boolean pawnPromotion = sign == 1 && pos.y == 6 || sign == -1 && pos.y == 1; + List moves = new ArrayList<>(); + int sign = getColor() == Color.WHITE ? -1 : 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 moves, Position pos, Position dest, boolean pawnPromotion) { + private void addMoveIfValid( + List 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)); @@ -77,8 +122,8 @@ public class Pawn extends Piece { } catch (Exception e) { e.printStackTrace(); } - } else moves.add(move); - } + else + moves.add(move); } @Override diff --git a/src/dev/kske/chess/board/PawnPromotion.java b/src/dev/kske/chess/board/PawnPromotion.java index 2c78750..0bf1a4c 100644 --- a/src/dev/kske/chess/board/PawnPromotion.java +++ b/src/dev/kske/chess/board/PawnPromotion.java @@ -16,32 +16,78 @@ import dev.kske.chess.board.Piece.Color; */ public class PawnPromotion extends Move { - private final Constructor promotionPieceConstructor; - private final char promotionPieceChar; + private final Constructor promotionPieceConstructor; + private final char promotionPieceChar; - public PawnPromotion(Position pos, Position dest, Class promotionPieceClass) - throws ReflectiveOperationException, RuntimeException { + /** + * Initializes a pawn promotion move. + * + * @param pos the position of this 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 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)); } - public PawnPromotion(int xPos, int yPos, int xDest, int yDest, Class promotionPiece) - throws ReflectiveOperationException, RuntimeException { - this(new Position(xPos, yPos), new Position(xDest, yDest), promotionPiece); + /** + * Creates an instance of {@link PawnPromotion}. + * + * @param xPos the horizontal position of this move + * @param yPos the vertical position of this move + * @param xDest the horizontal destination of this move + * @param yDest the vertical 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( + int xPos, int yPos, int xDest, int yDest, + Class promotionPieceClass + ) + throws ReflectiveOperationException, RuntimeException { + 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(); } } @@ -53,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) { @@ -63,18 +111,23 @@ public class PawnPromotion extends Move { @Override public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + Objects.hash(promotionPieceChar, promotionPieceConstructor); + final int prime = 31; + int result = super.hashCode(); + 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); } } diff --git a/src/dev/kske/chess/board/Piece.java b/src/dev/kske/chess/board/Piece.java index 848dca5..4873ec7 100644 --- a/src/dev/kske/chess/board/Piece.java +++ b/src/dev/kske/chess/board/Piece.java @@ -5,59 +5,97 @@ import java.util.List; import java.util.Objects; /** + * Represents a piece on a board with a color.
+ *
* Project: Chess
* File: Piece.java
* Created: 01.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public abstract class Piece implements Cloneable { - private final Color color; - protected Board board; + private final Color color; + protected Board board; + /** + * Initializes a piece. + * + * @param color the color of this piece + * @param board the board on which this piece is placed + */ public Piece(Color color, Board board) { - this.color = color; - this.board = board; + this.color = color; + this.board = board; } + /** + * Generated a list of legal moves this piece can make. + * + * @param pos the position of this piece + * @return a list of legal moves this piece can make + */ public List getMoves(Position pos) { List moves = getPseudolegalMoves(pos); for (Iterator 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; } + /** + * Generates a list of pseudo legal moves this piece can make. + * + * @param pos the position of this piece + * @return a list of pseudo legal moves this piece can make + */ protected abstract List getPseudolegalMoves(Position pos); + /** + * Checks, if a given move is valid. + * + * @param move the move to check + * @return {@code true} if the move is valid + */ 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); } /** * Checks if the destination of a move is empty or a piece from the opposing * team - * + * * @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() { @@ -71,32 +109,49 @@ public abstract class Piece implements Cloneable { } @Override - public String toString() { return getClass().getSimpleName(); } + 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(toString().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} + * if no piece is associated with the given character + */ public static Class fromFirstChar(char firstChar) { switch (Character.toLowerCase(firstChar)) { case 'k': @@ -121,14 +176,48 @@ public abstract class Piece implements Cloneable { */ public Color getColor() { return color; } - public static enum Color { + /** + * Project: Chess
+ * File: Piece.java
+ * Created: 01.07.2019
+ * + * @author Kai S. K. Engelbart + * @since Chess v0.1-alpha + */ + public enum Color { - WHITE, BLACK; + /** + * Represents the color of the white pieces on a board. + */ + WHITE, - public static Color fromFirstChar(char c) { return Character.toLowerCase(c) == 'w' ? WHITE : BLACK; } + /** + * Represents the color of the black pieces on a board. + */ + BLACK; - public char firstChar() { return this == WHITE ? 'w' : 'b'; } + /** + * @param c the first character of a color's name + * @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 Color opposite() { return this == WHITE ? BLACK : WHITE; } + /** + * @return the first character (lower case) of this color + */ + public char firstChar() { + return this == WHITE ? 'w' : 'b'; + } + + /** + * @return the opposite of this color + */ + public Color opposite() { + return this == WHITE ? BLACK : WHITE; + } } } diff --git a/src/dev/kske/chess/board/Position.java b/src/dev/kske/chess/board/Position.java index 15a185a..ecd74cf 100644 --- a/src/dev/kske/chess/board/Position.java +++ b/src/dev/kske/chess/board/Position.java @@ -4,23 +4,51 @@ package dev.kske.chess.board; * Project: Chess
* File: Position.java
* Created: 02.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public class Position { - public final int x, y; + /** + * The horizontal component of this position. + */ + public final int x; + /** + * The vertical component of this position. + */ + public final int y; + + /** + * Initializes a position. + * + * @param x the horizontal component of this position + * @param y the vertical component of this position + */ public Position(int x, int y) { - this.x = x; - this.y = y; + this.x = x; + this.y = y; } + /** + * Constructs a position from Long Algebraic Notation (LAN) + * + * @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))); + 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); } @@ -32,21 +60,26 @@ public class Position { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + x; - result = prime * result + y; + final int prime = 31; + int result = 1; + result = prime * result + x; + result = prime * result + y; 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; 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; } } diff --git a/src/dev/kske/chess/board/Queen.java b/src/dev/kske/chess/board/Queen.java index b2482ce..42b85c4 100644 --- a/src/dev/kske/chess/board/Queen.java +++ b/src/dev/kske/chess/board/Queen.java @@ -7,19 +7,26 @@ import java.util.List; * Project: Chess
* File: Queen.java
* Created: 01.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public class Queen extends Piece { + /** + * Creates queen {@link Piece}. + * + * @param color the color of this queen + * @param board the board on which this queen will be placed + */ public Queen(Color color, Board board) { super(color, board); } @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 @@ -29,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; } diff --git a/src/dev/kske/chess/board/Rook.java b/src/dev/kske/chess/board/Rook.java index b2efd5c..c14512e 100644 --- a/src/dev/kske/chess/board/Rook.java +++ b/src/dev/kske/chess/board/Rook.java @@ -7,12 +7,18 @@ import java.util.List; * Project: Chess
* File: Rook.java
* Created: 01.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public class Rook extends Piece { + /** + * Creates rook {@link Piece}. + * + * @param color the color of this rook + * @param board the board on which this rook will be placed + */ public Rook(Color color, Board board) { super(color, board); } @@ -29,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; } diff --git a/src/dev/kske/chess/event/Event.java b/src/dev/kske/chess/event/Event.java index 5e31830..2cc982f 100644 --- a/src/dev/kske/chess/event/Event.java +++ b/src/dev/kske/chess/event/Event.java @@ -4,9 +4,10 @@ package dev.kske.chess.event; * Project: Chess
* File: Event.java
* Created: 7 Aug 2019
- * + * * @since Chess v0.4-alpha * @author Kai S. K. Engelbart + * @param the type of the event's value */ public interface Event { diff --git a/src/dev/kske/chess/event/EventBus.java b/src/dev/kske/chess/event/EventBus.java index b8cb2df..8ae27ae 100644 --- a/src/dev/kske/chess/event/EventBus.java +++ b/src/dev/kske/chess/event/EventBus.java @@ -4,21 +4,27 @@ import java.util.ArrayList; import java.util.List; /** + * Dispatches {@link Event}s to various {@link Subscriber}s.
+ *
* Project: Chess
* File: EventBus.java
* Created: 7 Aug 2019
- * + * * @since Chess v0.4-alpha * @author Kai S. K. Engelbart */ public class EventBus { - private List subscribers; + private List subscribers; private static EventBus instance; + /** + * @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; } @@ -26,13 +32,28 @@ public class EventBus { subscribers = new ArrayList<>(); } - public void register(Subscribable subscribable) { + /** + * Registers a subscriber to which future events will be dispatched. + * + * @param subscribable the subscriber to register + */ + public void register(Subscriber subscribable) { subscribers.add(subscribable); } + /** + * Dispatches an event to all {@Subscriber}s registered at this event bus. + * + * @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)); } - public List getSubscribers() { return subscribers; } + /** + * @return a list of all registered subscribers + */ + public List getSubscribers() { return subscribers; } } diff --git a/src/dev/kske/chess/event/GameStartEvent.java b/src/dev/kske/chess/event/GameStartEvent.java index 4097550..f2dbd47 100644 --- a/src/dev/kske/chess/event/GameStartEvent.java +++ b/src/dev/kske/chess/event/GameStartEvent.java @@ -6,7 +6,7 @@ import dev.kske.chess.game.Game; * Project: Chess
* File: GameStartEvent.java
* Created: 30 Oct 2019
- * + * * @since Chess v0.5-alpha * @author Kai S. K. Engelbart */ @@ -14,7 +14,14 @@ public class GameStartEvent implements Event { private final Game game; - public GameStartEvent(Game source) { game = source; } + /** + * Creates an instance of {@link GameStartEvent}. + * + * @param source the game started + */ + public GameStartEvent(Game source) { + game = source; + } @Override public Game getData() { return game; } diff --git a/src/dev/kske/chess/event/MoveEvent.java b/src/dev/kske/chess/event/MoveEvent.java index a60a785..87a91b5 100644 --- a/src/dev/kske/chess/event/MoveEvent.java +++ b/src/dev/kske/chess/event/MoveEvent.java @@ -7,22 +7,31 @@ import dev.kske.chess.board.Move; * Project: Chess
* File: MoveEvent.java
* Created: 7 Aug 2019
- * + * * @since Chess v0.4-alpha * @author Kai S. K. Engelbart */ public class MoveEvent implements Event { private final Move move; - private final BoardState boardState; + private final BoardState boardState; + /** + * Creates an instance of {@link MoveEvent}. + * + * @param move the move by which the event was triggered + * @param boardState the state of the board after the move + */ public MoveEvent(Move move, BoardState boardState) { this.move = move; - this.boardState = boardState; + this.boardState = boardState; } @Override public Move getData() { return move; } + /** + * @return the state of the board after the move + */ public BoardState getBoardState() { return boardState; } } diff --git a/src/dev/kske/chess/event/Subscribable.java b/src/dev/kske/chess/event/Subscriber.java similarity index 65% rename from src/dev/kske/chess/event/Subscribable.java rename to src/dev/kske/chess/event/Subscriber.java index 4de8380..b967996 100644 --- a/src/dev/kske/chess/event/Subscribable.java +++ b/src/dev/kske/chess/event/Subscriber.java @@ -3,19 +3,24 @@ package dev.kske.chess.event; import java.util.Set; /** + * Implementations of this interface can register themselves at the + * {@link EventBus} and will be triggered every time an {@link Event} of a + * supported type.
+ *
* Project: Chess
* File: Subscribable.java
* Created: 7 Aug 2019
- * + * * @since Chess v0.4-alpha * @author Kai S. K. Engelbart */ -public interface Subscribable { +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); diff --git a/src/dev/kske/chess/exception/ChessException.java b/src/dev/kske/chess/exception/ChessException.java index 3bedea7..e2c8bfc 100644 --- a/src/dev/kske/chess/exception/ChessException.java +++ b/src/dev/kske/chess/exception/ChessException.java @@ -4,7 +4,7 @@ package dev.kske.chess.exception; * Project: Chess
* File: ChessException.java
* Created: 22 Sep 2019
- * + * * @since Chess v0.5-alpha * @author Kai S. K. Engelbart */ @@ -12,14 +12,30 @@ public class ChessException extends Exception { private static final long serialVersionUID = -2208596063548245189L; + /** + * Initializes chess exception. + * + * @param message the message associated with this exception + * @param cause the cause of this exception + */ public ChessException(String message, Throwable cause) { super(message, cause); } + /** + * Initializes chess exception. + * + * @param message the message associated with this exception + */ public ChessException(String message) { super(message); } + /** + * Initializes chess exception. + * + * @param cause the cause of this exception + */ public ChessException(Throwable cause) { super(cause); } diff --git a/src/dev/kske/chess/game/Game.java b/src/dev/kske/chess/game/Game.java index 2005cd0..d4bb36c 100644 --- a/src/dev/kske/chess/game/Game.java +++ b/src/dev/kske/chess/game/Game.java @@ -1,6 +1,6 @@ package dev.kske.chess.game; -import java.util.HashMap; +import java.util.EnumMap; import java.util.Map; import javax.swing.JOptionPane; @@ -23,23 +23,42 @@ import dev.kske.chess.ui.OverlayComponent; * Project: Chess
* File: Game.java
* Created: 06.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public class Game { - private Map players = new HashMap<>(); - private Board board; - private OverlayComponent overlayComponent; - private BoardComponent boardComponent; + private Map players = new EnumMap<>(Color.class); + private Board board; + private OverlayComponent overlayComponent; + private BoardComponent boardComponent; + /** + * Initializes game with a new {@link 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 + */ public Game(BoardPane boardPane, String whiteName, String blackName) { board = new Board(); init(boardPane, whiteName, blackName); } - public Game(BoardPane boardPane, String whiteName, String blackName, Board board) { + /** + * Initializes game with an existing {@link 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 + ) { this.board = board; init(boardPane, whiteName, blackName); } @@ -47,34 +66,57 @@ public class Game { private void init(BoardPane boardPane, String whiteName, String blackName) { // Initialize / synchronize UI - overlayComponent = boardPane.getOverlayComponent(); - boardComponent = boardPane.getBoardComponent(); + overlayComponent = boardPane.getOverlayComponent(); + boardComponent = boardPane.getBoardComponent(); boardComponent.setBoard(board); // Initialize players players.put(Color.WHITE, getPlayer(whiteName, Color.WHITE)); players.put(Color.BLACK, getPlayer(blackName, Color.BLACK)); - - // Initialize the game variable in each player - players.values().forEach(player -> player.setGame(this)); } + /** + * Initializes player subclass. + * + * @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 + * with that name + * @param color the color of the player + * @return the instantiated player or {@code null} if the name could not be + * recognized + */ private Player getPlayer(String name, Color color) { switch (name) { case "Natural Player": - return new NaturalPlayer(color, overlayComponent); + return new NaturalPlayer(this, color, overlayComponent); case "AI Player": - return new AIPlayer(color, 4, -10); + return new AIPlayer(this, color, 4, -10); default: for (EngineInfo info : EngineUtil.getEngineInfos()) - if (info.name.equals(name)) return new UCIPlayer(color, info.path); + if (info.name.equals(name)) + return new UCIPlayer(this, color, info.path); System.err.println("Invalid player name: " + name); return null; } } - public void onMove(Player player, Move move) { - if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) { + /** + * 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 + * 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) + ) { // Redraw boardComponent.repaint(); @@ -83,29 +125,46 @@ public class Game { // Run garbage collection System.gc(); - BoardState boardState = board.getGameEventType(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(); } - public void start() { + /** + * Starts the game by requesting a move from the player of the currently + * active + * color. + */ + public synchronized void start() { EventBus.getInstance().dispatch(new GameStartEvent(this)); players.get(board.getLog().getActiveColor()).requestMove(); } - public void reset() { + /** + * Cancels move calculations, initializes the default position and clears + * the + * {@link OverlayComponent}. + */ + public synchronized void reset() { players.values().forEach(Player::cancelMove); board.initDefaultPositions(); boardComponent.repaint(); @@ -114,19 +173,19 @@ public class Game { } /** - * Stops the game by disconnecting its players form the UI. + * Stops the game by disconnecting its players from the UI. */ - public void stop() { + public synchronized void stop() { players.values().forEach(Player::disconnect); } /** * Assigns the players their opposite colors. */ - public void swapColors() { + public synchronized void swapColors() { players.values().forEach(Player::cancelMove); - Player white = players.get(Color.WHITE); - Player black = players.get(Color.BLACK); + Player white = players.get(Color.WHITE); + Player black = players.get(Color.BLACK); white.setColor(Color.BLACK); black.setColor(Color.WHITE); players.put(Color.WHITE, black); diff --git a/src/dev/kske/chess/game/NaturalPlayer.java b/src/dev/kske/chess/game/NaturalPlayer.java index 864c4e5..b9def9d 100644 --- a/src/dev/kske/chess/game/NaturalPlayer.java +++ b/src/dev/kske/chess/game/NaturalPlayer.java @@ -15,10 +15,14 @@ import dev.kske.chess.board.Position; import dev.kske.chess.ui.OverlayComponent; /** + * Enables the user to make moves in a {@link Game} by clicking on a + * {@link Piece} and then selecting one of the highlighted positions as the move + * destination.
+ *
* Project: Chess
* File: NaturalPlayer.java
* Created: 06.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ @@ -26,55 +30,83 @@ public class NaturalPlayer extends Player implements MouseListener { private final OverlayComponent overlayComponent; - private boolean moveRequested; - private Piece selectedPiece; - private List possibleMoves; + private boolean moveRequested; + private Piece selectedPiece; + private List possibleMoves; - public NaturalPlayer(Color color, OverlayComponent overlayComponent) { - super(color); - this.overlayComponent = overlayComponent; - name = "Player"; - moveRequested = false; + /** + * Creates an instance of {@link NaturalPlayer}. + * + * @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 + * possible moves to the user + */ + public NaturalPlayer( + Game game, Color color, OverlayComponent overlayComponent + ) { + super(game, color); + this.overlayComponent = overlayComponent; + name = "Player"; + moveRequested = false; overlayComponent.addMouseListener(this); } @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 selectedMoves = possibleMoves.stream().filter(m -> m.getDest().equals(dest)).collect(Collectors.toList()); + List selectedMoves = possibleMoves.stream() + .filter(m -> m.getDest().equals(dest)) + .collect(Collectors.toList()); if (!selectedMoves.isEmpty()) { Move move; @@ -82,21 +114,26 @@ public class NaturalPlayer extends Player implements MouseListener { if (selectedMoves.size() > 1) { // Let the user select a promotion piece - JComboBox comboBox = new JComboBox(selectedMoves.toArray(new Move[0])); - JOptionPane.showMessageDialog(overlayComponent, comboBox, "Select a promotion", JOptionPane.QUESTION_MESSAGE); + JComboBox 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; - possibleMoves = null; + selectedPiece = null; + possibleMoves = null; } } diff --git a/src/dev/kske/chess/game/Player.java b/src/dev/kske/chess/game/Player.java index 0b88f9d..c2035ae 100644 --- a/src/dev/kske/chess/game/Player.java +++ b/src/dev/kske/chess/game/Player.java @@ -1,49 +1,87 @@ package dev.kske.chess.game; import dev.kske.chess.board.Board; +import dev.kske.chess.board.Move; import dev.kske.chess.board.Piece.Color; /** + * Acts as the interface between the {@link Game} class and some kind of move + * generation backend implemented as a subclass.
+ *
* Project: Chess
* File: Player.java
* Created: 06.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public abstract class Player { - protected Game game; - protected Board board; - protected Color color; - protected String name; + protected final Game game; + protected final Board board; - public Player(Color color) { + protected String name; + protected Color color; + + /** + * Initializes the color of this player. + * + * @param game the game in which this player will be used + * @param color the piece color that this player will control + */ + public Player(Game game, Color color) { + this.game = game; + board = game.getBoard(); this.color = color; } + /** + * Initiates a move generation and reports the result to the game by calling + * {@link Game#onMove(Player, Move)}. + */ public abstract void requestMove(); + /** + * Cancels the move generation process. + */ public abstract void cancelMove(); + /** + * Closes all resources required for move generation. + */ public abstract void disconnect(); + /** + * @return the game in which this player is used + */ public Game getGame() { return game; } - public void setGame(Game game) { - this.game = game; - board = game.getBoard(); - } - + /** + * @return the board on which this player is used + */ public Board getBoard() { return board; } - public void setBoard(Board board) { this.board = board; } - + /** + * @return the color of pieces controlled by this player + */ public Color getColor() { return color; } + /** + * Sets the color of pieces controlled by this player. + * + * @param color the color to set + */ public void setColor(Color color) { this.color = color; } + /** + * @return the name of this player + */ public String getName() { return name; } + /** + * Sets the name of this player + * + * @param name the name to set + */ public void setName(String name) { this.name = name; } } diff --git a/src/dev/kske/chess/game/UCIPlayer.java b/src/dev/kske/chess/game/UCIPlayer.java index 2aa320d..4331e3e 100644 --- a/src/dev/kske/chess/game/UCIPlayer.java +++ b/src/dev/kske/chess/game/UCIPlayer.java @@ -9,22 +9,33 @@ import dev.kske.chess.uci.UCIHandle; import dev.kske.chess.uci.UCIListener; /** + * Acts as the interface between the {@link Game} class and the + * {@link dev.kske.chess.uci} package enabling an engine to make moves in a + * game.
+ *
* Project: Chess
* File: UCIPlayer.java
* Created: 18.07.2019
- * + * * @since Chess v0.3-alpha * @author Kai S. K. Engelbart */ public class UCIPlayer extends Player implements UCIListener { - private UCIHandle handle; + private UCIHandle handle; - public UCIPlayer(Color color, String enginePath) { - super(color); + /** + * Creates an instance of {@link UCIPlayer}. + * + * @param game the game in which this player will be used + * @param color the piece color that this player will control + * @param enginePath the path to the engine executable + */ + public UCIPlayer(Game game, Color color, String enginePath) { + super(game, color); try { handle = new UCIHandle(enginePath); - handle.setListener(this); + handle.registerListener(this); handle.start(); } catch (IOException ex) { ex.printStackTrace(); diff --git a/src/dev/kske/chess/game/ai/AIPlayer.java b/src/dev/kske/chess/game/ai/AIPlayer.java index 9e53326..e737e00 100644 --- a/src/dev/kske/chess/game/ai/AIPlayer.java +++ b/src/dev/kske/chess/game/ai/AIPlayer.java @@ -2,90 +2,99 @@ package dev.kske.chess.game.ai; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; +import java.util.concurrent.*; import javax.swing.SwingUtilities; import dev.kske.chess.board.Board; import dev.kske.chess.board.Move; import dev.kske.chess.board.Piece.Color; +import dev.kske.chess.game.Game; import dev.kske.chess.game.Player; /** * Project: Chess
* File: AIPlayer.java
* Created: 06.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public class AIPlayer extends Player { - private int availableProcessors; - private int maxDepth; - private int alphaBetaThreshold; + private int availableProcessors; + private int maxDepth; + private int alphaBetaThreshold; - private volatile boolean exitRequested; - private volatile ExecutorService executor; + private volatile boolean exitRequested; + private volatile ExecutorService executor; - public AIPlayer(Color color, int maxDepth, int alphaBetaThreshold) { - super(color); - name = "AIPlayer"; - availableProcessors = Runtime.getRuntime().availableProcessors(); - this.maxDepth = maxDepth; - this.alphaBetaThreshold = alphaBetaThreshold; - exitRequested = false; + /** + * Creates an instance of {@link AIPlayer}. + * + * @param game the game in which this player will be used + * @param color the piece color this player will control + * @param maxDepth the maximum search depth + * @param alphaBetaThreshold the board evaluation threshold that has to be + * reached to continue searching the children of a + * move + */ + public AIPlayer( + Game game, Color color, int maxDepth, int alphaBetaThreshold + ) { + super(game, color); + name = "AIPlayer"; + availableProcessors = Runtime.getRuntime().availableProcessors(); + this.maxDepth = maxDepth; + this.alphaBetaThreshold = alphaBetaThreshold; } @Override public void requestMove() { exitRequested = false; - /* - * Define some processing threads, split the available moves between them and - * retrieve the result after their execution. - */ - new Thread(() -> { - /* - * Get a copy of the board and the available moves. - */ - Board board = new Board(this.board, false); - List moves = board.getMoves(color); - /* - * Define move processors and split the available moves between them. - */ - int numThreads = Math.min(moves.size(), availableProcessors); - List processors = new ArrayList<>(numThreads); - final int step = moves.size() / numThreads; - int rem = moves.size() % numThreads; - int beginIndex = 0, endIndex = 0; + // Define some processing threads, split the available moves between + // them and + // retrieve the result after their execution. + new Thread(() -> { + + // Get a copy of the board and the available moves. + Board board = new Board(this.board, false); + List moves = board.getMoves(color); + + // Define move processors and split the available moves between + // them. + int numThreads = Math.min(moves.size(), availableProcessors); + List 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 results = new ArrayList<>(numThreads); try { - List> futures = executor.invokeAll(processors); + List> futures + = executor.invokeAll(processors); for (Future f : futures) results.add(f.get()); - executor.shutdown(); } catch (InterruptedException | ExecutionException ex) { ex.printStackTrace(); + } finally { + 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(); } diff --git a/src/dev/kske/chess/game/ai/MoveProcessor.java b/src/dev/kske/chess/game/ai/MoveProcessor.java index 112bc30..ef933d6 100644 --- a/src/dev/kske/chess/game/ai/MoveProcessor.java +++ b/src/dev/kske/chess/game/ai/MoveProcessor.java @@ -5,32 +5,26 @@ import java.util.List; import java.util.Map; import java.util.concurrent.Callable; -import dev.kske.chess.board.Bishop; -import dev.kske.chess.board.Board; -import dev.kske.chess.board.King; -import dev.kske.chess.board.Knight; -import dev.kske.chess.board.Move; -import dev.kske.chess.board.Pawn; -import dev.kske.chess.board.Piece; +import dev.kske.chess.board.*; import dev.kske.chess.board.Piece.Color; -import dev.kske.chess.board.Queen; -import dev.kske.chess.board.Rook; /** + * Implements a basic minimax move search algorithm for testing purposes.
+ *
* Project: Chess
* File: MoveProcessor.java
* Created: 08.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public class MoveProcessor implements Callable { - private final Board board; - private final List rootMoves; - private final Color color; - private final int maxDepth; - private final int alphaBetaThreshold; + private final Board board; + private final List rootMoves; + private final Color color; + private final int maxDepth; + private final int alphaBetaThreshold; private Move bestMove; @@ -38,42 +32,194 @@ public class MoveProcessor implements Callable { 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 + } + } + ); } - public MoveProcessor(Board board, List rootMoves, Color color, int maxDepth, int alphaBetaThreshold) { - this.board = board; - this.rootMoves = rootMoves; - this.color = color; - this.maxDepth = maxDepth; - this.alphaBetaThreshold = alphaBetaThreshold; + /** + * Creates an instance of {@link MoveProcessor}. + * + * @param board the board to search + * @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 + * specific move + */ + public MoveProcessor( + Board board, List rootMoves, Color color, int maxDepth, + int alphaBetaThreshold + ) { + this.board = board; + this.rootMoves = rootMoves; + this.color = color; + this.maxDepth = maxDepth; + this.alphaBetaThreshold = alphaBetaThreshold; } @Override @@ -86,18 +232,23 @@ public class MoveProcessor implements Callable { int bestValue = Integer.MIN_VALUE; for (Move move : moves) { board.move(move); - int teamValue = evaluate(board, color); - int enemyValue = evaluate(board, color.opposite()); - int valueChange = teamValue - enemyValue; + int teamValue = evaluate(board, color); + int enemyValue = evaluate(board, color.opposite()); + 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; @@ -105,18 +256,27 @@ public class MoveProcessor implements Callable { /** * Evaluated a board. - * + * + * @param board the board to evaluate * @param color The color to evaluate for - * @return An positive number representing how good the position is + * @return a positive number representing how good the position is */ private int evaluate(Board board, Color color) { 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; } diff --git a/src/dev/kske/chess/game/ai/ProcessingResult.java b/src/dev/kske/chess/game/ai/ProcessingResult.java index 951ac71..0a13612 100644 --- a/src/dev/kske/chess/game/ai/ProcessingResult.java +++ b/src/dev/kske/chess/game/ai/ProcessingResult.java @@ -3,25 +3,41 @@ package dev.kske.chess.game.ai; import dev.kske.chess.board.Move; /** + * Contains information about a move search performed by a chess engine. + *
* Project: Chess
* File: ProcessingResult.java
* Created: 08.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public class ProcessingResult { - public final Move move; - public final int score; + /** + * The best move found by the search + */ + public final Move move; + /** + * The score associated with the best move + */ + public final int score; + + /** + * Creates an instance of {@link ProcessingResult}. + * + * @param move the best move found by the search + * @param score the score associated with the best move + */ public ProcessingResult(Move move, int score) { - this.move = move; - this.score = score; + this.move = move; + this.score = score; } @Override public String toString() { - return String.format("ProcessingResult[Move = %s, Score = %d]", move, score); + return String + .format("ProcessingResult[Move = %s,Score = %d]", move, score); } } diff --git a/src/dev/kske/chess/io/EngineUtil.java b/src/dev/kske/chess/io/EngineUtil.java index 4c82842..90216ab 100644 --- a/src/dev/kske/chess/io/EngineUtil.java +++ b/src/dev/kske/chess/io/EngineUtil.java @@ -1,11 +1,6 @@ package dev.kske.chess.io; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.Serializable; +import java.io.*; import java.util.ArrayList; import java.util.List; @@ -14,9 +9,9 @@ import dev.kske.chess.uci.UCIListener; /** * Project: Chess
- * File: MenuBar.java
+ * File: EngineUtil.java
* Created: 23.07.2019
- * + * * @since Chess v0.2-alpha * @author Kai S. K. Engelbart * @author Leon Hofmeister @@ -33,11 +28,16 @@ public class EngineUtil { private EngineUtil() {} + /** + * Stores information about an engine while checking its availability. + * + * @param enginePath the path to the executable of the engine + */ public static void addEngine(String enginePath) { try { - EngineInfo info = new EngineInfo(enginePath); - UCIHandle handle = new UCIHandle(enginePath); - handle.setListener(new UCIListener() { + EngineInfo info = new EngineInfo(enginePath); + UCIHandle handle = new UCIHandle(enginePath); + handle.registerListener(new UCIListener() { @Override public void onIdName(String name) { @@ -64,29 +64,66 @@ 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) obj; - else throw new IOException("Serialized object has the wrong class."); + if (obj instanceof ArrayList) + engineInfos = (ArrayList) 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(); } } + /** + * Stores the name and author of an engine, as well as a path to its + * executable.
+ *
+ * Project: Chess
+ * File: EngineUtil.java
+ * Created: 23.07.2019
+ * + * @since Chess v0.2-alpha + * @author Kai S. K. Engelbart + */ public static class EngineInfo implements Serializable { private static final long serialVersionUID = -474177108900833005L; - public String path, name, author; + /** + * The path to the executable of the engine + */ + public String path; + /** + * The name of the engine + */ + public String name; + + /** + * The author of the engine + */ + public String author; + + /** + * Creates an instance of {@link EngineInfo}. + * + * @param path the path of the engine executable + */ public EngineInfo(String path) { this.path = path; } @@ -97,5 +134,8 @@ public class EngineUtil { } } + /** + * @return a list of all stored engine infos + */ public static List getEngineInfos() { return engineInfos; } } diff --git a/src/dev/kske/chess/io/TextureUtil.java b/src/dev/kske/chess/io/TextureUtil.java index 79d251b..7bffddc 100644 --- a/src/dev/kske/chess/io/TextureUtil.java +++ b/src/dev/kske/chess/io/TextureUtil.java @@ -15,13 +15,14 @@ import dev.kske.chess.board.Piece; * Project: Chess
* File: TextureUtil.java
* Created: 01.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ public class TextureUtil { - private static Map textures = new HashMap<>(), scaledTextures = new HashMap<>(); + private static Map textures = new HashMap<>(), + scaledTextures = new HashMap<>(); static { loadPieceTextures(); @@ -32,28 +33,31 @@ public class TextureUtil { /** * Loads a piece texture fitting to a piece object. - * + * * @param piece The piece from which the texture properties are taken * @return The fitting texture */ public static Image getPieceTexture(Piece piece) { - String key = piece.toString().toLowerCase() + "_" + piece.getColor().toString().toLowerCase(); + String key = piece.getClass().getSimpleName().toLowerCase() + "_" + + piece.getColor().toString().toLowerCase(); return scaledTextures.get(key); } /** * Scales all piece textures to fit the current tile size. - * + * * @param tileSize the new width and height of the piece textures */ 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)) + ); } /** * Loads an image from a file in the resource folder. - * + * * @param fileName The name of the image resource * @return The loaded image */ @@ -73,18 +77,22 @@ public class TextureUtil { */ private static void loadPieceTextures() { Arrays - .asList("king_white", - "king_black", - "queen_white", - "queen_black", - "rook_white", - "rook_black", - "knight_white", - "knight_black", - "bishop_white", - "bishop_black", - "pawn_white", - "pawn_black") - .forEach(name -> textures.put(name, loadImage("/pieces/" + name + ".png"))); + .asList( + "king_white", + "king_black", + "queen_white", + "queen_black", + "rook_white", + "rook_black", + "knight_white", + "knight_black", + "bishop_white", + "bishop_black", + "pawn_white", + "pawn_black" + ) + .forEach( + name -> textures.put(name, loadImage("/pieces/" + name + ".png")) + ); } } diff --git a/src/dev/kske/chess/pgn/PGNDatabase.java b/src/dev/kske/chess/pgn/PGNDatabase.java index 127f9a6..c4a6607 100644 --- a/src/dev/kske/chess/pgn/PGNDatabase.java +++ b/src/dev/kske/chess/pgn/PGNDatabase.java @@ -1,9 +1,6 @@ package dev.kske.chess.pgn; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.PrintWriter; +import java.io.*; import java.util.ArrayList; import java.util.List; import java.util.Scanner; @@ -17,7 +14,7 @@ import dev.kske.chess.exception.ChessException; *
* Contains a series of {@link PGNGame} objects that can be stored inside a PGN * file. - * + * * @since Chess v0.5-alpha * @author Kai S. K. Engelbart */ @@ -27,30 +24,34 @@ public class PGNDatabase { /** * Loads PGN games from a file. - * + * * @param pgnFile the file to load the games from * @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 { - Scanner sc = new Scanner(pgnFile); - while (sc.hasNext()) - games.add(PGNGame.parse(sc)); - sc.close(); + public void load(File pgnFile) + throws FileNotFoundException, ChessException { + try (Scanner sc = new Scanner(pgnFile)) { + while (sc.hasNext()) + games.add(PGNGame.parse(sc)); + } } /** * Saves PGN games to a file. - * + * * @param pgnFile the file to save the games to. * @throws IOException if the file could not be created */ public void save(File pgnFile) throws IOException { pgnFile.getParentFile().mkdirs(); - PrintWriter pw = new PrintWriter(pgnFile); - games.forEach(g -> g.writePGN(pw)); - pw.close(); + try (PrintWriter pw = new PrintWriter(pgnFile)) { + games.forEach(g -> g.writePGN(pw)); + } } + /** + * @return all games contained inside this database + */ public List getGames() { return games; } } diff --git a/src/dev/kske/chess/pgn/PGNGame.java b/src/dev/kske/chess/pgn/PGNGame.java index 609a8f9..456f4d1 100644 --- a/src/dev/kske/chess/pgn/PGNGame.java +++ b/src/dev/kske/chess/pgn/PGNGame.java @@ -1,12 +1,7 @@ package dev.kske.chess.pgn; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Scanner; +import java.util.*; import java.util.regex.MatchResult; import java.util.regex.Pattern; @@ -14,7 +9,6 @@ import dev.kske.chess.board.Board; import dev.kske.chess.board.FENString; import dev.kske.chess.board.Move; import dev.kske.chess.board.Piece.Color; -import dev.kske.chess.exception.ChessException; /** * Project: Chess
@@ -26,29 +20,53 @@ import dev.kske.chess.exception.ChessException; */ public class PGNGame { - private final Map tagPairs = new HashMap<>(7); - private final Board board; + private final Map tagPairs = new HashMap<>(7); + private final Board board; - public PGNGame() { board = new Board(); } + /** + * Creates an instance of {@link PGNGame}. A new default {@link Board} will + * be + * created. + */ + public PGNGame() { + board = new Board(); + } - public PGNGame(Board board) { this.board = board; } + /** + * Creates an instance of {@link PGNGame}. + * + * @param board the board associated with the game + */ + public PGNGame(Board board) { + this.board = board; + } - public static PGNGame parse(Scanner sc) throws ChessException { + /** + * Parses a game in {@code PGN} format from a {@link Scanner} instance + * + * @param sc the {@link Scanner} to parse the game from, which is not closed + * after this process + * @return the parsed {@link PGNGame} + */ + public static PGNGame parse(Scanner sc) { PGNGame game = new PGNGame(); - MatchResult matchResult; - Pattern tagPairPattern = Pattern.compile("\\[(\\w+) \"(.*)\"]"), - movePattern = Pattern.compile("\\d+\\.\\s+(?:(?:(\\S+)\\s+(\\S+))|(?:O-O-O)|(?:O-O))(?:\\+{0,2}|\\#)"), - nagPattern = Pattern.compile("(\\$\\d{1,3})*"), terminationMarkerPattern = Pattern.compile("1-0|0-1|1\\/2-1\\/2|\\*"); + MatchResult matchResult; + Pattern tagPairPattern = Pattern.compile("\\[(\\w+) \"(.*)\"]"), + movePattern = Pattern.compile( + "\\d+\\.\\s+(?:(?:(\\S+)\\s+(\\S+))|(?:O-O-O)|(?:O-O))(?:\\+{0,2}|\\#)" + ), + nagPattern = Pattern.compile("(\\$\\d{1,3})*"), terminationMarkerPattern = Pattern.compile("1-0|0-1|1\\/2-1\\/2|\\*"); // 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) @@ -58,20 +76,31 @@ 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++) { - game.board.move(matchResult.group(i)); - System.out.println(game.getBoard().getLog().getLast().move.toLAN() + ": " + new FENString(game.board).toString()); - } - else break; - } else break; + 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() + ); + } + 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; } + /** + * Serializes this game to {@code PGN} format. + * + * @param pw the writer to write the game to + */ public void writePGN(PrintWriter pw) { // Set the unknown result tag if no result tag is specified tagPairs.putIfAbsent("Result", "*"); @@ -80,18 +109,21 @@ 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 - Board clone = new Board(board, true); - List chunks = new ArrayList<>(); - boolean flag = true; + Board clone = new Board(board, true); + List chunks = new ArrayList<>(); + boolean flag = true; while (flag) { 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); } @@ -100,22 +132,47 @@ 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")); } - public String getTag(String tagName) { return tagPairs.get(tagName); } + /** + * @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 boolean hasTag(String tagName) { return tagPairs.containsKey(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 void setTag(String tagName, String tagValue) { tagPairs.put(tagName, tagValue); } + /** + * Sets a game tag. + * + * @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); + } + /** + * @return the board associated with this game + */ public Board getBoard() { return board; } } diff --git a/src/dev/kske/chess/uci/UCIHandle.java b/src/dev/kske/chess/uci/UCIHandle.java index 777d769..da8d9d4 100644 --- a/src/dev/kske/chess/uci/UCIHandle.java +++ b/src/dev/kske/chess/uci/UCIHandle.java @@ -11,22 +11,33 @@ import dev.kske.chess.board.Move; * Project: Chess
* File: UCIHandle.java
* Created: 18.07.2019
- * + * * @since Chess v0.3-alpha * @author Kai S. K. Engelbart */ public class UCIHandle { - private final Process process; - private final PrintWriter out; - private final UCIReceiver receiver; + private final Process process; + private final PrintWriter out; + private final UCIReceiver receiver; + /** + * 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 + * @throws IOException if the engine process could not be started + */ public UCIHandle(String enginePath) throws IOException { - process = new ProcessBuilder(enginePath).start(); - out = new PrintWriter(process.getOutputStream(), true); - receiver = new UCIReceiver(process.getInputStream()); + process = new ProcessBuilder(enginePath).start(); + out = new PrintWriter(process.getOutputStream(), true); + receiver = new UCIReceiver(process.getInputStream()); } + /** + * Starts the {@link UCIReceiver} used to gather engine output. + */ public void start() { new Thread(receiver, "UCI Receiver").start(); uci(); @@ -35,68 +46,89 @@ 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. - * + * * @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 - * + * * @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. - * + * * @param moves the moves to execute from the starting position to reach the * desired position */ @@ -109,15 +141,19 @@ 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. - * + * * @param searchMoves restrict the search to these moves only * @param ponder start the search in ponder mode * @param wTime the amount of milliseconds left on white's clock @@ -131,8 +167,11 @@ public class UCIHandle { * @param moveTime the exact search time * @param infinite search until the {@code stop} command */ - public void go(List 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 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"); @@ -140,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)); @@ -177,24 +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"); + } - public void setListener(UCIListener listener) { receiver.addListener(listener); } + /** + * Registers a UCI listener. + * + * @param listener the UCI listener to register + */ + public void registerListener(UCIListener listener) { + receiver.registerListener(listener); + } } diff --git a/src/dev/kske/chess/uci/UCIInfo.java b/src/dev/kske/chess/uci/UCIInfo.java index 074b09a..d06eed3 100644 --- a/src/dev/kske/chess/uci/UCIInfo.java +++ b/src/dev/kske/chess/uci/UCIInfo.java @@ -1,10 +1,6 @@ package dev.kske.chess.uci; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import dev.kske.chess.board.Move; @@ -12,41 +8,51 @@ import dev.kske.chess.board.Move; * Project: Chess
* File: UCIInfo.java
* Created: 28.07.2019
- * + * * @since Chess v0.3-alpha * @author Kai S. K. Engelbart */ public class UCIInfo { - private int depth, seldepth, time, nodes, multipv, currmovenumber, hashfull, nps, tbhits, sbhits, cpuload, cpunr; - private List pv = new ArrayList<>(), refutation = new ArrayList<>(); - private Map> currline = new HashMap<>(); - private Move currmove; - private Score score; - private String displayString; + private int depth, seldepth, time, nodes, multipv, currmovenumber, hashfull, + nps, tbhits, sbhits, cpuload, cpunr; + private List pv = new ArrayList<>(), refutation = new ArrayList<>(); + private Map> currline = new HashMap<>(); + private Move currmove; + private Score score; + private String displayString; /** * Contains every parameter for the UCI info command. Helpful for parsing * multi-value parameters. */ - private static final List params = Arrays.asList("depth", - "seldepth", - "time", - "nodes", - "multipv", - "currmove", - "currmovenumber", - "hashfull", - "nps", - "tbhits", - "sbhits", - "cpuload", - "string", - "score", - "pv", - "refutation", - "currline"); + private static final List params = Arrays.asList( + "depth", + "seldepth", + "time", + "nodes", + "multipv", + "currmove", + "currmovenumber", + "hashfull", + "nps", + "tbhits", + "sbhits", + "cpuload", + "string", + "score", + "pv", + "refutation", + "currline" + ); + /** + * 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 + */ public UCIInfo(String line) { String[] tokens = line.split(" "); @@ -93,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": @@ -106,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.valueOf(tokens[i++]) : 1; + final Integer cpu = tokens[i].matches("\\d+") + ? Integer.parseInt(tokens[i++]) + : 1; final ArrayList 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] + ); } } @@ -156,15 +170,16 @@ public class UCIInfo { public static class Score { - private int cp, mate; - private boolean lowerbound, upperbound; - private int length; + private int cp, mate; + private boolean lowerbound, upperbound; + private int length; public Score(String line) { - String[] tokens = line.split(" "); - int i = 0; + 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]); @@ -179,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; diff --git a/src/dev/kske/chess/uci/UCIListener.java b/src/dev/kske/chess/uci/UCIListener.java index 9d8b727..18b8442 100644 --- a/src/dev/kske/chess/uci/UCIListener.java +++ b/src/dev/kske/chess/uci/UCIListener.java @@ -6,7 +6,7 @@ import dev.kske.chess.board.Move; * Project: Chess
* File: UCIListener.java
* Created: 19.07.2019
- * + * * @since Chess v0.3-alpha * @author Kai S. K. Engelbart */ @@ -14,14 +14,14 @@ public interface UCIListener { /** * Identifies the name of the engine. - * + * * @param name The name of the engine */ default void onIdName(String name) {} /** * Identifies the author of the engine. - * + * * @param author The name of the engine's author */ default void onIdAuthor(String author) {} @@ -38,14 +38,14 @@ public interface UCIListener { /** * The engine has stopped searching and has found the best move. - * + * * @param move The best moves the engine has found */ default void onBestMove(String move) {} /** * The engine has stopped searching and has found the best move. - * + * * @param move The best move the engine has found * @param ponderMove The move the engine likes to ponder on */ @@ -83,14 +83,14 @@ public interface UCIListener { /** * The engine sends information to the GUI. - * + * * @param info Contains all pieces of information to be sent */ default void onInfo(UCIInfo info) {} /** * Tells the GUI which parameters can be changed in the engine. - * + * * @param option Option object describing the parameter */ default void onOption(UCIOption option) {} diff --git a/src/dev/kske/chess/uci/UCIOption.java b/src/dev/kske/chess/uci/UCIOption.java index ed38017..9e208ed 100644 --- a/src/dev/kske/chess/uci/UCIOption.java +++ b/src/dev/kske/chess/uci/UCIOption.java @@ -1,23 +1,20 @@ 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: Chess
* File: UCIOption.java
* Created: 22.07.2019
- * + * * @since Chess v0.3-alpha * @author Kai S. K. Engelbart */ public class UCIOption { - private String name, defaultVal, minVal, maxVal; - private GUIType type; - private List varList; + private String name, defaultVal, minVal, maxVal; + private GUIType type; + private List varList; public UCIOption(String line) { varList = new ArrayList<>(); @@ -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 getVarList() { return varList; } - public static enum GUIType { + public enum GUIType { CHECK, SPIN, COMBO, BUTTON, STRING } } diff --git a/src/dev/kske/chess/uci/UCIReceiver.java b/src/dev/kske/chess/uci/UCIReceiver.java index 1d80b6b..2c1ac98 100644 --- a/src/dev/kske/chess/uci/UCIReceiver.java +++ b/src/dev/kske/chess/uci/UCIReceiver.java @@ -1,9 +1,6 @@ package dev.kske.chess.uci; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; +import java.io.*; import java.util.ArrayList; import java.util.List; @@ -13,7 +10,7 @@ import dev.kske.chess.board.Move; * Project: Chess
* File: UCIReceiver.java
* Created: 19.07.2019
- * + * * @since Chess v0.3-alpha * @author Kai S. K. Engelbart */ @@ -23,17 +20,26 @@ public class UCIReceiver implements Runnable { private List listeners; + /** + * Creates an instance of {@link UCIReceiver}. + * + * @param in the input stream to parse for commands generated by the engine + */ public UCIReceiver(InputStream in) { - this.in = new BufferedReader(new InputStreamReader(in)); - listeners = new ArrayList<>(); + this.in = new BufferedReader(new InputStreamReader(in)); + listeners = new ArrayList<>(); } + /** + * Starts listening for UCI commands passed through the input stream. + */ @Override public void run() { 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(); @@ -43,8 +49,9 @@ public class UCIReceiver implements Runnable { } private void parse(String line) { - int spaceIndex = line.indexOf(' '); - String command = spaceIndex == -1 ? line : line.substring(0, spaceIndex); + int spaceIndex = line.indexOf(' '); + String command + = spaceIndex == -1 ? line : line.substring(0, spaceIndex); switch (command) { case "id": parseId(line.substring(command.length() + 1)); @@ -76,8 +83,8 @@ public class UCIReceiver implements Runnable { } private void parseId(String line) { - String param = line.substring(0, line.indexOf(' ')); - String arg = line.substring(param.length() + 1); + String param = line.substring(0, line.indexOf(' ')); + String arg = line.substring(param.length() + 1); switch (param) { case "name": listeners.forEach(l -> l.onIdName(arg)); @@ -86,17 +93,22 @@ 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 + ); } } private void parseBestMove(String line) { - String[] tokens = line.split(" "); - String move = tokens[0]; + String[] tokens = line.split(" "); + 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) { @@ -111,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 + ); } } @@ -127,13 +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))); + } - public void addListener(UCIListener listener) { listeners.add(listener); } + /** + * Registers a UCI listener + * + * @param listener the UCI listener to register + */ + public void registerListener(UCIListener listener) { + listeners.add(listener); + } } diff --git a/src/dev/kske/chess/ui/AIConfigDialog.java b/src/dev/kske/chess/ui/AIConfigDialog.java deleted file mode 100644 index 64515cb..0000000 --- a/src/dev/kske/chess/ui/AIConfigDialog.java +++ /dev/null @@ -1,84 +0,0 @@ -package dev.kske.chess.ui; - -import java.awt.Dimension; - -import javax.swing.JButton; -import javax.swing.JDialog; -import javax.swing.JLabel; -import javax.swing.JSpinner; -import javax.swing.SpinnerNumberModel; - -/** - * Project: Chess
- * File: AIConfigDialog.java
- * Created: 16.07.2019
- * - * @since Chess v0.1-alpha - * @author Kai S. K. Engelbart - */ -@Deprecated -public class AIConfigDialog extends JDialog { - - private static final long serialVersionUID = -8047984368152479992L; - - private int maxDepth; - private int alphaBetaThreshold; - private boolean startGame = false; - - public AIConfigDialog() { - setSize(new Dimension(337, 212)); - setModal(true); - setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); - setTitle("AI Configuration"); - getContentPane().setLayout(null); - - JSpinner spAlphaBetaThreshold = new JSpinner(); - spAlphaBetaThreshold.setBounds(222, 68, 95, 28); - getContentPane().add(spAlphaBetaThreshold); - spAlphaBetaThreshold.setModel(new SpinnerNumberModel(-10, -100, 100, 5)); - - JSpinner spMaxDepth = new JSpinner(); - spMaxDepth.setBounds(222, 6, 95, 28); - getContentPane().add(spMaxDepth); - spMaxDepth.setModel(new SpinnerNumberModel(4, 1, 10, 1)); - - JLabel lblAlphabetaThreshold = new JLabel("Alpha-Beta Threshold:"); - lblAlphabetaThreshold.setBounds(16, 68, 194, 28); - getContentPane().add(lblAlphabetaThreshold); - - JButton btnOk = new JButton("OK"); - btnOk.setBounds(16, 137, 84, 28); - getContentPane().add(btnOk); - btnOk.addActionListener((evt) -> { - maxDepth = ((Integer) spMaxDepth.getValue()).intValue(); - alphaBetaThreshold = ((Integer) spAlphaBetaThreshold.getValue()).intValue(); - startGame = true; - dispose(); - }); - btnOk.setToolTipText("Start the game"); - - JButton btnCancel = new JButton("Cancel"); - btnCancel.setBounds(222, 137, 95, 28); - getContentPane().add(btnCancel); - btnCancel.addActionListener((evt) -> dispose()); - btnCancel.setToolTipText("Cancel the game start"); - - JLabel lblMaximalRecursionDepth = new JLabel("Maximal Recursion Depth:"); - lblMaximalRecursionDepth.setBounds(16, 12, 194, 16); - getContentPane().add(lblMaximalRecursionDepth); - - setLocationRelativeTo(null); - } - - public int getMaxDepth() { return maxDepth; } - - public void setMaxDepth(int maxDepth) { this.maxDepth = maxDepth; } - - public int getAlphaBetaThreshold() { return alphaBetaThreshold; } - - public void setAlphaBetaThreshold(int alphaBetaThreshold) { this.alphaBetaThreshold = alphaBetaThreshold; } - - public boolean isStartGame() { return startGame; } - - public void setStartGame(boolean startGame) { this.startGame = startGame; } -} diff --git a/src/dev/kske/chess/ui/BoardComponent.java b/src/dev/kske/chess/ui/BoardComponent.java index b4120c8..b232bc2 100644 --- a/src/dev/kske/chess/ui/BoardComponent.java +++ b/src/dev/kske/chess/ui/BoardComponent.java @@ -16,7 +16,7 @@ import dev.kske.chess.io.TextureUtil; * A square panel for rendering the chess board. To work correctly, * this must be added to a parent component that allows the child to decide the * size. - * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ @@ -28,6 +28,12 @@ public class BoardComponent extends JComponent { private Board board; + /** + * Creates an instance of {@link BoardComponent}. + * + * @param boardPane the board pane inside which this board component is + * contained + */ public BoardComponent(BoardPane boardPane) { this.boardPane = boardPane; setSize(boardPane.getPreferredSize()); @@ -37,27 +43,44 @@ public class BoardComponent extends JComponent { protected void paintComponent(Graphics g) { super.paintComponent(g); - final int tileSize = getTileSize(); + final int tileSize = boardPane.getTileSize(); // Draw the board 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++) - 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); + 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 + ); } - public int getTileSize() { return boardPane.getTileSize(); } - + /** + * @return the board rendered by this board component + */ public Board getBoard() { return board; } + /** + * Sets the board rendered by this board component and repaints the + * component + * + * @param board the board rendered by this board component + */ public void setBoard(Board board) { this.board = board; repaint(); diff --git a/src/dev/kske/chess/ui/BoardPane.java b/src/dev/kske/chess/ui/BoardPane.java index cb9740e..8032e5c 100644 --- a/src/dev/kske/chess/ui/BoardPane.java +++ b/src/dev/kske/chess/ui/BoardPane.java @@ -5,10 +5,13 @@ import java.awt.Dimension; import javax.swing.JLayeredPane; /** + * Combines a {@link BoardComponent} and an {@link OverlayComponent} into a + * layered pane. + *
* Project: Chess
* File: BoardPane.java
* Created: 08.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ @@ -16,14 +19,17 @@ public class BoardPane extends JLayeredPane { private static final long serialVersionUID = -5415058382478806092L; - private final BoardComponent boardComponent; - private final OverlayComponent overlayComponent; + private final BoardComponent boardComponent; + private final OverlayComponent overlayComponent; private int tileSize; + /** + * Creates an instance of {@link BoardPane}. + */ public BoardPane() { - boardComponent = new BoardComponent(this); - overlayComponent = new OverlayComponent(this); + boardComponent = new BoardComponent(this); + overlayComponent = new OverlayComponent(this); setLayer(overlayComponent, 1); setLayout(null); @@ -37,9 +43,20 @@ public class BoardPane extends JLayeredPane { @Override public Dimension getPreferredSize() { return new Dimension(480, 480); } + /** + * @return the board component contained inside this board pane + */ public BoardComponent getBoardComponent() { return boardComponent; } - public OverlayComponent getOverlayComponent() { return overlayComponent; } + /** + * @return overlay component contained inside this board pane + */ + public OverlayComponent getOverlayComponent() { + return overlayComponent; + } + /** + * @return the size of an individual board tile in pixels + */ public int getTileSize() { return tileSize; } } diff --git a/src/dev/kske/chess/ui/DialogUtil.java b/src/dev/kske/chess/ui/DialogUtil.java index b8dc9b1..31753b4 100644 --- a/src/dev/kske/chess/ui/DialogUtil.java +++ b/src/dev/kske/chess/ui/DialogUtil.java @@ -3,20 +3,12 @@ package dev.kske.chess.ui; import java.awt.Component; import java.awt.Font; import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; +import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.prefs.Preferences; -import javax.swing.DefaultComboBoxModel; -import javax.swing.JComboBox; -import javax.swing.JFileChooser; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JPanel; +import javax.swing.*; import javax.swing.filechooser.FileNameExtensionFilter; import dev.kske.chess.io.EngineUtil; @@ -36,9 +28,20 @@ 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); - public static void showFileSelectionDialog(Component parent, Consumer> action, Collection filters) { + /** + * Displays a parameterized file opening dialog. + * + * @param parent the parent component of the dialog + * @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> action, + Collection filters + ) { JFileChooser fileChooser = createFileChooser(filters); if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) { action.accept(Arrays.asList(fileChooser.getSelectedFile())); @@ -46,27 +49,60 @@ public class DialogUtil { } } - public static void showFileSaveDialog(Component parent, Consumer action, Collection filters) { + /** + * Displays a parameterized file saving dialog. + * + * @param parent the parent component of the dialog + * @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 action, + Collection 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 filters) { + private static JFileChooser + createFileChooser(Collection 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; } - public static void showGameConfigurationDialog(Component parent, BiConsumer action) { + /** + * Displays a dialog in which the user can select the player types for a + * game.
+ *
+ * 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 + */ + public static void showGameConfigurationDialog( + Component parent, BiConsumer action + ) { JPanel dialogPanel = new JPanel(); - List options = new ArrayList<>(Arrays.asList("Natural Player", "AI Player")); + List options + = new ArrayList<>(Arrays.asList("Natural Player", "AI Player")); EngineUtil.getEngineInfos().forEach(info -> options.add(info.name)); JLabel lblWhite = new JLabel("White:"); @@ -89,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()) + ); } } diff --git a/src/dev/kske/chess/ui/GameDropTarget.java b/src/dev/kske/chess/ui/GameDropTarget.java index 4419495..58f1c17 100644 --- a/src/dev/kske/chess/ui/GameDropTarget.java +++ b/src/dev/kske/chess/ui/GameDropTarget.java @@ -10,10 +10,13 @@ import java.io.IOException; import java.util.List; /** + * Enables drag and drop support of {@code FEN} and {@code PGN} files for the + * {@link MainWindow}.
+ *
* Project: Chess
* File: GameDropTarget.java
* Created: 13 Aug 2019
- * + * * @since Chess v0.3-alpha * @author Kai S. K. Engelbart */ @@ -21,14 +24,25 @@ public class GameDropTarget extends DropTargetAdapter { private MainWindow mainWindow; - public GameDropTarget(MainWindow mainWindow) { this.mainWindow = mainWindow; } + /** + * Creates an instance of {@link GameDropTarget}. + * + * @param mainWindow the {@link MainWindow} onto which {@code FEN} and + * {@code PGN} files can be dropped + */ + 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) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor)); + mainWindow.loadFiles( + (List) evt.getTransferable() + .getTransferData(DataFlavor.javaFileListFlavor) + ); } catch (UnsupportedFlavorException | IOException ex) { ex.printStackTrace(); evt.rejectDrop(); diff --git a/src/dev/kske/chess/ui/GamePane.java b/src/dev/kske/chess/ui/GamePane.java index 7f891d2..44f36de 100644 --- a/src/dev/kske/chess/ui/GamePane.java +++ b/src/dev/kske/chess/ui/GamePane.java @@ -7,31 +7,23 @@ import java.util.Arrays; import java.util.HashSet; import java.util.Set; -import javax.swing.DefaultListModel; -import javax.swing.JButton; -import javax.swing.JComponent; -import javax.swing.JLabel; -import javax.swing.JList; -import javax.swing.JPanel; -import javax.swing.JScrollPane; -import javax.swing.ListSelectionModel; +import javax.swing.*; import dev.kske.chess.board.BoardState; import dev.kske.chess.board.MoveNode; import dev.kske.chess.board.Piece.Color; -import dev.kske.chess.event.Event; -import dev.kske.chess.event.EventBus; -import dev.kske.chess.event.GameStartEvent; -import dev.kske.chess.event.MoveEvent; -import dev.kske.chess.event.Subscribable; +import dev.kske.chess.event.*; import dev.kske.chess.game.Game; import dev.kske.chess.game.NaturalPlayer; /** + * The part of this application's {@link MainWindow} that displays {@link Game}s + * and other components allowing to manipulate them.
+ *
* Project: Chess
* File: GamePane.java
* Created: 23.08.2019
- * + * * @since Chess v0.4-alpha * @author Kai S. K. Engelbart */ @@ -39,28 +31,37 @@ public class GamePane extends JComponent { private static final long serialVersionUID = 4349772338239617477L; - private JButton btnRestart, btnSwapColors; - private BoardPane boardPane; - private Game game; - private Color activeColor; - private JPanel moveSelectionPanel; - private JButton btnNext; - private JButton btnFirst; - private JButton btnLast; + private JButton btnRestart, btnSwapColors; + private BoardPane boardPane; + private Game game; + private Color activeColor; + private JPanel moveSelectionPanel; + private JButton btnFirst, btnPrevious, btnNext, btnLast; + /** + * Creates an instance of {@link GamePane}. + */ public GamePane() { 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(); @@ -68,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(); }); @@ -78,71 +80,94 @@ public class GamePane extends JComponent { toolPanel.add(btnSwapColors); GridBagConstraints gbc_toolPanel = new GridBagConstraints(); - gbc_toolPanel.anchor = GridBagConstraints.NORTH; - gbc_toolPanel.fill = GridBagConstraints.HORIZONTAL; - gbc_toolPanel.gridx = 0; - gbc_toolPanel.gridy = 0; - gbc_toolPanel.gridwidth = 2; + gbc_toolPanel.anchor = GridBagConstraints.NORTH; + gbc_toolPanel.fill = GridBagConstraints.HORIZONTAL; + gbc_toolPanel.gridx = 0; + gbc_toolPanel.gridy = 0; + gbc_toolPanel.gridwidth = 2; add(toolPanel, gbc_toolPanel); moveSelectionPanel = new JPanel(); GridBagConstraints gbc_moveSelectionPanel = new GridBagConstraints(); - gbc_moveSelectionPanel.fill = GridBagConstraints.BOTH; - gbc_moveSelectionPanel.gridx = 2; - gbc_moveSelectionPanel.gridy = 0; + gbc_moveSelectionPanel.fill = GridBagConstraints.BOTH; + gbc_moveSelectionPanel.gridx = 2; + gbc_moveSelectionPanel.gridy = 0; add(moveSelectionPanel, gbc_moveSelectionPanel); btnFirst = new JButton("First"); btnFirst.setEnabled(false); moveSelectionPanel.add(btnFirst); - JButton btnPreviousMove = new JButton("Previous"); - btnPreviousMove.setEnabled(false); - moveSelectionPanel.add(btnPreviousMove); + btnPrevious = new JButton("Previous"); + btnPrevious.addActionListener(evt -> { + if (game != null) { + game.getBoard().selectPreviousNode(); + getBoardPane().getOverlayComponent().clearArrow(); + repaint(); + } + }); + moveSelectionPanel.add(btnPrevious); btnNext = new JButton("Next"); - btnNext.setEnabled(false); + btnNext.addActionListener(evt -> { + if (game != null) { + 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.") + ); + game.getBoard().selectNextNode(index); + getBoardPane().getOverlayComponent().clearArrow(); + repaint(); + } + }); moveSelectionPanel.add(btnNext); btnLast = new JButton("Last"); btnLast.setEnabled(false); moveSelectionPanel.add(btnLast); + boardPane = new BoardPane(); GridBagConstraints gbc_boardPane = new GridBagConstraints(); - gbc_boardPane.fill = GridBagConstraints.BOTH; - gbc_boardPane.gridx = 0; - gbc_boardPane.gridy = 1; + gbc_boardPane.fill = GridBagConstraints.BOTH; + gbc_boardPane.gridx = 0; + gbc_boardPane.gridy = 1; add(boardPane, gbc_boardPane); - JPanel numberPanel = new JPanel(new GridLayout(8, 1)); - GridBagConstraints gbc_numberPanel = new GridBagConstraints(); - gbc_numberPanel.anchor = GridBagConstraints.WEST; - gbc_numberPanel.fill = GridBagConstraints.VERTICAL; - gbc_numberPanel.gridx = 1; - gbc_numberPanel.gridy = 1; + JPanel numberPanel = new JPanel(new GridLayout(8, 1)); + GridBagConstraints gbc_numberPanel = new GridBagConstraints(); + gbc_numberPanel.anchor = GridBagConstraints.WEST; + gbc_numberPanel.fill = GridBagConstraints.VERTICAL; + gbc_numberPanel.gridx = 1; + gbc_numberPanel.gridy = 1; add(numberPanel, gbc_numberPanel); - JPanel letterPanel = new JPanel(new GridLayout(1, 8)); - GridBagConstraints gbc_letterPanel = new GridBagConstraints(); - gbc_letterPanel.anchor = GridBagConstraints.NORTH; - gbc_letterPanel.fill = GridBagConstraints.HORIZONTAL; - gbc_letterPanel.gridx = 0; - gbc_letterPanel.gridy = 2; + JPanel letterPanel = new JPanel(new GridLayout(1, 8)); + GridBagConstraints gbc_letterPanel = new GridBagConstraints(); + gbc_letterPanel.anchor = GridBagConstraints.NORTH; + gbc_letterPanel.fill = GridBagConstraints.HORIZONTAL; + gbc_letterPanel.gridx = 0; + gbc_letterPanel.gridy = 2; add(letterPanel, gbc_letterPanel); // Initialize board coordinates for (int i = 0; i < 8; i++) { numberPanel.add(new JLabel(String.valueOf(8 - i))); JLabel letterLabel = new JLabel(String.valueOf((char) (65 + i))); - letterLabel.setHorizontalAlignment(JLabel.CENTER); + letterLabel.setHorizontalAlignment(SwingConstants.CENTER); letterPanel.add(letterLabel); } - - JScrollPane scrollPane = new JScrollPane(); - GridBagConstraints gbc_scrollPane = new GridBagConstraints(); - gbc_scrollPane.fill = GridBagConstraints.BOTH; - gbc_scrollPane.gridx = 2; - gbc_scrollPane.gridy = 1; + JScrollPane scrollPane = new JScrollPane(); + GridBagConstraints gbc_scrollPane = new GridBagConstraints(); + gbc_scrollPane.fill = GridBagConstraints.BOTH; + gbc_scrollPane.gridx = 2; + gbc_scrollPane.gridy = 1; add(scrollPane, gbc_scrollPane); JList pgnList = new JList<>(); @@ -152,27 +177,40 @@ 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 Subscribable() { + 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 model = new DefaultListModel<>(); - game.getBoard().getLog().forEach(node -> model.addElement(node)); + game.getBoard().getLog().forEach(model::addElement); pgnList.setModel(model); } @Override - public Set> supports() { return new HashSet<>(Arrays.asList(MoveEvent.class, GameStartEvent.class)); } + public Set> supports() { + return new HashSet<>( + Arrays.asList(MoveEvent.class, GameStartEvent.class) + ); + } }); } @@ -187,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; } } diff --git a/src/dev/kske/chess/ui/GameTabComponent.java b/src/dev/kske/chess/ui/GameTabComponent.java index ff492b4..039d91b 100644 --- a/src/dev/kske/chess/ui/GameTabComponent.java +++ b/src/dev/kske/chess/ui/GameTabComponent.java @@ -1,22 +1,15 @@ package dev.kske.chess.ui; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Dimension; -import java.awt.FlowLayout; -import java.awt.Graphics; -import java.awt.Graphics2D; +import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import javax.swing.BorderFactory; -import javax.swing.JButton; -import javax.swing.JLabel; -import javax.swing.JPanel; -import javax.swing.JTabbedPane; +import javax.swing.*; import javax.swing.plaf.basic.BasicButtonUI; /** + * Renders the title and the closing button of a {@link JTabbedPane}.
+ *
* Project: Chess
* File: GameTabComponent.java
* Created: 11 Dec 2019
@@ -29,9 +22,16 @@ public class GameTabComponent extends JPanel { private static final long serialVersionUID = 9022979950018125935L; + /** + * Creates an instance of {@link GameTabComponent}. + * + * @param tabbedPane the tabbed pane which contains this + * {@link GameTabComponent} + */ 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 @@ -69,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 @@ -86,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(); } } diff --git a/src/dev/kske/chess/ui/MainWindow.java b/src/dev/kske/chess/ui/MainWindow.java index 22ccbc3..3e691bd 100644 --- a/src/dev/kske/chess/ui/MainWindow.java +++ b/src/dev/kske/chess/ui/MainWindow.java @@ -1,7 +1,6 @@ package dev.kske.chess.ui; import java.awt.Desktop; -import java.awt.EventQueue; import java.awt.Toolkit; import java.awt.dnd.DropTarget; import java.io.File; @@ -10,10 +9,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.List; -import javax.swing.JComboBox; -import javax.swing.JFrame; -import javax.swing.JOptionPane; -import javax.swing.JTabbedPane; +import javax.swing.*; import dev.kske.chess.board.Board; import dev.kske.chess.board.FENString; @@ -32,45 +28,36 @@ import dev.kske.chess.pgn.PGNGame; */ public class MainWindow extends JFrame { - private static final long serialVersionUID = -3100939302567978977L; + private JTabbedPane tabbedPane = new JTabbedPane(); - private JTabbedPane tabbedPane; + private static final long serialVersionUID = -3100939302567978977L; /** * Launch the application. + * + * @param args command line arguments are ignored */ public static void main(String[] args) { - EventQueue.invokeLater(() -> { - try { - new MainWindow(); - } catch (Exception ex) { - ex.printStackTrace(); - } - }); + SwingUtilities.invokeLater(MainWindow::new); } /** * Create the application. */ - public MainWindow() { + private MainWindow() { super("Chess by Kai S. K. Engelbart"); - initialize(); - } - /** - * Initialize the contents of the frame. - */ - private void initialize() { // 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 frame content - tabbedPane = new JTabbedPane(); + // Add tabbed pane, menu bar and drop target getContentPane().add(tabbedPane); - setJMenuBar(new MenuBar(this)); new DropTarget(this, new GameDropTarget(this)); @@ -81,40 +68,64 @@ public class MainWindow extends JFrame { } /** - * @return The currently selected {@link GamePane} component + * @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. * The new tab has the title {@code Game n} where {@code n} is its number. * - * @return The new {@link GamePane} + * @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. * * @param title The title of the {@link GamePane} - * @return The new {@link GamePane} + * @return the new {@link GamePane} */ 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 + * displays a game configuration dialog for a new game on an existing + * {@link Board}. + * + * @param title the title of the {@link GamePane} + * @param board the {@link Board} with which the new {@link Game} is started + * @return the new {@link GamePane} + */ public GamePane addGamePane(String title, Board board) { GamePane gamePane = addGamePane(title); - DialogUtil.showGameConfigurationDialog(this, - (whiteName, blackName) -> { - Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, board); - gamePane.setGame(game); - game.start(); - }); + DialogUtil.showGameConfigurationDialog( + this, + (whiteName, blackName) -> { + Game game = new Game( + gamePane.getBoardPane(), + whiteName, + blackName, + board + ); + gamePane.setGame(game); + game.start(); + } + ); return gamePane; } @@ -123,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}. @@ -132,65 +145,111 @@ public class MainWindow extends JFrame { */ public void loadFiles(List 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 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 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(), - "File loading error", - JOptionPane.ERROR_MESSAGE); + JOptionPane.showMessageDialog( + this, + "Failed to load the file " + file.getName() + ": " + + e.toString(), + "File loading error", + JOptionPane.ERROR_MESSAGE + ); } }); } + /** + * Saves the current {@link Game} as a file in {@code PGN} or {@code FEN} + * format. + * + * @param file the file in which to save the current {@link Game} + */ public void saveFile(File file) { - final int dotIndex = file.getName().lastIndexOf('.'); - final String extension = file.getName().substring(dotIndex).toLowerCase(); + final int dotIndex = file.getName().lastIndexOf('.'); + 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())); - pgnGame.setTag("Result", "*"); - PGNDatabase pgnDB = new PGNDatabase(); - pgnDB.getGames().add(pgnGame); - pgnDB.save(file); + 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, - "Game export finished. Do you want to view the created file?", - "Game export finished", - JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION) - Desktop.getDesktop().open(file); - } catch (IOException e) { - e.printStackTrace(); - JOptionPane.showMessageDialog(this, - "Failed to save the file " + file.getName() + ": " + e.toString(), + 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 + ) + Desktop.getDesktop().open(file); + } catch (IOException e) { + e.printStackTrace(); + JOptionPane.showMessageDialog( + this, + "Failed to save the file " + file.getName() + ": " + e, "File saving error", - JOptionPane.ERROR_MESSAGE); - } + JOptionPane.ERROR_MESSAGE + ); + } } } diff --git a/src/dev/kske/chess/ui/MenuBar.java b/src/dev/kske/chess/ui/MenuBar.java index 3ea86cc..ac478e0 100644 --- a/src/dev/kske/chess/ui/MenuBar.java +++ b/src/dev/kske/chess/ui/MenuBar.java @@ -4,10 +4,7 @@ import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; import java.util.Arrays; -import javax.swing.JMenu; -import javax.swing.JMenuBar; -import javax.swing.JMenuItem; -import javax.swing.JOptionPane; +import javax.swing.*; import javax.swing.filechooser.FileNameExtensionFilter; import dev.kske.chess.board.FENString; @@ -19,7 +16,7 @@ import dev.kske.chess.io.EngineUtil; * Project: Chess
* File: MenuBar.java
* Created: 16.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ @@ -29,6 +26,11 @@ public class MenuBar extends JMenuBar { private final MainWindow mainWindow; + /** + * Creates an instance of {@link MenuBar}. + * + * @param mainWindow the main window inside which this menu bar is contained + */ public MenuBar(MainWindow mainWindow) { this.mainWindow = mainWindow; @@ -41,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) -> { - GamePane gamePane = mainWindow.addGamePane(); - Game game = new Game(gamePane.getBoardPane(), whiteName, blackName); - gamePane.setGame(game); - game.start(); - })); + newGameMenuItem.addActionListener( + evt -> DialogUtil.showGameConfigurationDialog( + mainWindow, + (whiteName, blackName) -> { + GamePane gamePane = mainWindow.addGamePane(); + 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); @@ -70,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"); @@ -87,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) -> { - final GamePane gamePane = mainWindow.addGamePane(); - 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()); - 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); + loadFromFENMenuItem.addActionListener(evt -> { + final GamePane gamePane = mainWindow.addGamePane(); + 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() + ); + gamePane.setGame(game); + game.start(); + } catch (ChessException e) { + e.printStackTrace(); + JOptionPane + .showMessageDialog( + mainWindow, + "Failed to load FEN string: " + e.toString(), + "FEN loading error", + JOptionPane.ERROR_MESSAGE + ); + } } - }); + ); }); toolsMenu.add(loadFromFENMenuItem); diff --git a/src/dev/kske/chess/ui/MoveNodeRenderer.java b/src/dev/kske/chess/ui/MoveNodeRenderer.java index f78bea5..7775e00 100644 --- a/src/dev/kske/chess/ui/MoveNodeRenderer.java +++ b/src/dev/kske/chess/ui/MoveNodeRenderer.java @@ -14,19 +14,26 @@ import dev.kske.chess.board.MoveNode; * Project: Chess
* File: MoveNodeRenderer.java
* Created: 9 Oct 2019
- * + * * @since Chess v0.5-alpha * @author Kai S. K. Engelbart */ -public class MoveNodeRenderer extends JLabel implements ListCellRenderer { +public class MoveNodeRenderer extends JLabel + implements ListCellRenderer { private static final long serialVersionUID = 5242015788752442446L; @Override - public Component getListCellRendererComponent(JList list, MoveNode node, int index, - boolean isSelected, boolean cellHasFocus) { + public Component getListCellRendererComponent( + JList list, MoveNode node, int index, + boolean isSelected, boolean cellHasFocus + ) { setBorder(new EmptyBorder(5, 5, 5, 5)); - setText(node.move.toLAN()); + + int numVariations + = node.hasVariations() ? node.getVariations().size() : 0; + setText(String.format("%s (%d)", node.move.toLAN(), numVariations)); + setBackground(isSelected ? Color.red : Color.white); setOpaque(true); return this; diff --git a/src/dev/kske/chess/ui/OverlayComponent.java b/src/dev/kske/chess/ui/OverlayComponent.java index cee5449..a933198 100644 --- a/src/dev/kske/chess/ui/OverlayComponent.java +++ b/src/dev/kske/chess/ui/OverlayComponent.java @@ -1,12 +1,6 @@ package dev.kske.chess.ui; -import java.awt.BasicStroke; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Polygon; -import java.awt.Shape; +import java.awt.*; import java.awt.geom.AffineTransform; import java.util.ArrayList; import java.util.List; @@ -20,7 +14,7 @@ import dev.kske.chess.board.Position; * Project: Chess
* File: OverlayComponent.java
* Created: 08.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ @@ -30,9 +24,15 @@ public class OverlayComponent extends JComponent { private final BoardPane boardPane; - private List dots; - private Move arrow; + private List dots; + private Move arrow; + /** + * Creates an instance of {@link OverlayComponent}. + * + * @param boardPane the board pane inside which this overlay component is + * contained + */ public OverlayComponent(BoardPane boardPane) { this.boardPane = boardPane; dots = new ArrayList<>(); @@ -43,20 +43,36 @@ public class OverlayComponent extends JComponent { protected void paintComponent(Graphics g) { super.paintComponent(g); - final int tileSize = getTileSize(); + final int tileSize = boardPane.getTileSize(); // 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, - dot.y * tileSize + tileSize / 2 - radius / 2, - radius, - radius); + g.fillOval( + dot.x * tileSize + tileSize / 2 - radius / 2, + dot.y * tileSize + tileSize / 2 - radius / 2, + radius, + radius + ); } } @@ -89,10 +106,11 @@ public class OverlayComponent extends JComponent { Point midPoint = midpoint(pos, dest); - 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 - // polygon. + 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 + // polygon. AffineTransform transform = new AffineTransform(); @@ -104,29 +122,52 @@ 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) + ); } + /** + * Displays green dots at a list of positions. + * + * @param dots the positions at which the dots should be displayed + */ public void displayDots(List dots) { this.dots.clear(); this.dots.addAll(dots); repaint(); } + /** + * Clears all dots displayed at some positions. + */ public void clearDots() { dots.clear(); repaint(); } + /** + * Displays an arrow from the position to the destination of a move. + * + * @param arrow the move indicating the arrows position and destination + */ public void displayArrow(Move arrow) { this.arrow = arrow; repaint(); } + /** + * Clears the arrow displayed to indicate a move. + */ public void clearArrow() { arrow = null; repaint(); } + /** + * @return the size of one board tile in pixels. + * @see dev.kske.chess.ui.BoardPane#getTileSize() + */ public int getTileSize() { return boardPane.getTileSize(); } } diff --git a/test/dev/kske/chess/board/BoardTest.java b/test/dev/kske/chess/board/BoardTest.java index 776d221..795db7b 100644 --- a/test/dev/kske/chess/board/BoardTest.java +++ b/test/dev/kske/chess/board/BoardTest.java @@ -1,7 +1,6 @@ package dev.kske.chess.board; -import static org.junit.Assert.assertNotEquals; -import static org.junit.Assert.assertNotSame; +import static org.junit.Assert.*; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -16,7 +15,7 @@ import dev.kske.chess.board.Piece.Color; */ class BoardTest { - Board board; + private Board board; /** * @throws java.lang.Exception @@ -27,7 +26,7 @@ class BoardTest { } /** - * Test method for {@link dev.kske.chess.board.Board#clone()}. + * Test method for {@link Board#Board(Board, boolean)}. */ @Test void testClone() { @@ -38,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() + ); } } diff --git a/test/dev/kske/chess/board/FENStringTest.java b/test/dev/kske/chess/board/FENStringTest.java index b2c1e43..70be9b7 100644 --- a/test/dev/kske/chess/board/FENStringTest.java +++ b/test/dev/kske/chess/board/FENStringTest.java @@ -16,14 +16,19 @@ import dev.kske.chess.exception.ChessException; * Project: Chess
* File: FENStringTest.java
* Created: 24 Oct 2019
- * + * * @author Kai S. K. Engelbart */ class FENStringTest { - List fenStrings = new ArrayList<>(); - List boards = new ArrayList<>(); + private List fenStrings = new ArrayList<>(); + private List boards = new ArrayList<>(); + /** + * Removes all pieces from a board + * + * @param board the board to clean + */ void cleanBoard(Board board) { for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) @@ -35,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)); @@ -51,22 +60,28 @@ class FENStringTest { } /** - * Test method for {@link dev.kske.chess.board.FENString#toString()}. + * Test method for {@link FENString#toString()}. */ @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() + ); } /** - * Test method for {@link dev.kske.chess.board.FENString#getBoard()}. - * + * Test method for {@link FENString#getBoard()}. + * * @throws ChessException */ @Test void testGetBoard() throws ChessException { for (int i = 0; i < boards.size(); i++) - assertEquals(boards.get(i), new FENString(fenStrings.get(i)).getBoard()); + assertEquals( + boards.get(i), + new FENString(fenStrings.get(i)).getBoard() + ); } } diff --git a/test/dev/kske/chess/board/LogTest.java b/test/dev/kske/chess/board/LogTest.java index fafc6dc..83592e4 100644 --- a/test/dev/kske/chess/board/LogTest.java +++ b/test/dev/kske/chess/board/LogTest.java @@ -1,10 +1,6 @@ package dev.kske.chess.board; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.*; import org.junit.jupiter.api.Test; @@ -18,10 +14,10 @@ import dev.kske.chess.board.Piece.Color; */ class LogTest { - Log log = new Log(); + private Log log = new Log(); /** - * Test method for {@link dev.kske.chess.board.Log#Log()}. + * Test method for {@link Log#Log()}. */ @Test void testLog() { @@ -35,7 +31,7 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#clone()}. + * Test method for {@link Log#Log(Log, boolean)}. */ @Test void testClone() { @@ -48,11 +44,14 @@ 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() + ); } /** - * Test method for {@link dev.kske.chess.board.Log#add(dev.kske.chess.board.Move, dev.kske.chess.board.Piece, boolean)}. + * Test method for {@link Log#add(Move, Piece, Piece)}. */ @Test void testAdd() { @@ -60,7 +59,7 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#removeLast()}. + * Test method for {@link Log#removeLast()}. */ @Test void testRemoveLast() { @@ -68,7 +67,7 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#isEmpty()}. + * Test method for {@link Log#isEmpty()}. */ @Test void testIsEmpty() { @@ -76,7 +75,7 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#reset()}. + * Test method for {@link Log#reset()}. */ @Test void testReset() { @@ -84,7 +83,7 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#getRoot()}. + * Test method for {@link Log#getRoot()}. */ @Test void testGetRoot() { @@ -92,7 +91,7 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#getLast()}. + * Test method for {@link Log#getLast()}. */ @Test void testGetLast() { @@ -100,7 +99,7 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#getEnPassant()}. + * Test method for {@link Log#getEnPassant()}. */ @Test void testGetEnPassant() { @@ -108,7 +107,7 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#setEnPassant(dev.kske.chess.board.Position)}. + * Test method for {@link Log#setEnPassant(dev.kske.chess.board.Position)}. */ @Test void testSetEnPassant() { @@ -116,7 +115,7 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#getActiveColor()}. + * Test method for {@link Log#getActiveColor()}. */ @Test void testGetActiveColor() { @@ -124,7 +123,8 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#setActiveColor(dev.kske.chess.board.Piece.Color)}. + * Test method for + * {@link Log#setActiveColor(dev.kske.chess.board.Piece.Color)}. */ @Test void testSetActiveColor() { @@ -132,7 +132,7 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#getFullmoveNumber()}. + * Test method for {@link Log#getFullmoveNumber()}. */ @Test void testGetFullmoveCounter() { @@ -140,7 +140,7 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#setFullmoveNumber(int)}. + * Test method for {@link Log#setFullmoveNumber(int)}. */ @Test void testSetFullmoveCounter() { @@ -148,7 +148,7 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#getHalfmoveClock()}. + * Test method for {@link Log#getHalfmoveClock()}. */ @Test void testGetHalfmoveClock() { @@ -156,10 +156,10 @@ class LogTest { } /** - * Test method for {@link dev.kske.chess.board.Log#setHalfmoveClock(int)}. + * Test method for {@link Log#setHalfmoveClock(int)}. */ @Test void testSetHalfmoveClock() { fail("Not yet implemented"); } -} \ No newline at end of file +} diff --git a/test/dev/kske/chess/board/PositionTest.java b/test/dev/kske/chess/board/PositionTest.java index 5cb4a1b..3b63b0b 100644 --- a/test/dev/kske/chess/board/PositionTest.java +++ b/test/dev/kske/chess/board/PositionTest.java @@ -12,14 +12,23 @@ import org.junit.jupiter.api.Test; */ class PositionTest { - final int n = 4; - Position[] positions = new Position[] { new Position(0, 0), new Position(7, 7), new Position(0, 7), new Position(7, 0) }; - String[] sans = new String[] { "a8", "h1", "a1", "h8" }; - String[] strings = new String[] { "[0, 0]", "[7, 7]", "[0, 7]", "[7, 0]" }; + 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]" + }; /** * Test method for - * {@link dev.kske.chess.board.Position#fromLAN(java.lang.String)}. + * {@link Position#fromLAN(java.lang.String)}. */ @Test void testFromSAN() { @@ -28,7 +37,7 @@ class PositionTest { } /** - * Test method for {@link dev.kske.chess.board.Position#toLAN()}. + * Test method for {@link Position#toLAN()}. */ @Test void testToSAN() { @@ -37,7 +46,7 @@ class PositionTest { } /** - * Test method for {@link dev.kske.chess.board.Position#toString()}. + * Test method for {@link Position#toString()}. */ @Test void testToString() { diff --git a/test/dev/kske/chess/pgn/PGNDatabaseTest.java b/test/dev/kske/chess/pgn/PGNDatabaseTest.java index 35e31e1..bb8f0b5 100644 --- a/test/dev/kske/chess/pgn/PGNDatabaseTest.java +++ b/test/dev/kske/chess/pgn/PGNDatabaseTest.java @@ -1,7 +1,5 @@ package dev.kske.chess.pgn; -import static org.junit.jupiter.api.Assertions.fail; - import java.io.File; import java.io.FileNotFoundException; @@ -18,19 +16,18 @@ import dev.kske.chess.exception.ChessException; class PGNDatabaseTest { /** - * Test method for {@link dev.kske.chess.pgn.PGNDatabase#load(java.io.File)}. - * - * @throws ChessException - * @throws FileNotFoundException + * 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 + * present */ @Test - void testLoad() { + void testLoad() throws FileNotFoundException, ChessException { PGNDatabase db = new PGNDatabase(); - try { - db.load(new File(getClass().getClassLoader().getResource("test.pgn").getFile())); - } catch (FileNotFoundException | ChessException e) { - e.printStackTrace(); - fail(e); - } + db.load( + new File(getClass().getClassLoader().getResource("test.pgn").getFile()) + ); } }