Compare commits
	
		
			68 Commits
		
	
	
		
			v0.1-alpha
			...
			v0.4-alpha
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 54e4a0e2e4 | |||
| 1ecafa5485 | |||
| c987bfcebb | |||
| 3941a75c91 | |||
| 249480724a | |||
| 216877b76b | |||
| 19712a2bb7 | |||
| 08ac0ac114 | |||
| de9cd05602 | |||
| 3b73cd1efb | |||
| 964de02e24 | |||
| 76fa3859ef | |||
| c1a8589a04 | |||
| 358654b1ed | |||
| 8e2af63c35 | |||
| 642a0bf4d1 | |||
| 3ea48ef21b | |||
| d121e85897 | |||
| 14c7167ce4 | |||
| 90c100e0e1 | |||
| e7af9f40c2 | |||
| 3d8877ddbd | |||
| 83c6e48f03 | |||
| 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 | |||
| d8f5f3bbf4 | |||
| 4dcc9f7ca0 | |||
| cfd71af142 | |||
| fcd8bfb26b | |||
| cde7f63996 | |||
| 8ea0c7a603 | |||
| 8eda941284 | |||
| 7a986ab9c4 | |||
| c245cdb640 | |||
| 58340ca6ac | |||
| 199d2f06c6 | |||
| d12b06a1ff | |||
| 6d98d9a963 | |||
| c3a787c3a7 | 
@@ -1,12 +1,13 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
<classpath>
 | 
					<classpath>
 | 
				
			||||||
	<classpathentry kind="src" path="src"/>
 | 
						<classpathentry kind="src" path="src"/>
 | 
				
			||||||
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
 | 
						<classpathentry kind="src" path="res"/>
 | 
				
			||||||
 | 
						<classpathentry kind="src" output="bin_test" path="test">
 | 
				
			||||||
		<attributes>
 | 
							<attributes>
 | 
				
			||||||
			<attribute name="module" value="true"/>
 | 
								<attribute name="test" value="true"/>
 | 
				
			||||||
		</attributes>
 | 
							</attributes>
 | 
				
			||||||
	</classpathentry>
 | 
						</classpathentry>
 | 
				
			||||||
	<classpathentry kind="lib" path="res"/>
 | 
					 | 
				
			||||||
	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
 | 
						<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5"/>
 | 
				
			||||||
 | 
						<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
 | 
				
			||||||
	<classpathentry kind="output" path="bin"/>
 | 
						<classpathentry kind="output" path="bin"/>
 | 
				
			||||||
</classpath>
 | 
					</classpath>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,5 +1,6 @@
 | 
				
			|||||||
.metadata
 | 
					.metadata
 | 
				
			||||||
bin/
 | 
					bin/
 | 
				
			||||||
 | 
					/bin_test/
 | 
				
			||||||
tmp/
 | 
					tmp/
 | 
				
			||||||
*.tmp
 | 
					*.tmp
 | 
				
			||||||
*.bak
 | 
					*.bak
 | 
				
			||||||
@@ -21,3 +22,4 @@ local.properties
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# Annotation Processing
 | 
					# Annotation Processing
 | 
				
			||||||
.apt_generated/
 | 
					.apt_generated/
 | 
				
			||||||
 | 
					/engine_infos.ser
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,14 +20,6 @@ public class Bishop extends Piece {
 | 
				
			|||||||
		return move.isDiagonal() && isFreePath(move);
 | 
							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
 | 
						@Override
 | 
				
			||||||
	protected List<Move> getPseudolegalMoves(Position pos) {
 | 
						protected List<Move> getPseudolegalMoves(Position pos) {
 | 
				
			||||||
		List<Move> moves = new ArrayList<>();
 | 
							List<Move> moves = new ArrayList<>();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ import java.util.HashMap;
 | 
				
			|||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Log.MoveNode;
 | 
				
			||||||
import dev.kske.chess.board.Piece.Color;
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
import dev.kske.chess.board.Piece.Type;
 | 
					import dev.kske.chess.board.Piece.Type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,15 +15,88 @@ import dev.kske.chess.board.Piece.Type;
 | 
				
			|||||||
 * Created: <strong>01.07.2019</strong><br>
 | 
					 * Created: <strong>01.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class Board implements Cloneable {
 | 
					public class Board {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private Piece[][]				boardArr;
 | 
						private Piece[][]						boardArr		= new Piece[8][8];
 | 
				
			||||||
	private Map<Color, Position>	kingPos;
 | 
						private Map<Color, Position>			kingPos			= new HashMap<>();
 | 
				
			||||||
 | 
						private Map<Color, Map<Type, Boolean>>	castlingRights	= new HashMap<>();
 | 
				
			||||||
 | 
						private Log								log				= new Log();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final Map<Type, int[][]> positionScores;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static {
 | 
				
			||||||
 | 
							positionScores = new HashMap<>();
 | 
				
			||||||
 | 
							positionScores.put(Type.KING,
 | 
				
			||||||
 | 
									new int[][] { new int[] { -3, -4, -4, -5, -5, -4, -4, -3 },
 | 
				
			||||||
 | 
											new int[] { -3, -4, -4, -5, -4, -4, -4, -3 }, new int[] { -3, -4, -4, -5, -4, -4, -4, -3 },
 | 
				
			||||||
 | 
											new int[] { -3, -4, -4, -5, -4, -4, -4, -3 }, new int[] { -2, -3, -3, -2, -2, -2, -2, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, -2, -2, -2, -2, -2, -2, -1 }, new int[] { 2, 2, 0, 0, 0, 0, 2, 2 },
 | 
				
			||||||
 | 
											new int[] { 2, 3, 1, 0, 0, 1, 3, 2 } });
 | 
				
			||||||
 | 
							positionScores.put(Type.QUEEN,
 | 
				
			||||||
 | 
									new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, -2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { 0, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, 0, 1, 0, 0, 0, 0, -1 }, new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
 | 
				
			||||||
 | 
							positionScores.put(Type.ROOK,
 | 
				
			||||||
 | 
									new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 1, 1, 1, 1, 1, 1, 1, 1 },
 | 
				
			||||||
 | 
											new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { 0, 0, 0, 1, 1, 0, 0, 0 } });
 | 
				
			||||||
 | 
							positionScores.put(Type.KNIGHT,
 | 
				
			||||||
 | 
									new int[][] { new int[] { -5, -4, -3, -3, -3, -3, -4, -5 }, new int[] { -4, -2, 0, 0, 0, 0, -2, -4 },
 | 
				
			||||||
 | 
											new int[] { -3, 0, 1, 2, 2, 1, 0, -3 }, new int[] { -3, 1, 2, 2, 2, 2, 1, -3 },
 | 
				
			||||||
 | 
											new int[] { -3, 0, 2, 2, 2, 2, 0, -1 }, new int[] { -3, 1, 1, 2, 2, 1, 1, -3 },
 | 
				
			||||||
 | 
											new int[] { -4, -2, 0, 1, 1, 0, -2, -4 }, new int[] { -5, -4, -3, -3, -3, -3, -4, -5 } });
 | 
				
			||||||
 | 
							positionScores.put(Type.BISHOP,
 | 
				
			||||||
 | 
									new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, 2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 1, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 1, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, 1, 0, 0, 0, 0, 1, -1 }, new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
 | 
				
			||||||
 | 
							positionScores.put(Type.PAWN,
 | 
				
			||||||
 | 
									new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 5, 5, 5, 5, 5, 5, 5, 5 },
 | 
				
			||||||
 | 
											new int[] { 1, 1, 2, 3, 3, 2, 1, 1 }, new int[] { 0, 0, 1, 3, 3, 1, 0, 0 },
 | 
				
			||||||
 | 
											new int[] { 0, 0, 0, 2, 2, 0, 0, 0 }, new int[] { 0, 0, -1, 0, 0, -1, 0, 0 },
 | 
				
			||||||
 | 
											new int[] { 0, 1, 1, -2, -2, 1, 1, 0 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0 } });
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Initializes the board with the default chess starting position.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
	public Board() {
 | 
						public Board() {
 | 
				
			||||||
 | 
							initDefaultPositions();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Initializes the board with data from a FEN-string.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param fen The FEN-string to initialize the board from
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Board(String fen) {
 | 
				
			||||||
 | 
							initFromFEN(fen);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Creates a copy of another {@link Board} instance.<br>
 | 
				
			||||||
 | 
						 * The created object is a deep copy, but does not contain any move history
 | 
				
			||||||
 | 
						 * apart from the current {@link MoveNode}.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param other The {@link Board} instance to copy
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Board(Board other) {
 | 
				
			||||||
		boardArr = new Piece[8][8];
 | 
							boardArr = new Piece[8][8];
 | 
				
			||||||
		kingPos				= new HashMap<>();
 | 
							for (int i = 0; i < 8; i++)
 | 
				
			||||||
		initializeDefaultPositions();
 | 
								for (int j = 0; j < 8; j++) {
 | 
				
			||||||
 | 
									if (other.boardArr[i][j] == null) continue;
 | 
				
			||||||
 | 
									boardArr[i][j]			= (Piece) other.boardArr[i][j].clone();
 | 
				
			||||||
 | 
									boardArr[i][j].board	= this;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							kingPos.putAll(other.kingPos);
 | 
				
			||||||
 | 
							Map<Type, Boolean> whiteCastling = new HashMap<>(other.castlingRights.get(Color.WHITE)),
 | 
				
			||||||
 | 
									blackCastling = new HashMap<>(other.castlingRights.get(Color.BLACK));
 | 
				
			||||||
 | 
							castlingRights.put(Color.WHITE, whiteCastling);
 | 
				
			||||||
 | 
							castlingRights.put(Color.BLACK, blackCastling);
 | 
				
			||||||
 | 
							log = new Log(other.log, false);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -35,12 +109,15 @@ public class Board implements Cloneable {
 | 
				
			|||||||
		Piece piece = getPos(move);
 | 
							Piece piece = getPos(move);
 | 
				
			||||||
		if (piece == null || !piece.isValidMove(move)) return false;
 | 
							if (piece == null || !piece.isValidMove(move)) return false;
 | 
				
			||||||
		else {
 | 
							else {
 | 
				
			||||||
 | 
								// Set type after validation
 | 
				
			||||||
 | 
								if (move.type == Move.Type.UNKNOWN) move.type = Move.Type.NORMAL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Move piece
 | 
								// Move piece
 | 
				
			||||||
			Piece capturePiece = move(move);
 | 
								move(move);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// Revert move if it caused a check for its team
 | 
								// Revert move if it caused a check for its team
 | 
				
			||||||
			if (checkCheck(piece.getColor())) {
 | 
								if (checkCheck(piece.getColor())) {
 | 
				
			||||||
				revert(move, capturePiece);
 | 
									revert();
 | 
				
			||||||
				return false;
 | 
									return false;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -52,33 +129,133 @@ public class Board implements Cloneable {
 | 
				
			|||||||
	 * Moves a piece across the board without checking if the move is legal.
 | 
						 * Moves a piece across the board without checking if the move is legal.
 | 
				
			||||||
	 * 
 | 
						 * 
 | 
				
			||||||
	 * @param move The move to execute
 | 
						 * @param move The move to execute
 | 
				
			||||||
	 * @return The captures piece, or null if the move's destination was empty
 | 
					 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Piece move(Move move) {
 | 
						public void move(Move move) {
 | 
				
			||||||
		Piece	piece			= getPos(move);
 | 
							Piece	piece			= getPos(move);
 | 
				
			||||||
		Piece	capturePiece	= getDest(move);
 | 
							Piece	capturePiece	= getDest(move);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							switch (move.type) {
 | 
				
			||||||
 | 
								case PAWN_PROMOTION:
 | 
				
			||||||
 | 
									setPos(move, null);
 | 
				
			||||||
 | 
									// 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);
 | 
									setDest(move, piece);
 | 
				
			||||||
				setPos(move, null);
 | 
									setPos(move, null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Update the king's position if the moved piece is the king
 | 
									// Move the rook
 | 
				
			||||||
 | 
									Move rookMove = move.dest.x == 6 ? new Move(7, move.pos.y, 5, move.pos.y) // Kingside
 | 
				
			||||||
 | 
											: new Move(0, move.pos.y, 3, move.pos.y); // Queenside
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Move the rook
 | 
				
			||||||
 | 
									setDest(rookMove, getPos(rookMove));
 | 
				
			||||||
 | 
									setPos(rookMove, null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									getDest(rookMove).incMoveCounter();
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								case UNKNOWN:
 | 
				
			||||||
 | 
									System.err.printf("Move of unknown type %s found!%n", move);
 | 
				
			||||||
 | 
								case NORMAL:
 | 
				
			||||||
 | 
									setDest(move, piece);
 | 
				
			||||||
 | 
									setPos(move, null);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									System.err.printf("Move %s of unimplemented type found!%n", move);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Increment move counter
 | 
				
			||||||
 | 
							getDest(move).incMoveCounter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Update the king's position if the moved piece is the king and castling
 | 
				
			||||||
 | 
							// availability
 | 
				
			||||||
		if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest);
 | 
							if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return capturePiece;
 | 
							// Update log
 | 
				
			||||||
 | 
							log.add(move, capturePiece, piece.getType() == Type.PAWN);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							updateCastlingRights();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Reverts a move.
 | 
						 * Reverts the last move.
 | 
				
			||||||
	 * 
 | 
					 | 
				
			||||||
	 * @param move          The move to revert
 | 
					 | 
				
			||||||
	 * @param capturedPiece The piece that has been captured when the move has been
 | 
					 | 
				
			||||||
	 *                      applied
 | 
					 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void revert(Move move, Piece capturedPiece) {
 | 
						public void revert() {
 | 
				
			||||||
 | 
							MoveNode	moveNode		= log.getLast();
 | 
				
			||||||
 | 
							Move		move			= moveNode.move;
 | 
				
			||||||
 | 
							Piece		capturedPiece	= moveNode.capturedPiece;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							switch (move.type) {
 | 
				
			||||||
 | 
								case PAWN_PROMOTION:
 | 
				
			||||||
 | 
									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));
 | 
				
			||||||
 | 
									setDest(move, null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// 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
 | 
				
			||||||
 | 
									setDest(rookMove, getPos(rookMove));
 | 
				
			||||||
 | 
									setPos(rookMove, null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									getDest(rookMove).decMoveCounter();
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								case UNKNOWN:
 | 
				
			||||||
 | 
									System.err.printf("Move of unknown type %s found!%n", move);
 | 
				
			||||||
 | 
								case NORMAL:
 | 
				
			||||||
				setPos(move, getDest(move));
 | 
									setPos(move, getDest(move));
 | 
				
			||||||
				setDest(move, capturedPiece);
 | 
									setDest(move, capturedPiece);
 | 
				
			||||||
 | 
									break;
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									System.err.printf("Move %s of unimplemented type found!%n", move);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Decrement move counter
 | 
				
			||||||
 | 
							getPos(move).decMoveCounter();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Update the king's position if the moved piece is the king
 | 
							// Update the king's position if the moved piece is the king
 | 
				
			||||||
		if (getPos(move).getType() == Type.KING) kingPos.put(getPos(move).getColor(), move.pos);
 | 
							if (getPos(move).getType() == Type.KING) kingPos.put(getPos(move).getColor(), move.pos);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Update log
 | 
				
			||||||
 | 
							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);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -129,9 +306,9 @@ public class Board implements Cloneable {
 | 
				
			|||||||
		if (!getMoves(kingPos.get(color)).isEmpty()) return false;
 | 
							if (!getMoves(kingPos.get(color)).isEmpty()) return false;
 | 
				
			||||||
		else {
 | 
							else {
 | 
				
			||||||
			for (Move move : getMoves(color)) {
 | 
								for (Move move : getMoves(color)) {
 | 
				
			||||||
				Piece	capturePiece	= move(move);
 | 
									move(move);
 | 
				
			||||||
				boolean check = checkCheck(color);
 | 
									boolean check = checkCheck(color);
 | 
				
			||||||
				revert(move, capturePiece);
 | 
									revert();
 | 
				
			||||||
				if (!check) return false;
 | 
									if (!check) return false;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			return true;
 | 
								return true;
 | 
				
			||||||
@@ -139,9 +316,9 @@ public class Board implements Cloneable {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public GameState getGameEventType(Color color) {
 | 
						public GameState getGameEventType(Color color) {
 | 
				
			||||||
		return checkCheck(color)
 | 
							return checkCheck(color) ? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK
 | 
				
			||||||
				? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK
 | 
									: getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? GameState.STALEMATE
 | 
				
			||||||
				: getMoves(color).isEmpty() ? GameState.STALEMATE : GameState.NORMAL;
 | 
											: GameState.NORMAL;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -154,30 +331,34 @@ public class Board implements Cloneable {
 | 
				
			|||||||
		int score = 0;
 | 
							int score = 0;
 | 
				
			||||||
		for (int i = 0; i < 8; i++)
 | 
							for (int i = 0; i < 8; i++)
 | 
				
			||||||
			for (int j = 0; j < 8; j++)
 | 
								for (int j = 0; j < 8; j++)
 | 
				
			||||||
				if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) switch (boardArr[i][j].getType()) {
 | 
									if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) {
 | 
				
			||||||
 | 
										switch (boardArr[i][j].getType()) {
 | 
				
			||||||
						case QUEEN:
 | 
											case QUEEN:
 | 
				
			||||||
						score += 8;
 | 
												score += 90;
 | 
				
			||||||
							break;
 | 
												break;
 | 
				
			||||||
						case ROOK:
 | 
											case ROOK:
 | 
				
			||||||
						score += 5;
 | 
												score += 50;
 | 
				
			||||||
							break;
 | 
												break;
 | 
				
			||||||
						case KNIGHT:
 | 
											case KNIGHT:
 | 
				
			||||||
						score += 3;
 | 
												score += 30;
 | 
				
			||||||
							break;
 | 
												break;
 | 
				
			||||||
						case BISHOP:
 | 
											case BISHOP:
 | 
				
			||||||
						score += 3;
 | 
												score += 30;
 | 
				
			||||||
							break;
 | 
												break;
 | 
				
			||||||
						case PAWN:
 | 
											case PAWN:
 | 
				
			||||||
						score += 1;
 | 
												score += 10;
 | 
				
			||||||
							break;
 | 
												break;
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
										if (positionScores.containsKey(boardArr[i][j].getType()))
 | 
				
			||||||
 | 
											score += positionScores.get(boardArr[i][j].getType())[i][color == Color.WHITE ? j : 7 - j];
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
		return score;
 | 
							return score;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Initialized the board array with the default chess pieces and positions.
 | 
						 * Initialized the board array with the default chess pieces and positions.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void initializeDefaultPositions() {
 | 
						public void initDefaultPositions() {
 | 
				
			||||||
		// Initialize pawns
 | 
							// Initialize pawns
 | 
				
			||||||
		for (int i = 0; i < 8; i++) {
 | 
							for (int i = 0; i < 8; i++) {
 | 
				
			||||||
			boardArr[i][1]	= new Pawn(Color.BLACK, this);
 | 
								boardArr[i][1]	= new Pawn(Color.BLACK, this);
 | 
				
			||||||
@@ -218,54 +399,204 @@ public class Board implements Cloneable {
 | 
				
			|||||||
		for (int i = 0; i < 8; i++)
 | 
							for (int i = 0; i < 8; i++)
 | 
				
			||||||
			for (int j = 2; j < 6; j++)
 | 
								for (int j = 2; j < 6; j++)
 | 
				
			||||||
				boardArr[i][j] = null;
 | 
									boardArr[i][j] = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Initialize castling rights
 | 
				
			||||||
 | 
							Map<Type, Boolean> whiteCastling = new HashMap<>(), blackCastling = new HashMap<>();
 | 
				
			||||||
 | 
							whiteCastling.put(Type.KING, true);
 | 
				
			||||||
 | 
							whiteCastling.put(Type.QUEEN, true);
 | 
				
			||||||
 | 
							blackCastling.put(Type.KING, true);
 | 
				
			||||||
 | 
							blackCastling.put(Type.QUEEN, true);
 | 
				
			||||||
 | 
							castlingRights.put(Color.WHITE, whiteCastling);
 | 
				
			||||||
 | 
							castlingRights.put(Color.BLACK, blackCastling);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							log.reset();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return A new instance of this class with a shallow copy of both
 | 
						 * Initialized the board with a position specified in a FEN-encoded string.
 | 
				
			||||||
	 *         {@code kingPos} and {code boardArr}
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param fen The FEN-encoded string representing target state of the board
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Override
 | 
						public void initFromFEN(String fen) {
 | 
				
			||||||
	public Object clone() {
 | 
							String[] parts = fen.split(" ");
 | 
				
			||||||
		Board board = null;
 | 
							log.reset();
 | 
				
			||||||
		try {
 | 
					
 | 
				
			||||||
			board = (Board) super.clone();
 | 
							// Piece placement (from white's perspective)
 | 
				
			||||||
		} catch (CloneNotSupportedException ex) {
 | 
							String[] rows = parts[0].split("/");
 | 
				
			||||||
			ex.printStackTrace();
 | 
							for (int i = 0; i < 8; i++) {
 | 
				
			||||||
 | 
								char[] places = rows[i].toCharArray();
 | 
				
			||||||
 | 
								for (int j = 0, k = 0; k < places.length; j++, k++) {
 | 
				
			||||||
 | 
									if (Character.isDigit(places[k])) {
 | 
				
			||||||
 | 
										for (int l = j; l < Character.digit(places[k], 10); l++, j++)
 | 
				
			||||||
 | 
											boardArr[j][i] = null;
 | 
				
			||||||
 | 
										--j;
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										Color color = Character.isUpperCase(places[k]) ? Color.WHITE : Color.BLACK;
 | 
				
			||||||
 | 
										switch (Character.toLowerCase(places[k])) {
 | 
				
			||||||
 | 
											case 'k':
 | 
				
			||||||
 | 
												boardArr[j][i] = new King(color, this);
 | 
				
			||||||
 | 
												kingPos.put(color, new Position(j, i));
 | 
				
			||||||
 | 
												break;
 | 
				
			||||||
 | 
											case 'q':
 | 
				
			||||||
 | 
												boardArr[j][i] = new Queen(color, this);
 | 
				
			||||||
 | 
												break;
 | 
				
			||||||
 | 
											case 'r':
 | 
				
			||||||
 | 
												boardArr[j][i] = new Rook(color, this);
 | 
				
			||||||
 | 
												break;
 | 
				
			||||||
 | 
											case 'n':
 | 
				
			||||||
 | 
												boardArr[j][i] = new Knight(color, this);
 | 
				
			||||||
 | 
												break;
 | 
				
			||||||
 | 
											case 'b':
 | 
				
			||||||
 | 
												boardArr[j][i] = new Bishop(color, this);
 | 
				
			||||||
 | 
												break;
 | 
				
			||||||
 | 
											case 'p':
 | 
				
			||||||
 | 
												boardArr[j][i] = new Pawn(color, this);
 | 
				
			||||||
 | 
												break;
 | 
				
			||||||
 | 
											default:
 | 
				
			||||||
 | 
												System.err.printf("Unknown character '%c' in board declaration of FEN string '%s'%n",
 | 
				
			||||||
 | 
														places[k],
 | 
				
			||||||
 | 
														fen);
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
		board.boardArr = new Piece[8][8];
 | 
									}
 | 
				
			||||||
		for (int i = 0; i < 8; i++)
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Active color
 | 
				
			||||||
 | 
							log.setActiveColor(Color.fromFirstChar(parts[1].charAt(0)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Castling rights
 | 
				
			||||||
 | 
							Map<Type, Boolean> whiteCastling = new HashMap<>(), blackCastling = new HashMap<>();
 | 
				
			||||||
 | 
							for (char c : parts[2].toCharArray())
 | 
				
			||||||
 | 
								switch (c) {
 | 
				
			||||||
 | 
									case 'K':
 | 
				
			||||||
 | 
										whiteCastling.put(Type.KING, true);
 | 
				
			||||||
 | 
									case 'Q':
 | 
				
			||||||
 | 
										whiteCastling.put(Type.QUEEN, true);
 | 
				
			||||||
 | 
									case 'k':
 | 
				
			||||||
 | 
										blackCastling.put(Type.KING, true);
 | 
				
			||||||
 | 
									case 'q':
 | 
				
			||||||
 | 
										blackCastling.put(Type.QUEEN, true);
 | 
				
			||||||
 | 
									case '-':
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									default:
 | 
				
			||||||
 | 
										System.err
 | 
				
			||||||
 | 
											.printf("Unknown character '%c' in castling rights declaration of FEN string '%s'", c, fen);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							castlingRights.put(Color.WHITE, whiteCastling);
 | 
				
			||||||
 | 
							castlingRights.put(Color.BLACK, blackCastling);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// En passant availability
 | 
				
			||||||
 | 
							if (!parts[3].equals("-")) log.setEnPassant(Position.fromSAN(parts[3]));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Halfmove clock
 | 
				
			||||||
 | 
							log.setHalfmoveClock(Integer.parseInt(parts[4]));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Fullmove counter
 | 
				
			||||||
 | 
							log.setFullmoveCounter(Integer.parseInt(parts[5]));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return a FEN-encoded string representing the board
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public String toFEN() {
 | 
				
			||||||
 | 
							StringBuilder sb = new StringBuilder();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Piece placement (from white's perspective)
 | 
				
			||||||
 | 
							for (int i = 0; i < 8; i++) {
 | 
				
			||||||
 | 
								int emptyCount = 0;
 | 
				
			||||||
			for (int j = 0; j < 8; j++) {
 | 
								for (int j = 0; j < 8; j++) {
 | 
				
			||||||
				if (boardArr[i][j] == null) continue;
 | 
									final Piece piece = boardArr[j][i];
 | 
				
			||||||
				board.boardArr[i][j]		= (Piece) boardArr[i][j].clone();
 | 
									if (piece == null) ++emptyCount;
 | 
				
			||||||
				board.boardArr[i][j].board	= board;
 | 
									else {
 | 
				
			||||||
 | 
										if (emptyCount != 0) {
 | 
				
			||||||
 | 
											sb.append(emptyCount);
 | 
				
			||||||
 | 
											emptyCount = 0;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										char p = boardArr[j][i].getType().firstChar();
 | 
				
			||||||
 | 
										sb.append(piece.getColor() == Color.WHITE ? Character.toUpperCase(p) : p);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								if (emptyCount != 0) sb.append(emptyCount);
 | 
				
			||||||
 | 
								if (i < 7) sb.append('/');
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		board.kingPos	= new HashMap<>();
 | 
							// Active color
 | 
				
			||||||
		board.kingPos.putAll(kingPos);
 | 
							sb.append(" " + log.getActiveColor().firstChar());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return board;
 | 
							// Castling Rights
 | 
				
			||||||
 | 
							sb.append(' ');
 | 
				
			||||||
 | 
							StringBuilder castlingSb = new StringBuilder();
 | 
				
			||||||
 | 
							if (castlingRights.get(Color.WHITE).get(Type.KING)) castlingSb.append('K');
 | 
				
			||||||
 | 
							if (castlingRights.get(Color.WHITE).get(Type.QUEEN)) castlingSb.append('Q');
 | 
				
			||||||
 | 
							if (castlingRights.get(Color.BLACK).get(Type.KING)) castlingSb.append('k');
 | 
				
			||||||
 | 
							if (castlingRights.get(Color.BLACK).get(Type.QUEEN)) castlingSb.append('q');
 | 
				
			||||||
 | 
							if (castlingSb.length() == 0) sb.append("-");
 | 
				
			||||||
 | 
							sb.append(castlingSb);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							final MoveNode lastMove = log.getLast();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// En passant availability
 | 
				
			||||||
 | 
							sb.append(" " + (lastMove == null || lastMove.enPassant == null ? "-" : lastMove.enPassant.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();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param pos The position from which to return a piece
 | 
				
			||||||
 | 
						 * @return The piece at the position
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
	public Piece get(Position pos) {
 | 
						public Piece get(Position pos) {
 | 
				
			||||||
		return boardArr[pos.x][pos.y];
 | 
							return boardArr[pos.x][pos.y];
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Places a piece at a position.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param pos   The position to place the piece at
 | 
				
			||||||
 | 
						 * @param piece The piece to place
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
	public void set(Position pos, Piece piece) {
 | 
						public void set(Position pos, Piece piece) {
 | 
				
			||||||
		boardArr[pos.x][pos.y] = piece;
 | 
							boardArr[pos.x][pos.y] = piece;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param move The move from which position to return a piece
 | 
				
			||||||
 | 
						 * @return The piece at the position of the move
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
	public Piece getPos(Move move) {
 | 
						public Piece getPos(Move move) {
 | 
				
			||||||
		return get(move.pos);
 | 
							return get(move.pos);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @param move The move from which destination to return a piece
 | 
				
			||||||
 | 
						 * @return The piece at the destination of the move
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
	public Piece getDest(Move move) {
 | 
						public Piece getDest(Move move) {
 | 
				
			||||||
		return get(move.dest);
 | 
							return get(move.dest);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Places a piece at the position of a move.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param move  The move at which position to place the piece
 | 
				
			||||||
 | 
						 * @param piece The piece to place
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
	public void setPos(Move move, Piece piece) {
 | 
						public void setPos(Move move, Piece piece) {
 | 
				
			||||||
		set(move.pos, piece);
 | 
							set(move.pos, piece);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Places a piece at the destination of a move.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param move  The move at which destination to place the piece
 | 
				
			||||||
 | 
						 * @param piece The piece to place
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
	public void setDest(Move move, Piece piece) {
 | 
						public void setDest(Move move, Piece piece) {
 | 
				
			||||||
		set(move.dest, piece);
 | 
							set(move.dest, piece);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -274,4 +605,9 @@ public class Board implements Cloneable {
 | 
				
			|||||||
	 * @return The board array
 | 
						 * @return The board array
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Piece[][] getBoardArr() { return boardArr; }
 | 
						public Piece[][] getBoardArr() { return boardArr; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return The move log
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Log getLog() { return log; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,39 @@ public class King extends Piece {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public boolean isValidMove(Move move) {
 | 
						public boolean isValidMove(Move move) {
 | 
				
			||||||
		return move.xDist <= 1 && move.yDist <= 1 && isFreePath(move);
 | 
							// Castling
 | 
				
			||||||
 | 
							if (move.xDist == 2 && move.yDist == 0) {
 | 
				
			||||||
 | 
								if (canCastleKingside()) {
 | 
				
			||||||
 | 
									move.type = Move.Type.CASTLING;
 | 
				
			||||||
 | 
									return true;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								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
 | 
						@Override
 | 
				
			||||||
@@ -29,6 +61,12 @@ public class King extends Piece {
 | 
				
			|||||||
					Move move = new Move(pos, new Position(i, j));
 | 
										Move move = new Move(pos, new Position(i, j));
 | 
				
			||||||
					if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) moves.add(move);
 | 
										if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) moves.add(move);
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Castling
 | 
				
			||||||
 | 
							// 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;
 | 
							return moves;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,7 +17,8 @@ public class Knight extends Piece {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public boolean isValidMove(Move move) {
 | 
						public boolean isValidMove(Move move) {
 | 
				
			||||||
		return Math.abs(move.xDist - move.yDist) == 1 && (move.xDist == 1 || move.yDist == 1) && isFreePath(move);
 | 
							return Math.abs(move.xDist - move.yDist) == 1
 | 
				
			||||||
 | 
									&& (move.xDist == 1 && move.yDist == 2 || move.xDist == 2 && move.yDist == 1) && checkDestination(move);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void checkAndInsertMove(List<Move> moves, Position pos, int offsetX, int offsetY) {
 | 
						private void checkAndInsertMove(List<Move> moves, Position pos, int offsetX, int offsetY) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										200
									
								
								src/dev/kske/chess/board/Log.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										200
									
								
								src/dev/kske/chess/board/Log.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,200 @@
 | 
				
			|||||||
 | 
					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>
 | 
				
			||||||
 | 
					 * Created: <strong>09.07.2019</strong><br>
 | 
				
			||||||
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class Log {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private MoveNode root, current;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Position	enPassant;
 | 
				
			||||||
 | 
						private Color		activeColor;
 | 
				
			||||||
 | 
						private int			fullmoveCounter, halfmoveClock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Log() {
 | 
				
			||||||
 | 
							reset();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Creates a (partially deep) copy of another {@link Log} instance which begins
 | 
				
			||||||
 | 
						 * with the current {@link MoveNode}.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param other          The {@link Log} instance to copy
 | 
				
			||||||
 | 
						 * @param copyVariations If set to {@code true}, subsequent variations of the
 | 
				
			||||||
 | 
						 *                       current {@link MoveNode} are copied with the
 | 
				
			||||||
 | 
						 *                       {@link Log}
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Log(Log other, boolean copyVariations) {
 | 
				
			||||||
 | 
							enPassant		= other.enPassant;
 | 
				
			||||||
 | 
							activeColor		= other.activeColor;
 | 
				
			||||||
 | 
							fullmoveCounter	= other.fullmoveCounter;
 | 
				
			||||||
 | 
							halfmoveClock	= other.halfmoveClock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// The new root is the current node of the copied instance
 | 
				
			||||||
 | 
							if (!other.isEmpty()) {
 | 
				
			||||||
 | 
								root		= new MoveNode(other.current, copyVariations);
 | 
				
			||||||
 | 
								root.parent	= null;
 | 
				
			||||||
 | 
								current		= root;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Adds a move to the move history and adjusts the log to the new position.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param move          The move to log
 | 
				
			||||||
 | 
						 * @param capturedPiece The piece captured with the move
 | 
				
			||||||
 | 
						 * @param pawnMove      {@code true} if the move was made by a pawn
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void add(Move move, Piece capturedPiece, boolean pawnMove) {
 | 
				
			||||||
 | 
							enPassant = pawnMove && move.yDist == 2 ? new Position(move.pos.x, move.pos.y + move.ySign) : null;
 | 
				
			||||||
 | 
							if (activeColor == Color.BLACK) ++fullmoveCounter;
 | 
				
			||||||
 | 
							if (pawnMove || capturedPiece != null) halfmoveClock = 0;
 | 
				
			||||||
 | 
							else++halfmoveClock;
 | 
				
			||||||
 | 
							activeColor = activeColor.opposite();
 | 
				
			||||||
 | 
							final MoveNode leaf = new MoveNode(move, capturedPiece, enPassant, activeColor, fullmoveCounter, halfmoveClock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (isEmpty()) {
 | 
				
			||||||
 | 
								root	= leaf;
 | 
				
			||||||
 | 
								current	= leaf;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								current.addVariation(leaf);
 | 
				
			||||||
 | 
								current = leaf;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Removed the last move from the log and adjusts its state to the previous
 | 
				
			||||||
 | 
						 * move.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void removeLast() {
 | 
				
			||||||
 | 
							if (!isEmpty() && current.parent != null) {
 | 
				
			||||||
 | 
								current.parent.variations.remove(current);
 | 
				
			||||||
 | 
								current			= current.parent;
 | 
				
			||||||
 | 
								activeColor		= current.activeColor;
 | 
				
			||||||
 | 
								enPassant		= current.enPassant;
 | 
				
			||||||
 | 
								fullmoveCounter	= current.fullmoveCounter;
 | 
				
			||||||
 | 
								halfmoveClock	= current.halfmoveClock;
 | 
				
			||||||
 | 
							} else reset();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public boolean isEmpty() { return root == null; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Reverts the log to its initial state corresponding to the default board
 | 
				
			||||||
 | 
						 * position.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void reset() {
 | 
				
			||||||
 | 
							root			= null;
 | 
				
			||||||
 | 
							current			= null;
 | 
				
			||||||
 | 
							enPassant		= null;
 | 
				
			||||||
 | 
							activeColor		= Color.WHITE;
 | 
				
			||||||
 | 
							fullmoveCounter	= 1;
 | 
				
			||||||
 | 
							halfmoveClock	= 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return The first logged move, or {@code null} if there is none
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public MoveNode getRoot() { return root; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the last logged move, or {@code null} if there is none
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public MoveNode getLast() { return current; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Position getEnPassant() { return enPassant; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void setEnPassant(Position enPassant) { this.enPassant = enPassant; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Color getActiveColor() { return activeColor; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void setActiveColor(Color activeColor) { this.activeColor = activeColor; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public int getFullmoveCounter() { return fullmoveCounter; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void setFullmoveCounter(int fullmoveCounter) { this.fullmoveCounter = fullmoveCounter; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public int getHalfmoveClock() { return halfmoveClock; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void setHalfmoveClock(int halfmoveClock) { this.halfmoveClock = halfmoveClock; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static class MoveNode {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public final Move		move;
 | 
				
			||||||
 | 
							public final Piece		capturedPiece;
 | 
				
			||||||
 | 
							public final Position	enPassant;
 | 
				
			||||||
 | 
							public final Color		activeColor;
 | 
				
			||||||
 | 
							public final int		fullmoveCounter, halfmoveClock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							private MoveNode		parent;
 | 
				
			||||||
 | 
							private List<MoveNode>	variations;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * Creates a new {@link MoveNode}.
 | 
				
			||||||
 | 
							 * 
 | 
				
			||||||
 | 
							 * @param move            The logged {@link Move}
 | 
				
			||||||
 | 
							 * @param capturedPiece   The {@link Piece} captures by the logged {@link Move}
 | 
				
			||||||
 | 
							 * @param enPassant       The en passant {@link Position} valid after the logged
 | 
				
			||||||
 | 
							 *                        {@link Move}, or {@code null} if there is none
 | 
				
			||||||
 | 
							 * @param activeColor     The {@link Color} active after the logged {@link Move}
 | 
				
			||||||
 | 
							 * @param fullmoveCounter
 | 
				
			||||||
 | 
							 * @param halfmoveClock
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							public MoveNode(Move move, Piece capturedPiece, Position enPassant, Color activeColor, int fullmoveCounter,
 | 
				
			||||||
 | 
									int halfmoveClock) {
 | 
				
			||||||
 | 
								this.move				= move;
 | 
				
			||||||
 | 
								this.capturedPiece		= capturedPiece;
 | 
				
			||||||
 | 
								this.enPassant			= enPassant;
 | 
				
			||||||
 | 
								this.activeColor		= activeColor;
 | 
				
			||||||
 | 
								this.fullmoveCounter	= fullmoveCounter;
 | 
				
			||||||
 | 
								this.halfmoveClock		= halfmoveClock;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * Creates a (deep) copy of another {@link MoveNode}.
 | 
				
			||||||
 | 
							 * 
 | 
				
			||||||
 | 
							 * @param other          The {@link MoveNode} to copy
 | 
				
			||||||
 | 
							 * @param copyVariations When this is set to {@code true} a deep copy is
 | 
				
			||||||
 | 
							 *                       created, which
 | 
				
			||||||
 | 
							 *                       considers subsequent variations
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							public MoveNode(MoveNode other, boolean copyVariations) {
 | 
				
			||||||
 | 
								this(other.move, other.capturedPiece, other.enPassant, other.activeColor, other.fullmoveCounter,
 | 
				
			||||||
 | 
										other.halfmoveClock);
 | 
				
			||||||
 | 
								if (copyVariations && other.variations != null) {
 | 
				
			||||||
 | 
									if (variations == null) variations = new ArrayList<>();
 | 
				
			||||||
 | 
									other.variations.forEach(variation -> {
 | 
				
			||||||
 | 
										MoveNode copy = new MoveNode(variation, true);
 | 
				
			||||||
 | 
										copy.parent = this;
 | 
				
			||||||
 | 
										variations.add(copy);
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * Adds another {@link MoveNode} as a child node.
 | 
				
			||||||
 | 
							 * 
 | 
				
			||||||
 | 
							 * @param variation The {@link MoveNode} to append to this {@link MoveNode}
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							public void addVariation(MoveNode variation) {
 | 
				
			||||||
 | 
								if (variations == null) variations = new ArrayList<>();
 | 
				
			||||||
 | 
								if (!variations.contains(variation)) {
 | 
				
			||||||
 | 
									variations.add(variation);
 | 
				
			||||||
 | 
									variation.parent = this;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * @return A list of all variations associated with this {@link MoveNode}
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							public List<MoveNode> getVariations() { return variations; }
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -10,20 +10,35 @@ public class Move {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	public final Position	pos, dest;
 | 
						public final Position	pos, dest;
 | 
				
			||||||
	public final int		xDist, yDist, xSign, ySign;
 | 
						public final int		xDist, yDist, xSign, ySign;
 | 
				
			||||||
 | 
						public Type				type;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Move(Position pos, Position dest) {
 | 
						public Move(Position pos, Position dest, Type type) {
 | 
				
			||||||
		this.pos	= pos;
 | 
							this.pos	= pos;
 | 
				
			||||||
		this.dest	= dest;
 | 
							this.dest	= dest;
 | 
				
			||||||
 | 
							this.type	= type;
 | 
				
			||||||
		xDist		= Math.abs(dest.x - pos.x);
 | 
							xDist		= Math.abs(dest.x - pos.x);
 | 
				
			||||||
		yDist		= Math.abs(dest.y - pos.y);
 | 
							yDist		= Math.abs(dest.y - pos.y);
 | 
				
			||||||
		xSign		= (int) Math.signum(dest.x - pos.x);
 | 
							xSign		= (int) Math.signum(dest.x - pos.x);
 | 
				
			||||||
		ySign		= (int) Math.signum(dest.y - pos.y);
 | 
							ySign		= (int) Math.signum(dest.y - pos.y);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Move(Position pos, Position dest) {
 | 
				
			||||||
 | 
							this(pos, dest, Type.NORMAL);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Move(int xPos, int yPos, int xDest, int yDest) {
 | 
						public Move(int xPos, int yPos, int xDest, int yDest) {
 | 
				
			||||||
		this(new Position(xPos, yPos), new Position(xDest, yDest));
 | 
							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 String toSAN() {
 | 
				
			||||||
 | 
							return pos.toSAN() + dest.toSAN();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public boolean isHorizontal() { return yDist == 0; }
 | 
						public boolean isHorizontal() { return yDist == 0; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public boolean isVertical() { return xDist == 0; }
 | 
						public boolean isVertical() { return xDist == 0; }
 | 
				
			||||||
@@ -34,4 +49,8 @@ public class Move {
 | 
				
			|||||||
	public String toString() {
 | 
						public String toString() {
 | 
				
			||||||
		return String.format("%s -> %s", pos, dest);
 | 
							return String.format("%s -> %s", pos, dest);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static enum Type {
 | 
				
			||||||
 | 
							NORMAL, PAWN_PROMOTION, CASTLING, EN_PASSANT, UNKNOWN
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,13 +17,25 @@ public class Pawn extends Piece {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public boolean isValidMove(Move move) {
 | 
						public boolean isValidMove(Move move) {
 | 
				
			||||||
		// TODO: en passant, pawn promotion
 | 
					 | 
				
			||||||
		boolean	step		= move.isVertical() && move.yDist == 1;
 | 
							boolean	step		= move.isVertical() && move.yDist == 1;
 | 
				
			||||||
		boolean	doubleStep	= move.isVertical() && move.yDist == 2;
 | 
							boolean	doubleStep	= move.isVertical() && move.yDist == 2;
 | 
				
			||||||
		boolean	strafe		= move.isDiagonal() && move.xDist == 1;
 | 
							boolean	strafe		= move.isDiagonal() && move.xDist == 1;
 | 
				
			||||||
 | 
							boolean	enPassant	= false;
 | 
				
			||||||
		if (getColor() == Color.WHITE) doubleStep &= move.pos.y == 6;
 | 
							if (getColor() == Color.WHITE) doubleStep &= move.pos.y == 6;
 | 
				
			||||||
		else doubleStep &= move.pos.y == 1;
 | 
							else doubleStep &= move.pos.y == 1;
 | 
				
			||||||
		return (step ^ doubleStep ^ strafe) && move.ySign == (getColor() == Color.WHITE ? -1 : 1) && isFreePath(move);
 | 
					
 | 
				
			||||||
 | 
							// Mark move as pawn promotion if necessary
 | 
				
			||||||
 | 
							if (move.ySign == 1 && move.pos.y == 6 || move.ySign == -1 && move.pos.y == 1)
 | 
				
			||||||
 | 
								move.type = Move.Type.PAWN_PROMOTION;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Mark the move as en passant if necessary
 | 
				
			||||||
 | 
							if (strafe && move.dest.equals(board.getLog().getEnPassant())) {
 | 
				
			||||||
 | 
								enPassant	= true;
 | 
				
			||||||
 | 
								move.type	= Move.Type.EN_PASSANT;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return enPassant || (step ^ doubleStep ^ strafe) && move.ySign == (getColor() == Color.WHITE ? -1 : 1)
 | 
				
			||||||
 | 
									&& isFreePath(move);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
@@ -43,8 +55,6 @@ public class Pawn extends Piece {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		int sign = getColor() == Color.WHITE ? -1 : 1;
 | 
							int sign = getColor() == Color.WHITE ? -1 : 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (sign == -1 && pos.y == 1 || sign == 1 && pos.y == 7) return moves;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Strafe left
 | 
							// Strafe left
 | 
				
			||||||
		if (pos.x > 0) {
 | 
							if (pos.x > 0) {
 | 
				
			||||||
			Move move = new Move(pos, new Position(pos.x - 1, pos.y + sign));
 | 
								Move move = new Move(pos, new Position(pos.x - 1, pos.y + sign));
 | 
				
			||||||
@@ -68,6 +78,17 @@ public class Pawn extends Piece {
 | 
				
			|||||||
			Move move = new Move(pos, new Position(pos.x, pos.y + 2 * sign));
 | 
								Move move = new Move(pos, new Position(pos.x, pos.y + 2 * sign));
 | 
				
			||||||
			if (isFreePath(move)) moves.add(move);
 | 
								if (isFreePath(move)) moves.add(move);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 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.getLog().getEnPassant() != null) {
 | 
				
			||||||
 | 
								Move move = new Move(pos, board.getLog().getEnPassant(), Move.Type.EN_PASSANT);
 | 
				
			||||||
 | 
								if (move.isDiagonal() && move.xDist == 1) moves.add(move);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return moves;
 | 
							return moves;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,8 +11,9 @@ import java.util.List;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public abstract class Piece implements Cloneable {
 | 
					public abstract class Piece implements Cloneable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected Color	color;
 | 
						private final Color	color;
 | 
				
			||||||
	protected Board		board;
 | 
						protected Board		board;
 | 
				
			||||||
 | 
						private int			moveCounter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Piece(Color color, Board board) {
 | 
						public Piece(Color color, Board board) {
 | 
				
			||||||
		this.color	= color;
 | 
							this.color	= color;
 | 
				
			||||||
@@ -23,10 +24,9 @@ public abstract class Piece implements Cloneable {
 | 
				
			|||||||
		List<Move> moves = getPseudolegalMoves(pos);
 | 
							List<Move> moves = getPseudolegalMoves(pos);
 | 
				
			||||||
		for (Iterator<Move> iterator = moves.iterator(); iterator.hasNext();) {
 | 
							for (Iterator<Move> iterator = moves.iterator(); iterator.hasNext();) {
 | 
				
			||||||
			Move move = iterator.next();
 | 
								Move move = iterator.next();
 | 
				
			||||||
			Piece	capturePiece	= board.move(move);
 | 
								board.move(move);
 | 
				
			||||||
			if (board.checkCheck(getColor()))
 | 
								if (board.checkCheck(getColor())) iterator.remove();
 | 
				
			||||||
				iterator.remove();
 | 
								board.revert();
 | 
				
			||||||
			board.revert(move, capturePiece);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return moves;
 | 
							return moves;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -35,8 +35,16 @@ public abstract class Piece implements Cloneable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	public abstract boolean isValidMove(Move move);
 | 
						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) {
 | 
						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);
 | 
							return checkDestination(move);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -66,13 +74,38 @@ public abstract class Piece implements Cloneable {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	public Color getColor() { return color; }
 | 
						public Color getColor() { return color; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public int getMoveCounter() { return moveCounter; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void incMoveCounter() {
 | 
				
			||||||
 | 
							++moveCounter;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void decMoveCounter() {
 | 
				
			||||||
 | 
							--moveCounter;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static enum Type {
 | 
						public static enum Type {
 | 
				
			||||||
		KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN;
 | 
							KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN;
 | 
				
			||||||
 | 
							
 | 
				
			||||||
 | 
							/**
 | 
				
			||||||
 | 
							 * @return The first character of this {@link Type} in algebraic notation and lower case
 | 
				
			||||||
 | 
							 */
 | 
				
			||||||
 | 
							public char firstChar() {
 | 
				
			||||||
 | 
								return this == KNIGHT ? 'n' : Character.toLowerCase(this.toString().charAt(0));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static enum Color {
 | 
						public static enum Color {
 | 
				
			||||||
		WHITE, BLACK;
 | 
							WHITE, BLACK;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public static Color fromFirstChar(char c) {
 | 
				
			||||||
 | 
								return Character.toLowerCase(c) == 'w' ? WHITE : BLACK;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							public char firstChar() {
 | 
				
			||||||
 | 
								return this == WHITE ? 'w' : 'b';
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public Color opposite() {
 | 
							public Color opposite() {
 | 
				
			||||||
			return this == WHITE ? BLACK : WHITE;
 | 
								return this == WHITE ? BLACK : WHITE;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,8 +15,36 @@ public class Position {
 | 
				
			|||||||
		this.y	= y;
 | 
							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
 | 
						@Override
 | 
				
			||||||
	public String toString() {
 | 
						public String toString() {
 | 
				
			||||||
		return String.format("[%d, %d]", x, y);
 | 
							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);
 | 
							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
 | 
						@Override
 | 
				
			||||||
	protected List<Move> getPseudolegalMoves(Position pos) {
 | 
						protected List<Move> getPseudolegalMoves(Position pos) {
 | 
				
			||||||
		List<Move> moves = new ArrayList<>();
 | 
							List<Move> moves = new ArrayList<>();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,18 +20,6 @@ public class Rook extends Piece {
 | 
				
			|||||||
		return (move.isHorizontal() || move.isVertical()) && isFreePath(move);
 | 
							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
 | 
						@Override
 | 
				
			||||||
	protected List<Move> getPseudolegalMoves(Position pos) {
 | 
						protected List<Move> getPseudolegalMoves(Position pos) {
 | 
				
			||||||
		List<Move> moves = new ArrayList<>();
 | 
							List<Move> moves = new ArrayList<>();
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										15
									
								
								src/dev/kske/chess/event/Event.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								src/dev/kske/chess/event/Event.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>Event.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>7 Aug 2019</strong><br>
 | 
				
			||||||
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public interface Event<T> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return The data associated with the event
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						T getData();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										36
									
								
								src/dev/kske/chess/event/EventBus.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/dev/kske/chess/event/EventBus.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>EventBus.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>7 Aug 2019</strong><br>
 | 
				
			||||||
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class EventBus {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private List<Subscribable> subscribers;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static EventBus instance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static EventBus getInstance() {
 | 
				
			||||||
 | 
							if (instance == null) instance = new EventBus();
 | 
				
			||||||
 | 
							return instance;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private EventBus() {
 | 
				
			||||||
 | 
							subscribers = new ArrayList<>();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void register(Subscribable subscribable) {
 | 
				
			||||||
 | 
							subscribers.add(subscribable);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void dispatch(Event<?> event) {
 | 
				
			||||||
 | 
							subscribers.stream().filter(e -> e.supports().contains(event.getClass())).forEach(e -> e.handle(event));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public List<Subscribable> getSubscribers() { return subscribers; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										21
									
								
								src/dev/kske/chess/event/MoveEvent.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/dev/kske/chess/event/MoveEvent.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Move;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>MoveEvent.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>7 Aug 2019</strong><br>
 | 
				
			||||||
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class MoveEvent implements Event<Move> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Move move;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public MoveEvent(Move move) {
 | 
				
			||||||
 | 
							this.move = move;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public Move getData() { return move; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										24
									
								
								src/dev/kske/chess/event/Subscribable.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								src/dev/kske/chess/event/Subscribable.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>Subscribable.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>7 Aug 2019</strong><br>
 | 
				
			||||||
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public interface Subscribable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Consumes an event dispatched by an event bus.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param event The event dispatched by the event bus, only of supported type
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						void handle(Event<?> event);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return A set of classes this class is supposed to handle in events
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						Set<Class<?>> supports();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,12 +1,22 @@
 | 
				
			|||||||
package dev.kske.chess.game;
 | 
					package dev.kske.chess.game;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.swing.JOptionPane;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.chess.board.Board;
 | 
					import dev.kske.chess.board.Board;
 | 
				
			||||||
import dev.kske.chess.board.GameState;
 | 
					import dev.kske.chess.board.GameState;
 | 
				
			||||||
import dev.kske.chess.board.Move;
 | 
					import dev.kske.chess.board.Move;
 | 
				
			||||||
import dev.kske.chess.board.Piece.Color;
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
 | 
					import dev.kske.chess.event.EventBus;
 | 
				
			||||||
 | 
					import dev.kske.chess.event.MoveEvent;
 | 
				
			||||||
 | 
					import dev.kske.chess.game.ai.AIPlayer;
 | 
				
			||||||
import dev.kske.chess.ui.BoardComponent;
 | 
					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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
@@ -16,39 +26,119 @@ import dev.kske.chess.ui.BoardComponent;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public class Game {
 | 
					public class Game {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private Map<Color, Player>	players;
 | 
						private Map<Color, Player>	players	= new HashMap<>();
 | 
				
			||||||
	private Board				board;
 | 
						private Board				board;
 | 
				
			||||||
 | 
						private OverlayComponent	overlayComponent;
 | 
				
			||||||
	private BoardComponent		boardComponent;
 | 
						private BoardComponent		boardComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Game(Map<Color, Player> players, BoardComponent boardComponent) {
 | 
						public Game(BoardPane boardPane, String whiteName, String blackName) {
 | 
				
			||||||
		this.players		= players;
 | 
							board				= new Board();
 | 
				
			||||||
		this.boardComponent	= boardComponent;
 | 
							init(boardPane, whiteName, blackName);
 | 
				
			||||||
		this.board			= boardComponent.getBoard();
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Game(BoardPane boardPane, String whiteName, String blackName, String fen) {
 | 
				
			||||||
 | 
							board = new Board(fen);
 | 
				
			||||||
 | 
							init(boardPane, whiteName, blackName);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void init(BoardPane boardPane, String whiteName, String blackName) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Initialize / synchronize UI
 | 
				
			||||||
 | 
							overlayComponent	= boardPane.getOverlayComponent();
 | 
				
			||||||
 | 
							boardComponent		= boardPane.getBoardComponent();
 | 
				
			||||||
 | 
							boardComponent.setBoard(board);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Initialize players
 | 
				
			||||||
 | 
							players.put(Color.WHITE, getPlayer(whiteName, Color.WHITE));
 | 
				
			||||||
 | 
							players.put(Color.BLACK, getPlayer(blackName, Color.BLACK));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Initialize the game variable in each player
 | 
							// Initialize the game variable in each player
 | 
				
			||||||
		players.values().forEach(player -> player.setGame(this));
 | 
							players.values().forEach(player -> player.setGame(this));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void start() {
 | 
						private Player getPlayer(String name, Color color) {
 | 
				
			||||||
		players.get(Color.WHITE).requestMove();
 | 
							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 void onMove(Player player, Move move) {
 | 
						public void onMove(Player player, Move move) {
 | 
				
			||||||
		if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) {
 | 
							if (board.getPos(move).getColor() == player.color && board.attemptMove(move)) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Redraw
 | 
				
			||||||
 | 
								boardComponent.repaint();
 | 
				
			||||||
 | 
								overlayComponent.displayArrow(move);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Run garbage collection
 | 
				
			||||||
 | 
								System.gc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			System.out.printf("%s: %s%n", player.color, move);
 | 
								System.out.printf("%s: %s%n", player.color, move);
 | 
				
			||||||
 | 
								System.out.println("FEN: " + board.toFEN());
 | 
				
			||||||
 | 
								EventBus.getInstance().dispatch(new MoveEvent(move));
 | 
				
			||||||
			GameState eventType = board.getGameEventType(board.getDest(move).getColor().opposite());
 | 
								GameState eventType = board.getGameEventType(board.getDest(move).getColor().opposite());
 | 
				
			||||||
			switch (eventType) {
 | 
								switch (eventType) {
 | 
				
			||||||
				case CHECKMATE:
 | 
									case CHECKMATE:
 | 
				
			||||||
				case STALEMATE:
 | 
									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;
 | 
										break;
 | 
				
			||||||
				case CHECK:
 | 
									case CHECK:
 | 
				
			||||||
					System.out.printf("%s in check!%n", player.color.opposite());
 | 
										System.out.printf("%s in check!%n", player.color.opposite());
 | 
				
			||||||
				default:
 | 
									default:
 | 
				
			||||||
					boardComponent.repaint();
 | 
										players.get(board.getLog().getActiveColor()).requestMove();
 | 
				
			||||||
					players.get(player.color.opposite()).requestMove();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		} else player.requestMove();
 | 
							} else player.requestMove();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void start() {
 | 
				
			||||||
 | 
							players.get(board.getLog().getActiveColor()).requestMove();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void reset() {
 | 
				
			||||||
 | 
							players.values().forEach(Player::cancelMove);
 | 
				
			||||||
 | 
							board.initDefaultPositions();
 | 
				
			||||||
 | 
							boardComponent.repaint();
 | 
				
			||||||
 | 
							overlayComponent.clearDots();
 | 
				
			||||||
 | 
							overlayComponent.clearArrow();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Stops the game by disconnecting its players form the UI.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void stop() {
 | 
				
			||||||
 | 
							players.values().forEach(Player::disconnect);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Assigns the players their opposite colors.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						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.getLog().getActiveColor()).requestMove();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return The board on which this game's moves are made
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Board getBoard() { return board; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return The players participating in this game
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Map<Color, Player> getPlayers() { return players; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,13 @@
 | 
				
			|||||||
package dev.kske.chess.game;
 | 
					package dev.kske.chess.game;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.awt.event.MouseAdapter;
 | 
					 | 
				
			||||||
import java.awt.event.MouseEvent;
 | 
					import java.awt.event.MouseEvent;
 | 
				
			||||||
 | 
					import java.awt.event.MouseListener;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.chess.board.Board;
 | 
					import dev.kske.chess.board.Board;
 | 
				
			||||||
import dev.kske.chess.board.Move;
 | 
					import dev.kske.chess.board.Move;
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Move.Type;
 | 
				
			||||||
import dev.kske.chess.board.Piece.Color;
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
import dev.kske.chess.board.Position;
 | 
					import dev.kske.chess.board.Position;
 | 
				
			||||||
import dev.kske.chess.ui.OverlayComponent;
 | 
					import dev.kske.chess.ui.OverlayComponent;
 | 
				
			||||||
@@ -17,17 +18,37 @@ import dev.kske.chess.ui.OverlayComponent;
 | 
				
			|||||||
 * Created: <strong>06.07.2019</strong><br>
 | 
					 * Created: <strong>06.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class NaturalPlayer extends Player {
 | 
					public class NaturalPlayer extends Player implements MouseListener {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final OverlayComponent overlayComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private boolean		moveRequested;
 | 
						private boolean		moveRequested;
 | 
				
			||||||
 | 
					 | 
				
			||||||
	public NaturalPlayer(Board board, Color color, OverlayComponent overlayComponent) {
 | 
					 | 
				
			||||||
		super(board, color);
 | 
					 | 
				
			||||||
		moveRequested = false;
 | 
					 | 
				
			||||||
		overlayComponent.addMouseListener(new MouseAdapter() {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private Position	pos;
 | 
						private Position	pos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public NaturalPlayer(Color color, OverlayComponent overlayComponent) {
 | 
				
			||||||
 | 
							super(color);
 | 
				
			||||||
 | 
							this.overlayComponent	= overlayComponent;
 | 
				
			||||||
 | 
							name					= "Player";
 | 
				
			||||||
 | 
							moveRequested			= false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							overlayComponent.addMouseListener(this);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void requestMove() {
 | 
				
			||||||
 | 
							moveRequested = true;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void cancelMove() {
 | 
				
			||||||
 | 
							moveRequested = false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void disconnect() {
 | 
				
			||||||
 | 
							overlayComponent.removeMouseListener(this);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void mousePressed(MouseEvent evt) {
 | 
						public void mousePressed(MouseEvent evt) {
 | 
				
			||||||
		if (!moveRequested) return;
 | 
							if (!moveRequested) return;
 | 
				
			||||||
@@ -35,7 +56,7 @@ public class NaturalPlayer extends Player {
 | 
				
			|||||||
			pos = new Position(evt.getPoint().x / overlayComponent.getTileSize(),
 | 
								pos = new Position(evt.getPoint().x / overlayComponent.getTileSize(),
 | 
				
			||||||
					evt.getPoint().y / overlayComponent.getTileSize());
 | 
										evt.getPoint().y / overlayComponent.getTileSize());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					Board board = (Board) NaturalPlayer.this.board.clone();
 | 
								Board board = new Board(this.board);
 | 
				
			||||||
			if (board.get(pos) != null && board.get(pos).getColor() == color) {
 | 
								if (board.get(pos) != null && board.get(pos).getColor() == color) {
 | 
				
			||||||
				List<Position> positions = board.getMoves(pos)
 | 
									List<Position> positions = board.getMoves(pos)
 | 
				
			||||||
					.stream()
 | 
										.stream()
 | 
				
			||||||
@@ -49,15 +70,20 @@ public class NaturalPlayer extends Player {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			overlayComponent.clearDots();
 | 
								overlayComponent.clearDots();
 | 
				
			||||||
			moveRequested = false;
 | 
								moveRequested = false;
 | 
				
			||||||
					game.onMove(NaturalPlayer.this, new Move(pos, dest));
 | 
								game.onMove(NaturalPlayer.this, new Move(pos, dest, Type.UNKNOWN));
 | 
				
			||||||
			pos = null;
 | 
								pos = null;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void requestMove() {
 | 
						public void mouseClicked(MouseEvent e) {}
 | 
				
			||||||
		moveRequested = true;
 | 
					
 | 
				
			||||||
	}
 | 
						@Override
 | 
				
			||||||
 | 
						public void mouseReleased(MouseEvent e) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void mouseEntered(MouseEvent e) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void mouseExited(MouseEvent e) {}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,17 +14,24 @@ public abstract class Player {
 | 
				
			|||||||
	protected Game		game;
 | 
						protected Game		game;
 | 
				
			||||||
	protected Board		board;
 | 
						protected Board		board;
 | 
				
			||||||
	protected Color		color;
 | 
						protected Color		color;
 | 
				
			||||||
 | 
						protected String	name;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Player(Board board, Color color) {
 | 
						public Player(Color color) {
 | 
				
			||||||
		this.board	= board;
 | 
					 | 
				
			||||||
		this.color = color;
 | 
							this.color = color;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public abstract void requestMove();
 | 
						public abstract void requestMove();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public abstract void cancelMove();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public abstract void disconnect();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Game getGame() { return game; }
 | 
						public Game getGame() { return game; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void setGame(Game game) { this.game = game; }
 | 
						public void setGame(Game game) {
 | 
				
			||||||
 | 
							this.game	= game;
 | 
				
			||||||
 | 
							board		= game.getBoard();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Board getBoard() { return board; }
 | 
						public Board getBoard() { return board; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -33,4 +40,8 @@ public abstract class Player {
 | 
				
			|||||||
	public Color getColor() { return color; }
 | 
						public Color getColor() { return color; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void setColor(Color color) { this.color = 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!");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -6,6 +6,7 @@ import java.util.concurrent.ExecutionException;
 | 
				
			|||||||
import java.util.concurrent.ExecutorService;
 | 
					import java.util.concurrent.ExecutorService;
 | 
				
			||||||
import java.util.concurrent.Executors;
 | 
					import java.util.concurrent.Executors;
 | 
				
			||||||
import java.util.concurrent.Future;
 | 
					import java.util.concurrent.Future;
 | 
				
			||||||
 | 
					import java.util.concurrent.TimeUnit;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.swing.SwingUtilities;
 | 
					import javax.swing.SwingUtilities;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,15 +25,23 @@ public class AIPlayer extends Player {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private int	availableProcessors;
 | 
						private int	availableProcessors;
 | 
				
			||||||
	private int	maxDepth;
 | 
						private int	maxDepth;
 | 
				
			||||||
 | 
						private int	alphaBetaThreshold;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public AIPlayer(Board board, Color color, int maxDepth) {
 | 
						private volatile boolean			exitRequested;
 | 
				
			||||||
		super(board, color);
 | 
						private volatile ExecutorService	executor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public AIPlayer(Color color, int maxDepth, int alphaBetaThreshold) {
 | 
				
			||||||
 | 
							super(color);
 | 
				
			||||||
 | 
							name					= "AIPlayer";
 | 
				
			||||||
		availableProcessors		= Runtime.getRuntime().availableProcessors();
 | 
							availableProcessors		= Runtime.getRuntime().availableProcessors();
 | 
				
			||||||
		this.maxDepth			= maxDepth;
 | 
							this.maxDepth			= maxDepth;
 | 
				
			||||||
 | 
							this.alphaBetaThreshold	= alphaBetaThreshold;
 | 
				
			||||||
 | 
							exitRequested			= false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void requestMove() {
 | 
						public void requestMove() {
 | 
				
			||||||
 | 
							exitRequested = false;
 | 
				
			||||||
		/*
 | 
							/*
 | 
				
			||||||
		 * Define some processing threads, split the available moves between them and
 | 
							 * Define some processing threads, split the available moves between them and
 | 
				
			||||||
		 * retrieve the result after their execution.
 | 
							 * retrieve the result after their execution.
 | 
				
			||||||
@@ -41,7 +50,7 @@ public class AIPlayer extends Player {
 | 
				
			|||||||
			/*
 | 
								/*
 | 
				
			||||||
			 * Get a copy of the board and the available moves.
 | 
								 * Get a copy of the board and the available moves.
 | 
				
			||||||
			 */
 | 
								 */
 | 
				
			||||||
			Board				board		= (Board) AIPlayer.this.board.clone();
 | 
								Board		board	= new Board(this.board);
 | 
				
			||||||
			List<Move>	moves	= board.getMoves(color);
 | 
								List<Move>	moves	= board.getMoves(color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			/*
 | 
								/*
 | 
				
			||||||
@@ -55,15 +64,15 @@ public class AIPlayer extends Player {
 | 
				
			|||||||
			for (int i = 0; i < numThreads; i++) {
 | 
								for (int i = 0; i < numThreads; i++) {
 | 
				
			||||||
				if (rem-- > 0) ++endIndex;
 | 
									if (rem-- > 0) ++endIndex;
 | 
				
			||||||
				endIndex += step;
 | 
									endIndex += step;
 | 
				
			||||||
				processors.add(
 | 
									processors.add(new MoveProcessor(new Board(board), moves.subList(beginIndex, endIndex), color,
 | 
				
			||||||
						new MoveProcessor((Board) board.clone(), moves.subList(beginIndex, endIndex), color, maxDepth));
 | 
											maxDepth, alphaBetaThreshold));
 | 
				
			||||||
				beginIndex = endIndex;
 | 
									beginIndex = endIndex;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			/*
 | 
								/*
 | 
				
			||||||
			 * Execute processors, get the best result and pass it back to the Game class
 | 
								 * Execute processors, get the best result and pass it back to the Game class
 | 
				
			||||||
			 */
 | 
								 */
 | 
				
			||||||
			ExecutorService			executor	= Executors.newFixedThreadPool(numThreads);
 | 
								executor = Executors.newFixedThreadPool(numThreads);
 | 
				
			||||||
			List<ProcessingResult> results = new ArrayList<>(numThreads);
 | 
								List<ProcessingResult> results = new ArrayList<>(numThreads);
 | 
				
			||||||
			try {
 | 
								try {
 | 
				
			||||||
				List<Future<ProcessingResult>> futures = executor.invokeAll(processors);
 | 
									List<Future<ProcessingResult>> futures = executor.invokeAll(processors);
 | 
				
			||||||
@@ -74,7 +83,23 @@ public class AIPlayer extends Player {
 | 
				
			|||||||
				ex.printStackTrace();
 | 
									ex.printStackTrace();
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			results.sort((r1, r2) -> Integer.compare(r2.score, r1.score));
 | 
								results.sort((r1, r2) -> Integer.compare(r2.score, r1.score));
 | 
				
			||||||
			SwingUtilities.invokeLater(() -> game.onMove(this, results.get(0).move));
 | 
								if (!exitRequested) SwingUtilities.invokeLater(() -> game.onMove(this, results.get(0).move));
 | 
				
			||||||
		}, "AIPlayer calculation setup").start();
 | 
							}, "AIPlayer calculation setup").start();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void cancelMove() {
 | 
				
			||||||
 | 
							exitRequested = true;
 | 
				
			||||||
 | 
							if (executor != null) {
 | 
				
			||||||
 | 
								executor.shutdownNow();
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									executor.awaitTermination(500, TimeUnit.MILLISECONDS);
 | 
				
			||||||
 | 
								} catch (InterruptedException e) {
 | 
				
			||||||
 | 
									e.printStackTrace();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void disconnect() {}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,6 @@ import java.util.concurrent.Callable;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import dev.kske.chess.board.Board;
 | 
					import dev.kske.chess.board.Board;
 | 
				
			||||||
import dev.kske.chess.board.Move;
 | 
					import dev.kske.chess.board.Move;
 | 
				
			||||||
import dev.kske.chess.board.Piece;
 | 
					 | 
				
			||||||
import dev.kske.chess.board.Piece.Color;
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -17,17 +16,19 @@ import dev.kske.chess.board.Piece.Color;
 | 
				
			|||||||
public class MoveProcessor implements Callable<ProcessingResult> {
 | 
					public class MoveProcessor implements Callable<ProcessingResult> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final Board			board;
 | 
						private final Board			board;
 | 
				
			||||||
	private final List<Move>	rootMoves;;
 | 
						private final List<Move>	rootMoves;
 | 
				
			||||||
	private final Color			color;
 | 
						private final Color			color;
 | 
				
			||||||
	private final int			maxDepth;
 | 
						private final int			maxDepth;
 | 
				
			||||||
 | 
						private final int			alphaBetaThreshold;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private Move bestMove;
 | 
						private Move bestMove;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public MoveProcessor(Board board, List<Move> rootMoves, Color color, int maxDepth) {
 | 
						public MoveProcessor(Board board, List<Move> rootMoves, Color color, int maxDepth, int alphaBetaThreshold) {
 | 
				
			||||||
		this.board				= board;
 | 
							this.board				= board;
 | 
				
			||||||
		this.rootMoves			= rootMoves;
 | 
							this.rootMoves			= rootMoves;
 | 
				
			||||||
		this.color				= color;
 | 
							this.color				= color;
 | 
				
			||||||
		this.maxDepth			= maxDepth;
 | 
							this.maxDepth			= maxDepth;
 | 
				
			||||||
 | 
							this.alphaBetaThreshold	= alphaBetaThreshold;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
@@ -39,12 +40,12 @@ public class MoveProcessor implements Callable<ProcessingResult> {
 | 
				
			|||||||
	private int miniMax(Board board, List<Move> moves, Color color, int depth) {
 | 
						private int miniMax(Board board, List<Move> moves, Color color, int depth) {
 | 
				
			||||||
		int bestValue = Integer.MIN_VALUE;
 | 
							int bestValue = Integer.MIN_VALUE;
 | 
				
			||||||
		for (Move move : moves) {
 | 
							for (Move move : moves) {
 | 
				
			||||||
			Piece	capturePiece	= board.move(move);
 | 
								board.move(move);
 | 
				
			||||||
			int	teamValue	= board.evaluate(color);
 | 
								int	teamValue	= board.evaluate(color);
 | 
				
			||||||
			int	enemyValue	= board.evaluate(color.opposite());
 | 
								int	enemyValue	= board.evaluate(color.opposite());
 | 
				
			||||||
			int	valueChange	= teamValue - enemyValue;
 | 
								int	valueChange	= teamValue - enemyValue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (depth < maxDepth && valueChange >= 0)
 | 
								if (depth < maxDepth && valueChange >= alphaBetaThreshold)
 | 
				
			||||||
				valueChange -= miniMax(board, board.getMoves(color.opposite()), color.opposite(), depth + 1);
 | 
									valueChange -= miniMax(board, board.getMoves(color.opposite()), color.opposite(), depth + 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (valueChange > bestValue) {
 | 
								if (valueChange > bestValue) {
 | 
				
			||||||
@@ -52,7 +53,7 @@ public class MoveProcessor implements Callable<ProcessingResult> {
 | 
				
			|||||||
				if (depth == 0) bestMove = move;
 | 
									if (depth == 0) bestMove = move;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			board.revert(move, capturePiece);
 | 
								board.revert();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return bestValue;
 | 
							return bestValue;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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 info Contains all pieces of information to be sent
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						default void onInfo(UCIInfo info) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Tells the GUI which parameters can be changed in the engine.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param option Option object describing the parameter
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						default void onOption(UCIOption option) {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										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);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										81
									
								
								src/dev/kske/chess/ui/AIConfigDialog.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/dev/kske/chess/ui/AIConfigDialog.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.awt.Dimension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.swing.JButton;
 | 
				
			||||||
 | 
					import javax.swing.JDialog;
 | 
				
			||||||
 | 
					import javax.swing.JLabel;
 | 
				
			||||||
 | 
					import javax.swing.JSpinner;
 | 
				
			||||||
 | 
					import javax.swing.SpinnerNumberModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>AIConfigDialog.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>16.07.2019</strong><br>
 | 
				
			||||||
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class AIConfigDialog extends JDialog {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final long serialVersionUID = -8047984368152479992L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private int		maxDepth;
 | 
				
			||||||
 | 
						private int		alphaBetaThreshold;
 | 
				
			||||||
 | 
						private boolean	startGame	= false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public AIConfigDialog() {
 | 
				
			||||||
 | 
							setSize(new Dimension(337, 212));
 | 
				
			||||||
 | 
							setModal(true);
 | 
				
			||||||
 | 
							setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
 | 
				
			||||||
 | 
							setTitle("AI Configuration");
 | 
				
			||||||
 | 
							getContentPane().setLayout(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JSpinner spAlphaBetaThreshold = new JSpinner();
 | 
				
			||||||
 | 
							spAlphaBetaThreshold.setBounds(222, 68, 95, 28);
 | 
				
			||||||
 | 
							getContentPane().add(spAlphaBetaThreshold);
 | 
				
			||||||
 | 
							spAlphaBetaThreshold.setModel(new SpinnerNumberModel(-10, -100, 100, 5));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JSpinner spMaxDepth = new JSpinner();
 | 
				
			||||||
 | 
							spMaxDepth.setBounds(222, 6, 95, 28);
 | 
				
			||||||
 | 
							getContentPane().add(spMaxDepth);
 | 
				
			||||||
 | 
							spMaxDepth.setModel(new SpinnerNumberModel(4, 1, 10, 1));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JLabel lblAlphabetaThreshold = new JLabel("Alpha-Beta Threshold:");
 | 
				
			||||||
 | 
							lblAlphabetaThreshold.setBounds(16, 68, 194, 28);
 | 
				
			||||||
 | 
							getContentPane().add(lblAlphabetaThreshold);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JButton btnOk = new JButton("OK");
 | 
				
			||||||
 | 
							btnOk.setBounds(16, 137, 84, 28);
 | 
				
			||||||
 | 
							getContentPane().add(btnOk);
 | 
				
			||||||
 | 
							btnOk.addActionListener((evt) -> {
 | 
				
			||||||
 | 
								maxDepth			= ((Integer) spMaxDepth.getValue()).intValue();
 | 
				
			||||||
 | 
								alphaBetaThreshold	= ((Integer) spAlphaBetaThreshold.getValue()).intValue();
 | 
				
			||||||
 | 
								startGame			= true;
 | 
				
			||||||
 | 
								dispose();
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							btnOk.setToolTipText("Start the game");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JButton btnCancel = new JButton("Cancel");
 | 
				
			||||||
 | 
							btnCancel.setBounds(222, 137, 95, 28);
 | 
				
			||||||
 | 
							getContentPane().add(btnCancel);
 | 
				
			||||||
 | 
							btnCancel.addActionListener((evt) -> dispose());
 | 
				
			||||||
 | 
							btnCancel.setToolTipText("Cancel the game start");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JLabel lblMaximalRecursionDepth = new JLabel("Maximal Recursion Depth:");
 | 
				
			||||||
 | 
							lblMaximalRecursionDepth.setBounds(16, 12, 194, 16);
 | 
				
			||||||
 | 
							getContentPane().add(lblMaximalRecursionDepth);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							setLocationRelativeTo(null);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public int getMaxDepth() { return maxDepth; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void setMaxDepth(int maxDepth) { this.maxDepth = maxDepth; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public int getAlphaBetaThreshold() { return alphaBetaThreshold; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void setAlphaBetaThreshold(int alphaBetaThreshold) { this.alphaBetaThreshold = alphaBetaThreshold; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public boolean isStartGame() { return startGame; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void setStartGame(boolean startGame) { this.startGame = startGame; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,8 +1,6 @@
 | 
				
			|||||||
package dev.kske.chess.ui;
 | 
					package dev.kske.chess.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.awt.Dimension;
 | 
					import java.awt.Dimension;
 | 
				
			||||||
import java.awt.event.ComponentAdapter;
 | 
					 | 
				
			||||||
import java.awt.event.ComponentEvent;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.swing.JLayeredPane;
 | 
					import javax.swing.JLayeredPane;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,23 +22,13 @@ public class BoardPane extends JLayeredPane {
 | 
				
			|||||||
	public BoardPane() {
 | 
						public BoardPane() {
 | 
				
			||||||
		boardComponent		= new BoardComponent(this);
 | 
							boardComponent		= new BoardComponent(this);
 | 
				
			||||||
		overlayComponent	= new OverlayComponent(this);
 | 
							overlayComponent	= new OverlayComponent(this);
 | 
				
			||||||
 | 
							setLayer(overlayComponent, 1);
 | 
				
			||||||
 | 
							setLayout(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		add(boardComponent, Integer.valueOf(1));
 | 
							add(boardComponent);
 | 
				
			||||||
		add(overlayComponent, Integer.valueOf(2));
 | 
							add(overlayComponent);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		/*
 | 
							tileSize = 60;
 | 
				
			||||||
		 * 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);
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
		setSize(getPreferredSize());
 | 
							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; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										55
									
								
								src/dev/kske/chess/ui/FENDropTarget.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/dev/kske/chess/ui/FENDropTarget.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,55 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.awt.datatransfer.DataFlavor;
 | 
				
			||||||
 | 
					import java.awt.datatransfer.UnsupportedFlavorException;
 | 
				
			||||||
 | 
					import java.awt.dnd.DnDConstants;
 | 
				
			||||||
 | 
					import java.awt.dnd.DropTargetAdapter;
 | 
				
			||||||
 | 
					import java.awt.dnd.DropTargetDropEvent;
 | 
				
			||||||
 | 
					import java.io.BufferedReader;
 | 
				
			||||||
 | 
					import java.io.File;
 | 
				
			||||||
 | 
					import java.io.FileReader;
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.game.Game;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>FENDropTarget.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>13 Aug 2019</strong><br>
 | 
				
			||||||
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class FENDropTarget extends DropTargetAdapter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private MainWindow mainWindow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public FENDropTarget(MainWindow mainWindow) {
 | 
				
			||||||
 | 
							this.mainWindow = mainWindow;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@SuppressWarnings("unchecked")
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void drop(DropTargetDropEvent evt) {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
 | 
				
			||||||
 | 
								((List<File>) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor)).forEach(file -> {
 | 
				
			||||||
 | 
									try (BufferedReader br = new BufferedReader(new FileReader(file))) {
 | 
				
			||||||
 | 
										final GamePane	gamePane	= mainWindow.addGamePane();
 | 
				
			||||||
 | 
										final String	fen			= br.readLine();
 | 
				
			||||||
 | 
										GameConfigurationDialog.show((whiteName, blackName) -> {
 | 
				
			||||||
 | 
											final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, fen);
 | 
				
			||||||
 | 
											gamePane.setGame(game);
 | 
				
			||||||
 | 
											game.start();
 | 
				
			||||||
 | 
										});
 | 
				
			||||||
 | 
										evt.dropComplete(true);
 | 
				
			||||||
 | 
									} catch (IOException e) {
 | 
				
			||||||
 | 
										e.printStackTrace();
 | 
				
			||||||
 | 
										evt.rejectDrop();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							} catch (UnsupportedFlavorException | IOException ex) {
 | 
				
			||||||
 | 
								ex.printStackTrace();
 | 
				
			||||||
 | 
								evt.rejectDrop();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										76
									
								
								src/dev/kske/chess/ui/GameConfigurationDialog.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/dev/kske/chess/ui/GameConfigurationDialog.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.awt.Font;
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.function.BiConsumer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private GameConfigurationDialog() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static void show(BiConsumer<String, String> action) {
 | 
				
			||||||
 | 
							new JDialog() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								private static final long serialVersionUID = -5768339760489440385L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								{
 | 
				
			||||||
 | 
									setTitle("Game Configuration");
 | 
				
			||||||
 | 
									setBounds(100, 100, 281, 142);
 | 
				
			||||||
 | 
									setModal(true);
 | 
				
			||||||
 | 
									setLocationRelativeTo(null);
 | 
				
			||||||
 | 
									getContentPane().setLayout(null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									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) -> {
 | 
				
			||||||
 | 
										dispose();
 | 
				
			||||||
 | 
										action.accept(options.get(cbWhite.getSelectedIndex()), options.get(cbBlack.getSelectedIndex()));
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
									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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}.setVisible(true);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,72 +0,0 @@
 | 
				
			|||||||
package dev.kske.chess.ui;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import java.awt.FlowLayout;
 | 
					 | 
				
			||||||
import java.util.HashMap;
 | 
					 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import javax.swing.JButton;
 | 
					 | 
				
			||||||
import javax.swing.JDialog;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import dev.kske.chess.board.Board;
 | 
					 | 
				
			||||||
import dev.kske.chess.board.Piece.Color;
 | 
					 | 
				
			||||||
import dev.kske.chess.game.Game;
 | 
					 | 
				
			||||||
import dev.kske.chess.game.NaturalPlayer;
 | 
					 | 
				
			||||||
import dev.kske.chess.game.Player;
 | 
					 | 
				
			||||||
import dev.kske.chess.game.ai.AIPlayer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 | 
				
			||||||
 * File: <strong>GameModeDialog.java</strong><br>
 | 
					 | 
				
			||||||
 * Created: <strong>06.07.2019</strong><br>
 | 
					 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
public class GameModeDialog extends JDialog {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	private static final long serialVersionUID = 5470026233924735607L;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Create the dialog.
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	public GameModeDialog(BoardPane boardPane) {
 | 
					 | 
				
			||||||
		super();
 | 
					 | 
				
			||||||
		setModal(true);
 | 
					 | 
				
			||||||
		setTitle("Game Mode Selection");
 | 
					 | 
				
			||||||
		setBounds(100, 100, 231, 133);
 | 
					 | 
				
			||||||
		setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
 | 
					 | 
				
			||||||
		getContentPane().setLayout(new FlowLayout(FlowLayout.CENTER, 5, 5));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		final BoardComponent	boardComponent		= boardPane.getBoardComponent();
 | 
					 | 
				
			||||||
		final OverlayComponent	overlayComponent	= boardPane.getOverlayComponent();
 | 
					 | 
				
			||||||
		final Board				board				= boardComponent.getBoard();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		JButton btnNatural = new JButton("Game against natural opponent");
 | 
					 | 
				
			||||||
		btnNatural.addActionListener((evt) -> {
 | 
					 | 
				
			||||||
			Map<Color, Player> players = new HashMap<>();
 | 
					 | 
				
			||||||
			players.put(Color.WHITE, new NaturalPlayer(board, Color.WHITE, overlayComponent));
 | 
					 | 
				
			||||||
			players.put(Color.BLACK, new NaturalPlayer(board, Color.BLACK, overlayComponent));
 | 
					 | 
				
			||||||
			new Game(players, boardComponent).start();
 | 
					 | 
				
			||||||
			dispose();
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
		getContentPane().add(btnNatural);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		JButton btnAI = new JButton("Game against AI");
 | 
					 | 
				
			||||||
		btnAI.addActionListener((evt) -> {
 | 
					 | 
				
			||||||
			Map<Color, Player> players = new HashMap<>();
 | 
					 | 
				
			||||||
			players.put(Color.WHITE, new NaturalPlayer(board, Color.WHITE, overlayComponent));
 | 
					 | 
				
			||||||
			players.put(Color.BLACK, new AIPlayer(board, Color.BLACK, 4));
 | 
					 | 
				
			||||||
			new Game(players, boardComponent).start();
 | 
					 | 
				
			||||||
			dispose();
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
		getContentPane().add(btnAI);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		JButton btnAI2 = new JButton("AI against AI");
 | 
					 | 
				
			||||||
		btnAI2.addActionListener((evt) -> {
 | 
					 | 
				
			||||||
			Map<Color, Player> players = new HashMap<>();
 | 
					 | 
				
			||||||
			players.put(Color.WHITE, new AIPlayer(board, Color.WHITE, 4));
 | 
					 | 
				
			||||||
			players.put(Color.BLACK, new AIPlayer(board, Color.BLACK, 3));
 | 
					 | 
				
			||||||
			new Game(players, boardComponent).start();
 | 
					 | 
				
			||||||
			dispose();
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
		getContentPane().add(btnAI2);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										126
									
								
								src/dev/kske/chess/ui/GamePane.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/dev/kske/chess/ui/GamePane.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.awt.GridBagConstraints;
 | 
				
			||||||
 | 
					import java.awt.GridBagLayout;
 | 
				
			||||||
 | 
					import java.awt.GridLayout;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.swing.JButton;
 | 
				
			||||||
 | 
					import javax.swing.JComponent;
 | 
				
			||||||
 | 
					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>
 | 
				
			||||||
 | 
					 * File: <strong>GamePane.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>23.08.2019</strong><br>
 | 
				
			||||||
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class GamePane extends JComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final long serialVersionUID = 4349772338239617477L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private JButton		btnRestart, btnSwapColors;
 | 
				
			||||||
 | 
						private BoardPane	boardPane;
 | 
				
			||||||
 | 
						private LogPanel	logPanel;
 | 
				
			||||||
 | 
						private Game		game;
 | 
				
			||||||
 | 
						private Color		activeColor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public GamePane() {
 | 
				
			||||||
 | 
							activeColor = Color.WHITE;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							GridBagLayout gridBagLayout = new GridBagLayout();
 | 
				
			||||||
 | 
							gridBagLayout.columnWidths	= new int[] { 450, 1, 0 };
 | 
				
			||||||
 | 
							gridBagLayout.rowHeights	= new int[] { 33, 267, 1, 0 };
 | 
				
			||||||
 | 
							gridBagLayout.columnWeights	= new double[] { 0.0, 0.0, Double.MIN_VALUE };
 | 
				
			||||||
 | 
							gridBagLayout.rowWeights	= new double[] { 0.0, 0.0, 0.0, Double.MIN_VALUE };
 | 
				
			||||||
 | 
							setLayout(gridBagLayout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JPanel toolPanel = new JPanel();
 | 
				
			||||||
 | 
							btnRestart = new JButton("Restart");
 | 
				
			||||||
 | 
							btnRestart.addActionListener((evt) -> { if (game != null) game.reset(); game.start(); });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							GridBagConstraints gbc_toolPanel = new GridBagConstraints();
 | 
				
			||||||
 | 
							gbc_toolPanel.anchor	= GridBagConstraints.NORTH;
 | 
				
			||||||
 | 
							gbc_toolPanel.fill		= GridBagConstraints.HORIZONTAL;
 | 
				
			||||||
 | 
							gbc_toolPanel.gridx		= 0;
 | 
				
			||||||
 | 
							gbc_toolPanel.gridy		= 0;
 | 
				
			||||||
 | 
							add(toolPanel, gbc_toolPanel);
 | 
				
			||||||
 | 
							boardPane = new BoardPane();
 | 
				
			||||||
 | 
							GridBagConstraints gbc_boardPane = new GridBagConstraints();
 | 
				
			||||||
 | 
							gbc_boardPane.fill	= GridBagConstraints.BOTH;
 | 
				
			||||||
 | 
							gbc_boardPane.gridx	= 0;
 | 
				
			||||||
 | 
							gbc_boardPane.gridy	= 1;
 | 
				
			||||||
 | 
							add(boardPane, gbc_boardPane);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JPanel				numberPanel		= new JPanel(new GridLayout(8, 1));
 | 
				
			||||||
 | 
							GridBagConstraints	gbc_numberPanel	= new GridBagConstraints();
 | 
				
			||||||
 | 
							gbc_numberPanel.anchor	= GridBagConstraints.WEST;
 | 
				
			||||||
 | 
							gbc_numberPanel.fill	= GridBagConstraints.VERTICAL;
 | 
				
			||||||
 | 
							gbc_numberPanel.gridx	= 1;
 | 
				
			||||||
 | 
							gbc_numberPanel.gridy	= 1;
 | 
				
			||||||
 | 
							add(numberPanel, gbc_numberPanel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JPanel				letterPanel		= new JPanel(new GridLayout(1, 8));
 | 
				
			||||||
 | 
							GridBagConstraints	gbc_letterPanel	= new GridBagConstraints();
 | 
				
			||||||
 | 
							gbc_letterPanel.anchor	= GridBagConstraints.NORTH;
 | 
				
			||||||
 | 
							gbc_letterPanel.fill	= GridBagConstraints.HORIZONTAL;
 | 
				
			||||||
 | 
							gbc_letterPanel.gridx	= 0;
 | 
				
			||||||
 | 
							gbc_letterPanel.gridy	= 2;
 | 
				
			||||||
 | 
							add(letterPanel, gbc_letterPanel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Initialize board coordinates
 | 
				
			||||||
 | 
							for (int i = 0; i < 8; i++) {
 | 
				
			||||||
 | 
								numberPanel.add(new JLabel(String.valueOf(8 - i)));
 | 
				
			||||||
 | 
								JLabel letterLabel = new JLabel(String.valueOf((char) (65 + i)));
 | 
				
			||||||
 | 
								letterLabel.setHorizontalAlignment(JLabel.CENTER);
 | 
				
			||||||
 | 
								letterPanel.add(letterLabel);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Initialize LogPanel
 | 
				
			||||||
 | 
							logPanel = new LogPanel();
 | 
				
			||||||
 | 
							GridBagConstraints gbc_logPanel = new GridBagConstraints();
 | 
				
			||||||
 | 
							gbc_logPanel.anchor	= GridBagConstraints.EAST;
 | 
				
			||||||
 | 
							gbc_logPanel.fill	= GridBagConstraints.VERTICAL;
 | 
				
			||||||
 | 
							gbc_logPanel.gridx	= 2;
 | 
				
			||||||
 | 
							gbc_logPanel.gridy	= 1;
 | 
				
			||||||
 | 
							add(logPanel, gbc_logPanel);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return The {@link BoardPane} instance associated with this game pane
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public BoardPane getBoardPane() { return boardPane; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return The {@link Game} instance associated with this game pane
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Game getGame() { return game; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Assigns a new {@link Game} instance to this game pane. If exactly one of the
 | 
				
			||||||
 | 
						 * players is natural, color swapping functionality is enabled.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param game The {@link Game} to assign to this game pane.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void setGame(Game game) {
 | 
				
			||||||
 | 
							if (this.game != null) this.game.stop();
 | 
				
			||||||
 | 
							this.game = game;
 | 
				
			||||||
 | 
							btnSwapColors.setEnabled(game.getPlayers().get(Color.WHITE) instanceof NaturalPlayer
 | 
				
			||||||
 | 
									^ game.getPlayers().get(Color.BLACK) instanceof NaturalPlayer);
 | 
				
			||||||
 | 
							logPanel.setLog(game.getBoard().getLog());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										76
									
								
								src/dev/kske/chess/ui/LogPanel.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/dev/kske/chess/ui/LogPanel.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.awt.BorderLayout;
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.HashSet;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.swing.JPanel;
 | 
				
			||||||
 | 
					import javax.swing.JScrollPane;
 | 
				
			||||||
 | 
					import javax.swing.JTable;
 | 
				
			||||||
 | 
					import javax.swing.border.EmptyBorder;
 | 
				
			||||||
 | 
					import javax.swing.table.DefaultTableModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Log;
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Log.MoveNode;
 | 
				
			||||||
 | 
					import dev.kske.chess.event.Event;
 | 
				
			||||||
 | 
					import dev.kske.chess.event.EventBus;
 | 
				
			||||||
 | 
					import dev.kske.chess.event.MoveEvent;
 | 
				
			||||||
 | 
					import dev.kske.chess.event.Subscribable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>LogPanel.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>17.07.2019</strong><br>
 | 
				
			||||||
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class LogPanel extends JPanel implements Subscribable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final long serialVersionUID = 1932671698254197119L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private JTable	mtable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Log log;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Create the frame.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public LogPanel() {
 | 
				
			||||||
 | 
							setBorder(new EmptyBorder(5, 5, 5, 5));
 | 
				
			||||||
 | 
							setLayout(new BorderLayout(0, 0));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							mtable = new JTable();
 | 
				
			||||||
 | 
							mtable.setEnabled(false);
 | 
				
			||||||
 | 
							add(new JScrollPane(mtable), BorderLayout.CENTER);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							EventBus.getInstance().register(this);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public Set<Class<?>> supports() {
 | 
				
			||||||
 | 
							return new HashSet<>(Arrays.asList(MoveEvent.class));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void handle(Event<?> event) {
 | 
				
			||||||
 | 
							if (log == null) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO: Display log with variations
 | 
				
			||||||
 | 
							final List<MoveNode>	moves	= /* log.getLoggedMoves() */ new ArrayList<>();
 | 
				
			||||||
 | 
							String[][]				data	= new String[moves.size() / 2 + moves.size() % 2][2];
 | 
				
			||||||
 | 
							for (int i = 0; i < data.length; i++) {
 | 
				
			||||||
 | 
								data[i][0] = moves.get(i * 2).move.toSAN();
 | 
				
			||||||
 | 
								if (i * 2 + 1 < moves.size()) data[i][1] = moves.get(i * 2 + 1).move.toSAN();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							mtable.setModel(new DefaultTableModel(data, new String[] { "White", "Black" }));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Log getLog() { return log; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void setLog(Log log) {
 | 
				
			||||||
 | 
							this.log = log;
 | 
				
			||||||
 | 
							handle(null);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,14 +1,11 @@
 | 
				
			|||||||
package dev.kske.chess.ui;
 | 
					package dev.kske.chess.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.awt.BorderLayout;
 | 
					 | 
				
			||||||
import java.awt.EventQueue;
 | 
					import java.awt.EventQueue;
 | 
				
			||||||
 | 
					import java.awt.Toolkit;
 | 
				
			||||||
 | 
					import java.awt.dnd.DropTarget;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.swing.JButton;
 | 
					 | 
				
			||||||
import javax.swing.JFrame;
 | 
					import javax.swing.JFrame;
 | 
				
			||||||
import javax.swing.JPanel;
 | 
					import javax.swing.JTabbedPane;
 | 
				
			||||||
 | 
					 | 
				
			||||||
import dev.kske.chess.board.Board;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
@@ -16,23 +13,21 @@ import dev.kske.chess.board.Board;
 | 
				
			|||||||
 * Created: <strong>01.07.2019</strong><br>
 | 
					 * Created: <strong>01.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class MainWindow {
 | 
					public class MainWindow extends JFrame {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private JFrame mframe;
 | 
						private static final long serialVersionUID = -3100939302567978977L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private JTabbedPane tabbedPane;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Launch the application.
 | 
						 * Launch the application.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static void main(String[] args) {
 | 
						public static void main(String[] args) {
 | 
				
			||||||
		EventQueue.invokeLater(new Runnable() {
 | 
							EventQueue.invokeLater(() -> {
 | 
				
			||||||
 | 
					 | 
				
			||||||
			public void run() {
 | 
					 | 
				
			||||||
			try {
 | 
								try {
 | 
				
			||||||
					MainWindow window = new MainWindow();
 | 
									new MainWindow();
 | 
				
			||||||
					window.mframe.setVisible(true);
 | 
								} catch (Exception ex) {
 | 
				
			||||||
				} catch (Exception e) {
 | 
									ex.printStackTrace();
 | 
				
			||||||
					e.printStackTrace();
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -41,6 +36,7 @@ public class MainWindow {
 | 
				
			|||||||
	 * Create the application.
 | 
						 * Create the application.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public MainWindow() {
 | 
						public MainWindow() {
 | 
				
			||||||
 | 
							super("Chess by Kai S. K. Engelbart");
 | 
				
			||||||
		initialize();
 | 
							initialize();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -48,24 +44,48 @@ public class MainWindow {
 | 
				
			|||||||
	 * Initialize the contents of the frame.
 | 
						 * Initialize the contents of the frame.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private void initialize() {
 | 
						private void initialize() {
 | 
				
			||||||
		mframe = new JFrame();
 | 
							// Configure frame
 | 
				
			||||||
		mframe.setResizable(false);
 | 
							setResizable(false);
 | 
				
			||||||
		mframe.setBounds(100, 100, 494, 565);
 | 
							setBounds(100, 100, 494, 565);
 | 
				
			||||||
		mframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 | 
							setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 | 
				
			||||||
 | 
							setIconImage(Toolkit.getDefaultToolkit().getImage(getClass().getResource("/pieces/queen_white.png")));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		BoardPane boardPane = new BoardPane();
 | 
							// Add frame content
 | 
				
			||||||
		boardPane.getBoardComponent().setBoard(new Board());
 | 
							tabbedPane = new JTabbedPane();
 | 
				
			||||||
		mframe.getContentPane().add(boardPane, BorderLayout.CENTER);
 | 
							getContentPane().add(tabbedPane);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		JPanel toolPanel = new JPanel();
 | 
							setJMenuBar(new MenuBar(this));
 | 
				
			||||||
		mframe.getContentPane().add(toolPanel, BorderLayout.NORTH);
 | 
							new DropTarget(this, new FENDropTarget(this));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		JButton btnRestart = new JButton("Restart");
 | 
							// Update position and dimensions
 | 
				
			||||||
		btnRestart.addActionListener((evt) -> System.err.println("Resetting not implemented!"));
 | 
							pack();
 | 
				
			||||||
		toolPanel.add(btnRestart);
 | 
							setLocationRelativeTo(null);
 | 
				
			||||||
		mframe.pack();
 | 
							setVisible(true);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Display dialog for game mode selection
 | 
						/**
 | 
				
			||||||
		new GameModeDialog(boardPane).setVisible(true);
 | 
						 * @return The currently selected {@link GamePane} component
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public GamePane getSelectedGamePane() { return (GamePane) tabbedPane.getSelectedComponent(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Creates a new {@link GamePane}, adds it to the tabbed pane and opens it.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @return The new {@link GamePane}
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public GamePane addGamePane() {
 | 
				
			||||||
 | 
							GamePane gamePane = new GamePane();
 | 
				
			||||||
 | 
							tabbedPane.add("Game " + (tabbedPane.getComponentCount() + 1), gamePane);
 | 
				
			||||||
 | 
							tabbedPane.setSelectedIndex(tabbedPane.getComponentCount() - 1);
 | 
				
			||||||
 | 
							return gamePane;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Removes a {@link GamePane} form the tabbed pane.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param index The index of the {@link GamePane} to remove
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void removeGamePane(int index) {
 | 
				
			||||||
 | 
							tabbedPane.remove(index);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										93
									
								
								src/dev/kske/chess/ui/MenuBar.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										93
									
								
								src/dev/kske/chess/ui/MenuBar.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,93 @@
 | 
				
			|||||||
 | 
					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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>MenuBar.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>16.07.2019</strong><br>
 | 
				
			||||||
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class MenuBar extends JMenuBar {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final long serialVersionUID = -7221583703531248228L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final MainWindow mainWindow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public MenuBar(MainWindow mainWindow) {
 | 
				
			||||||
 | 
							this.mainWindow = mainWindow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							initGameMenu();
 | 
				
			||||||
 | 
							initEngineMenu();
 | 
				
			||||||
 | 
							initToolsMenu();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void initGameMenu() {
 | 
				
			||||||
 | 
							JMenu		gameMenu		= new JMenu("Game");
 | 
				
			||||||
 | 
							JMenuItem	newGameMenuItem	= new JMenuItem("New Game");
 | 
				
			||||||
 | 
							newGameMenuItem.addActionListener((evt) -> {
 | 
				
			||||||
 | 
								GameConfigurationDialog.show((whiteName, blackName) -> {
 | 
				
			||||||
 | 
									GamePane	gamePane	= mainWindow.addGamePane();
 | 
				
			||||||
 | 
									Game		game		= new Game(gamePane.getBoardPane(), whiteName, blackName);
 | 
				
			||||||
 | 
									gamePane.setGame(game);
 | 
				
			||||||
 | 
									game.start();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							gameMenu.add(newGameMenuItem);
 | 
				
			||||||
 | 
							add(gameMenu);
 | 
				
			||||||
 | 
							newGameMenuItem.doClick();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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) -> {
 | 
				
			||||||
 | 
								final String fen = mainWindow.getSelectedGamePane().getGame().getBoard().toFEN();
 | 
				
			||||||
 | 
								Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(fen), null);
 | 
				
			||||||
 | 
								JOptionPane.showMessageDialog(mainWindow, String.format("FEN-string copied to clipboard!%n%s", fen));
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							toolsMenu.add(exportFENMenuItem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JMenuItem loadFromFENMenuItem = new JMenuItem("Load board from FEN");
 | 
				
			||||||
 | 
							loadFromFENMenuItem.addActionListener((evt) -> {
 | 
				
			||||||
 | 
								final GamePane	gamePane	= mainWindow.addGamePane();
 | 
				
			||||||
 | 
								final String	fen			= JOptionPane.showInputDialog("Enter a FEN string: ");
 | 
				
			||||||
 | 
								GameConfigurationDialog.show((whiteName, blackName) -> {
 | 
				
			||||||
 | 
									final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, fen);
 | 
				
			||||||
 | 
									gamePane.setGame(game);
 | 
				
			||||||
 | 
									game.start();
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
							toolsMenu.add(loadFromFENMenuItem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							add(toolsMenu);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,12 +1,19 @@
 | 
				
			|||||||
package dev.kske.chess.ui;
 | 
					package dev.kske.chess.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.awt.BasicStroke;
 | 
				
			||||||
import java.awt.Color;
 | 
					import java.awt.Color;
 | 
				
			||||||
import java.awt.Graphics;
 | 
					import java.awt.Graphics;
 | 
				
			||||||
 | 
					import java.awt.Graphics2D;
 | 
				
			||||||
 | 
					import java.awt.Point;
 | 
				
			||||||
 | 
					import java.awt.Polygon;
 | 
				
			||||||
 | 
					import java.awt.Shape;
 | 
				
			||||||
 | 
					import java.awt.geom.AffineTransform;
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.swing.JComponent;
 | 
					import javax.swing.JComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Move;
 | 
				
			||||||
import dev.kske.chess.board.Position;
 | 
					import dev.kske.chess.board.Position;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -22,11 +29,12 @@ public class OverlayComponent extends JComponent {
 | 
				
			|||||||
	private final BoardPane boardPane;
 | 
						private final BoardPane boardPane;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private List<Position>	dots;
 | 
						private List<Position>	dots;
 | 
				
			||||||
 | 
						private Move			arrow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public OverlayComponent(BoardPane boardPane) {
 | 
						public OverlayComponent(BoardPane boardPane) {
 | 
				
			||||||
		this.boardPane = boardPane;
 | 
							this.boardPane = boardPane;
 | 
				
			||||||
		setSize(boardPane.getPreferredSize());
 | 
					 | 
				
			||||||
		dots = new ArrayList<>();
 | 
							dots = new ArrayList<>();
 | 
				
			||||||
 | 
							setSize(boardPane.getPreferredSize());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
@@ -35,6 +43,26 @@ public class OverlayComponent extends JComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		final int tileSize = getTileSize();
 | 
							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
 | 
							// Draw possible moves if a piece was selected
 | 
				
			||||||
		if (!dots.isEmpty()) {
 | 
							if (!dots.isEmpty()) {
 | 
				
			||||||
			g.setColor(Color.green);
 | 
								g.setColor(Color.green);
 | 
				
			||||||
@@ -47,6 +75,36 @@ public class OverlayComponent extends JComponent {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Shape createArrowShape(Point pos, Point dest) {
 | 
				
			||||||
 | 
							Polygon arrowPolygon = new Polygon();
 | 
				
			||||||
 | 
							arrowPolygon.addPoint(-6, 1);
 | 
				
			||||||
 | 
							arrowPolygon.addPoint(3, 1);
 | 
				
			||||||
 | 
							arrowPolygon.addPoint(3, 3);
 | 
				
			||||||
 | 
							arrowPolygon.addPoint(6, 0);
 | 
				
			||||||
 | 
							arrowPolygon.addPoint(3, -3);
 | 
				
			||||||
 | 
							arrowPolygon.addPoint(3, -1);
 | 
				
			||||||
 | 
							arrowPolygon.addPoint(-6, -1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							Point midPoint = midpoint(pos, dest);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							double	rotate		= Math.atan2(dest.y - pos.y, dest.x - pos.x);
 | 
				
			||||||
 | 
							double	ptDistance	= pos.distance(dest);
 | 
				
			||||||
 | 
							double	scale		= ptDistance / 12.0;							// 12 because it's the length of the arrow
 | 
				
			||||||
 | 
																								// polygon.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							AffineTransform transform = new AffineTransform();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							transform.translate(midPoint.x, midPoint.y);
 | 
				
			||||||
 | 
							transform.rotate(rotate);
 | 
				
			||||||
 | 
							transform.scale(scale, 5);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return transform.createTransformedShape(arrowPolygon);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Point midpoint(Point p1, Point p2) {
 | 
				
			||||||
 | 
							return new Point((int) ((p1.x + p2.x) / 2.0), (int) ((p1.y + p2.y) / 2.0));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void displayDots(List<Position> dots) {
 | 
						public void displayDots(List<Position> dots) {
 | 
				
			||||||
		this.dots.clear();
 | 
							this.dots.clear();
 | 
				
			||||||
		this.dots.addAll(dots);
 | 
							this.dots.addAll(dots);
 | 
				
			||||||
@@ -58,5 +116,15 @@ public class OverlayComponent extends JComponent {
 | 
				
			|||||||
		repaint();
 | 
							repaint();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void displayArrow(Move arrow) {
 | 
				
			||||||
 | 
							this.arrow = arrow;
 | 
				
			||||||
 | 
							repaint();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void clearArrow() {
 | 
				
			||||||
 | 
							arrow = null;
 | 
				
			||||||
 | 
							repaint();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public int getTileSize() { return boardPane.getTileSize(); }
 | 
						public int getTileSize() { return boardPane.getTileSize(); }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,8 +2,8 @@ package dev.kske.chess.ui;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.awt.Image;
 | 
					import java.awt.Image;
 | 
				
			||||||
import java.awt.image.BufferedImage;
 | 
					import java.awt.image.BufferedImage;
 | 
				
			||||||
import java.io.File;
 | 
					 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
import java.util.HashMap;
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,11 +19,11 @@ import dev.kske.chess.board.Piece;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public class TextureUtil {
 | 
					public class TextureUtil {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static Map<String, Image> textures;
 | 
						private static Map<String, Image> textures = new HashMap<>(), scaledTextures = new HashMap<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	static {
 | 
						static {
 | 
				
			||||||
		textures = new HashMap<>();
 | 
					 | 
				
			||||||
		loadPieceTextures();
 | 
							loadPieceTextures();
 | 
				
			||||||
 | 
							scaledTextures.putAll(textures);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private TextureUtil() {}
 | 
						private TextureUtil() {}
 | 
				
			||||||
@@ -36,26 +36,28 @@ public class TextureUtil {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static Image getPieceTexture(Piece piece) {
 | 
						public static Image getPieceTexture(Piece piece) {
 | 
				
			||||||
		String key = piece.getType().toString().toLowerCase() + "_" + piece.getColor().toString().toLowerCase();
 | 
							String key = piece.getType().toString().toLowerCase() + "_" + piece.getColor().toString().toLowerCase();
 | 
				
			||||||
		return textures.get(key);
 | 
							return scaledTextures.get(key);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Scales all piece textures to fit the current tile size
 | 
						 * Scales all piece textures to fit the current tile size
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static void scalePieceTextures(int scale) {
 | 
						public static void scalePieceTextures(int scale) {
 | 
				
			||||||
		textures.replaceAll((key, img) -> img.getScaledInstance(scale, scale, Image.SCALE_SMOOTH));
 | 
							scaledTextures.clear();
 | 
				
			||||||
 | 
							textures
 | 
				
			||||||
 | 
								.forEach((key, img) -> scaledTextures.put(key, img.getScaledInstance(scale, scale, Image.SCALE_SMOOTH)));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Loads an image from a file.
 | 
						 * Loads an image from a file in the resource folder.
 | 
				
			||||||
	 * 
 | 
						 * 
 | 
				
			||||||
	 * @param file The image file
 | 
						 * @param fileName The name of the image resource
 | 
				
			||||||
	 * @return The loaded image
 | 
						 * @return The loaded image
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private static Image loadImage(File file) {
 | 
						private static Image loadImage(String fileName) {
 | 
				
			||||||
		BufferedImage in = null;
 | 
							BufferedImage in = null;
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			in = ImageIO.read(file);
 | 
								in = ImageIO.read(TextureUtil.class.getResourceAsStream(fileName));
 | 
				
			||||||
		} catch (IOException e) {
 | 
							} catch (IOException e) {
 | 
				
			||||||
			e.printStackTrace();
 | 
								e.printStackTrace();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -67,10 +69,20 @@ public class TextureUtil {
 | 
				
			|||||||
	 * The filenames without extensions are used as keys in the map textures.
 | 
						 * The filenames without extensions are used as keys in the map textures.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	private static void loadPieceTextures() {
 | 
						private static void loadPieceTextures() {
 | 
				
			||||||
		File	dir		= new File("res/pieces");
 | 
							Arrays
 | 
				
			||||||
		File[]	files	= dir.listFiles((File parentDir, String name) -> name.toLowerCase().endsWith(".png"));
 | 
								.asList("king_white",
 | 
				
			||||||
		for (File file : files)
 | 
										"king_black",
 | 
				
			||||||
			textures.put(file.getName().replaceFirst("[.][^.]+$", ""), TextureUtil.loadImage(file));
 | 
										"queen_white",
 | 
				
			||||||
 | 
										"queen_black",
 | 
				
			||||||
 | 
										"rook_white",
 | 
				
			||||||
 | 
										"rook_black",
 | 
				
			||||||
 | 
										"knight_white",
 | 
				
			||||||
 | 
										"knight_black",
 | 
				
			||||||
 | 
										"bishop_white",
 | 
				
			||||||
 | 
										"bishop_black",
 | 
				
			||||||
 | 
										"pawn_white",
 | 
				
			||||||
 | 
										"pawn_black")
 | 
				
			||||||
 | 
								.forEach(name -> textures.put(name, loadImage("/pieces/" + name + ".png")));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
package dev.kske.chess.test;
 | 
					package dev.kske.chess.board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import static org.junit.Assert.assertNotEquals;
 | 
					import static org.junit.Assert.assertNotEquals;
 | 
				
			||||||
import static org.junit.Assert.assertNotSame;
 | 
					import static org.junit.Assert.assertNotSame;
 | 
				
			||||||
@@ -6,17 +6,15 @@ import static org.junit.Assert.assertNotSame;
 | 
				
			|||||||
import org.junit.jupiter.api.BeforeEach;
 | 
					import org.junit.jupiter.api.BeforeEach;
 | 
				
			||||||
import org.junit.jupiter.api.Test;
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.chess.board.Board;
 | 
					 | 
				
			||||||
import dev.kske.chess.board.Piece.Color;
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
import dev.kske.chess.board.Queen;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>BoardCloneTest.java</strong><br>
 | 
					 * File: <strong>BoardTest.java</strong><br>
 | 
				
			||||||
 * Created: <strong>08.07.2019</strong><br>
 | 
					 * Created: <strong>08.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class BoardCloneTest {
 | 
					class BoardTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	Board board;
 | 
						Board board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -33,12 +31,13 @@ class BoardCloneTest {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	void testClone() {
 | 
						void testClone() {
 | 
				
			||||||
		Board clone = (Board) board.clone();
 | 
							Board clone = new Board(board);
 | 
				
			||||||
		assertNotSame(clone, board);
 | 
							assertNotSame(clone, board);
 | 
				
			||||||
		assertNotSame(clone.getBoardArr(), board.getBoardArr());
 | 
							assertNotSame(clone.getBoardArr(), board.getBoardArr());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		clone.getBoardArr()[0][0] = new Queen(Color.BLACK, clone);
 | 
							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.getBoardArr()[0][0], board.getBoardArr()[0][0]);
 | 
				
			||||||
 | 
							assertNotEquals(clone.getLog().getActiveColor(), board.getLog().getActiveColor());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										165
									
								
								test/dev/kske/chess/board/LogTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								test/dev/kske/chess/board/LogTest.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,165 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.assertEquals;
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.assertNotEquals;
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.assertNull;
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.assertTrue;
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.fail;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>LogTest.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>13 Sep 2019</strong><br>
 | 
				
			||||||
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class LogTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Log log = new Log();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#Log()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testLog() {
 | 
				
			||||||
 | 
							assertTrue(log.isEmpty());
 | 
				
			||||||
 | 
							assertNull(log.getLast());
 | 
				
			||||||
 | 
							assertNull(log.getRoot());
 | 
				
			||||||
 | 
							assertEquals(log.getActiveColor(), Color.WHITE);
 | 
				
			||||||
 | 
							assertNull(log.getEnPassant());
 | 
				
			||||||
 | 
							assertEquals(log.getFullmoveCounter(), 1);
 | 
				
			||||||
 | 
							assertEquals(log.getHalfmoveClock(), 0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#clone()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testClone() {
 | 
				
			||||||
 | 
							Log other = new Log(log, false);
 | 
				
			||||||
 | 
							log.setActiveColor(Color.WHITE);
 | 
				
			||||||
 | 
							other.setActiveColor(Color.BLACK);
 | 
				
			||||||
 | 
							assertNotEquals(log.getActiveColor(), other.getActiveColor());
 | 
				
			||||||
 | 
							log.add(Move.fromSAN("a2a4"), null, true);
 | 
				
			||||||
 | 
							log.add(Move.fromSAN("a4a5"), null, true);
 | 
				
			||||||
 | 
							other.add(Move.fromSAN("a2a4"), null, true);
 | 
				
			||||||
 | 
							other.add(Move.fromSAN("a4a5"), null, true);
 | 
				
			||||||
 | 
							assertNotEquals(log.getRoot(), other.getRoot());
 | 
				
			||||||
 | 
							assertNotEquals(log.getRoot().getVariations(), other.getRoot().getVariations());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#add(dev.kske.chess.board.Move, dev.kske.chess.board.Piece, boolean)}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testAdd() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#removeLast()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testRemoveLast() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#isEmpty()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testIsEmpty() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#reset()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testReset() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#getRoot()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testGetRoot() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#getLast()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testGetLast() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#getEnPassant()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testGetEnPassant() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#setEnPassant(dev.kske.chess.board.Position)}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testSetEnPassant() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#getActiveColor()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testGetActiveColor() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#setActiveColor(dev.kske.chess.board.Piece.Color)}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testSetActiveColor() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#getFullmoveCounter()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testGetFullmoveCounter() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#setFullmoveCounter(int)}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testSetFullmoveCounter() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#getHalfmoveClock()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testGetHalfmoveClock() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Log#setHalfmoveClock(int)}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testSetHalfmoveClock() {
 | 
				
			||||||
 | 
							fail("Not yet implemented");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										47
									
								
								test/dev/kske/chess/board/PositionTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								test/dev/kske/chess/board/PositionTest.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,47 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.Assert.assertEquals;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * 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 {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						final int	n			= 4;
 | 
				
			||||||
 | 
						Position[]	positions	= new Position[] { new Position(0, 0), new Position(7, 7), new Position(0, 7), new Position(7, 0) };
 | 
				
			||||||
 | 
						String[]	sans		= new String[] { "a8", "h1", "a1", "h8" };
 | 
				
			||||||
 | 
						String[]	strings		= new String[] { "[0, 0]", "[7, 7]", "[0, 7]", "[7, 0]" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for
 | 
				
			||||||
 | 
						 * {@link dev.kske.chess.board.Position#fromSAN(java.lang.String)}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testFromSAN() {
 | 
				
			||||||
 | 
							for (int i = 0; i < n; i++)
 | 
				
			||||||
 | 
								assertEquals(positions[i], Position.fromSAN(sans[i]));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Position#toSAN()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testToSAN() {
 | 
				
			||||||
 | 
							for (int i = 0; i < n; i++)
 | 
				
			||||||
 | 
								assertEquals(sans[i], positions[i].toSAN());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.Position#toString()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testToString() {
 | 
				
			||||||
 | 
							for (int i = 0; i < n; i++)
 | 
				
			||||||
 | 
								assertEquals(strings[i], positions[i].toString());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user