Compare commits
	
		
			31 Commits
		
	
	
		
			v0.2-alpha
			...
			v0.3-alpha
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 1ce8b8355a | |||
| 36597ac6f1 | |||
| e72297bebf | |||
| 545f946aa0 | |||
| 984bedfafe | |||
| cac235a0db | |||
| 1f5242935f | |||
| 51558797cc | |||
| ae38e67a90 | |||
| 5abc51688b | |||
| 8bcd89d975 | |||
| 4c0432ca30 | |||
| 36832733b6 | |||
| 601104485c | |||
| 2da185a8fb | |||
| 0ed80228fe | |||
| e353aef867 | |||
| 
						 | 
					b25acff367 | ||
| 
						 | 
					184c96db8c | ||
| 
						 | 
					309495cfae | ||
| 
						 | 
					91962c01e0 | ||
| 
						 | 
					b3710a878f | ||
| 68d1996bd6 | |||
| efe7ab2b60 | |||
| a68a87962c | |||
| ab54f88a89 | |||
| b5b7a749d6 | |||
| 709383e758 | |||
| 347eb5d531 | |||
| 062a5c3075 | |||
| 29e17d90a5 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -22,3 +22,4 @@ local.properties
 | 
			
		||||
 | 
			
		||||
# Annotation Processing
 | 
			
		||||
.apt_generated/
 | 
			
		||||
/engine_infos.ser
 | 
			
		||||
 
 | 
			
		||||
@@ -20,14 +20,6 @@ public class Bishop extends Piece {
 | 
			
		||||
		return move.isDiagonal() && isFreePath(move);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected boolean isFreePath(Move move) {
 | 
			
		||||
		for (int i = move.pos.x + move.xSign, j = move.pos.y
 | 
			
		||||
				+ move.ySign; i != move.dest.x; i += move.xSign, j += move.ySign)
 | 
			
		||||
			if (board.getBoardArr()[i][j] != null) return false;
 | 
			
		||||
		return checkDestination(move);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected List<Move> getPseudolegalMoves(Position pos) {
 | 
			
		||||
		List<Move> moves = new ArrayList<>();
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ public class Board implements Cloneable {
 | 
			
		||||
 | 
			
		||||
	private Piece[][]						boardArr;
 | 
			
		||||
	private Map<Color, Position>			kingPos;
 | 
			
		||||
	private Map<Color, Map<Type, Boolean>>	castlingRights;
 | 
			
		||||
	private Log								log;
 | 
			
		||||
 | 
			
		||||
	private static final Map<Type, int[][]> positionScores;
 | 
			
		||||
@@ -61,6 +62,7 @@ public class Board implements Cloneable {
 | 
			
		||||
	public Board() {
 | 
			
		||||
		boardArr		= new Piece[8][8];
 | 
			
		||||
		kingPos			= new HashMap<>();
 | 
			
		||||
		castlingRights	= new HashMap<>();
 | 
			
		||||
		log				= new Log();
 | 
			
		||||
		initializeDefaultPositions();
 | 
			
		||||
	}
 | 
			
		||||
@@ -107,6 +109,11 @@ public class Board implements Cloneable {
 | 
			
		||||
				// TODO: Select promotion
 | 
			
		||||
				setDest(move, new Queen(piece.getColor(), this));
 | 
			
		||||
				break;
 | 
			
		||||
			case EN_PASSANT:
 | 
			
		||||
				setDest(move, piece);
 | 
			
		||||
				setPos(move, null);
 | 
			
		||||
				boardArr[move.dest.x][move.dest.y - move.ySign] = null;
 | 
			
		||||
				break;
 | 
			
		||||
			case CASTLING:
 | 
			
		||||
				// Move the king
 | 
			
		||||
				setDest(move, piece);
 | 
			
		||||
@@ -135,11 +142,14 @@ public class Board implements Cloneable {
 | 
			
		||||
		// Increment move counter
 | 
			
		||||
		getDest(move).incMoveCounter();
 | 
			
		||||
 | 
			
		||||
		// Update the king's position if the moved piece is the king
 | 
			
		||||
		// Update the king's position if the moved piece is the king and castling
 | 
			
		||||
		// availability
 | 
			
		||||
		if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest);
 | 
			
		||||
 | 
			
		||||
		// Update log
 | 
			
		||||
		log.add(move, capturePiece);
 | 
			
		||||
		log.add(move, capturePiece, piece.getType() == Type.PAWN);
 | 
			
		||||
 | 
			
		||||
		updateCastlingRights();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
@@ -155,6 +165,11 @@ public class Board implements Cloneable {
 | 
			
		||||
				setPos(move, new Pawn(getDest(move).getColor(), this));
 | 
			
		||||
				setDest(move, capturedPiece);
 | 
			
		||||
				break;
 | 
			
		||||
			case EN_PASSANT:
 | 
			
		||||
				setPos(move, getDest(move));
 | 
			
		||||
				setDest(move, null);
 | 
			
		||||
				boardArr[move.dest.x][move.dest.y - move.ySign] = new Pawn(getPos(move).getColor().opposite(), this);
 | 
			
		||||
				break;
 | 
			
		||||
			case CASTLING:
 | 
			
		||||
				// Move the king
 | 
			
		||||
				setPos(move, getDest(move));
 | 
			
		||||
@@ -163,8 +178,6 @@ public class Board implements Cloneable {
 | 
			
		||||
				// Move the rook
 | 
			
		||||
				Move rookMove = move.dest.x == 6 ? new Move(5, move.pos.y, 7, move.pos.y) // Kingside
 | 
			
		||||
						: new Move(3, move.pos.y, 0, move.pos.y); // Queenside
 | 
			
		||||
 | 
			
		||||
				// Move the rook
 | 
			
		||||
				setDest(rookMove, getPos(rookMove));
 | 
			
		||||
				setPos(rookMove, null);
 | 
			
		||||
 | 
			
		||||
@@ -188,6 +201,30 @@ public class Board implements Cloneable {
 | 
			
		||||
 | 
			
		||||
		// Update log
 | 
			
		||||
		log.removeLast();
 | 
			
		||||
 | 
			
		||||
		updateCastlingRights();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void updateCastlingRights() {
 | 
			
		||||
		// White
 | 
			
		||||
		if (new Position(4, 7).equals(kingPos.get(Color.WHITE))) {
 | 
			
		||||
			final King king = (King) get(kingPos.get(Color.WHITE));
 | 
			
		||||
			castlingRights.get(Color.WHITE).put(Type.KING, king.canCastleKingside());
 | 
			
		||||
			castlingRights.get(Color.WHITE).put(Type.QUEEN, king.canCastleQueenside());
 | 
			
		||||
		} else {
 | 
			
		||||
			castlingRights.get(Color.WHITE).put(Type.KING, false);
 | 
			
		||||
			castlingRights.get(Color.WHITE).put(Type.QUEEN, false);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Black
 | 
			
		||||
		if (new Position(4, 0).equals(kingPos.get(Color.BLACK))) {
 | 
			
		||||
			final King king = (King) get(kingPos.get(Color.BLACK));
 | 
			
		||||
			castlingRights.get(Color.BLACK).put(Type.KING, king.canCastleKingside());
 | 
			
		||||
			castlingRights.get(Color.BLACK).put(Type.QUEEN, king.canCastleQueenside());
 | 
			
		||||
		} else {
 | 
			
		||||
			castlingRights.get(Color.BLACK).put(Type.KING, false);
 | 
			
		||||
			castlingRights.get(Color.BLACK).put(Type.QUEEN, false);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
@@ -249,7 +286,8 @@ public class Board implements Cloneable {
 | 
			
		||||
 | 
			
		||||
	public GameState getGameEventType(Color color) {
 | 
			
		||||
		return checkCheck(color) ? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK
 | 
			
		||||
				: getMoves(color).isEmpty() ? GameState.STALEMATE : GameState.NORMAL;
 | 
			
		||||
				: getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? GameState.STALEMATE
 | 
			
		||||
						: GameState.NORMAL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
@@ -330,6 +368,18 @@ public class Board implements Cloneable {
 | 
			
		||||
		for (int i = 0; i < 8; i++)
 | 
			
		||||
			for (int j = 2; j < 6; j++)
 | 
			
		||||
				boardArr[i][j] = null;
 | 
			
		||||
 | 
			
		||||
		// Initialize castling rights
 | 
			
		||||
		castlingRights.clear();
 | 
			
		||||
		Map<Type, Boolean> whiteCastling = new HashMap<>(), blackCastling = new HashMap<>();
 | 
			
		||||
		whiteCastling.put(Type.KING, true);
 | 
			
		||||
		whiteCastling.put(Type.QUEEN, true);
 | 
			
		||||
		blackCastling.put(Type.KING, true);
 | 
			
		||||
		blackCastling.put(Type.QUEEN, true);
 | 
			
		||||
		castlingRights.put(Color.WHITE, whiteCastling);
 | 
			
		||||
		castlingRights.put(Color.BLACK, blackCastling);
 | 
			
		||||
 | 
			
		||||
		log.reset();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
@@ -354,12 +404,83 @@ public class Board implements Cloneable {
 | 
			
		||||
 | 
			
		||||
		board.kingPos = new HashMap<>();
 | 
			
		||||
		board.kingPos.putAll(kingPos);
 | 
			
		||||
 | 
			
		||||
		board.log = (Log) log.clone();
 | 
			
		||||
 | 
			
		||||
		return board;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return A FEN string representing the board
 | 
			
		||||
	 */
 | 
			
		||||
	public String toFEN() {
 | 
			
		||||
		StringBuilder sb = new StringBuilder();
 | 
			
		||||
 | 
			
		||||
		// Piece placement (from white's perspective)
 | 
			
		||||
		for (int i = 0; i < 8; i++) {
 | 
			
		||||
			int emptyCount = 0;
 | 
			
		||||
			for (int j = 0; j < 8; j++)
 | 
			
		||||
				if (boardArr[j][i] == null) ++emptyCount;
 | 
			
		||||
				else {
 | 
			
		||||
					if (emptyCount != 0) {
 | 
			
		||||
						sb.append(emptyCount);
 | 
			
		||||
						emptyCount = 0;
 | 
			
		||||
					}
 | 
			
		||||
					char piece;
 | 
			
		||||
					switch (boardArr[j][i].getType()) {
 | 
			
		||||
						case KING:
 | 
			
		||||
							piece = 'K';
 | 
			
		||||
							break;
 | 
			
		||||
						case QUEEN:
 | 
			
		||||
							piece = 'Q';
 | 
			
		||||
							break;
 | 
			
		||||
						case ROOK:
 | 
			
		||||
							piece = 'R';
 | 
			
		||||
							break;
 | 
			
		||||
						case KNIGHT:
 | 
			
		||||
							piece = 'N';
 | 
			
		||||
							break;
 | 
			
		||||
						case BISHOP:
 | 
			
		||||
							piece = 'B';
 | 
			
		||||
							break;
 | 
			
		||||
						case PAWN:
 | 
			
		||||
							piece = 'P';
 | 
			
		||||
							break;
 | 
			
		||||
						default:
 | 
			
		||||
							piece = '-';
 | 
			
		||||
					}
 | 
			
		||||
					if (boardArr[j][i].getColor() == Color.BLACK) piece = Character.toLowerCase(piece);
 | 
			
		||||
					sb.append(piece);
 | 
			
		||||
				}
 | 
			
		||||
			if (emptyCount != 0) sb.append(emptyCount);
 | 
			
		||||
			if (i < 7) sb.append('/');
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Active color
 | 
			
		||||
		sb.append(" " + log.getActiveColor().firstChar());
 | 
			
		||||
 | 
			
		||||
		sb.append(' ');
 | 
			
		||||
		StringBuilder castlingSb = new StringBuilder();
 | 
			
		||||
		if (castlingRights.get(Color.WHITE).get(Type.KING)) castlingSb.append('K');
 | 
			
		||||
		if (castlingRights.get(Color.WHITE).get(Type.QUEEN)) castlingSb.append('Q');
 | 
			
		||||
		if (castlingRights.get(Color.BLACK).get(Type.KING)) castlingSb.append('k');
 | 
			
		||||
		if (castlingRights.get(Color.BLACK).get(Type.QUEEN)) castlingSb.append('q');
 | 
			
		||||
		if (castlingSb.length() == 0) sb.append("-");
 | 
			
		||||
		sb.append(castlingSb);
 | 
			
		||||
 | 
			
		||||
		final LoggedMove lastMove = log.getLast();
 | 
			
		||||
 | 
			
		||||
		// En passant availabillity
 | 
			
		||||
		sb.append(" " + (lastMove == null || lastMove.enPassant == null ? "-" : lastMove.enPassant.toSAN()));
 | 
			
		||||
 | 
			
		||||
		// Halfmove clock
 | 
			
		||||
		sb.append(" " + String.valueOf(lastMove == null ? 0 : lastMove.halfmoveClock));
 | 
			
		||||
 | 
			
		||||
		// Fullmove counter
 | 
			
		||||
		sb.append(" " + String.valueOf(lastMove == null ? 1 : lastMove.fullmoveCounter));
 | 
			
		||||
 | 
			
		||||
		return sb.toString();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Piece get(Position pos) {
 | 
			
		||||
		return boardArr[pos.x][pos.y];
 | 
			
		||||
	}
 | 
			
		||||
@@ -388,4 +509,14 @@ public class Board implements Cloneable {
 | 
			
		||||
	 * @return The board array
 | 
			
		||||
	 */
 | 
			
		||||
	public Piece[][] getBoardArr() { return boardArr; }
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return The active color for the next move
 | 
			
		||||
	 */
 | 
			
		||||
	public Color getActiveColor() { return log.getActiveColor(); }
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @return The current en passant square, or null if there isn't any
 | 
			
		||||
	 */
 | 
			
		||||
	public Position getEnPassantSquare() { return log.getLast() == null ? null : log.getLast().enPassant; }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -18,27 +18,40 @@ public class King extends Piece {
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean isValidMove(Move move) {
 | 
			
		||||
		// Castling
 | 
			
		||||
		if (getMoveCounter() == 0 && move.xDist == 2 && move.yDist == 0) {
 | 
			
		||||
 | 
			
		||||
			// Kingside
 | 
			
		||||
			if (board.getBoardArr()[7][move.pos.y] != null && board.getBoardArr()[7][move.pos.y].getType() == Type.ROOK
 | 
			
		||||
					&& isFreePath(new Move(new Position(5, move.pos.y), new Position(7, move.pos.y)))) {
 | 
			
		||||
		if (move.xDist == 2 && move.yDist == 0) {
 | 
			
		||||
			if (canCastleKingside()) {
 | 
			
		||||
				move.type = Move.Type.CASTLING;
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// Queenside
 | 
			
		||||
			if (board.getBoardArr()[0][move.pos.y] != null && board.getBoardArr()[0][move.pos.y].getType() == Type.ROOK
 | 
			
		||||
					&& isFreePath(new Move(new Position(1, move.pos.y), new Position(4, move.pos.y)))) {
 | 
			
		||||
			if (canCastleQueenside()) {
 | 
			
		||||
				move.type = Move.Type.CASTLING;
 | 
			
		||||
				return true;
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return move.xDist <= 1 && move.yDist <= 1 && checkDestination(move);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean canCastleKingside() {
 | 
			
		||||
		if (getMoveCounter() == 0) {
 | 
			
		||||
			int			y		= getColor() == Color.WHITE ? 7 : 0;
 | 
			
		||||
			Position	rookPos	= new Position(7, y);
 | 
			
		||||
			Piece		rook	= board.get(rookPos);
 | 
			
		||||
			return rook != null && rook.getType() == Type.ROOK && rook.getMoveCounter() == 0
 | 
			
		||||
					&& isFreePath(new Move(new Position(4, y), new Position(6, y)));
 | 
			
		||||
		} else return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean canCastleQueenside() {
 | 
			
		||||
		if (getMoveCounter() == 0) {
 | 
			
		||||
			int			y		= getColor() == Color.WHITE ? 7 : 0;
 | 
			
		||||
			Position	rookPos	= new Position(0, y);
 | 
			
		||||
			Piece		rook	= board.get(rookPos);
 | 
			
		||||
			return rook != null && rook.getType() == Type.ROOK && rook.getMoveCounter() == 0
 | 
			
		||||
					&& isFreePath(new Move(new Position(4, y), new Position(1, y)));
 | 
			
		||||
		} else return false;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected List<Move> getPseudolegalMoves(Position pos) {
 | 
			
		||||
		List<Move> moves = new ArrayList<>();
 | 
			
		||||
@@ -50,31 +63,13 @@ public class King extends Piece {
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
		// Castling
 | 
			
		||||
		// TODO: Check attacked squares in between
 | 
			
		||||
		// TODO: Castling out of check?
 | 
			
		||||
		if (getMoveCounter() == 0) {
 | 
			
		||||
 | 
			
		||||
			// Kingside
 | 
			
		||||
			if (board.getBoardArr()[7][pos.y] != null && board.getBoardArr()[7][pos.y].getType() == Type.ROOK
 | 
			
		||||
					&& isFreePath(new Move(new Position(5, pos.y), new Position(7, pos.y))))
 | 
			
		||||
				moves.add(new Move(pos, new Position(6, pos.y), Move.Type.CASTLING));
 | 
			
		||||
 | 
			
		||||
			// Queenside
 | 
			
		||||
			if (board.getBoardArr()[0][pos.y] != null && board.getBoardArr()[0][pos.y].getType() == Type.ROOK
 | 
			
		||||
					&& isFreePath(new Move(new Position(1, pos.y), new Position(4, pos.y))))
 | 
			
		||||
				moves.add(new Move(pos, new Position(2, pos.y), Move.Type.CASTLING));
 | 
			
		||||
		}
 | 
			
		||||
		// TODO: Condition: cannot castle out of, through or into check
 | 
			
		||||
		if (canCastleKingside()) moves.add(new Move(pos, new Position(6, pos.y), Move.Type.CASTLING));
 | 
			
		||||
		if (canCastleQueenside()) moves.add(new Move(pos, new Position(2, pos.y), Move.Type.CASTLING));
 | 
			
		||||
 | 
			
		||||
		return moves;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected boolean isFreePath(Move move) {
 | 
			
		||||
		for (int i = move.pos.x, j = move.pos.y; i != move.dest.x || j != move.dest.y; i += move.xSign, j += move.ySign)
 | 
			
		||||
			if (board.getBoardArr()[i][j] != null) return false;
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public Type getType() { return Type.KING; }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ package dev.kske.chess.board;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import dev.kske.chess.board.Piece.Color;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>Chess</strong><br>
 | 
			
		||||
 * File: <strong>Log.java</strong><br>
 | 
			
		||||
@@ -12,21 +14,44 @@ import java.util.List;
 | 
			
		||||
public class Log implements Cloneable {
 | 
			
		||||
 | 
			
		||||
	private List<LoggedMove>	moves;
 | 
			
		||||
	private Color				activeColor;
 | 
			
		||||
 | 
			
		||||
	public Log() {
 | 
			
		||||
		moves		= new ArrayList<>();
 | 
			
		||||
		activeColor	= Color.WHITE;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void add(Move move, Piece capturedPiece) {
 | 
			
		||||
		moves.add(new LoggedMove(move, capturedPiece));
 | 
			
		||||
	public void add(Move move, Piece capturedPiece, boolean pawnMove) {
 | 
			
		||||
		// En passant availability
 | 
			
		||||
		Position enPassant = null;
 | 
			
		||||
		if (pawnMove && move.yDist == 2) enPassant = new Position(move.pos.x, move.pos.y + move.ySign);
 | 
			
		||||
 | 
			
		||||
		// Fullmove counter and halfmove clock
 | 
			
		||||
		int fullmoveCounter, halfmoveClock;
 | 
			
		||||
		if (moves.isEmpty()) {
 | 
			
		||||
			fullmoveCounter	= 1;
 | 
			
		||||
			halfmoveClock	= 0;
 | 
			
		||||
		} else {
 | 
			
		||||
			fullmoveCounter = getLast().fullmoveCounter;
 | 
			
		||||
			if (activeColor == Color.BLACK) ++fullmoveCounter;
 | 
			
		||||
			halfmoveClock = capturedPiece != null || pawnMove ? 0 : getLast().halfmoveClock + 1;
 | 
			
		||||
		}
 | 
			
		||||
		activeColor = activeColor.opposite();
 | 
			
		||||
		moves.add(new LoggedMove(move, capturedPiece, enPassant, fullmoveCounter, halfmoveClock));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public LoggedMove getLast() {
 | 
			
		||||
		return moves.get(moves.size() - 1);
 | 
			
		||||
	}
 | 
			
		||||
	public LoggedMove getLast() { return moves.isEmpty() ? null : moves.get(moves.size() - 1); }
 | 
			
		||||
 | 
			
		||||
	public void removeLast() {
 | 
			
		||||
		if (!moves.isEmpty()) moves.remove(moves.size() - 1);
 | 
			
		||||
		if (!moves.isEmpty()) {
 | 
			
		||||
			activeColor = activeColor.opposite();
 | 
			
		||||
			moves.remove(moves.size() - 1);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void reset() {
 | 
			
		||||
		moves.clear();
 | 
			
		||||
		activeColor = Color.WHITE;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
	@Override
 | 
			
		||||
@@ -42,14 +67,21 @@ public class Log implements Cloneable {
 | 
			
		||||
		return log;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Color getActiveColor() { return activeColor; }
 | 
			
		||||
 | 
			
		||||
	public static class LoggedMove {
 | 
			
		||||
 | 
			
		||||
		public final Move	move;
 | 
			
		||||
		public final Piece	capturedPiece;
 | 
			
		||||
		public final Position	enPassant;
 | 
			
		||||
		public final int	fullmoveCounter, halfmoveClock;
 | 
			
		||||
 | 
			
		||||
		public LoggedMove(Move move, Piece capturedPiece) {
 | 
			
		||||
		public LoggedMove(Move move, Piece capturedPiece, Position enPassant, int fullmoveCounter, int halfmoveClock) {
 | 
			
		||||
			this.move				= move;
 | 
			
		||||
			this.capturedPiece		= capturedPiece;
 | 
			
		||||
			this.enPassant			= enPassant;
 | 
			
		||||
			this.fullmoveCounter	= fullmoveCounter;
 | 
			
		||||
			this.halfmoveClock		= halfmoveClock;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -30,6 +30,11 @@ public class Move {
 | 
			
		||||
		this(new Position(xPos, yPos), new Position(xDest, yDest));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static Move fromSAN(String move) {
 | 
			
		||||
		return new Move(Position.fromSAN(move.substring(0, 2)),
 | 
			
		||||
				Position.fromSAN(move.substring(2)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public boolean isHorizontal() { return yDist == 0; }
 | 
			
		||||
 | 
			
		||||
	public boolean isVertical() { return xDist == 0; }
 | 
			
		||||
 
 | 
			
		||||
@@ -17,10 +17,10 @@ public class Pawn extends Piece {
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean isValidMove(Move move) {
 | 
			
		||||
		// TODO: en passant
 | 
			
		||||
		boolean	step		= move.isVertical() && move.yDist == 1;
 | 
			
		||||
		boolean	doubleStep	= move.isVertical() && move.yDist == 2;
 | 
			
		||||
		boolean	strafe		= move.isDiagonal() && move.xDist == 1;
 | 
			
		||||
		boolean	enPassant	= false;
 | 
			
		||||
		if (getColor() == Color.WHITE) doubleStep &= move.pos.y == 6;
 | 
			
		||||
		else doubleStep &= move.pos.y == 1;
 | 
			
		||||
 | 
			
		||||
@@ -28,7 +28,14 @@ public class Pawn extends Piece {
 | 
			
		||||
		if (move.ySign == 1 && move.pos.y == 6 || move.ySign == -1 && move.pos.y == 1)
 | 
			
		||||
			move.type = Move.Type.PAWN_PROMOTION;
 | 
			
		||||
 | 
			
		||||
		return (step ^ doubleStep ^ strafe) && move.ySign == (getColor() == Color.WHITE ? -1 : 1) && isFreePath(move);
 | 
			
		||||
		// Mark the move as en passant if necessary
 | 
			
		||||
		if (strafe && move.dest.equals(board.getEnPassantSquare())) {
 | 
			
		||||
			enPassant	= true;
 | 
			
		||||
			move.type	= Move.Type.EN_PASSANT;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return enPassant || (step ^ doubleStep ^ strafe) && move.ySign == (getColor() == Color.WHITE ? -1 : 1)
 | 
			
		||||
				&& isFreePath(move);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
@@ -72,10 +79,16 @@ public class Pawn extends Piece {
 | 
			
		||||
			if (isFreePath(move)) moves.add(move);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Mark moves as pawn promotions if necessary
 | 
			
		||||
		// Mark moves as pawn promotion if necessary
 | 
			
		||||
		if (sign == 1 && pos.y == 6 || sign == -1 && pos.y == 1)
 | 
			
		||||
			moves.parallelStream().forEach(m -> m.type = Move.Type.PAWN_PROMOTION);
 | 
			
		||||
 | 
			
		||||
		// Add en passant move if necessary
 | 
			
		||||
		if (board.getEnPassantSquare() != null) {
 | 
			
		||||
			Move move = new Move(pos, board.getEnPassantSquare(), Move.Type.EN_PASSANT);
 | 
			
		||||
			if (move.isDiagonal() && move.xDist == 1) moves.add(move);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return moves;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -35,8 +35,16 @@ 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
 | 
			
		||||
	 * free.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param move The move to check
 | 
			
		||||
	 */
 | 
			
		||||
	protected boolean isFreePath(Move move) {
 | 
			
		||||
		// Only check destination by default
 | 
			
		||||
		for (int i = move.pos.x + move.xSign, j = move.pos.y + move.ySign; i != move.dest.x
 | 
			
		||||
				|| j != move.dest.y; i += move.xSign, j += move.ySign)
 | 
			
		||||
			if (board.getBoardArr()[i][j] != null) return false;
 | 
			
		||||
		return checkDestination(move);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -86,5 +94,9 @@ public abstract class Piece implements Cloneable {
 | 
			
		||||
		public Color opposite() {
 | 
			
		||||
			return this == WHITE ? BLACK : WHITE;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public char firstChar() {
 | 
			
		||||
			return this == WHITE ? 'w' : 'b';
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,8 +15,36 @@ public class Position {
 | 
			
		||||
		this.y	= y;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static Position fromSAN(String pos) {
 | 
			
		||||
		return new Position(pos.charAt(0) - 97, 8 - Character.getNumericValue(pos.charAt(1)));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public String toSAN() {
 | 
			
		||||
		return String.valueOf((char) (x + 97)) + String.valueOf(8 - y);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	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;
 | 
			
		||||
		return result;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public boolean equals(Object obj) {
 | 
			
		||||
		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;
 | 
			
		||||
		return true;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,22 +20,6 @@ public class Queen extends Piece {
 | 
			
		||||
		return ((move.isHorizontal() || move.isVertical()) || move.isDiagonal()) && isFreePath(move);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected boolean isFreePath(Move move) {
 | 
			
		||||
		if (move.isHorizontal()) {
 | 
			
		||||
			for (int i = move.pos.x + move.xSign; i != move.dest.x; i += move.xSign)
 | 
			
		||||
				if (board.getBoardArr()[i][move.pos.y] != null) return false;
 | 
			
		||||
		} else if (move.isVertical()) {
 | 
			
		||||
			for (int i = move.pos.y + move.ySign; i != move.dest.y; i += move.ySign)
 | 
			
		||||
				if (board.getBoardArr()[move.pos.x][i] != null) return false;
 | 
			
		||||
		} else {
 | 
			
		||||
			for (int i = move.pos.x + move.xSign, j = move.pos.y
 | 
			
		||||
					+ move.ySign; i != move.dest.x; i += move.xSign, j += move.ySign)
 | 
			
		||||
				if (board.getBoardArr()[i][j] != null) return false;
 | 
			
		||||
		}
 | 
			
		||||
		return checkDestination(move);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected List<Move> getPseudolegalMoves(Position pos) {
 | 
			
		||||
		List<Move> moves = new ArrayList<>();
 | 
			
		||||
 
 | 
			
		||||
@@ -20,18 +20,6 @@ public class Rook extends Piece {
 | 
			
		||||
		return (move.isHorizontal() || move.isVertical()) && isFreePath(move);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected boolean isFreePath(Move move) {
 | 
			
		||||
		if (move.isHorizontal()) {
 | 
			
		||||
			for (int i = move.pos.x + move.xSign; i != move.dest.x; i += move.xSign)
 | 
			
		||||
				if (board.getBoardArr()[i][move.pos.y] != null) return false;
 | 
			
		||||
		} else {
 | 
			
		||||
			for (int i = move.pos.y + move.ySign; i != move.dest.y; i += move.ySign)
 | 
			
		||||
				if (board.getBoardArr()[move.pos.x][i] != null) return false;
 | 
			
		||||
		}
 | 
			
		||||
		return checkDestination(move);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	protected List<Move> getPseudolegalMoves(Position pos) {
 | 
			
		||||
		List<Move> moves = new ArrayList<>();
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ package dev.kske.chess.game;
 | 
			
		||||
import java.util.HashMap;
 | 
			
		||||
import java.util.Map;
 | 
			
		||||
 | 
			
		||||
import javax.swing.JOptionPane;
 | 
			
		||||
 | 
			
		||||
import dev.kske.chess.board.Board;
 | 
			
		||||
import dev.kske.chess.board.GameState;
 | 
			
		||||
import dev.kske.chess.board.Move;
 | 
			
		||||
@@ -10,6 +12,8 @@ import dev.kske.chess.board.Piece.Color;
 | 
			
		||||
import dev.kske.chess.game.ai.AIPlayer;
 | 
			
		||||
import dev.kske.chess.ui.BoardComponent;
 | 
			
		||||
import dev.kske.chess.ui.BoardPane;
 | 
			
		||||
import dev.kske.chess.ui.EngineUtil;
 | 
			
		||||
import dev.kske.chess.ui.EngineUtil.EngineInfo;
 | 
			
		||||
import dev.kske.chess.ui.OverlayComponent;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -25,59 +29,55 @@ public class Game {
 | 
			
		||||
	private OverlayComponent	overlayComponent;
 | 
			
		||||
	private BoardComponent		boardComponent;
 | 
			
		||||
 | 
			
		||||
	public Game(Map<Color, Player> players, BoardPane boardPane) {
 | 
			
		||||
		this.players			= players;
 | 
			
		||||
		this.overlayComponent	= boardPane.getOverlayComponent();
 | 
			
		||||
		this.boardComponent		= boardPane.getBoardComponent();
 | 
			
		||||
		this.board				= new Board();
 | 
			
		||||
	public Game(BoardPane boardPane, String whiteName, String blackName) {
 | 
			
		||||
		players				= new HashMap<>();
 | 
			
		||||
		board				= new Board();
 | 
			
		||||
		overlayComponent	= boardPane.getOverlayComponent();
 | 
			
		||||
		boardComponent		= boardPane.getBoardComponent();
 | 
			
		||||
		boardComponent.setBoard(board);
 | 
			
		||||
 | 
			
		||||
		players.put(Color.WHITE, getPlayer(whiteName, Color.WHITE));
 | 
			
		||||
		players.put(Color.BLACK, getPlayer(blackName, Color.BLACK));
 | 
			
		||||
 | 
			
		||||
		// Initialize the game variable in each player
 | 
			
		||||
		players.values().forEach(player -> player.setGame(this));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static Game createNatural(BoardPane boardPane) {
 | 
			
		||||
		Map<Color, Player>	players	= new HashMap<>();
 | 
			
		||||
		OverlayComponent	overlay	= boardPane.getOverlayComponent();
 | 
			
		||||
 | 
			
		||||
		players.put(Color.WHITE, new NaturalPlayer(Color.WHITE, overlay));
 | 
			
		||||
		players.put(Color.BLACK, new NaturalPlayer(Color.BLACK, overlay));
 | 
			
		||||
		return new Game(players, boardPane);
 | 
			
		||||
	private Player getPlayer(String name, Color color) {
 | 
			
		||||
		switch (name) {
 | 
			
		||||
			case "Natural Player":
 | 
			
		||||
				return new NaturalPlayer(color, overlayComponent);
 | 
			
		||||
			case "AI Player":
 | 
			
		||||
				return new AIPlayer(color, 4, -10);
 | 
			
		||||
			default:
 | 
			
		||||
				for (EngineInfo info : EngineUtil.getEngineInfos())
 | 
			
		||||
					if (info.name.equals(name)) return new UCIPlayer(color, info.path);
 | 
			
		||||
				System.err.println("Invalid player name: " + name);
 | 
			
		||||
				return null;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	public static Game createNaturalVsAI(BoardPane boardPane, int maxDepth, int alphaBeta) {
 | 
			
		||||
		Map<Color, Player>	players	= new HashMap<>();
 | 
			
		||||
		OverlayComponent	overlay	= boardPane.getOverlayComponent();
 | 
			
		||||
 | 
			
		||||
		players.put(Color.WHITE, new NaturalPlayer(Color.WHITE, overlay));
 | 
			
		||||
		players.put(Color.BLACK, new AIPlayer(Color.BLACK, maxDepth, alphaBeta));
 | 
			
		||||
		return new Game(players, boardPane);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static Game createAIVsAI(BoardPane boardPane, int maxDepthW, int maxDepthB, int alphaBetaW, int alphaBetaB) {
 | 
			
		||||
		Map<Color, Player> players = new HashMap<>();
 | 
			
		||||
 | 
			
		||||
		players.put(Color.WHITE, new AIPlayer(Color.WHITE, maxDepthW, alphaBetaW));
 | 
			
		||||
		players.put(Color.BLACK, new AIPlayer(Color.BLACK, maxDepthB, alphaBetaB));
 | 
			
		||||
		return new Game(players, boardPane);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void onMove(Player player, Move move) {
 | 
			
		||||
		if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) {
 | 
			
		||||
			// Redraw
 | 
			
		||||
			boardComponent.repaint();
 | 
			
		||||
			overlayComponent.displayArrow(move);
 | 
			
		||||
 | 
			
		||||
			System.out.printf("%s: %s%n", player.color, move);
 | 
			
		||||
			System.out.println("FEN: " + board.toFEN());
 | 
			
		||||
			GameState eventType = board.getGameEventType(board.getDest(move).getColor().opposite());
 | 
			
		||||
			switch (eventType) {
 | 
			
		||||
				case CHECKMATE:
 | 
			
		||||
				case STALEMATE:
 | 
			
		||||
					System.out.printf("%s in %s!%n", player.color.opposite(), eventType);
 | 
			
		||||
					String result = String.format("%s in %s!%n", player.color.opposite(), eventType);
 | 
			
		||||
					System.out.print(result);
 | 
			
		||||
					JOptionPane.showMessageDialog(boardComponent, result);
 | 
			
		||||
					break;
 | 
			
		||||
				case CHECK:
 | 
			
		||||
					System.out.printf("%s in check!%n", player.color.opposite());
 | 
			
		||||
				default:
 | 
			
		||||
					boardComponent.repaint();
 | 
			
		||||
					players.get(player.color.opposite()).requestMove();
 | 
			
		||||
					players.get(board.getActiveColor()).requestMove();
 | 
			
		||||
			}
 | 
			
		||||
			overlayComponent.displayArrow(move);
 | 
			
		||||
		} else player.requestMove();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -86,7 +86,7 @@ public class Game {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void reset() {
 | 
			
		||||
		players.forEach((k, v) -> v.cancelMove());
 | 
			
		||||
		players.values().forEach(Player::cancelMove);
 | 
			
		||||
		board.initializeDefaultPositions();
 | 
			
		||||
		boardComponent.repaint();
 | 
			
		||||
		overlayComponent.clearDots();
 | 
			
		||||
@@ -94,11 +94,24 @@ public class Game {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Removed all connections between the game and the ui.
 | 
			
		||||
	 * Removed all connections between the game and the UI.
 | 
			
		||||
	 */
 | 
			
		||||
	public void disconnect() {
 | 
			
		||||
		players.values().forEach(Player::disconnect);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void swapColors() {
 | 
			
		||||
		players.values().forEach(Player::cancelMove);
 | 
			
		||||
		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);
 | 
			
		||||
		players.put(Color.BLACK, white);
 | 
			
		||||
		players.get(board.getActiveColor()).requestMove();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public Board getBoard() { return board; }
 | 
			
		||||
 | 
			
		||||
	public Map<Color, Player> getPlayers() { return players; }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,7 @@ public class NaturalPlayer extends Player implements MouseListener {
 | 
			
		||||
	public NaturalPlayer(Color color, OverlayComponent overlayComponent) {
 | 
			
		||||
		super(color);
 | 
			
		||||
		this.overlayComponent	= overlayComponent;
 | 
			
		||||
		name					= "Player";
 | 
			
		||||
		moveRequested			= false;
 | 
			
		||||
 | 
			
		||||
		overlayComponent.addMouseListener(this);
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ public abstract class Player {
 | 
			
		||||
	protected Game		game;
 | 
			
		||||
	protected Board		board;
 | 
			
		||||
	protected Color		color;
 | 
			
		||||
	protected String	name;
 | 
			
		||||
 | 
			
		||||
	public Player(Color color) {
 | 
			
		||||
		this.color = color;
 | 
			
		||||
@@ -39,4 +40,8 @@ public abstract class Player {
 | 
			
		||||
	public Color getColor() { return color; }
 | 
			
		||||
 | 
			
		||||
	public void setColor(Color color) { this.color = color; }
 | 
			
		||||
 | 
			
		||||
	public String getName() { return name; }
 | 
			
		||||
 | 
			
		||||
	public void setName(String name) { this.name = name; }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										92
									
								
								src/dev/kske/chess/game/UCIPlayer.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/dev/kske/chess/game/UCIPlayer.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
package dev.kske.chess.game;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
import dev.kske.chess.board.Move;
 | 
			
		||||
import dev.kske.chess.board.Piece.Color;
 | 
			
		||||
import dev.kske.chess.uci.UCIHandle;
 | 
			
		||||
import dev.kske.chess.uci.UCIListener;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>Chess</strong><br>
 | 
			
		||||
 * File: <strong>UCIPlayer.java</strong><br>
 | 
			
		||||
 * Created: <strong>18.07.2019</strong><br>
 | 
			
		||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
			
		||||
 */
 | 
			
		||||
public class UCIPlayer extends Player implements UCIListener {
 | 
			
		||||
 | 
			
		||||
	private UCIHandle		handle;
 | 
			
		||||
 | 
			
		||||
	public UCIPlayer(Color color, String enginePath) {
 | 
			
		||||
		super(color);
 | 
			
		||||
		try {
 | 
			
		||||
			handle = new UCIHandle(enginePath);
 | 
			
		||||
			handle.setListener(this);
 | 
			
		||||
			handle.start();
 | 
			
		||||
		} catch (IOException ex) {
 | 
			
		||||
			ex.printStackTrace();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void requestMove() {
 | 
			
		||||
		handle.positionFEN(board.toFEN());
 | 
			
		||||
		handle.go();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void cancelMove() {
 | 
			
		||||
		handle.stop();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void disconnect() {
 | 
			
		||||
		handle.quit();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onIdName(String name) {
 | 
			
		||||
		this.name = name;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onBestMove(String move) {
 | 
			
		||||
		Move moveObj = Move.fromSAN(move);
 | 
			
		||||
		game.onMove(this, moveObj);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onBestMove(String move, Move ponderMove) {
 | 
			
		||||
		onBestMove(move);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onCopyProtectionChecking() {
 | 
			
		||||
		System.out.println("Copy protection checking...");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onCopyProtectionOk() {
 | 
			
		||||
		System.out.println("Copy protection ok");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onCopyProtectionError() {
 | 
			
		||||
		System.err.println("Copy protection error!");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onRegistrationChecking() {
 | 
			
		||||
		System.out.println("Registration checking...");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onRegistrationOk() {
 | 
			
		||||
		System.out.println("Registration ok");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void onRegistrationError() {
 | 
			
		||||
		System.err.println("Registration error!");
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -32,6 +32,7 @@ public class AIPlayer extends Player {
 | 
			
		||||
 | 
			
		||||
	public AIPlayer(Color color, int maxDepth, int alphaBetaThreshold) {
 | 
			
		||||
		super(color);
 | 
			
		||||
		name					= "AIPlayer";
 | 
			
		||||
		availableProcessors		= Runtime.getRuntime().availableProcessors();
 | 
			
		||||
		this.maxDepth			= maxDepth;
 | 
			
		||||
		this.alphaBetaThreshold	= alphaBetaThreshold;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										143
									
								
								src/dev/kske/chess/uci/UCIHandle.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/dev/kske/chess/uci/UCIHandle.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
package dev.kske.chess.uci;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.PrintWriter;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>Chess</strong><br>
 | 
			
		||||
 * File: <strong>UCIHandle.java</strong><br>
 | 
			
		||||
 * Created: <strong>18.07.2019</strong><br>
 | 
			
		||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
			
		||||
 */
 | 
			
		||||
public class UCIHandle {
 | 
			
		||||
 | 
			
		||||
	private final Process		process;
 | 
			
		||||
	private final PrintWriter	out;
 | 
			
		||||
	private final UCIReceiver	receiver;
 | 
			
		||||
 | 
			
		||||
	public UCIHandle(String enginePath) throws IOException {
 | 
			
		||||
		process		= new ProcessBuilder(enginePath).start();
 | 
			
		||||
		out			= new PrintWriter(process.getOutputStream(), true);
 | 
			
		||||
		receiver	= new UCIReceiver(process.getInputStream());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void start() {
 | 
			
		||||
		new Thread(receiver, "UCI Receiver").start();
 | 
			
		||||
		uci();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Tells the engine to use 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
 | 
			
		||||
	 */
 | 
			
		||||
	public void debug(boolean debug) {
 | 
			
		||||
		out.println("debug " + (debug ? "on" : "off"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Synchronized the engine with the GUI
 | 
			
		||||
	 */
 | 
			
		||||
	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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Changes an internal parameter of the engine.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param name  The name of the parameter
 | 
			
		||||
	 * @param value The value of the parameter
 | 
			
		||||
	 */
 | 
			
		||||
	public void setOption(String name, String value) {
 | 
			
		||||
		out.printf("setoption name %s value %s%n", name, value);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Registers the engine
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param name The name the engine should be registered with
 | 
			
		||||
	 * @param code The code the engine should be registered with
 | 
			
		||||
	 */
 | 
			
		||||
	public void register(String name, String code) {
 | 
			
		||||
		out.printf("register %s %s%n", name, code);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Tells the engine to postpone the registration.
 | 
			
		||||
	 */
 | 
			
		||||
	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");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: position
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Sets up the position in its initial state.
 | 
			
		||||
	 */
 | 
			
		||||
	public void startPosition() {
 | 
			
		||||
		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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// TODO: go with parameters
 | 
			
		||||
 | 
			
		||||
	public void go() {
 | 
			
		||||
		out.println("go");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Stops calculation as soon as possible.
 | 
			
		||||
	 */
 | 
			
		||||
	public void stop() {
 | 
			
		||||
		out.println("stop");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Tells the engine that the user has played the expected move.
 | 
			
		||||
	 */
 | 
			
		||||
	public void ponderHit() {
 | 
			
		||||
		out.println("ponderhit");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Quits the engine process as soon as possible.
 | 
			
		||||
	 */
 | 
			
		||||
	public void quit() {
 | 
			
		||||
		out.println("quit");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void setListener(UCIListener listener) {
 | 
			
		||||
		receiver.addListener(listener);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										198
									
								
								src/dev/kske/chess/uci/UCIInfo.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								src/dev/kske/chess/uci/UCIInfo.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,198 @@
 | 
			
		||||
package dev.kske.chess.uci;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import dev.kske.chess.board.Move;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>Chess</strong><br>
 | 
			
		||||
 * File: <strong>UCIInfo.java</strong><br>
 | 
			
		||||
 * Created: <strong>28.07.2019</strong><br>
 | 
			
		||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
			
		||||
 */
 | 
			
		||||
public class UCIInfo {
 | 
			
		||||
 | 
			
		||||
	private int			depth, seldepth, time, nodes, multipv, currmovenumber, hashfull, nps, tbhits, sbhits, cpuload,
 | 
			
		||||
			cpunr;
 | 
			
		||||
	private List<Move>	pv, refutation, currline;
 | 
			
		||||
	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");
 | 
			
		||||
 | 
			
		||||
	public UCIInfo(String line) {
 | 
			
		||||
		pv			= new ArrayList<>();
 | 
			
		||||
		refutation	= new ArrayList<>();
 | 
			
		||||
		currline	= new ArrayList<>();
 | 
			
		||||
		String[] tokens = line.split(" ");
 | 
			
		||||
 | 
			
		||||
		for (int i = 0; i < tokens.length; i++)
 | 
			
		||||
			switch (tokens[i]) {
 | 
			
		||||
				// Single parameter info
 | 
			
		||||
				case "depth":
 | 
			
		||||
					depth = Integer.parseInt(tokens[++i]);
 | 
			
		||||
					break;
 | 
			
		||||
				case "seldepth":
 | 
			
		||||
					seldepth = Integer.parseInt(tokens[++i]);
 | 
			
		||||
					break;
 | 
			
		||||
				case "time":
 | 
			
		||||
					time = Integer.parseInt(tokens[++i]);
 | 
			
		||||
					break;
 | 
			
		||||
				case "nodes":
 | 
			
		||||
					nodes = Integer.parseInt(tokens[++i]);
 | 
			
		||||
					break;
 | 
			
		||||
				case "multipv":
 | 
			
		||||
					multipv = Integer.parseInt(tokens[++i]);
 | 
			
		||||
					break;
 | 
			
		||||
				case "currmove":
 | 
			
		||||
					currmove = Move.fromSAN(tokens[++i]);
 | 
			
		||||
					break;
 | 
			
		||||
				case "currmovenumber":
 | 
			
		||||
					currmovenumber = Integer.parseInt(tokens[++i]);
 | 
			
		||||
					break;
 | 
			
		||||
				case "hashfull":
 | 
			
		||||
					hashfull = Integer.parseInt(tokens[++i]);
 | 
			
		||||
					break;
 | 
			
		||||
				case "nps":
 | 
			
		||||
					nps = Integer.parseInt(tokens[++i]);
 | 
			
		||||
					break;
 | 
			
		||||
				case "tbhits":
 | 
			
		||||
					tbhits = Integer.parseInt(tokens[++i]);
 | 
			
		||||
					break;
 | 
			
		||||
				case "sbhits":
 | 
			
		||||
					sbhits = Integer.parseInt(tokens[++i]);
 | 
			
		||||
					break;
 | 
			
		||||
				case "cpuload":
 | 
			
		||||
					cpuload = Integer.parseInt(tokens[++i]);
 | 
			
		||||
					break;
 | 
			
		||||
				case "string":
 | 
			
		||||
					displayString = tokens[++i];
 | 
			
		||||
					break;
 | 
			
		||||
				case "score":
 | 
			
		||||
					score = new Score(line.substring(line.indexOf("score") + tokens[i].length() + 1));
 | 
			
		||||
					i += score.getLength() + 1;
 | 
			
		||||
					break;
 | 
			
		||||
				case "pv":
 | 
			
		||||
					while (++i < tokens.length && !params.contains(tokens[i]))
 | 
			
		||||
						pv.add(Move.fromSAN(tokens[i]));
 | 
			
		||||
					break;
 | 
			
		||||
				case "refutation":
 | 
			
		||||
					while (++i < tokens.length && !params.contains(tokens[i]))
 | 
			
		||||
						refutation.add(Move.fromSAN(tokens[i]));
 | 
			
		||||
					break;
 | 
			
		||||
				// TODO: currline
 | 
			
		||||
				case "currline":
 | 
			
		||||
					while (++i < tokens.length && !params.contains(tokens[i]))
 | 
			
		||||
						;
 | 
			
		||||
					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]);
 | 
			
		||||
			}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public int getDepth() { return depth; }
 | 
			
		||||
 | 
			
		||||
	public int getSeldepth() { return seldepth; }
 | 
			
		||||
 | 
			
		||||
	public int getTime() { return time; }
 | 
			
		||||
 | 
			
		||||
	public int getNodes() { return nodes; }
 | 
			
		||||
 | 
			
		||||
	public int getMultipv() { return multipv; }
 | 
			
		||||
 | 
			
		||||
	public int getCurrmovenumber() { return currmovenumber; }
 | 
			
		||||
 | 
			
		||||
	public int getHashfull() { return hashfull; }
 | 
			
		||||
 | 
			
		||||
	public int getNps() { return nps; }
 | 
			
		||||
 | 
			
		||||
	public int getTbhits() { return tbhits; }
 | 
			
		||||
 | 
			
		||||
	public int getSbhits() { return sbhits; }
 | 
			
		||||
 | 
			
		||||
	public int getCpuload() { return cpuload; }
 | 
			
		||||
 | 
			
		||||
	public int getCpunr() { return cpunr; }
 | 
			
		||||
 | 
			
		||||
	public List<Move> getPv() { return pv; }
 | 
			
		||||
 | 
			
		||||
	public List<Move> getRefutation() { return refutation; }
 | 
			
		||||
 | 
			
		||||
	public List<Move> getCurrline() { return currline; }
 | 
			
		||||
 | 
			
		||||
	public Move getCurrmove() { return currmove; }
 | 
			
		||||
 | 
			
		||||
	public Score getScore() { return score; }
 | 
			
		||||
 | 
			
		||||
	public String getDisplayString() { return displayString; }
 | 
			
		||||
 | 
			
		||||
	public static class Score {
 | 
			
		||||
 | 
			
		||||
		private int		cp, mate;
 | 
			
		||||
		private boolean	lowerbound, upperbound;
 | 
			
		||||
		private int		length;
 | 
			
		||||
 | 
			
		||||
		public Score(String line) {
 | 
			
		||||
			String[]	tokens	= line.split(" ");
 | 
			
		||||
			int			i		= 0;
 | 
			
		||||
			for (; i < tokens.length; i++) {
 | 
			
		||||
				if (params.contains(tokens[i])) break;
 | 
			
		||||
				switch (tokens[i]) {
 | 
			
		||||
					case "cp":
 | 
			
		||||
						cp = Integer.parseInt(tokens[++i]);
 | 
			
		||||
						break;
 | 
			
		||||
					case "mate":
 | 
			
		||||
						mate = Integer.parseInt(tokens[++i]);
 | 
			
		||||
						break;
 | 
			
		||||
					case "lowerbound":
 | 
			
		||||
						lowerbound = true;
 | 
			
		||||
						break;
 | 
			
		||||
					case "upperbound":
 | 
			
		||||
						upperbound = true;
 | 
			
		||||
						break;
 | 
			
		||||
					default:
 | 
			
		||||
						System.err.printf("Unknown parameter '%s' for command 'score' found!%n", tokens[i]);
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
			length = i + 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		public int getCp() { return cp; }
 | 
			
		||||
 | 
			
		||||
		public int getMate() { return mate; }
 | 
			
		||||
 | 
			
		||||
		public boolean isLowerbound() { return lowerbound; }
 | 
			
		||||
 | 
			
		||||
		public boolean isUpperbound() { return upperbound; }
 | 
			
		||||
 | 
			
		||||
		/**
 | 
			
		||||
		 * @return The number of tokens this 'score' command contains (including
 | 
			
		||||
		 *         itself).
 | 
			
		||||
		 */
 | 
			
		||||
		public int getLength() { return length; }
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										94
									
								
								src/dev/kske/chess/uci/UCIListener.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/dev/kske/chess/uci/UCIListener.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
package dev.kske.chess.uci;
 | 
			
		||||
 | 
			
		||||
import dev.kske.chess.board.Move;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>Chess</strong><br>
 | 
			
		||||
 * File: <strong>UCIListener.java</strong><br>
 | 
			
		||||
 * Created: <strong>19.07.2019</strong><br>
 | 
			
		||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
			
		||||
 */
 | 
			
		||||
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) {}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The engine is ready in UCI mode.
 | 
			
		||||
	 */
 | 
			
		||||
	default void onUCIOk() {}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The engine has processed all inputs and is ready for new commands.
 | 
			
		||||
	 */
 | 
			
		||||
	default void onReadyOk() {}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * 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
 | 
			
		||||
	 */
 | 
			
		||||
	default void onBestMove(String move, Move ponderMove) {}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The engine will check the copy protection now.
 | 
			
		||||
	 */
 | 
			
		||||
	default void onCopyProtectionChecking() {}
 | 
			
		||||
	/**
 | 
			
		||||
	 * The engine has successfully checked the copy protection.
 | 
			
		||||
	 */
 | 
			
		||||
	default void onCopyProtectionOk() {}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The engine has encountered an error during copy protection checking.
 | 
			
		||||
	 */
 | 
			
		||||
	default void onCopyProtectionError() {}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The engine will check the registration now.
 | 
			
		||||
	 */
 | 
			
		||||
	default void onRegistrationChecking() {}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The engine has successfully checked the registration.
 | 
			
		||||
	 */
 | 
			
		||||
	default void onRegistrationOk() {}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The engine has encountered an error during registration checking.
 | 
			
		||||
	 */
 | 
			
		||||
	default void onRegistrationError() {}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * The engine sends information to the GUI.
 | 
			
		||||
	 * 
 | 
			
		||||
	 * @param additionalInfo 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) {}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										68
									
								
								src/dev/kske/chess/uci/UCIOption.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/dev/kske/chess/uci/UCIOption.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
			
		||||
package dev.kske.chess.uci;
 | 
			
		||||
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.StringJoiner;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>Chess</strong><br>
 | 
			
		||||
 * File: <strong>UCIOption.java</strong><br>
 | 
			
		||||
 * Created: <strong>22.07.2019</strong><br>
 | 
			
		||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
			
		||||
 */
 | 
			
		||||
public class UCIOption {
 | 
			
		||||
 | 
			
		||||
	private String			name, defaultVal, minVal, maxVal;
 | 
			
		||||
	private GUIType			type;
 | 
			
		||||
	private List<String>	varList;
 | 
			
		||||
 | 
			
		||||
	public UCIOption(String line) {
 | 
			
		||||
		varList = new ArrayList<>();
 | 
			
		||||
		String[] tokens = line.split(" ");
 | 
			
		||||
 | 
			
		||||
		for (int i = 0; i < tokens.length; i++)
 | 
			
		||||
			switch (tokens[i]) {
 | 
			
		||||
				case "name":
 | 
			
		||||
					StringJoiner nameJoiner = new StringJoiner(" ");
 | 
			
		||||
					while (!Arrays.asList("type", "default", "min", "max", "var").contains(tokens[i + 1]))
 | 
			
		||||
						nameJoiner.add(tokens[++i]);
 | 
			
		||||
					name = nameJoiner.toString();
 | 
			
		||||
					break;
 | 
			
		||||
				case "type":
 | 
			
		||||
					type = GUIType.valueOf(tokens[++i].toUpperCase());
 | 
			
		||||
					break;
 | 
			
		||||
				case "default":
 | 
			
		||||
					// Default string may be empty
 | 
			
		||||
					defaultVal = i == tokens.length - 1 ? "" : tokens[++i];
 | 
			
		||||
					break;
 | 
			
		||||
				case "min":
 | 
			
		||||
					minVal = tokens[++i];
 | 
			
		||||
					break;
 | 
			
		||||
				case "max":
 | 
			
		||||
					maxVal = tokens[++i];
 | 
			
		||||
					break;
 | 
			
		||||
				case "var":
 | 
			
		||||
					varList.add(tokens[++i]);
 | 
			
		||||
					break;
 | 
			
		||||
				default:
 | 
			
		||||
					System.err.printf("Unknown parameter '%s' for command 'option' found!%n", tokens[i]);
 | 
			
		||||
			}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public String getName() { return name; }
 | 
			
		||||
 | 
			
		||||
	public String getDefaultVal() { return defaultVal; }
 | 
			
		||||
 | 
			
		||||
	public String getMinVal() { return minVal; }
 | 
			
		||||
 | 
			
		||||
	public String getMaxVal() { return maxVal; }
 | 
			
		||||
 | 
			
		||||
	public GUIType getType() { return type; }
 | 
			
		||||
 | 
			
		||||
	public List<String> getVarList() { return varList; }
 | 
			
		||||
 | 
			
		||||
	public static enum GUIType {
 | 
			
		||||
		CHECK, SPIN, COMBO, BUTTON, STRING
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										143
									
								
								src/dev/kske/chess/uci/UCIReceiver.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										143
									
								
								src/dev/kske/chess/uci/UCIReceiver.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,143 @@
 | 
			
		||||
package dev.kske.chess.uci;
 | 
			
		||||
 | 
			
		||||
import java.io.BufferedReader;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.InputStream;
 | 
			
		||||
import java.io.InputStreamReader;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import dev.kske.chess.board.Move;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>Chess</strong><br>
 | 
			
		||||
 * File: <strong>UCIReceiver.java</strong><br>
 | 
			
		||||
 * Created: <strong>19.07.2019</strong><br>
 | 
			
		||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
			
		||||
 */
 | 
			
		||||
public class UCIReceiver implements Runnable {
 | 
			
		||||
 | 
			
		||||
	private final BufferedReader in;
 | 
			
		||||
 | 
			
		||||
	private List<UCIListener> listeners;
 | 
			
		||||
 | 
			
		||||
	public UCIReceiver(InputStream in) {
 | 
			
		||||
		this.in		= new BufferedReader(new InputStreamReader(in));
 | 
			
		||||
		listeners	= new ArrayList<>();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
	public void run() {
 | 
			
		||||
		String line;
 | 
			
		||||
		while (!Thread.currentThread().isInterrupted())
 | 
			
		||||
			try {
 | 
			
		||||
				if ((line = in.readLine()) != null && !line.isEmpty()) parse(line);
 | 
			
		||||
			} catch (IndexOutOfBoundsException ex) {
 | 
			
		||||
				System.err.println("Too few arguments were provided!");
 | 
			
		||||
				ex.printStackTrace();
 | 
			
		||||
			} catch (IOException ex) {
 | 
			
		||||
				ex.printStackTrace();
 | 
			
		||||
			}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void parse(String line) {
 | 
			
		||||
		int		spaceIndex	= line.indexOf(' ');
 | 
			
		||||
		String	command		= spaceIndex == -1 ? line : line.substring(0, spaceIndex);
 | 
			
		||||
		switch (command) {
 | 
			
		||||
			case "id":
 | 
			
		||||
				parseId(line.substring(command.length() + 1));
 | 
			
		||||
				break;
 | 
			
		||||
			case "uciok":
 | 
			
		||||
				listeners.forEach(UCIListener::onUCIOk);
 | 
			
		||||
				break;
 | 
			
		||||
			case "readyok":
 | 
			
		||||
				listeners.forEach(UCIListener::onReadyOk);
 | 
			
		||||
				break;
 | 
			
		||||
			case "bestmove":
 | 
			
		||||
				parseBestMove(line.substring(command.length() + 1));
 | 
			
		||||
				break;
 | 
			
		||||
			case "copyprotection":
 | 
			
		||||
				parseCopyProtection(line.substring(command.length() + 1));
 | 
			
		||||
				break;
 | 
			
		||||
			case "registration":
 | 
			
		||||
				parseRegistration(line.substring(command.length() + 1));
 | 
			
		||||
				break;
 | 
			
		||||
			case "info":
 | 
			
		||||
				parseInfo(line.substring(command.length() + 1));
 | 
			
		||||
				break;
 | 
			
		||||
			case "option":
 | 
			
		||||
				parseOption(line.substring(command.length() + 1));
 | 
			
		||||
				break;
 | 
			
		||||
			default:
 | 
			
		||||
				System.err.printf("Unknown command '%s' found!%n", command);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void parseId(String line) {
 | 
			
		||||
		String	param	= line.substring(0, line.indexOf(' '));
 | 
			
		||||
		String	arg		= line.substring(param.length() + 1);
 | 
			
		||||
		switch (param) {
 | 
			
		||||
			case "name":
 | 
			
		||||
				listeners.forEach(l -> l.onIdName(arg));
 | 
			
		||||
				break;
 | 
			
		||||
			case "author":
 | 
			
		||||
				listeners.forEach(l -> l.onIdAuthor(arg));
 | 
			
		||||
				break;
 | 
			
		||||
			default:
 | 
			
		||||
				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];
 | 
			
		||||
 | 
			
		||||
		// Ponder move
 | 
			
		||||
		if (tokens.length == 3) listeners.forEach(l -> l.onBestMove(move, Move.fromSAN(tokens[2])));
 | 
			
		||||
		else listeners.forEach(l -> l.onBestMove(move));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void parseCopyProtection(String line) {
 | 
			
		||||
		switch (line) {
 | 
			
		||||
			case "checking":
 | 
			
		||||
				listeners.forEach(UCIListener::onCopyProtectionChecking);
 | 
			
		||||
				break;
 | 
			
		||||
			case "ok":
 | 
			
		||||
				listeners.forEach(UCIListener::onCopyProtectionOk);
 | 
			
		||||
				break;
 | 
			
		||||
			case "error":
 | 
			
		||||
				listeners.forEach(UCIListener::onCopyProtectionError);
 | 
			
		||||
				break;
 | 
			
		||||
			default:
 | 
			
		||||
				System.err.printf("Unknown parameter '%s' for command 'copyprotection' found!%n", line);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void parseRegistration(String line) {
 | 
			
		||||
		switch (line) {
 | 
			
		||||
			case "checking":
 | 
			
		||||
				listeners.forEach(UCIListener::onRegistrationChecking);
 | 
			
		||||
				break;
 | 
			
		||||
			case "ok":
 | 
			
		||||
				listeners.forEach(UCIListener::onRegistrationOk);
 | 
			
		||||
				break;
 | 
			
		||||
			case "error":
 | 
			
		||||
				listeners.forEach(UCIListener::onRegistrationError);
 | 
			
		||||
				break;
 | 
			
		||||
			default:
 | 
			
		||||
				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 parseOption(String line) {
 | 
			
		||||
		listeners.forEach(l -> l.onOption(new UCIOption((line))));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void addListener(UCIListener listener) {
 | 
			
		||||
		listeners.add(listener);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -78,5 +78,4 @@ public class AIConfigDialog extends JDialog {
 | 
			
		||||
	public boolean isStartGame() { return startGame; }
 | 
			
		||||
 | 
			
		||||
	public void setStartGame(boolean startGame) { this.startGame = startGame; }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,6 @@
 | 
			
		||||
package dev.kske.chess.ui;
 | 
			
		||||
 | 
			
		||||
import java.awt.Dimension;
 | 
			
		||||
import java.awt.event.ComponentAdapter;
 | 
			
		||||
import java.awt.event.ComponentEvent;
 | 
			
		||||
 | 
			
		||||
import javax.swing.JLayeredPane;
 | 
			
		||||
 | 
			
		||||
@@ -24,23 +22,13 @@ public class BoardPane extends JLayeredPane {
 | 
			
		||||
	public BoardPane() {
 | 
			
		||||
		boardComponent		= new BoardComponent(this);
 | 
			
		||||
		overlayComponent	= new OverlayComponent(this);
 | 
			
		||||
		setLayer(overlayComponent, 1);
 | 
			
		||||
		setLayout(null);
 | 
			
		||||
 | 
			
		||||
		add(boardComponent, Integer.valueOf(1));
 | 
			
		||||
		add(overlayComponent, Integer.valueOf(2));
 | 
			
		||||
		add(boardComponent);
 | 
			
		||||
		add(overlayComponent);
 | 
			
		||||
 | 
			
		||||
		/*
 | 
			
		||||
		 * Add a component listener for adjusting the tile size on resizing.
 | 
			
		||||
		 * The size of the board is assumed to be 8x8, as well as the both the board and
 | 
			
		||||
		 * the tiles being square.
 | 
			
		||||
		 */
 | 
			
		||||
		addComponentListener(new ComponentAdapter() {
 | 
			
		||||
 | 
			
		||||
			@Override
 | 
			
		||||
			public void componentResized(ComponentEvent e) {
 | 
			
		||||
				tileSize = getWidth() / 8;
 | 
			
		||||
				TextureUtil.scalePieceTextures(tileSize);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
		tileSize = 60;
 | 
			
		||||
		setSize(getPreferredSize());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										98
									
								
								src/dev/kske/chess/ui/EngineUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/dev/kske/chess/ui/EngineUtil.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
			
		||||
package dev.kske.chess.ui;
 | 
			
		||||
 | 
			
		||||
import java.io.FileInputStream;
 | 
			
		||||
import java.io.FileOutputStream;
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.io.ObjectInputStream;
 | 
			
		||||
import java.io.ObjectOutputStream;
 | 
			
		||||
import java.io.Serializable;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import dev.kske.chess.uci.UCIHandle;
 | 
			
		||||
import dev.kske.chess.uci.UCIListener;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>Chess</strong><br>
 | 
			
		||||
 * File: <strong>MenuBar.java</strong><br>
 | 
			
		||||
 * Created: <strong>23.07.2019</strong><br>
 | 
			
		||||
 * Author: <strong>Leon Hofmeister</strong>
 | 
			
		||||
 */
 | 
			
		||||
public class EngineUtil {
 | 
			
		||||
 | 
			
		||||
	private static volatile List<EngineInfo> engineInfos;
 | 
			
		||||
 | 
			
		||||
	private static final String engineInfoFile = "engine_infos.ser";
 | 
			
		||||
 | 
			
		||||
	static {
 | 
			
		||||
		loadEngineInfos();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private EngineUtil() {}
 | 
			
		||||
 | 
			
		||||
	public static void addEngine(String enginePath) {
 | 
			
		||||
		try {
 | 
			
		||||
			EngineInfo	info	= new EngineInfo(enginePath);
 | 
			
		||||
			UCIHandle	handle	= new UCIHandle(enginePath);
 | 
			
		||||
			handle.setListener(new UCIListener() {
 | 
			
		||||
 | 
			
		||||
				@Override
 | 
			
		||||
				public void onIdName(String name) {
 | 
			
		||||
					info.name = name;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				@Override
 | 
			
		||||
				public void onIdAuthor(String author) {
 | 
			
		||||
					info.author = author;
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				@Override
 | 
			
		||||
				public void onUCIOk() {
 | 
			
		||||
					engineInfos.add(info);
 | 
			
		||||
					handle.quit();
 | 
			
		||||
					saveEngineInfos();
 | 
			
		||||
				}
 | 
			
		||||
			});
 | 
			
		||||
			handle.start();
 | 
			
		||||
		} catch (IOException ex) {
 | 
			
		||||
			ex.printStackTrace();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@SuppressWarnings("unchecked")
 | 
			
		||||
	private static void loadEngineInfos() {
 | 
			
		||||
		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.");
 | 
			
		||||
		} catch (ClassNotFoundException | IOException ex) {
 | 
			
		||||
			engineInfos = new ArrayList<>();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private static void saveEngineInfos() {
 | 
			
		||||
		try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(engineInfoFile))) {
 | 
			
		||||
			out.writeObject(engineInfos);
 | 
			
		||||
		} catch (IOException ex) {
 | 
			
		||||
			ex.printStackTrace();
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static class EngineInfo implements Serializable {
 | 
			
		||||
 | 
			
		||||
		private static final long serialVersionUID = -474177108900833005L;
 | 
			
		||||
 | 
			
		||||
		public String path, name, author;
 | 
			
		||||
 | 
			
		||||
		public EngineInfo(String path) {
 | 
			
		||||
			this.path = path;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		@Override
 | 
			
		||||
		public String toString() {
 | 
			
		||||
			return name + " by " + author + " at " + path;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public static List<EngineInfo> getEngineInfos() { return engineInfos; }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										82
									
								
								src/dev/kske/chess/ui/GameConfigurationDialog.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/dev/kske/chess/ui/GameConfigurationDialog.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
package dev.kske.chess.ui;
 | 
			
		||||
 | 
			
		||||
import java.awt.Font;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import javax.swing.DefaultComboBoxModel;
 | 
			
		||||
import javax.swing.JButton;
 | 
			
		||||
import javax.swing.JComboBox;
 | 
			
		||||
import javax.swing.JDialog;
 | 
			
		||||
import javax.swing.JLabel;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>Chess</strong><br>
 | 
			
		||||
 * File: <strong>GameConfigurationDialog.java</strong><br>
 | 
			
		||||
 * Created: <strong>24.07.2019</strong><br>
 | 
			
		||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
			
		||||
 */
 | 
			
		||||
public class GameConfigurationDialog extends JDialog {
 | 
			
		||||
 | 
			
		||||
	private static final long serialVersionUID = 9080577278529876972L;
 | 
			
		||||
 | 
			
		||||
	private String	whiteName, blackName;
 | 
			
		||||
	private boolean	startGame;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Create the dialog.
 | 
			
		||||
	 */
 | 
			
		||||
	public GameConfigurationDialog() {
 | 
			
		||||
		setTitle("Game Configuration");
 | 
			
		||||
		setBounds(100, 100, 281, 142);
 | 
			
		||||
		setModal(true);
 | 
			
		||||
		setLocationRelativeTo(null);
 | 
			
		||||
		getContentPane().setLayout(null);
 | 
			
		||||
 | 
			
		||||
		startGame = false;
 | 
			
		||||
		List<String> options = new ArrayList<>(Arrays.asList("Natural Player", "AI Player"));
 | 
			
		||||
		EngineUtil.getEngineInfos().forEach(info -> options.add(info.name));
 | 
			
		||||
 | 
			
		||||
		JLabel lblWhite = new JLabel("White:");
 | 
			
		||||
		lblWhite.setFont(new Font("Tahoma", Font.PLAIN, 14));
 | 
			
		||||
		lblWhite.setBounds(10, 11, 49, 14);
 | 
			
		||||
		getContentPane().add(lblWhite);
 | 
			
		||||
 | 
			
		||||
		JComboBox<Object> cbWhite = new JComboBox<>();
 | 
			
		||||
		cbWhite.setModel(new DefaultComboBoxModel<Object>(options.toArray()));
 | 
			
		||||
		cbWhite.setBounds(98, 9, 159, 22);
 | 
			
		||||
		getContentPane().add(cbWhite);
 | 
			
		||||
 | 
			
		||||
		JLabel lblBlack = new JLabel("Black:");
 | 
			
		||||
		lblBlack.setFont(new Font("Tahoma", Font.PLAIN, 14));
 | 
			
		||||
		lblBlack.setBounds(10, 38, 49, 14);
 | 
			
		||||
		getContentPane().add(lblBlack);
 | 
			
		||||
 | 
			
		||||
		JComboBox<Object> cbBlack = new JComboBox<>();
 | 
			
		||||
		cbBlack.setModel(new DefaultComboBoxModel<Object>(options.toArray()));
 | 
			
		||||
		cbBlack.setBounds(98, 36, 159, 22);
 | 
			
		||||
		getContentPane().add(cbBlack);
 | 
			
		||||
 | 
			
		||||
		JButton btnStart = new JButton("Start");
 | 
			
		||||
		btnStart.addActionListener((evt) -> {
 | 
			
		||||
			startGame	= true;
 | 
			
		||||
			whiteName	= options.get(cbWhite.getSelectedIndex());
 | 
			
		||||
			blackName	= options.get(cbBlack.getSelectedIndex());
 | 
			
		||||
			dispose();
 | 
			
		||||
		});
 | 
			
		||||
		btnStart.setBounds(20, 73, 89, 23);
 | 
			
		||||
		getContentPane().add(btnStart);
 | 
			
		||||
 | 
			
		||||
		JButton btnCancel = new JButton("Cancel");
 | 
			
		||||
		btnCancel.addActionListener((evt) -> dispose());
 | 
			
		||||
		btnCancel.setBounds(157, 73, 89, 23);
 | 
			
		||||
		getContentPane().add(btnCancel);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public String getWhiteName() { return whiteName; }
 | 
			
		||||
 | 
			
		||||
	public String getBlackName() { return blackName; }
 | 
			
		||||
 | 
			
		||||
	public boolean isStartGame() { return startGame; }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								src/dev/kske/chess/ui/LogFrame.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								src/dev/kske/chess/ui/LogFrame.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
			
		||||
package dev.kske.chess.ui;
 | 
			
		||||
 | 
			
		||||
import java.awt.BorderLayout;
 | 
			
		||||
 | 
			
		||||
import javax.swing.JFrame;
 | 
			
		||||
import javax.swing.JPanel;
 | 
			
		||||
import javax.swing.JTable;
 | 
			
		||||
import javax.swing.border.EmptyBorder;
 | 
			
		||||
import javax.swing.table.DefaultTableModel;
 | 
			
		||||
 | 
			
		||||
import dev.kske.chess.board.Move;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>Chess</strong><br>
 | 
			
		||||
 * File: <strong>LogFrame.java</strong><br>
 | 
			
		||||
 * Created: <strong>17.07.2019</strong><br>
 | 
			
		||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
			
		||||
 */
 | 
			
		||||
public class LogFrame extends JFrame {
 | 
			
		||||
 | 
			
		||||
	private static final long serialVersionUID = 1932671698254197119L;
 | 
			
		||||
 | 
			
		||||
	private JPanel	mcontentPane;
 | 
			
		||||
	private JTable	mtable;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Create the frame.
 | 
			
		||||
	 */
 | 
			
		||||
	public LogFrame() {
 | 
			
		||||
		setTitle("Move History");
 | 
			
		||||
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 | 
			
		||||
		setBounds(100, 100, 450, 300);
 | 
			
		||||
		mcontentPane = new JPanel();
 | 
			
		||||
		mcontentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
 | 
			
		||||
		mcontentPane.setLayout(new BorderLayout(0, 0));
 | 
			
		||||
		setContentPane(mcontentPane);
 | 
			
		||||
 | 
			
		||||
		mtable = new JTable();
 | 
			
		||||
		mtable.setModel(new DefaultTableModel(new Object[][] {}, new String[] { "White", "Black" }));
 | 
			
		||||
		mtable.setEnabled(false);
 | 
			
		||||
		mcontentPane.add(mtable, BorderLayout.CENTER);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	public void add(Move move) {
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -2,13 +2,17 @@ package dev.kske.chess.ui;
 | 
			
		||||
 | 
			
		||||
import java.awt.BorderLayout;
 | 
			
		||||
import java.awt.EventQueue;
 | 
			
		||||
import java.awt.GridLayout;
 | 
			
		||||
import java.awt.Toolkit;
 | 
			
		||||
 | 
			
		||||
import javax.swing.JButton;
 | 
			
		||||
import javax.swing.JFrame;
 | 
			
		||||
import javax.swing.JLabel;
 | 
			
		||||
import javax.swing.JPanel;
 | 
			
		||||
 | 
			
		||||
import dev.kske.chess.board.Piece.Color;
 | 
			
		||||
import dev.kske.chess.game.Game;
 | 
			
		||||
import dev.kske.chess.game.NaturalPlayer;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>Chess</strong><br>
 | 
			
		||||
@@ -19,8 +23,10 @@ import dev.kske.chess.game.Game;
 | 
			
		||||
public class MainWindow {
 | 
			
		||||
 | 
			
		||||
	private JFrame		mframe;
 | 
			
		||||
	private JButton		btnRestart, btnSwapColors;
 | 
			
		||||
	private BoardPane	boardPane;
 | 
			
		||||
	private Game		game;
 | 
			
		||||
	private Color		activeColor;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Launch the application.
 | 
			
		||||
@@ -50,24 +56,46 @@ public class MainWindow {
 | 
			
		||||
	 * Initialize the contents of the frame.
 | 
			
		||||
	 */
 | 
			
		||||
	private void initialize() {
 | 
			
		||||
		mframe = new JFrame();
 | 
			
		||||
		mframe = new JFrame("Chess by Kai S. K. Engelbart");
 | 
			
		||||
		mframe.setResizable(false);
 | 
			
		||||
		mframe.setBounds(100, 100, 494, 565);
 | 
			
		||||
		mframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 | 
			
		||||
 | 
			
		||||
		mframe.setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/pieces/king_white.png")));
 | 
			
		||||
		mframe.setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/pieces/queen_white.png")));
 | 
			
		||||
 | 
			
		||||
		boardPane = new BoardPane();
 | 
			
		||||
		mframe.getContentPane().add(boardPane, BorderLayout.CENTER);
 | 
			
		||||
 | 
			
		||||
		mframe.setJMenuBar(new MenuBar(this));
 | 
			
		||||
 | 
			
		||||
		JPanel toolPanel = new JPanel();
 | 
			
		||||
		btnRestart = new JButton("Restart");
 | 
			
		||||
		btnRestart.addActionListener((evt) -> { if (game != null) game.reset(); game.start(); });
 | 
			
		||||
 | 
			
		||||
		activeColor		= Color.WHITE;
 | 
			
		||||
		btnSwapColors	= new JButton("Play as black");
 | 
			
		||||
		btnSwapColors.addActionListener((evt) -> {
 | 
			
		||||
			game.swapColors();
 | 
			
		||||
			btnSwapColors.setText("Play as " + activeColor.toString().toLowerCase());
 | 
			
		||||
			activeColor = activeColor.opposite();
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		toolPanel.add(btnRestart);
 | 
			
		||||
		toolPanel.add(btnSwapColors);
 | 
			
		||||
		mframe.getContentPane().add(toolPanel, BorderLayout.NORTH);
 | 
			
		||||
 | 
			
		||||
		JButton btnRestart = new JButton("Restart");
 | 
			
		||||
		btnRestart.addActionListener((evt) -> { if (game != null) game.reset(); game.start(); });
 | 
			
		||||
		toolPanel.add(btnRestart);
 | 
			
		||||
		JPanel letterPanel = new JPanel(new GridLayout(1, 8));
 | 
			
		||||
		for (int i = 0; i < 8; i++) {
 | 
			
		||||
			JLabel letterLabel = new JLabel(String.valueOf((char) (65 + i)));
 | 
			
		||||
			letterLabel.setHorizontalAlignment(JLabel.CENTER);
 | 
			
		||||
			letterPanel.add(letterLabel);
 | 
			
		||||
		}
 | 
			
		||||
		mframe.add(letterPanel, BorderLayout.SOUTH);
 | 
			
		||||
 | 
			
		||||
		JPanel numberPanel = new JPanel(new GridLayout(8, 1));
 | 
			
		||||
		for (int i = 0; i < 8; i++)
 | 
			
		||||
			numberPanel.add(new JLabel(String.valueOf(8 - i)));
 | 
			
		||||
		mframe.add(numberPanel, BorderLayout.EAST);
 | 
			
		||||
 | 
			
		||||
		mframe.setJMenuBar(new MenuBar(this));
 | 
			
		||||
 | 
			
		||||
		mframe.pack();
 | 
			
		||||
		mframe.setLocationRelativeTo(null);
 | 
			
		||||
	}
 | 
			
		||||
@@ -76,5 +104,10 @@ public class MainWindow {
 | 
			
		||||
 | 
			
		||||
	public Game getGame() { return game; }
 | 
			
		||||
 | 
			
		||||
	public void setGame(Game game) { this.game = game; }
 | 
			
		||||
	public void setGame(Game game) {
 | 
			
		||||
		if (this.game != null) this.game.disconnect();
 | 
			
		||||
		this.game = game;
 | 
			
		||||
		btnSwapColors.setEnabled(game.getPlayers().get(Color.WHITE) instanceof NaturalPlayer
 | 
			
		||||
				^ game.getPlayers().get(Color.BLACK) instanceof NaturalPlayer);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,12 @@
 | 
			
		||||
package dev.kske.chess.ui;
 | 
			
		||||
 | 
			
		||||
import java.awt.Toolkit;
 | 
			
		||||
import java.awt.datatransfer.StringSelection;
 | 
			
		||||
 | 
			
		||||
import javax.swing.JMenu;
 | 
			
		||||
import javax.swing.JMenuBar;
 | 
			
		||||
import javax.swing.JMenuItem;
 | 
			
		||||
import javax.swing.JOptionPane;
 | 
			
		||||
 | 
			
		||||
import dev.kske.chess.game.Game;
 | 
			
		||||
 | 
			
		||||
@@ -24,34 +28,55 @@ public class MenuBar extends JMenuBar {
 | 
			
		||||
		boardPane		= mainWindow.getBoardPane();
 | 
			
		||||
 | 
			
		||||
		initGameMenu();
 | 
			
		||||
		initEngineMenu();
 | 
			
		||||
		initToolsMenu();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void initGameMenu() {
 | 
			
		||||
		JMenu		gameMenu		= new JMenu("Game");
 | 
			
		||||
 | 
			
		||||
		JMenuItem	naturalMenuItem	= new JMenuItem("Game against natural opponent");
 | 
			
		||||
		JMenuItem	aiMenuItem		= new JMenuItem("Game against artificial opponent");
 | 
			
		||||
		JMenuItem	aiVsAiMenuItem	= new JMenuItem("Watch AI vs. AI");
 | 
			
		||||
 | 
			
		||||
		naturalMenuItem.addActionListener((evt) -> startGame(Game.createNatural(boardPane)));
 | 
			
		||||
 | 
			
		||||
		aiMenuItem.addActionListener((evt) -> {
 | 
			
		||||
			AIConfigDialog dialog = new AIConfigDialog();
 | 
			
		||||
		JMenuItem	newGameMenuItem	= new JMenuItem("New Game");
 | 
			
		||||
		newGameMenuItem.addActionListener((evt) -> {
 | 
			
		||||
			GameConfigurationDialog dialog = new GameConfigurationDialog();
 | 
			
		||||
			dialog.setVisible(true);
 | 
			
		||||
			if (dialog.isStartGame())
 | 
			
		||||
				startGame(Game.createNaturalVsAI(boardPane, dialog.getMaxDepth(), dialog.getAlphaBetaThreshold()));
 | 
			
		||||
			if (dialog.isStartGame()) startGame(new Game(boardPane, dialog.getWhiteName(), dialog.getBlackName()));
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		aiVsAiMenuItem.addActionListener((evt) -> startGame(Game.createAIVsAI(boardPane, 4, 3, -10, -10)));
 | 
			
		||||
 | 
			
		||||
		gameMenu.add(naturalMenuItem);
 | 
			
		||||
		gameMenu.add(aiMenuItem);
 | 
			
		||||
		gameMenu.add(aiVsAiMenuItem);
 | 
			
		||||
		gameMenu.add(newGameMenuItem);
 | 
			
		||||
 | 
			
		||||
		add(gameMenu);
 | 
			
		||||
 | 
			
		||||
		// Start a game
 | 
			
		||||
		naturalMenuItem.doClick();
 | 
			
		||||
		startGame(new Game(boardPane, "Natural Player", "Natural Player"));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void initEngineMenu() {
 | 
			
		||||
		JMenu engineMenu = new JMenu("Engine");
 | 
			
		||||
 | 
			
		||||
		JMenuItem addEngineMenuItem = new JMenuItem("Add engine");
 | 
			
		||||
		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);
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		JMenuItem showInfoMenuItem = new JMenuItem("Show engine info");
 | 
			
		||||
 | 
			
		||||
		engineMenu.add(addEngineMenuItem);
 | 
			
		||||
		engineMenu.add(showInfoMenuItem);
 | 
			
		||||
		add(engineMenu);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void initToolsMenu() {
 | 
			
		||||
		JMenu toolsMenu = new JMenu("Tools");
 | 
			
		||||
 | 
			
		||||
		JMenuItem exportFENMenuItem = new JMenuItem("Export board to FEN");
 | 
			
		||||
		exportFENMenuItem.addActionListener((evt) -> Toolkit.getDefaultToolkit()
 | 
			
		||||
			.getSystemClipboard()
 | 
			
		||||
			.setContents(new StringSelection(mainWindow.getGame().getBoard().toFEN()), null));
 | 
			
		||||
		toolsMenu.add(exportFENMenuItem);
 | 
			
		||||
 | 
			
		||||
		add(toolsMenu);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private void startGame(Game game) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
package dev.kske.chess.ui;
 | 
			
		||||
 | 
			
		||||
import java.awt.BasicStroke;
 | 
			
		||||
import java.awt.Color;
 | 
			
		||||
import java.awt.Graphics;
 | 
			
		||||
import java.awt.Graphics2D;
 | 
			
		||||
@@ -32,8 +33,8 @@ public class OverlayComponent extends JComponent {
 | 
			
		||||
 | 
			
		||||
	public OverlayComponent(BoardPane boardPane) {
 | 
			
		||||
		this.boardPane = boardPane;
 | 
			
		||||
		setSize(boardPane.getPreferredSize());
 | 
			
		||||
		dots = new ArrayList<>();
 | 
			
		||||
		setSize(boardPane.getPreferredSize());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	@Override
 | 
			
		||||
@@ -42,6 +43,26 @@ public class OverlayComponent extends JComponent {
 | 
			
		||||
 | 
			
		||||
		final int tileSize = getTileSize();
 | 
			
		||||
 | 
			
		||||
		// Draw an arrow representing the last move and mark its position and
 | 
			
		||||
		// destination
 | 
			
		||||
		if (arrow != null) {
 | 
			
		||||
			Point	pos		= new Point(arrow.pos.x * tileSize + tileSize / 2, arrow.pos.y * tileSize + tileSize / 2);
 | 
			
		||||
			Point	dest	= new Point(arrow.dest.x * tileSize + tileSize / 2, arrow.dest.y * tileSize + tileSize / 2);
 | 
			
		||||
 | 
			
		||||
			Graphics2D g2d = (Graphics2D) g;
 | 
			
		||||
			g2d.setStroke(new BasicStroke(3));
 | 
			
		||||
 | 
			
		||||
			g2d.setColor(Color.yellow);
 | 
			
		||||
			g2d.drawRect(arrow.pos.x * tileSize, arrow.pos.y * tileSize, tileSize, tileSize);
 | 
			
		||||
			g2d.drawRect(arrow.dest.x * tileSize, arrow.dest.y * tileSize, tileSize, tileSize);
 | 
			
		||||
 | 
			
		||||
			Shape arrowShape = createArrowShape(pos, dest);
 | 
			
		||||
			g.setColor(new Color(255, 0, 0, 127));
 | 
			
		||||
			g2d.fill(arrowShape);
 | 
			
		||||
			g2d.setColor(Color.black);
 | 
			
		||||
			g2d.draw(arrowShape);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Draw possible moves if a piece was selected
 | 
			
		||||
		if (!dots.isEmpty()) {
 | 
			
		||||
			g.setColor(Color.green);
 | 
			
		||||
@@ -52,13 +73,6 @@ public class OverlayComponent extends JComponent {
 | 
			
		||||
						radius,
 | 
			
		||||
						radius);
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (arrow != null) {
 | 
			
		||||
			g.setColor(new Color(255, 0, 0, 127));
 | 
			
		||||
			Point	pos		= new Point(arrow.pos.x * tileSize + tileSize / 2, arrow.pos.y * tileSize + tileSize / 2);
 | 
			
		||||
			Point	dest	= new Point(arrow.dest.x * tileSize + tileSize / 2, arrow.dest.y * tileSize + tileSize / 2);
 | 
			
		||||
			((Graphics2D) g).fill(createArrowShape(pos, dest));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	private Shape createArrowShape(Point pos, Point dest) {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@ import org.junit.jupiter.api.BeforeEach;
 | 
			
		||||
import org.junit.jupiter.api.Test;
 | 
			
		||||
 | 
			
		||||
import dev.kske.chess.board.Board;
 | 
			
		||||
import dev.kske.chess.board.Move;
 | 
			
		||||
import dev.kske.chess.board.Piece.Color;
 | 
			
		||||
import dev.kske.chess.board.Queen;
 | 
			
		||||
 | 
			
		||||
@@ -38,6 +39,8 @@ class BoardTest {
 | 
			
		||||
		assertNotSame(clone.getBoardArr(), board.getBoardArr());
 | 
			
		||||
 | 
			
		||||
		clone.getBoardArr()[0][0] = new Queen(Color.BLACK, clone);
 | 
			
		||||
		clone.move(new Move(1, 1, 1, 2));
 | 
			
		||||
		assertNotEquals(clone.getBoardArr()[0][0], board.getBoardArr()[0][0]);
 | 
			
		||||
		assertNotEquals(clone.getActiveColor(), board.getActiveColor());
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								test/dev/kske/chess/test/PositionTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								test/dev/kske/chess/test/PositionTest.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
package dev.kske.chess.test;
 | 
			
		||||
 | 
			
		||||
import static org.junit.Assert.assertEquals;
 | 
			
		||||
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.stream.IntStream;
 | 
			
		||||
 | 
			
		||||
import org.junit.jupiter.api.BeforeEach;
 | 
			
		||||
import org.junit.jupiter.api.Test;
 | 
			
		||||
 | 
			
		||||
import dev.kske.chess.board.Position;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Project: <strong>Chess</strong><br>
 | 
			
		||||
 * File: <strong>PositionTest.java</strong><br>
 | 
			
		||||
 * Created: <strong>24.07.2019</strong><br>
 | 
			
		||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
			
		||||
 */
 | 
			
		||||
class PositionTest {
 | 
			
		||||
 | 
			
		||||
	List<Position>	positions;
 | 
			
		||||
	List<String>	sans;
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * @throws java.lang.Exception
 | 
			
		||||
	 */
 | 
			
		||||
	@BeforeEach
 | 
			
		||||
	void setUp() throws Exception {
 | 
			
		||||
		positions	= Arrays.asList(new Position(0, 0), new Position(7, 7), new Position(0, 7), new Position(7, 0));
 | 
			
		||||
		sans		= Arrays.asList("a8", "h1", "a1", "h8");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Test method for
 | 
			
		||||
	 * {@link dev.kske.chess.board.Position#fromSAN(java.lang.String)}.
 | 
			
		||||
	 */
 | 
			
		||||
	@Test
 | 
			
		||||
	void testFromSAN() {
 | 
			
		||||
		IntStream.range(0, positions.size())
 | 
			
		||||
			.forEach(i -> assertEquals(positions.get(i), Position.fromSAN(sans.get(i))));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/**
 | 
			
		||||
	 * Test method for {@link dev.kske.chess.board.Position#toSAN()}.
 | 
			
		||||
	 */
 | 
			
		||||
	@Test
 | 
			
		||||
	void testToSAN() {
 | 
			
		||||
		IntStream.range(0, positions.size()).forEach(i -> assertEquals(sans.get(i), positions.get(i).toSAN()));
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user