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