Compare commits
	
		
			45 Commits
		
	
	
		
			v0.4-alpha
			...
			v0.5-alpha
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 95773c49ba | |||
| 3bff5aa56e | |||
| 0487b47691 | |||
| 39b7ce20b1 | |||
| 91512b7774 | |||
| 44f91591b4 | |||
| 6af213ed4f | |||
| fbf66e6ec1 | |||
| 6360a8a4ba | |||
| 78bac2e913 | |||
| 44f6a4b9b8 | |||
| 6bbef7deee | |||
| a98ca5e350 | |||
| 15cdf00eb1 | |||
| 37c5f2bd23 | |||
| 02c5e33f10 | |||
| 28939f0471 | |||
| b5c30d59af | |||
| 2e672841cd | |||
| 1e44234f7e | |||
| 792f93fc3f | |||
| 27f37a8cf0 | |||
| 4d943c1a19 | |||
| 1d78b8f071 | |||
| a41a2819da | |||
| 40d80fdc12 | |||
| cbf6aa2013 | |||
| 5dbd38d1c0 | |||
| 4857b48e4e | |||
| c438dd00cb | |||
| 7e8f75a008 | |||
| db8fe1c4c0 | |||
| acb0e63c82 | |||
| 2f1ae6e9c8 | |||
| 1f2cedd455 | |||
| 1d19f17c56 | |||
| 86cf2afc8f | |||
| 8b9793611a | |||
| 446f895ae1 | |||
| 9839d5a23e | |||
| 713c95338e | |||
| 84b3e1503f | |||
| cc0440233b | |||
| 286ea93ee3 | |||
| 994cb84729 | 
@@ -7,6 +7,11 @@
 | 
				
			|||||||
			<attribute name="test" value="true"/>
 | 
								<attribute name="test" value="true"/>
 | 
				
			||||||
		</attributes>
 | 
							</attributes>
 | 
				
			||||||
	</classpathentry>
 | 
						</classpathentry>
 | 
				
			||||||
 | 
						<classpathentry kind="src" output="bin_test" path="test_res">
 | 
				
			||||||
 | 
							<attributes>
 | 
				
			||||||
 | 
								<attribute name="test" value="true"/>
 | 
				
			||||||
 | 
							</attributes>
 | 
				
			||||||
 | 
						</classpathentry>
 | 
				
			||||||
	<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="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"/>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,9 @@ import java.util.List;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Bishop.java</strong><br>
 | 
					 * File: <strong>Bishop.java</strong><br>
 | 
				
			||||||
 * Created: <strong>01.07.2019</strong><br>
 | 
					 * Created: <strong>01.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class Bishop extends Piece {
 | 
					public class Bishop extends Piece {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -63,5 +65,5 @@ public class Bishop extends Piece {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public Type getType() { return Type.BISHOP; }
 | 
						public int getValue() { return 30; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,79 +1,32 @@
 | 
				
			|||||||
package dev.kske.chess.board;
 | 
					package dev.kske.chess.board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
import java.util.HashMap;
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Board.java</strong><br>
 | 
					 * File: <strong>Board.java</strong><br>
 | 
				
			||||||
 * Created: <strong>01.07.2019</strong><br>
 | 
					 * Created: <strong>01.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 *
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class Board {
 | 
					public class Board {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private Piece[][]				boardArr	= new Piece[8][8];
 | 
						private Piece[][]				boardArr	= new Piece[8][8];
 | 
				
			||||||
	private Map<Color, Position>	kingPos		= new HashMap<>();
 | 
						private Map<Color, Position>	kingPos		= new HashMap<>();
 | 
				
			||||||
	private Map<Color, Map<Type, Boolean>>	castlingRights	= new HashMap<>();
 | 
					 | 
				
			||||||
	private Log						log			= new Log();
 | 
						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.
 | 
						 * Initializes the board with the default chess starting position.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Board() {
 | 
						public Board() { initDefaultPositions(); }
 | 
				
			||||||
		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>
 | 
						 * Creates a copy of another {@link Board} instance.<br>
 | 
				
			||||||
@@ -81,9 +34,9 @@ public class Board {
 | 
				
			|||||||
	 * apart from the current {@link MoveNode}.
 | 
						 * apart from the current {@link MoveNode}.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param other The {@link Board} instance to copy
 | 
						 * @param other The {@link Board} instance to copy
 | 
				
			||||||
 | 
						 * @param copyVariations TODO
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Board(Board other) {
 | 
						public Board(Board other, boolean copyVariations) {
 | 
				
			||||||
		boardArr = new Piece[8][8];
 | 
					 | 
				
			||||||
		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 (other.boardArr[i][j] == null) continue;
 | 
									if (other.boardArr[i][j] == null) continue;
 | 
				
			||||||
@@ -92,11 +45,11 @@ public class Board {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		kingPos.putAll(other.kingPos);
 | 
							kingPos.putAll(other.kingPos);
 | 
				
			||||||
		Map<Type, Boolean> whiteCastling = new HashMap<>(other.castlingRights.get(Color.WHITE)),
 | 
							log = new Log(other.log, copyVariations);
 | 
				
			||||||
				blackCastling = new HashMap<>(other.castlingRights.get(Color.BLACK));
 | 
					
 | 
				
			||||||
		castlingRights.put(Color.WHITE, whiteCastling);
 | 
							// Synchronize the current move node with the board
 | 
				
			||||||
		castlingRights.put(Color.BLACK, blackCastling);
 | 
							while (log.getLast().hasVariations())
 | 
				
			||||||
		log = new Log(other.log, false);
 | 
								log.selectNextNode(0);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -109,9 +62,6 @@ public class Board {
 | 
				
			|||||||
		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
 | 
				
			||||||
			move(move);
 | 
								move(move);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -134,128 +84,40 @@ public class Board {
 | 
				
			|||||||
		Piece	piece			= getPos(move);
 | 
							Piece	piece			= getPos(move);
 | 
				
			||||||
		Piece	capturePiece	= getDest(move);
 | 
							Piece	capturePiece	= getDest(move);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		switch (move.type) {
 | 
							// Execute the move
 | 
				
			||||||
			case PAWN_PROMOTION:
 | 
							move.execute(this);
 | 
				
			||||||
				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);
 | 
					 | 
				
			||||||
				setPos(move, null);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				// Move the rook
 | 
							// Update the king's position if the moved piece is the king
 | 
				
			||||||
				Move rookMove = move.dest.x == 6 ? new Move(7, move.pos.y, 5, move.pos.y) // Kingside
 | 
							if (piece instanceof King) kingPos.put(piece.getColor(), move.getDest());
 | 
				
			||||||
						: 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);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Update log
 | 
							// Update log
 | 
				
			||||||
		log.add(move, capturePiece, piece.getType() == Type.PAWN);
 | 
							log.add(move, piece, capturePiece);
 | 
				
			||||||
 | 
					 | 
				
			||||||
		updateCastlingRights();
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Reverts the last move.
 | 
						 * Moves a piece across the board without checking if the move is legal.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param sanMove The move to execute in SAN (Standard Algebraic Notation)
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void move(String sanMove) {
 | 
				
			||||||
 | 
							move(Move.fromSAN(sanMove, this));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Reverts the last move and removes it from the log.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void revert() {
 | 
						public void revert() {
 | 
				
			||||||
		MoveNode	moveNode	= log.getLast();
 | 
							MoveNode	moveNode	= log.getLast();
 | 
				
			||||||
		Move		move		= moveNode.move;
 | 
							Move		move		= moveNode.move;
 | 
				
			||||||
		Piece		capturedPiece	= moveNode.capturedPiece;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		switch (move.type) {
 | 
							// Revert the move
 | 
				
			||||||
			case PAWN_PROMOTION:
 | 
							move.revert(this, moveNode.capturedPiece);
 | 
				
			||||||
				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));
 | 
					 | 
				
			||||||
				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) instanceof King) kingPos.put(getPos(move).getColor(), move.getPos());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Update log
 | 
							// Update log
 | 
				
			||||||
		log.removeLast();
 | 
							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);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -268,14 +130,11 @@ public class Board {
 | 
				
			|||||||
		List<Move> moves = new ArrayList<>();
 | 
							List<Move> moves = new ArrayList<>();
 | 
				
			||||||
		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)
 | 
									if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) moves.addAll(boardArr[i][j].getMoves(new Position(i, j)));
 | 
				
			||||||
					moves.addAll(boardArr[i][j].getMoves(new Position(i, j)));
 | 
					 | 
				
			||||||
		return moves;
 | 
							return moves;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public List<Move> getMoves(Position pos) {
 | 
						public List<Move> getMoves(Position pos) { return get(pos).getMoves(pos); }
 | 
				
			||||||
		return get(pos).getMoves(pos);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Checks, if the king is in check.
 | 
						 * Checks, if the king is in check.
 | 
				
			||||||
@@ -283,13 +142,20 @@ public class Board {
 | 
				
			|||||||
	 * @param color The color of the king to check
 | 
						 * @param color The color of the king to check
 | 
				
			||||||
	 * @return {@code true}, if the king is in check
 | 
						 * @return {@code true}, if the king is in check
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public boolean checkCheck(Color color) {
 | 
						public boolean checkCheck(Color color) { return isAttacked(kingPos.get(color), color.opposite()); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Checks, if a field can be attacked by pieces of a certain color.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param dest  the field to check
 | 
				
			||||||
 | 
						 * @param color the color of a potential attacker piece
 | 
				
			||||||
 | 
						 * @return {@code true} if a move with the destination {@code dest}
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public boolean isAttacked(Position dest, Color color) {
 | 
				
			||||||
		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++) {
 | 
				
			||||||
				Position pos = new Position(i, j);
 | 
									Position pos = new Position(i, j);
 | 
				
			||||||
				if (get(pos) != null && get(pos).getColor() != color
 | 
									if (get(pos) != null && get(pos).getColor() == color && get(pos).isValidMove(new Move(pos, dest))) return true;
 | 
				
			||||||
						&& get(pos).isValidMove(new Move(pos, kingPos.get(color))))
 | 
					 | 
				
			||||||
					return true;
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		return false;
 | 
							return false;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -315,44 +181,9 @@ public class Board {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public GameState getGameEventType(Color color) {
 | 
						public BoardState getGameEventType(Color color) {
 | 
				
			||||||
		return checkCheck(color) ? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK
 | 
							return checkCheck(color) ? checkCheckmate(color) ? BoardState.CHECKMATE : BoardState.CHECK
 | 
				
			||||||
				: getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? GameState.STALEMATE
 | 
									: getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? BoardState.STALEMATE : BoardState.NORMAL;
 | 
				
			||||||
						: GameState.NORMAL;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	/**
 | 
					 | 
				
			||||||
	 * Evaluated the board.
 | 
					 | 
				
			||||||
	 * 
 | 
					 | 
				
			||||||
	 * @param color The color to evaluate for
 | 
					 | 
				
			||||||
	 * @return An positive number representing how good the position is
 | 
					 | 
				
			||||||
	 */
 | 
					 | 
				
			||||||
	public int evaluate(Color color) {
 | 
					 | 
				
			||||||
		int score = 0;
 | 
					 | 
				
			||||||
		for (int i = 0; i < 8; i++)
 | 
					 | 
				
			||||||
			for (int j = 0; j < 8; j++)
 | 
					 | 
				
			||||||
				if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) {
 | 
					 | 
				
			||||||
					switch (boardArr[i][j].getType()) {
 | 
					 | 
				
			||||||
						case QUEEN:
 | 
					 | 
				
			||||||
							score += 90;
 | 
					 | 
				
			||||||
							break;
 | 
					 | 
				
			||||||
						case ROOK:
 | 
					 | 
				
			||||||
							score += 50;
 | 
					 | 
				
			||||||
							break;
 | 
					 | 
				
			||||||
						case KNIGHT:
 | 
					 | 
				
			||||||
							score += 30;
 | 
					 | 
				
			||||||
							break;
 | 
					 | 
				
			||||||
						case BISHOP:
 | 
					 | 
				
			||||||
							score += 30;
 | 
					 | 
				
			||||||
							break;
 | 
					 | 
				
			||||||
						case PAWN:
 | 
					 | 
				
			||||||
							score += 10;
 | 
					 | 
				
			||||||
							break;
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
					if (positionScores.containsKey(boardArr[i][j].getType()))
 | 
					 | 
				
			||||||
						score += positionScores.get(boardArr[i][j].getType())[i][color == Color.WHITE ? j : 7 - j];
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
		return score;
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -400,159 +231,79 @@ public class Board {
 | 
				
			|||||||
			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();
 | 
							log.reset();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						@Override
 | 
				
			||||||
	 * Initialized the board with a position specified in a FEN-encoded string.
 | 
						public int hashCode() {
 | 
				
			||||||
	 * 
 | 
							final int	prime	= 31;
 | 
				
			||||||
	 * @param fen The FEN-encoded string representing target state of the board
 | 
							int			result	= 1;
 | 
				
			||||||
	 */
 | 
							result	= prime * result + Arrays.deepHashCode(boardArr);
 | 
				
			||||||
	public void initFromFEN(String fen) {
 | 
							result	= prime * result + Objects.hash(kingPos, log);
 | 
				
			||||||
		String[] parts = fen.split(" ");
 | 
							return result;
 | 
				
			||||||
		log.reset();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Piece placement (from white's perspective)
 | 
					 | 
				
			||||||
		String[] rows = parts[0].split("/");
 | 
					 | 
				
			||||||
		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);
 | 
					 | 
				
			||||||
					}
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Active color
 | 
						@Override
 | 
				
			||||||
		log.setActiveColor(Color.fromFirstChar(parts[1].charAt(0)));
 | 
						public boolean equals(Object obj) {
 | 
				
			||||||
 | 
							if (this == obj) return true;
 | 
				
			||||||
		// Castling rights
 | 
							if (obj == null) return false;
 | 
				
			||||||
		Map<Type, Boolean> whiteCastling = new HashMap<>(), blackCastling = new HashMap<>();
 | 
							if (getClass() != obj.getClass()) return false;
 | 
				
			||||||
		for (char c : parts[2].toCharArray())
 | 
							Board other = (Board) obj;
 | 
				
			||||||
			switch (c) {
 | 
							return Arrays.deepEquals(boardArr, other.boardArr) && Objects.equals(kingPos, other.kingPos) && Objects.equals(log, other.log);
 | 
				
			||||||
				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++) {
 | 
					 | 
				
			||||||
				final Piece piece = boardArr[j][i];
 | 
					 | 
				
			||||||
				if (piece == null) ++emptyCount;
 | 
					 | 
				
			||||||
				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('/');
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// Active color
 | 
					 | 
				
			||||||
		sb.append(" " + log.getActiveColor().firstChar());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// 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
 | 
						 * @param pos The position from which to return a piece
 | 
				
			||||||
	 * @return The piece at the position
 | 
						 * @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];
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Searches for a {@link Piece} inside a file (A - H).
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param pieceClass The class of the piece to search for
 | 
				
			||||||
 | 
						 * @param file       The file in which to search for the piece
 | 
				
			||||||
 | 
						 * @return The rank (1 - 8) of the first piece with the specified type and
 | 
				
			||||||
 | 
						 *         current color in the file, or {@code -1} if there isn't any
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public int get(Class<? extends Piece> pieceClass, char file) {
 | 
				
			||||||
 | 
							int x = file - 97;
 | 
				
			||||||
 | 
							for (int i = 0; i < 8; i++)
 | 
				
			||||||
 | 
								if (boardArr[x][i] != null && boardArr[x][i].getClass() == pieceClass && boardArr[x][i].getColor() == log.getActiveColor()) return 8 - i;
 | 
				
			||||||
 | 
							return -1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Searches for a {@link Piece} inside a rank (1 - 8).
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param pieceClass The class of the piece to search for
 | 
				
			||||||
 | 
						 * @param rank       The rank in which to search for the piece
 | 
				
			||||||
 | 
						 * @return The file (A - H) of the first piece with the specified type and
 | 
				
			||||||
 | 
						 *         current color in the file, or {@code -} if there isn't any
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public char get(Class<? extends Piece> pieceClass, int rank) {
 | 
				
			||||||
 | 
							int y = rank - 1;
 | 
				
			||||||
 | 
							for (int i = 0; i < 8; i++)
 | 
				
			||||||
 | 
								if (boardArr[i][y] != null && boardArr[i][y].getClass() == pieceClass && boardArr[i][y].getColor() == log.getActiveColor())
 | 
				
			||||||
 | 
									return (char) (i + 97);
 | 
				
			||||||
 | 
							return '-';
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Searches for a {@link Piece} that can move to a {@link Position}.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param pieceClass The class of the piece to search for
 | 
				
			||||||
 | 
						 * @param dest       The destination that the piece is required to reach
 | 
				
			||||||
 | 
						 * @return The position of a piece that can move to the specified destination
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Position get(Class<? extends Piece> pieceClass, Position dest) {
 | 
				
			||||||
 | 
							for (int i = 0; i < 8; i++)
 | 
				
			||||||
 | 
								for (int j = 0; j < 8; j++)
 | 
				
			||||||
 | 
									if (boardArr[i][j] != null && boardArr[i][j].getClass() == pieceClass && boardArr[i][j].getColor() == log.getActiveColor()) {
 | 
				
			||||||
 | 
										Position pos = new Position(i, j);
 | 
				
			||||||
 | 
										if (boardArr[i][j].isValidMove(new Move(pos, dest))) return pos;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
							return null;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -561,25 +312,19 @@ public class Board {
 | 
				
			|||||||
	 * @param pos   The position to place the piece at
 | 
						 * @param pos   The position to place the piece at
 | 
				
			||||||
	 * @param piece The piece to place
 | 
						 * @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
 | 
						 * @param move The move from which position to return a piece
 | 
				
			||||||
	 * @return The piece at the position of the move
 | 
						 * @return The piece at the position of the move
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Piece getPos(Move move) {
 | 
						public Piece getPos(Move move) { return get(move.getPos()); }
 | 
				
			||||||
		return get(move.pos);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @param move The move from which destination to return a piece
 | 
						 * @param move The move from which destination to return a piece
 | 
				
			||||||
	 * @return The piece at the destination of the move
 | 
						 * @return The piece at the destination of the move
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Piece getDest(Move move) {
 | 
						public Piece getDest(Move move) { return get(move.getDest()); }
 | 
				
			||||||
		return get(move.dest);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Places a piece at the position of a move.
 | 
						 * Places a piece at the position of a move.
 | 
				
			||||||
@@ -587,9 +332,7 @@ public class Board {
 | 
				
			|||||||
	 * @param move  The move at which position to place the piece
 | 
						 * @param move  The move at which position to place the piece
 | 
				
			||||||
	 * @param piece The piece to place
 | 
						 * @param piece The piece to place
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setPos(Move move, Piece piece) {
 | 
						public void setPos(Move move, Piece piece) { set(move.getPos(), piece); }
 | 
				
			||||||
		set(move.pos, piece);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Places a piece at the destination of a move.
 | 
						 * Places a piece at the destination of a move.
 | 
				
			||||||
@@ -597,9 +340,7 @@ public class Board {
 | 
				
			|||||||
	 * @param move  The move at which destination to place the piece
 | 
						 * @param move  The move at which destination to place the piece
 | 
				
			||||||
	 * @param piece The piece to place
 | 
						 * @param piece The piece to place
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setDest(Move move, Piece piece) {
 | 
						public void setDest(Move move, Piece piece) { set(move.getDest(), piece); }
 | 
				
			||||||
		set(move.dest, piece);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return The board array
 | 
						 * @return The board array
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,10 +2,12 @@ package dev.kske.chess.board;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>GameState.java</strong><br>
 | 
					 * File: <strong>BoardState.java</strong><br>
 | 
				
			||||||
 * Created: <strong>07.07.2019</strong><br>
 | 
					 * Created: <strong>07.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public enum GameState {
 | 
					public enum BoardState {
 | 
				
			||||||
	CHECK, CHECKMATE, STALEMATE, NORMAL;
 | 
						CHECK, CHECKMATE, STALEMATE, NORMAL;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										44
									
								
								src/dev/kske/chess/board/Castling.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/dev/kske/chess/board/Castling.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>Castling.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>2 Nov 2019</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @since Chess v0.5-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class Castling extends Move {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Move rookMove;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Castling(Position pos, Position dest) {
 | 
				
			||||||
 | 
							super(pos, dest);
 | 
				
			||||||
 | 
							rookMove = dest.x == 6 ? new Move(7, pos.y, 5, pos.y) : new Move(0, pos.y, 3, pos.y);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Castling(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void execute(Board board) {
 | 
				
			||||||
 | 
							// Move the king and the rook
 | 
				
			||||||
 | 
							super.execute(board);
 | 
				
			||||||
 | 
							rookMove.execute(board);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void revert(Board board, Piece capturedPiece) {
 | 
				
			||||||
 | 
							// Move the king and the rook
 | 
				
			||||||
 | 
							super.revert(board, capturedPiece);
 | 
				
			||||||
 | 
							rookMove.revert(board, null);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return {@code O-O-O} for a queenside castling or {@code O-O} for a kingside
 | 
				
			||||||
 | 
						 *         castling
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public String toSAN(Board board) {
 | 
				
			||||||
 | 
							return rookMove.pos.x == 0 ? "O-O-O" : "O-O";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										35
									
								
								src/dev/kske/chess/board/EnPassant.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								src/dev/kske/chess/board/EnPassant.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>EnPassant.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>2 Nov 2019</strong><br>
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.5-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class EnPassant extends Move {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Position capturePos;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public EnPassant(Position pos, Position dest) {
 | 
				
			||||||
 | 
							super(pos, dest);
 | 
				
			||||||
 | 
							capturePos = new Position(dest.x, dest.y - ySign);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public EnPassant(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void execute(Board board) {
 | 
				
			||||||
 | 
							super.execute(board);
 | 
				
			||||||
 | 
							board.set(capturePos, null);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void revert(Board board, Piece capturedPiece) {
 | 
				
			||||||
 | 
							super.revert(board, capturedPiece);
 | 
				
			||||||
 | 
							board.set(capturePos, new Pawn(board.get(pos).getColor().opposite(), board));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Position getCapturePos() { return capturePos; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										212
									
								
								src/dev/kske/chess/board/FENString.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										212
									
								
								src/dev/kske/chess/board/FENString.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,212 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.reflect.Constructor;
 | 
				
			||||||
 | 
					import java.lang.reflect.InvocationTargetException;
 | 
				
			||||||
 | 
					import java.util.regex.Matcher;
 | 
				
			||||||
 | 
					import java.util.regex.Pattern;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
 | 
					import dev.kske.chess.exception.ChessException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>FENString.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>20 Oct 2019</strong><br>
 | 
				
			||||||
 | 
					 * <br>
 | 
				
			||||||
 | 
					 * Represents a FEN string and enables parsing an existing FEN string or
 | 
				
			||||||
 | 
					 * serializing a {@link Board} to one.
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.5-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class FENString {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private Board		board;
 | 
				
			||||||
 | 
						private String		piecePlacement, castlingAvailability;
 | 
				
			||||||
 | 
						private int			halfmoveClock, fullmoveNumber;
 | 
				
			||||||
 | 
						private Color		activeColor;
 | 
				
			||||||
 | 
						private Position	enPassantTargetSquare;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Constructs a {@link FENString} representing the starting position
 | 
				
			||||||
 | 
						 * {@code rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public FENString() {
 | 
				
			||||||
 | 
							board					= new Board();
 | 
				
			||||||
 | 
							piecePlacement			= "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR";
 | 
				
			||||||
 | 
							activeColor				= Color.WHITE;
 | 
				
			||||||
 | 
							castlingAvailability	= "KQkq";
 | 
				
			||||||
 | 
							halfmoveClock			= 0;
 | 
				
			||||||
 | 
							fullmoveNumber			= 1;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Constructs a {@link FENString} by parsing an existing string.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param fen the FEN string to parse
 | 
				
			||||||
 | 
						 * @throws ChessException
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public FENString(String fen) throws ChessException {
 | 
				
			||||||
 | 
							// Check fen string against regex
 | 
				
			||||||
 | 
							Pattern	fenPattern	= Pattern.compile(
 | 
				
			||||||
 | 
									"^(?<piecePlacement>(?:[1-8nbrqkpNBRQKP]{1,8}\\/){7}[1-8nbrqkpNBRQKP]{1,8}) (?<activeColor>[wb]) (?<castlingAvailability>-|[KQkq]{1,4}) (?<enPassantTargetSquare>-|[a-h][1-8]) (?<halfmoveClock>\\d+) (?<fullmoveNumber>\\d+)$");
 | 
				
			||||||
 | 
							Matcher	matcher		= fenPattern.matcher(fen);
 | 
				
			||||||
 | 
							if (!matcher.find()) throw new ChessException("FEN string does not match pattern " + fenPattern.pattern());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Initialize data fields
 | 
				
			||||||
 | 
							piecePlacement			= matcher.group("piecePlacement");
 | 
				
			||||||
 | 
							activeColor				= Color.fromFirstChar(matcher.group("activeColor").charAt(0));
 | 
				
			||||||
 | 
							castlingAvailability	= matcher.group("castlingAvailability");
 | 
				
			||||||
 | 
							if (!matcher.group("enPassantTargetSquare").equals("-")) enPassantTargetSquare = Position.fromLAN(matcher.group("enPassantTargetSquare"));
 | 
				
			||||||
 | 
							halfmoveClock	= Integer.parseInt(matcher.group("halfmoveClock"));
 | 
				
			||||||
 | 
							fullmoveNumber	= Integer.parseInt(matcher.group("fullmoveNumber"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Initialize and clean board
 | 
				
			||||||
 | 
							board = new Board();
 | 
				
			||||||
 | 
							for (int i = 0; i < 8; i++)
 | 
				
			||||||
 | 
								for (int j = 0; j < 8; j++)
 | 
				
			||||||
 | 
									board.getBoardArr()[i][j] = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Parse individual fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Piece placement
 | 
				
			||||||
 | 
							final String[] rows = piecePlacement.split("/");
 | 
				
			||||||
 | 
							if (rows.length != 8) throw new ChessException("FEN string contains invalid piece placement");
 | 
				
			||||||
 | 
							for (int i = 0; i < 8; i++) {
 | 
				
			||||||
 | 
								final char[]	cols	= rows[i].toCharArray();
 | 
				
			||||||
 | 
								int				j		= 0;
 | 
				
			||||||
 | 
								for (char c : cols) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Empty space
 | 
				
			||||||
 | 
									if (Character.isDigit(c)) {
 | 
				
			||||||
 | 
										j += Character.getNumericValue(c);
 | 
				
			||||||
 | 
									} else {
 | 
				
			||||||
 | 
										Color color = Character.isUpperCase(c) ? Color.WHITE : Color.BLACK;
 | 
				
			||||||
 | 
										try {
 | 
				
			||||||
 | 
											Constructor<? extends Piece> pieceConstructor = Piece.fromFirstChar(c).getDeclaredConstructor(Color.class, Board.class);
 | 
				
			||||||
 | 
											pieceConstructor.setAccessible(true);
 | 
				
			||||||
 | 
											board.getBoardArr()[j][i] = pieceConstructor.newInstance(color, board);
 | 
				
			||||||
 | 
										} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
 | 
				
			||||||
 | 
												| NoSuchMethodException | SecurityException e) {
 | 
				
			||||||
 | 
											e.printStackTrace();
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										++j;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Active color
 | 
				
			||||||
 | 
							board.getLog().setActiveColor(activeColor);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Castling availability
 | 
				
			||||||
 | 
							boolean castlingRights[] = new boolean[4];
 | 
				
			||||||
 | 
							for (char c : castlingAvailability.toCharArray())
 | 
				
			||||||
 | 
								switch (c) {
 | 
				
			||||||
 | 
									case 'K':
 | 
				
			||||||
 | 
										castlingRights[MoveNode.WHITE_KINGSIDE] = true;
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									case 'Q':
 | 
				
			||||||
 | 
										castlingRights[MoveNode.WHITE_QUEENSIDE] = true;
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									case 'k':
 | 
				
			||||||
 | 
										castlingRights[MoveNode.BLACK_KINGSIDE] = true;
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
									case 'q':
 | 
				
			||||||
 | 
										castlingRights[MoveNode.BLACK_QUEENSIDE] = true;
 | 
				
			||||||
 | 
										break;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							board.getLog().setCastlingRights(castlingRights);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// En passant square
 | 
				
			||||||
 | 
							board.getLog().setEnPassant(enPassantTargetSquare);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Halfmove clock
 | 
				
			||||||
 | 
							board.getLog().setHalfmoveClock(halfmoveClock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Fullmove number
 | 
				
			||||||
 | 
							board.getLog().setFullmoveNumber(fullmoveNumber);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Constructs a {@link FENString} form a {@link Board} object.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param board the {@link Board} object to encode in this {@link FENString}
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public FENString(Board board) {
 | 
				
			||||||
 | 
							this.board = board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Serialize individual fields
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Piece placement
 | 
				
			||||||
 | 
							StringBuilder sb = new StringBuilder();
 | 
				
			||||||
 | 
							for (int i = 0; i < 8; i++) {
 | 
				
			||||||
 | 
								int empty = 0;
 | 
				
			||||||
 | 
								for (int j = 0; j < 8; j++) {
 | 
				
			||||||
 | 
									final Piece piece = board.getBoardArr()[j][i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (piece == null) ++empty;
 | 
				
			||||||
 | 
									else {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Write empty field count
 | 
				
			||||||
 | 
										if (empty > 0) {
 | 
				
			||||||
 | 
											sb.append(empty);
 | 
				
			||||||
 | 
											empty = 0;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Write piece character
 | 
				
			||||||
 | 
										char p = piece.firstChar();
 | 
				
			||||||
 | 
										sb.append(piece.getColor() == Color.WHITE ? Character.toUpperCase(p) : p);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Write empty field count
 | 
				
			||||||
 | 
								if (empty > 0) {
 | 
				
			||||||
 | 
									sb.append(empty);
 | 
				
			||||||
 | 
									empty = 0;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (i < 7) sb.append('/');
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							piecePlacement = sb.toString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Active color
 | 
				
			||||||
 | 
							activeColor = board.getLog().getActiveColor();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Castling availability
 | 
				
			||||||
 | 
							castlingAvailability = "";
 | 
				
			||||||
 | 
							final char castlingRightsChars[] = new char[] { 'K', 'Q', 'k', 'q' };
 | 
				
			||||||
 | 
							for (int i = 0; i < 4; i++)
 | 
				
			||||||
 | 
								if (board.getLog().getCastlingRights()[i]) castlingAvailability += castlingRightsChars[i];
 | 
				
			||||||
 | 
							if (castlingAvailability.isEmpty()) castlingAvailability = "-";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// En passant availability
 | 
				
			||||||
 | 
							enPassantTargetSquare = board.getLog().getEnPassant();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Halfmove clock
 | 
				
			||||||
 | 
							halfmoveClock = board.getLog().getHalfmoveClock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Fullmove counter
 | 
				
			||||||
 | 
							fullmoveNumber = board.getLog().getFullmoveNumber();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Exports this {@link FENString} object to a FEN string.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @return a FEN string representing the board
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public String toString() {
 | 
				
			||||||
 | 
							return String.format("%s %c %s %s %d %d",
 | 
				
			||||||
 | 
									piecePlacement,
 | 
				
			||||||
 | 
									activeColor.firstChar(),
 | 
				
			||||||
 | 
									castlingAvailability,
 | 
				
			||||||
 | 
									enPassantTargetSquare == null ? "-" : enPassantTargetSquare.toLAN(),
 | 
				
			||||||
 | 
									halfmoveClock,
 | 
				
			||||||
 | 
									fullmoveNumber);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return a {@link Board} object corresponding to this {@link FENString}
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Board getBoard() { return board; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -7,49 +7,19 @@ import java.util.List;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>King.java</strong><br>
 | 
					 * File: <strong>King.java</strong><br>
 | 
				
			||||||
 * Created: <strong>01.07.2019</strong><br>
 | 
					 * Created: <strong>01.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class King extends Piece {
 | 
					public class King extends Piece {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public King(Color color, Board board) {
 | 
						public King(Color color, Board board) { super(color, board); }
 | 
				
			||||||
		super(color, board);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public boolean isValidMove(Move move) {
 | 
						public boolean isValidMove(Move move) {
 | 
				
			||||||
		// Castling
 | 
							return (move.getxDist() == 2 && move.getyDist() == 0
 | 
				
			||||||
		if (move.xDist == 2 && move.yDist == 0) {
 | 
									&& (move.getDest().x == 6 && canCastleKingside() || move.getDest().x == 2 && canCastleQueenside()))
 | 
				
			||||||
			if (canCastleKingside()) {
 | 
									|| move.getxDist() <= 1 && move.getyDist() <= 1 && checkDestination(move);
 | 
				
			||||||
				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
 | 
				
			||||||
@@ -63,13 +33,40 @@ public class King extends Piece {
 | 
				
			|||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Castling
 | 
							// Castling
 | 
				
			||||||
		// TODO: Condition: cannot castle out of, through or into check
 | 
							if (canCastleKingside()) moves.add(new Castling(pos, new Position(6, pos.y)));
 | 
				
			||||||
		if (canCastleKingside()) moves.add(new Move(pos, new Position(6, pos.y), Move.Type.CASTLING));
 | 
							if (canCastleQueenside()) moves.add(new Castling(pos, new Position(2, pos.y)));
 | 
				
			||||||
		if (canCastleQueenside()) moves.add(new Move(pos, new Position(2, pos.y), Move.Type.CASTLING));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return moves;
 | 
							return moves;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private boolean canCastleKingside() {
 | 
				
			||||||
 | 
							if (board.getLog().getCastlingRights()[getColor() == Color.WHITE ? MoveNode.WHITE_KINGSIDE : MoveNode.BLACK_KINGSIDE]) {
 | 
				
			||||||
 | 
								int			y			= getColor() == Color.WHITE ? 7 : 0;
 | 
				
			||||||
 | 
								Position	kingPos		= new Position(4, y);
 | 
				
			||||||
 | 
								Position	jumpPos		= new Position(5, y);
 | 
				
			||||||
 | 
								Position	kingDest	= new Position(6, y);
 | 
				
			||||||
 | 
								Position	rookPos		= new Position(7, y);
 | 
				
			||||||
 | 
								return canCastle(kingPos, kingDest, rookPos, jumpPos);
 | 
				
			||||||
 | 
							} else return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private boolean canCastleQueenside() {
 | 
				
			||||||
 | 
							if (board.getLog().getCastlingRights()[getColor() == Color.WHITE ? MoveNode.WHITE_QUEENSIDE : MoveNode.BLACK_QUEENSIDE]) {
 | 
				
			||||||
 | 
								int			y			= getColor() == Color.WHITE ? 7 : 0;
 | 
				
			||||||
 | 
								Position	kingPos		= new Position(4, y);
 | 
				
			||||||
 | 
								Position	jumpPos		= new Position(3, y);
 | 
				
			||||||
 | 
								Position	freeDest	= new Position(1, y);
 | 
				
			||||||
 | 
								Position	rookPos		= new Position(0, y);
 | 
				
			||||||
 | 
								return canCastle(kingPos, freeDest, rookPos, jumpPos);
 | 
				
			||||||
 | 
							} else return false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private boolean canCastle(Position kingPos, Position freeDest, Position rookPos, Position jumpPos) {
 | 
				
			||||||
 | 
							Piece rook = board.get(rookPos);
 | 
				
			||||||
 | 
							return rook != null && rook instanceof Rook && isFreePath(new Move(kingPos, freeDest)) && !board.isAttacked(kingPos, getColor().opposite())
 | 
				
			||||||
 | 
									&& !board.isAttacked(jumpPos, getColor().opposite());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public Type getType() { return Type.KING; }
 | 
						public int getValue() { return 0; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -7,7 +7,9 @@ import java.util.List;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Knight.java</strong><br>
 | 
					 * File: <strong>Knight.java</strong><br>
 | 
				
			||||||
 * Created: <strong>01.07.2019</strong><br>
 | 
					 * Created: <strong>01.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class Knight extends Piece {
 | 
					public class Knight extends Piece {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,8 +19,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
 | 
							return Math.abs(move.getxDist() - move.getyDist()) == 1
 | 
				
			||||||
				&& (move.xDist == 1 && move.yDist == 2 || move.xDist == 2 && move.yDist == 1) && checkDestination(move);
 | 
									&& (move.getxDist() == 1 && move.getyDist() == 2 || move.getxDist() == 2 && move.getyDist() == 1) && checkDestination(move);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void checkAndInsertMove(List<Move> moves, Position pos, int offsetX, int offsetY) {
 | 
						private void checkAndInsertMove(List<Move> moves, Position pos, int offsetX, int offsetY) {
 | 
				
			||||||
@@ -43,5 +45,8 @@ public class Knight extends Piece {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public Type getType() { return Type.KNIGHT; }
 | 
						public int getValue() { return 35; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public char firstChar() { return 'n'; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,8 @@
 | 
				
			|||||||
package dev.kske.chess.board;
 | 
					package dev.kske.chess.board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.Arrays;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.Iterator;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.chess.board.Piece.Color;
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,19 +10,20 @@ import dev.kske.chess.board.Piece.Color;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Log.java</strong><br>
 | 
					 * File: <strong>Log.java</strong><br>
 | 
				
			||||||
 * Created: <strong>09.07.2019</strong><br>
 | 
					 * Created: <strong>09.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 *
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class Log {
 | 
					public class Log implements Iterable<MoveNode> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private MoveNode root, current;
 | 
						private MoveNode root, current;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private Position	enPassant;
 | 
					 | 
				
			||||||
	private Color		activeColor;
 | 
						private Color		activeColor;
 | 
				
			||||||
	private int			fullmoveCounter, halfmoveClock;
 | 
						private boolean[]	castlingRights;
 | 
				
			||||||
 | 
						private Position	enPassant;
 | 
				
			||||||
 | 
						private int			fullmoveNumber, halfmoveClock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Log() {
 | 
						public Log() { reset(); }
 | 
				
			||||||
		reset();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates a (partially deep) copy of another {@link Log} instance which begins
 | 
						 * Creates a (partially deep) copy of another {@link Log} instance which begins
 | 
				
			||||||
@@ -34,32 +36,62 @@ public class Log {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	public Log(Log other, boolean copyVariations) {
 | 
						public Log(Log other, boolean copyVariations) {
 | 
				
			||||||
		enPassant		= other.enPassant;
 | 
							enPassant		= other.enPassant;
 | 
				
			||||||
 | 
							castlingRights	= other.castlingRights.clone();
 | 
				
			||||||
		activeColor		= other.activeColor;
 | 
							activeColor		= other.activeColor;
 | 
				
			||||||
		fullmoveCounter	= other.fullmoveCounter;
 | 
							fullmoveNumber	= other.fullmoveNumber;
 | 
				
			||||||
		halfmoveClock	= other.halfmoveClock;
 | 
							halfmoveClock	= other.halfmoveClock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// The new root is the current node of the copied instance
 | 
							// The new root is the current node of the copied instance
 | 
				
			||||||
		if (!other.isEmpty()) {
 | 
							if (!other.isEmpty()) {
 | 
				
			||||||
			root		= new MoveNode(other.current, copyVariations);
 | 
								root = new MoveNode(other.root, copyVariations);
 | 
				
			||||||
			root.parent	= null;
 | 
								root.setParent(null);
 | 
				
			||||||
			current = root;
 | 
								current = root;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return an iterator over all {@link MoveNode} objects that are either the
 | 
				
			||||||
 | 
						 *         root node or a first variation of another node, starting from the
 | 
				
			||||||
 | 
						 *         root node
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public Iterator<MoveNode> iterator() {
 | 
				
			||||||
 | 
							return new Iterator<MoveNode>() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								private MoveNode	current	= root;
 | 
				
			||||||
 | 
								private boolean		hasNext	= !isEmpty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public boolean hasNext() { return hasNext; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public MoveNode next() {
 | 
				
			||||||
 | 
									MoveNode result = current;
 | 
				
			||||||
 | 
									if (current.hasVariations()) current = current.getVariations().get(0);
 | 
				
			||||||
 | 
									else hasNext = false;
 | 
				
			||||||
 | 
									return result;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Adds a move to the move history and adjusts the log to the new position.
 | 
						 * Adds a move to the move history and adjusts the log to the new position.
 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param move          The move to log
 | 
						 * @param move          The move to log
 | 
				
			||||||
 | 
						 * @param piece         The piece that performed the move
 | 
				
			||||||
	 * @param capturedPiece The piece captured with the move
 | 
						 * @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) {
 | 
						public void add(Move move, Piece piece, Piece capturedPiece) {
 | 
				
			||||||
		enPassant = pawnMove && move.yDist == 2 ? new Position(move.pos.x, move.pos.y + move.ySign) : null;
 | 
							enPassant = piece instanceof Pawn && move.getyDist() == 2 ? new Position(move.getPos().x, move.getPos().y + move.getySign()) : null;
 | 
				
			||||||
		if (activeColor == Color.BLACK) ++fullmoveCounter;
 | 
							if (activeColor == Color.BLACK) ++fullmoveNumber;
 | 
				
			||||||
		if (pawnMove || capturedPiece != null) halfmoveClock = 0;
 | 
							if (piece instanceof Pawn || capturedPiece != null) halfmoveClock = 0;
 | 
				
			||||||
		else++halfmoveClock;
 | 
							else++halfmoveClock;
 | 
				
			||||||
		activeColor = activeColor.opposite();
 | 
							activeColor = activeColor.opposite();
 | 
				
			||||||
		final MoveNode leaf = new MoveNode(move, capturedPiece, enPassant, activeColor, fullmoveCounter, halfmoveClock);
 | 
					
 | 
				
			||||||
 | 
							// Disable castling rights if a king or a rook has been moved
 | 
				
			||||||
 | 
							if (piece instanceof King || piece instanceof Rook) disableCastlingRights(piece, move.getPos());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							final MoveNode leaf = new MoveNode(move, capturedPiece, castlingRights.clone(), enPassant, activeColor, fullmoveNumber, halfmoveClock);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if (isEmpty()) {
 | 
							if (isEmpty()) {
 | 
				
			||||||
			root	= leaf;
 | 
								root	= leaf;
 | 
				
			||||||
@@ -71,22 +103,27 @@ public class Log {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Removed the last move from the log and adjusts its state to the previous
 | 
						 * Removes the last move from the log and adjusts its state to the previous
 | 
				
			||||||
	 * move.
 | 
						 * move.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void removeLast() {
 | 
						public void removeLast() {
 | 
				
			||||||
		if (!isEmpty() && current.parent != null) {
 | 
							if (hasParent()) {
 | 
				
			||||||
			current.parent.variations.remove(current);
 | 
								current.getParent().getVariations().remove(current);
 | 
				
			||||||
			current			= current.parent;
 | 
								current = current.getParent();
 | 
				
			||||||
			activeColor		= current.activeColor;
 | 
								update();
 | 
				
			||||||
			enPassant		= current.enPassant;
 | 
					 | 
				
			||||||
			fullmoveCounter	= current.fullmoveCounter;
 | 
					 | 
				
			||||||
			halfmoveClock	= current.halfmoveClock;
 | 
					 | 
				
			||||||
		} else reset();
 | 
							} else reset();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return {@code true} if the root node exists
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
	public boolean isEmpty() { return root == null; }
 | 
						public boolean isEmpty() { return root == null; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return {@code true} if the current node has a parent node
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public boolean hasParent() { return !isEmpty() && current.hasParent(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Reverts the log to its initial state corresponding to the default board
 | 
						 * Reverts the log to its initial state corresponding to the default board
 | 
				
			||||||
	 * position.
 | 
						 * position.
 | 
				
			||||||
@@ -94,12 +131,95 @@ public class Log {
 | 
				
			|||||||
	public void reset() {
 | 
						public void reset() {
 | 
				
			||||||
		root			= null;
 | 
							root			= null;
 | 
				
			||||||
		current			= null;
 | 
							current			= null;
 | 
				
			||||||
 | 
							castlingRights	= new boolean[] { true, true, true, true };
 | 
				
			||||||
		enPassant		= null;
 | 
							enPassant		= null;
 | 
				
			||||||
		activeColor		= Color.WHITE;
 | 
							activeColor		= Color.WHITE;
 | 
				
			||||||
		fullmoveCounter	= 1;
 | 
							fullmoveNumber	= 1;
 | 
				
			||||||
		halfmoveClock	= 0;
 | 
							halfmoveClock	= 0;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Changes the current node to one of its children (variations).
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param index the index of the variation to select
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void selectNextNode(int index) {
 | 
				
			||||||
 | 
							if (!isEmpty() && current.hasVariations() && index < current.getVariations().size()) {
 | 
				
			||||||
 | 
								current = current.getVariations().get(index);
 | 
				
			||||||
 | 
								update();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Selects the parent of the current {@link MoveNode} as the current node.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void selectPreviousNode() {
 | 
				
			||||||
 | 
							if (hasParent()) {
 | 
				
			||||||
 | 
								current = current.getParent();
 | 
				
			||||||
 | 
								update();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Selects the root {@link MoveNode} as the current node.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void selectRootNode() {
 | 
				
			||||||
 | 
							if (!isEmpty()) {
 | 
				
			||||||
 | 
								current = root;
 | 
				
			||||||
 | 
								update();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Sets the active color, castling rights, en passant target square, fullmove
 | 
				
			||||||
 | 
						 * number and halfmove clock to those of the current {@link MoveNode}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private void update() {
 | 
				
			||||||
 | 
							activeColor		= current.activeColor;
 | 
				
			||||||
 | 
							castlingRights	= current.castlingRights.clone();
 | 
				
			||||||
 | 
							enPassant		= current.enPassant;
 | 
				
			||||||
 | 
							fullmoveNumber	= current.fullmoveCounter;
 | 
				
			||||||
 | 
							halfmoveClock	= current.halfmoveClock;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Removed the castling rights bound to a rook or king for the rest of the game.
 | 
				
			||||||
 | 
						 * This method should be called once the piece has been moved, as a castling
 | 
				
			||||||
 | 
						 * move involving this piece is forbidden afterwards.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param piece           the rook or king to disable the castling rights for
 | 
				
			||||||
 | 
						 * @param initialPosition the initial position of the piece during the start of
 | 
				
			||||||
 | 
						 *                        the game
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private void disableCastlingRights(Piece piece, Position initialPosition) {
 | 
				
			||||||
 | 
							// Kingside
 | 
				
			||||||
 | 
							if (piece instanceof King || piece instanceof Rook && initialPosition.x == 7)
 | 
				
			||||||
 | 
								castlingRights[piece.getColor() == Color.WHITE ? MoveNode.WHITE_KINGSIDE : MoveNode.BLACK_KINGSIDE] = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Queenside
 | 
				
			||||||
 | 
							if (piece instanceof King || piece instanceof Rook && initialPosition.x == 0)
 | 
				
			||||||
 | 
								castlingRights[piece.getColor() == Color.WHITE ? MoveNode.WHITE_QUEENSIDE : MoveNode.BLACK_QUEENSIDE] = false;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public int hashCode() {
 | 
				
			||||||
 | 
							final int	prime	= 31;
 | 
				
			||||||
 | 
							int			result	= 1;
 | 
				
			||||||
 | 
							result	= prime * result + Arrays.hashCode(castlingRights);
 | 
				
			||||||
 | 
							result	= prime * result + Objects.hash(activeColor, current, enPassant, fullmoveNumber, halfmoveClock);
 | 
				
			||||||
 | 
							return result;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public boolean equals(Object obj) {
 | 
				
			||||||
 | 
							if (this == obj) return true;
 | 
				
			||||||
 | 
							if (obj == null) return false;
 | 
				
			||||||
 | 
							if (getClass() != obj.getClass()) return false;
 | 
				
			||||||
 | 
							Log other = (Log) obj;
 | 
				
			||||||
 | 
							return activeColor == other.activeColor && Arrays.equals(castlingRights, other.castlingRights) && Objects.equals(current, other.current)
 | 
				
			||||||
 | 
									&& Objects.equals(enPassant, other.enPassant) && fullmoveNumber == other.fullmoveNumber && halfmoveClock == other.halfmoveClock;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * @return The first logged move, or {@code null} if there is none
 | 
						 * @return The first logged move, or {@code null} if there is none
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
@@ -110,6 +230,10 @@ public class Log {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	public MoveNode getLast() { return current; }
 | 
						public MoveNode getLast() { return current; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public boolean[] getCastlingRights() { return castlingRights; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void setCastlingRights(boolean[] castlingRights) { this.castlingRights = castlingRights; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Position getEnPassant() { return enPassant; }
 | 
						public Position getEnPassant() { return enPassant; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void setEnPassant(Position enPassant) { this.enPassant = enPassant; }
 | 
						public void setEnPassant(Position enPassant) { this.enPassant = enPassant; }
 | 
				
			||||||
@@ -118,83 +242,11 @@ public class Log {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	public void setActiveColor(Color activeColor) { this.activeColor = activeColor; }
 | 
						public void setActiveColor(Color activeColor) { this.activeColor = activeColor; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public int getFullmoveCounter() { return fullmoveCounter; }
 | 
						public int getFullmoveNumber() { return fullmoveNumber; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void setFullmoveCounter(int fullmoveCounter) { this.fullmoveCounter = fullmoveCounter; }
 | 
						public void setFullmoveNumber(int fullmoveNumber) { this.fullmoveNumber = fullmoveNumber; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public int getHalfmoveClock() { return halfmoveClock; }
 | 
						public int getHalfmoveClock() { return halfmoveClock; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void setHalfmoveClock(int halfmoveClock) { this.halfmoveClock = 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; }
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,56 +1,205 @@
 | 
				
			|||||||
package dev.kske.chess.board;
 | 
					package dev.kske.chess.board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					import java.util.regex.Matcher;
 | 
				
			||||||
 | 
					import java.util.regex.Pattern;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Move.java</strong><br>
 | 
					 * File: <strong>Move.java</strong><br>
 | 
				
			||||||
 * Created: <strong>02.07.2019</strong><br>
 | 
					 * Created: <strong>02.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 *
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class Move {
 | 
					public class Move {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public final Position	pos, dest;
 | 
						protected final Position	pos, dest;
 | 
				
			||||||
	public final int		xDist, yDist, xSign, ySign;
 | 
						protected final int			xDist, yDist, xSign, ySign;
 | 
				
			||||||
	public Type				type;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Move(Position pos, Position dest, Type type) {
 | 
						public Move(Position pos, Position dest) {
 | 
				
			||||||
		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) {
 | 
						public Move(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); }
 | 
				
			||||||
		this(pos, dest, Type.NORMAL);
 | 
					
 | 
				
			||||||
 | 
						public void execute(Board board) {
 | 
				
			||||||
 | 
							// Move the piece to the move's destination square and clean the old position
 | 
				
			||||||
 | 
							board.set(dest, board.get(pos));
 | 
				
			||||||
 | 
							board.set(pos, null);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Move(int xPos, int yPos, int xDest, int yDest) {
 | 
						public void revert(Board board, Piece capturedPiece) {
 | 
				
			||||||
		this(new Position(xPos, yPos), new Position(xDest, yDest));
 | 
							// Move the piece to the move's position square and clean the destination
 | 
				
			||||||
 | 
							board.set(pos, board.get(dest));
 | 
				
			||||||
 | 
							board.set(dest, capturedPiece);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static Move fromSAN(String move) {
 | 
						public static Move fromLAN(String move) {
 | 
				
			||||||
		return new Move(Position.fromSAN(move.substring(0, 2)),
 | 
							Position	pos		= Position.fromLAN(move.substring(0, 2));
 | 
				
			||||||
				Position.fromSAN(move.substring(2)));
 | 
							Position	dest	= Position.fromLAN(move.substring(2));
 | 
				
			||||||
 | 
							if (move.length() == 5) {
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									return new PawnPromotion(pos, dest, Piece.fromFirstChar(move.charAt(4)));
 | 
				
			||||||
 | 
								} catch (Exception e) {
 | 
				
			||||||
 | 
									e.printStackTrace();
 | 
				
			||||||
 | 
									return null;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else return new Move(pos, dest);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public String toSAN() {
 | 
						public String toLAN() { return getPos().toLAN() + getDest().toLAN(); }
 | 
				
			||||||
		return pos.toSAN() + dest.toSAN();
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Converts a move string from standard algebraic notation to a {@link Move}
 | 
				
			||||||
 | 
						 * object.
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param sanMove the move string to convert from
 | 
				
			||||||
 | 
						 * @param board   the board on which the move has to be executed
 | 
				
			||||||
 | 
						 * @return the converted {@link Move} object
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public static Move fromSAN(String sanMove, Board board) {
 | 
				
			||||||
 | 
							Map<String, Pattern> patterns = new HashMap<>();
 | 
				
			||||||
 | 
							patterns.put("pieceMove",
 | 
				
			||||||
 | 
									Pattern.compile(
 | 
				
			||||||
 | 
											"^(?<pieceType>[NBRQK])(?:(?<fromFile>[a-h])|(?<fromRank>[1-8])|(?<fromSquare>[a-h][1-8]))?x?(?<toSquare>[a-h][1-8])(?:\\+{0,2}|\\#)$"));
 | 
				
			||||||
 | 
							patterns.put("pawnCapture",
 | 
				
			||||||
 | 
									Pattern.compile("^(?<fromFile>[a-h])(?<fromRank>[1-8])?x(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQ])?(?:\\+{0,2}|\\#)?$"));
 | 
				
			||||||
 | 
							patterns.put("pawnPush", Pattern.compile("^(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQ])?(?:\\+{0,2}|\\#)$"));
 | 
				
			||||||
 | 
							patterns.put("castling", Pattern.compile("^(?<queenside>O-O-O)|(?<kingside>O-O)(?:\\+{0,2}|\\#)?$"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for (Map.Entry<String, Pattern> entry : patterns.entrySet()) {
 | 
				
			||||||
 | 
								Matcher m = entry.getValue().matcher(sanMove);
 | 
				
			||||||
 | 
								if (m.find()) {
 | 
				
			||||||
 | 
									Position	pos		= null, dest = null;
 | 
				
			||||||
 | 
									Move		move	= null;
 | 
				
			||||||
 | 
									switch (entry.getKey()) {
 | 
				
			||||||
 | 
										case "pieceMove":
 | 
				
			||||||
 | 
											dest = Position.fromLAN(m.group("toSquare"));
 | 
				
			||||||
 | 
											if (m.group("fromSquare") != null) pos = Position.fromLAN(m.group("fromSquare"));
 | 
				
			||||||
 | 
											else {
 | 
				
			||||||
 | 
												Class<? extends Piece>	pieceClass	= Piece.fromFirstChar(m.group("pieceType").charAt(0));
 | 
				
			||||||
 | 
												char					file;
 | 
				
			||||||
 | 
												int						rank;
 | 
				
			||||||
 | 
												if (m.group("fromFile") != null) {
 | 
				
			||||||
 | 
													file	= m.group("fromFile").charAt(0);
 | 
				
			||||||
 | 
													rank	= board.get(pieceClass, file);
 | 
				
			||||||
 | 
													pos		= Position.fromLAN(String.format("%c%d", file, rank));
 | 
				
			||||||
 | 
												} else if (m.group("fromRank") != null) {
 | 
				
			||||||
 | 
													rank	= Integer.parseInt(m.group("fromRank").substring(0, 1));
 | 
				
			||||||
 | 
													file	= board.get(pieceClass, rank);
 | 
				
			||||||
 | 
													pos		= Position.fromLAN(String.format("%c%d", file, rank));
 | 
				
			||||||
 | 
												} else pos = board.get(pieceClass, dest);
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											move = new Move(pos, dest);
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										case "pawnCapture":
 | 
				
			||||||
 | 
											char file = m.group("fromFile").charAt(0);
 | 
				
			||||||
 | 
											int rank = m.group("fromRank") == null ? board.get(Pawn.class, file) : Integer.parseInt(m.group("fromRank"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											dest = Position.fromLAN(m.group("toSquare"));
 | 
				
			||||||
 | 
											pos = Position.fromLAN(String.format("%c%d", file, rank));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if (m.group("promotedTo") != null) {
 | 
				
			||||||
 | 
												try {
 | 
				
			||||||
 | 
													move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0)));
 | 
				
			||||||
 | 
												} catch (Exception e) {
 | 
				
			||||||
 | 
													e.printStackTrace();
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											} else move = new Move(pos, dest);
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										case "pawnPush":
 | 
				
			||||||
 | 
											dest = Position.fromLAN(m.group("toSquare"));
 | 
				
			||||||
 | 
											int step = board.getLog().getActiveColor() == Color.WHITE ? 1 : -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// One step forward
 | 
				
			||||||
 | 
											if (board.getBoardArr()[dest.x][dest.y + step] != null) pos = new Position(dest.x, dest.y + step);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											// Double step forward
 | 
				
			||||||
 | 
											else pos = new Position(dest.x, dest.y + 2 * step);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if (m.group("promotedTo") != null) {
 | 
				
			||||||
 | 
												try {
 | 
				
			||||||
 | 
													move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0)));
 | 
				
			||||||
 | 
												} catch (Exception e) {
 | 
				
			||||||
 | 
													e.printStackTrace();
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
											} else move = new Move(pos, dest);
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										case "castling":
 | 
				
			||||||
 | 
											pos = new Position(4, board.getLog().getActiveColor() == Color.WHITE ? 7 : 0);
 | 
				
			||||||
 | 
											dest = new Position(m.group("kingside") != null ? 6 : 2, pos.y);
 | 
				
			||||||
 | 
											move = new Castling(pos, dest);
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									return move;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return null;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public boolean isHorizontal() { return yDist == 0; }
 | 
						public String toSAN(Board board) {
 | 
				
			||||||
 | 
							final Piece		piece	= board.get(pos);
 | 
				
			||||||
 | 
							StringBuilder	sb		= new StringBuilder(8);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public boolean isVertical() { return xDist == 0; }
 | 
							// Piece symbol
 | 
				
			||||||
 | 
							if(!(piece instanceof Pawn))
 | 
				
			||||||
 | 
								sb.append(Character.toUpperCase(piece.firstChar()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public boolean isDiagonal() { return xDist == yDist; }
 | 
							// Position
 | 
				
			||||||
 | 
							// TODO: Deconstruct position into optional file or rank
 | 
				
			||||||
 | 
							// Omit position if the move is a pawn push
 | 
				
			||||||
 | 
							if (!(piece instanceof Pawn && xDist == 0)) sb.append(pos.toLAN());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Capture indicator
 | 
				
			||||||
 | 
							if (board.get(dest) != null) sb.append('x');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Destination
 | 
				
			||||||
 | 
							sb.append(dest.toLAN());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return sb.toString();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public boolean isHorizontal() { return getyDist() == 0; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public boolean isVertical() { return getxDist() == 0; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public boolean isDiagonal() { return getxDist() == getyDist(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toString() {
 | 
						public String toString() { return toLAN(); }
 | 
				
			||||||
		return String.format("%s -> %s", pos, dest);
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public int hashCode() { return Objects.hash(getDest(), getPos(), getxDist(), getxSign(), getyDist(), getySign()); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public boolean equals(Object obj) {
 | 
				
			||||||
 | 
							if (this == obj) return true;
 | 
				
			||||||
 | 
							if (obj == null) return false;
 | 
				
			||||||
 | 
							if (getClass() != obj.getClass()) return false;
 | 
				
			||||||
 | 
							Move other = (Move) obj;
 | 
				
			||||||
 | 
							return Objects.equals(getDest(), other.getDest()) && Objects.equals(getPos(), other.getPos()) && getxDist() == other.getxDist()
 | 
				
			||||||
 | 
									&& getxSign() == other.getxSign() && getyDist() == other.getyDist() && getySign() == other.getySign();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static enum Type {
 | 
						public Position getPos() { return pos; }
 | 
				
			||||||
		NORMAL, PAWN_PROMOTION, CASTLING, EN_PASSANT, UNKNOWN
 | 
					
 | 
				
			||||||
	}
 | 
						public Position getDest() { return dest; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public int getxDist() { return xDist; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public int getyDist() { return yDist; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public int getxSign() { return xSign; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public int getySign() { return ySign; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										126
									
								
								src/dev/kske/chess/board/MoveNode.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										126
									
								
								src/dev/kske/chess/board/MoveNode.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,126 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>MoveNode.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>02.10.2019</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @since Chess v0.5-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class MoveNode {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static final int WHITE_KINGSIDE = 0, WHITE_QUEENSIDE = 1, BLACK_KINGSIDE = 2, BLACK_QUEENSIDE = 3;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public final Move		move;
 | 
				
			||||||
 | 
						public final Piece		capturedPiece;
 | 
				
			||||||
 | 
						public final boolean[]	castlingRights;
 | 
				
			||||||
 | 
						public final Position	enPassant;
 | 
				
			||||||
 | 
						public final Color		activeColor;
 | 
				
			||||||
 | 
						public final int		fullmoveCounter, halfmoveClock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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, boolean castlingRights[], Position enPassant, Color activeColor,
 | 
				
			||||||
 | 
								int fullmoveCounter, int halfmoveClock) {
 | 
				
			||||||
 | 
							this.move				= move;
 | 
				
			||||||
 | 
							this.capturedPiece		= capturedPiece;
 | 
				
			||||||
 | 
							this.castlingRights		= castlingRights;
 | 
				
			||||||
 | 
							this.enPassant			= enPassant;
 | 
				
			||||||
 | 
							this.activeColor		= activeColor;
 | 
				
			||||||
 | 
							this.fullmoveCounter	= fullmoveCounter;
 | 
				
			||||||
 | 
							this.halfmoveClock		= halfmoveClock;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * 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.castlingRights.clone(), other.enPassant, other.activeColor,
 | 
				
			||||||
 | 
									other.fullmoveCounter, other.halfmoveClock);
 | 
				
			||||||
 | 
							if (copyVariations && other.variations != null) {
 | 
				
			||||||
 | 
								if (variations == null) variations = new ArrayList<>();
 | 
				
			||||||
 | 
								for (MoveNode variation : other.variations) {
 | 
				
			||||||
 | 
									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; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public boolean hasVariations() {
 | 
				
			||||||
 | 
							return variations != null && variations.size() > 0;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public MoveNode getParent() { return parent; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void setParent(MoveNode parent) { this.parent = parent; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public boolean hasParent() {
 | 
				
			||||||
 | 
							return parent != null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public int hashCode() {
 | 
				
			||||||
 | 
							final int	prime	= 31;
 | 
				
			||||||
 | 
							int			result	= 1;
 | 
				
			||||||
 | 
							result	= prime * result + Arrays.hashCode(castlingRights);
 | 
				
			||||||
 | 
							result	= prime * result
 | 
				
			||||||
 | 
									+ Objects.hash(activeColor, capturedPiece, enPassant, fullmoveCounter, halfmoveClock, move);
 | 
				
			||||||
 | 
							return result;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public boolean equals(Object obj) {
 | 
				
			||||||
 | 
							if (this == obj) return true;
 | 
				
			||||||
 | 
							if (obj == null) return false;
 | 
				
			||||||
 | 
							if (getClass() != obj.getClass()) return false;
 | 
				
			||||||
 | 
							MoveNode other = (MoveNode) obj;
 | 
				
			||||||
 | 
							return activeColor == other.activeColor && Objects.equals(capturedPiece, other.capturedPiece)
 | 
				
			||||||
 | 
									&& Arrays.equals(castlingRights, other.castlingRights) && Objects.equals(enPassant, other.enPassant)
 | 
				
			||||||
 | 
									&& fullmoveCounter == other.fullmoveCounter && halfmoveClock == other.halfmoveClock
 | 
				
			||||||
 | 
									&& Objects.equals(move, other.move);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -7,44 +7,33 @@ import java.util.List;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Pawn.java</strong><br>
 | 
					 * File: <strong>Pawn.java</strong><br>
 | 
				
			||||||
 * Created: <strong>01.07.2019</strong><br>
 | 
					 * Created: <strong>01.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 *
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class Pawn extends Piece {
 | 
					public class Pawn extends Piece {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Pawn(Color color, Board board) {
 | 
						public Pawn(Color color, Board board) { super(color, board); }
 | 
				
			||||||
		super(color, board);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public boolean isValidMove(Move move) {
 | 
						public boolean isValidMove(Move move) {
 | 
				
			||||||
		boolean	step		= move.isVertical() && move.yDist == 1;
 | 
							boolean	step		= move.isVertical() && move.getyDist() == 1;
 | 
				
			||||||
		boolean	doubleStep	= move.isVertical() && move.yDist == 2;
 | 
							boolean	doubleStep	= move.isVertical() && move.getyDist() == 2;
 | 
				
			||||||
		boolean	strafe		= move.isDiagonal() && move.xDist == 1;
 | 
							boolean	strafe		= move.isDiagonal() && move.getxDist() == 1;
 | 
				
			||||||
		boolean	enPassant	= false;
 | 
							boolean	enPassant	= strafe && move.getDest().equals(board.getLog().getEnPassant());
 | 
				
			||||||
		if (getColor() == Color.WHITE) doubleStep &= move.pos.y == 6;
 | 
							if (getColor() == Color.WHITE) doubleStep &= move.getPos().y == 6;
 | 
				
			||||||
		else doubleStep &= move.pos.y == 1;
 | 
							else doubleStep &= move.getPos().y == 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Mark move as pawn promotion if necessary
 | 
							return enPassant || (step ^ doubleStep ^ strafe) && move.getySign() == (getColor() == Color.WHITE ? -1 : 1) && isFreePath(move);
 | 
				
			||||||
		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
 | 
				
			||||||
	protected boolean isFreePath(Move move) {
 | 
						protected boolean isFreePath(Move move) {
 | 
				
			||||||
		// Two steps forward
 | 
							// Two steps forward
 | 
				
			||||||
		if (move.yDist == 2)
 | 
							if (move.getyDist() == 2)
 | 
				
			||||||
			return board.getBoardArr()[move.pos.x][move.dest.y - move.ySign] == null && board.getDest(move) == null;
 | 
								return board.getBoardArr()[move.getPos().x][move.getDest().y - move.getySign()] == null && board.getDest(move) == null;
 | 
				
			||||||
		// One step forward
 | 
							// One step forward
 | 
				
			||||||
		else if (move.xDist == 0) return board.getDest(move) == null;
 | 
							else if (move.getxDist() == 0) return board.getDest(move) == null;
 | 
				
			||||||
		// Capture move
 | 
							// Capture move
 | 
				
			||||||
		else return board.getDest(move) != null && board.getDest(move).getColor() != getColor();
 | 
							else return board.getDest(move) != null && board.getDest(move).getColor() != getColor();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -52,46 +41,46 @@ public class Pawn extends Piece {
 | 
				
			|||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	protected List<Move> getPseudolegalMoves(Position pos) {
 | 
						protected List<Move> getPseudolegalMoves(Position pos) {
 | 
				
			||||||
		List<Move>	moves			= new ArrayList<>();
 | 
							List<Move>	moves			= new ArrayList<>();
 | 
				
			||||||
 | 
					 | 
				
			||||||
		int			sign			= getColor() == Color.WHITE ? -1 : 1;
 | 
							int			sign			= getColor() == Color.WHITE ? -1 : 1;
 | 
				
			||||||
 | 
							boolean		pawnPromotion	= sign == 1 && pos.y == 6 || sign == -1 && pos.y == 1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Strafe left
 | 
							// Strafe left
 | 
				
			||||||
		if (pos.x > 0) {
 | 
							if (pos.x > 0) addMoveIfValid(moves, pos, new Position(pos.x - 1, pos.y + sign), pawnPromotion);
 | 
				
			||||||
			Move move = new Move(pos, new Position(pos.x - 1, pos.y + sign));
 | 
					 | 
				
			||||||
			if (isFreePath(move)) moves.add(move);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Strafe right
 | 
							// Strafe right
 | 
				
			||||||
		if (pos.x < 7) {
 | 
							if (pos.x < 7) addMoveIfValid(moves, pos, new Position(pos.x + 1, pos.y + sign), pawnPromotion);
 | 
				
			||||||
			Move move = new Move(pos, new Position(pos.x + 1, pos.y + sign));
 | 
					 | 
				
			||||||
			if (isFreePath(move)) moves.add(move);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Step forward
 | 
							// Step forward
 | 
				
			||||||
		if (sign == 1 && pos.y < 7 || sign == -1 && pos.y > 0) {
 | 
							if (sign == 1 && pos.y < 7 || sign == -1 && pos.y > 0) addMoveIfValid(moves, pos, new Position(pos.x, pos.y + sign), pawnPromotion);
 | 
				
			||||||
			Move move = new Move(pos, new Position(pos.x, pos.y + sign));
 | 
					 | 
				
			||||||
			if (isFreePath(move)) moves.add(move);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Double step forward
 | 
							// Double step forward
 | 
				
			||||||
		if (sign == 1 && pos.y == 1 || sign == -1 && pos.y == 6) {
 | 
							if (sign == 1 && pos.y == 1 || sign == -1 && pos.y == 6) addMoveIfValid(moves, pos, new Position(pos.x, pos.y + 2 * sign), pawnPromotion);
 | 
				
			||||||
			Move move = new Move(pos, new Position(pos.x, pos.y + 2 * sign));
 | 
					 | 
				
			||||||
			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
 | 
							// Add en passant move if necessary
 | 
				
			||||||
		if (board.getLog().getEnPassant() != null) {
 | 
							if (board.getLog().getEnPassant() != null) {
 | 
				
			||||||
			Move move = new Move(pos, board.getLog().getEnPassant(), Move.Type.EN_PASSANT);
 | 
								Move move = new EnPassant(pos, board.getLog().getEnPassant());
 | 
				
			||||||
			if (move.isDiagonal() && move.xDist == 1) moves.add(move);
 | 
								if (move.isDiagonal() && move.getxDist() == 1) moves.add(move);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return moves;
 | 
							return moves;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private void addMoveIfValid(List<Move> moves, Position pos, Position dest, boolean pawnPromotion) {
 | 
				
			||||||
 | 
							Move move = new Move(pos, dest);
 | 
				
			||||||
 | 
							if (isFreePath(move)) {
 | 
				
			||||||
 | 
								if (pawnPromotion) {
 | 
				
			||||||
 | 
									try {
 | 
				
			||||||
 | 
										moves.add(new PawnPromotion(pos, dest, Queen.class));
 | 
				
			||||||
 | 
										moves.add(new PawnPromotion(pos, dest, Rook.class));
 | 
				
			||||||
 | 
										moves.add(new PawnPromotion(pos, dest, Knight.class));
 | 
				
			||||||
 | 
										moves.add(new PawnPromotion(pos, dest, Bishop.class));
 | 
				
			||||||
 | 
									} catch (Exception e) {
 | 
				
			||||||
 | 
										e.printStackTrace();
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else moves.add(move);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public Type getType() { return Type.PAWN; }
 | 
						public int getValue() { return 10; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										80
									
								
								src/dev/kske/chess/board/PawnPromotion.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								src/dev/kske/chess/board/PawnPromotion.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.lang.reflect.Constructor;
 | 
				
			||||||
 | 
					import java.lang.reflect.InvocationTargetException;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>PawnPromotion.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>2 Nov 2019</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @since Chess v0.5-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class PawnPromotion extends Move {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Constructor<? extends Piece>	promotionPieceConstructor;
 | 
				
			||||||
 | 
						private final char							promotionPieceChar;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public PawnPromotion(Position pos, Position dest, Class<? extends Piece> promotionPieceClass)
 | 
				
			||||||
 | 
								throws ReflectiveOperationException, RuntimeException {
 | 
				
			||||||
 | 
							super(pos, dest);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Cache piece constructor
 | 
				
			||||||
 | 
							promotionPieceConstructor = promotionPieceClass.getDeclaredConstructor(Color.class, Board.class);
 | 
				
			||||||
 | 
							promotionPieceConstructor.setAccessible(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Get piece char
 | 
				
			||||||
 | 
							promotionPieceChar = (char) promotionPieceClass.getMethod("firstChar").invoke(promotionPieceConstructor.newInstance(null, null));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public PawnPromotion(int xPos, int yPos, int xDest, int yDest, Class<? extends Piece> promotionPiece)
 | 
				
			||||||
 | 
								throws ReflectiveOperationException, RuntimeException {
 | 
				
			||||||
 | 
							this(new Position(xPos, yPos), new Position(xDest, yDest), promotionPiece);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void execute(Board board) {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								board.set(pos, promotionPieceConstructor.newInstance(board.get(pos).getColor(), board));
 | 
				
			||||||
 | 
								super.execute(board);
 | 
				
			||||||
 | 
							} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException e) {
 | 
				
			||||||
 | 
								e.printStackTrace();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void revert(Board board, Piece capturedPiece) {
 | 
				
			||||||
 | 
							board.set(dest, new Pawn(board.get(dest).getColor(), board));
 | 
				
			||||||
 | 
							super.revert(board, capturedPiece);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public String toLAN() { return pos.toLAN() + dest.toLAN() + promotionPieceChar; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public String toSAN(Board board) {
 | 
				
			||||||
 | 
							String san = super.toSAN(board);
 | 
				
			||||||
 | 
							return san + Character.toUpperCase(promotionPieceChar);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public int hashCode() {
 | 
				
			||||||
 | 
							final int	prime	= 31;
 | 
				
			||||||
 | 
							int			result	= super.hashCode();
 | 
				
			||||||
 | 
							result = prime * result + Objects.hash(promotionPieceChar, promotionPieceConstructor);
 | 
				
			||||||
 | 
							return result;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public boolean equals(Object obj) {
 | 
				
			||||||
 | 
							if (this == obj) return true;
 | 
				
			||||||
 | 
							if (!super.equals(obj)) return false;
 | 
				
			||||||
 | 
							if (!(obj instanceof PawnPromotion)) return false;
 | 
				
			||||||
 | 
							PawnPromotion other = (PawnPromotion) obj;
 | 
				
			||||||
 | 
							return promotionPieceChar == other.promotionPieceChar && Objects.equals(promotionPieceConstructor, other.promotionPieceConstructor);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,18 +2,20 @@ package dev.kske.chess.board;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.util.Iterator;
 | 
					import java.util.Iterator;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Objects;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Piece.java</strong><br>
 | 
					 * File: <strong>Piece.java</strong><br>
 | 
				
			||||||
 * Created: <strong>01.07.2019</strong><br>
 | 
					 * Created: <strong>01.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public abstract class Piece implements Cloneable {
 | 
					public abstract class Piece implements Cloneable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final 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;
 | 
				
			||||||
@@ -42,8 +44,8 @@ public abstract class Piece implements Cloneable {
 | 
				
			|||||||
	 * @param move The move to check
 | 
						 * @param move The move to check
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	protected boolean isFreePath(Move move) {
 | 
						protected boolean isFreePath(Move move) {
 | 
				
			||||||
		for (int i = move.pos.x + move.xSign, j = move.pos.y + move.ySign; i != move.dest.x
 | 
							for (int i = move.getPos().x + move.getxSign(), j = move.getPos().y + move.getySign(); i != move.getDest().x
 | 
				
			||||||
				|| j != move.dest.y; i += move.xSign, j += move.ySign)
 | 
									|| j != move.getDest().y; i += move.getxSign(), j += move.getySign())
 | 
				
			||||||
			if (board.getBoardArr()[i][j] != null) return false;
 | 
								if (board.getBoardArr()[i][j] != null) return false;
 | 
				
			||||||
		return checkDestination(move);
 | 
							return checkDestination(move);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -55,9 +57,7 @@ public abstract class Piece implements Cloneable {
 | 
				
			|||||||
	 * @param move The move to check
 | 
						 * @param move The move to check
 | 
				
			||||||
	 * @return {@code false} if the move's destination is from the same team
 | 
						 * @return {@code false} if the move's destination is from the same team
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	protected final boolean checkDestination(Move move) {
 | 
						protected final boolean checkDestination(Move move) { return board.getDest(move) == null || board.getDest(move).getColor() != getColor(); }
 | 
				
			||||||
		return board.getDest(move) == null || board.getDest(move).getColor() != getColor();
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public Object clone() {
 | 
						public Object clone() {
 | 
				
			||||||
@@ -70,44 +70,65 @@ public abstract class Piece implements Cloneable {
 | 
				
			|||||||
		return piece;
 | 
							return piece;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public abstract Type getType();
 | 
						@Override
 | 
				
			||||||
 | 
						public String toString() { return getClass().getSimpleName(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Color getColor() { return color; }
 | 
						@Override
 | 
				
			||||||
 | 
						public int hashCode() { return Objects.hash(color); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public int getMoveCounter() { return moveCounter; }
 | 
						@Override
 | 
				
			||||||
 | 
						public boolean equals(Object obj) {
 | 
				
			||||||
	public void incMoveCounter() {
 | 
							if (this == obj) return true;
 | 
				
			||||||
		++moveCounter;
 | 
							if (obj == null) return false;
 | 
				
			||||||
 | 
							if (getClass() != obj.getClass()) return false;
 | 
				
			||||||
 | 
							Piece other = (Piece) obj;
 | 
				
			||||||
 | 
							return color == other.color;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void decMoveCounter() {
 | 
					 | 
				
			||||||
		--moveCounter;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	public static enum Type {
 | 
					 | 
				
			||||||
		KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN;
 | 
					 | 
				
			||||||
		
 | 
					 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
		 * @return The first character of this {@link Type} in algebraic notation and lower case
 | 
						 * @return the standard value of this {@link Piece} that can be used for board
 | 
				
			||||||
 | 
						 *         evaluation
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
		public char firstChar() {
 | 
						public abstract int getValue();
 | 
				
			||||||
			return this == KNIGHT ? 'n' : Character.toLowerCase(this.toString().charAt(0));
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return The first character of this {@link Piece} in algebraic notation and
 | 
				
			||||||
 | 
						 *         lower case
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public char firstChar() { return Character.toLowerCase(toString().charAt(0)); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static Class<? extends Piece> fromFirstChar(char firstChar) {
 | 
				
			||||||
 | 
							switch (Character.toLowerCase(firstChar)) {
 | 
				
			||||||
 | 
								case 'k':
 | 
				
			||||||
 | 
									return King.class;
 | 
				
			||||||
 | 
								case 'q':
 | 
				
			||||||
 | 
									return Queen.class;
 | 
				
			||||||
 | 
								case 'r':
 | 
				
			||||||
 | 
									return Rook.class;
 | 
				
			||||||
 | 
								case 'n':
 | 
				
			||||||
 | 
									return Knight.class;
 | 
				
			||||||
 | 
								case 'b':
 | 
				
			||||||
 | 
									return Bishop.class;
 | 
				
			||||||
 | 
								case 'p':
 | 
				
			||||||
 | 
									return Pawn.class;
 | 
				
			||||||
 | 
								default:
 | 
				
			||||||
 | 
									return null;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @return the {@link Color} of this {@link Piece}
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public Color getColor() { return color; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static enum Color {
 | 
						public static enum Color {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		WHITE, BLACK;
 | 
							WHITE, BLACK;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public static Color fromFirstChar(char c) {
 | 
							public static Color fromFirstChar(char c) { return Character.toLowerCase(c) == 'w' ? WHITE : BLACK; }
 | 
				
			||||||
			return Character.toLowerCase(c) == 'w' ? WHITE : BLACK;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public char firstChar() {
 | 
							public char firstChar() { return this == WHITE ? 'w' : 'b'; }
 | 
				
			||||||
			return this == WHITE ? 'w' : 'b';
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public Color opposite() {
 | 
							public Color opposite() { return this == WHITE ? BLACK : WHITE; }
 | 
				
			||||||
			return this == WHITE ? BLACK : WHITE;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,9 @@ package dev.kske.chess.board;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Position.java</strong><br>
 | 
					 * File: <strong>Position.java</strong><br>
 | 
				
			||||||
 * Created: <strong>02.07.2019</strong><br>
 | 
					 * Created: <strong>02.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class Position {
 | 
					public class Position {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -15,11 +17,11 @@ public class Position {
 | 
				
			|||||||
		this.y	= y;
 | 
							this.y	= y;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static Position fromSAN(String pos) {
 | 
						public static Position fromLAN(String pos) {
 | 
				
			||||||
		return new Position(pos.charAt(0) - 97, 8 - Character.getNumericValue(pos.charAt(1)));
 | 
							return new Position(pos.charAt(0) - 97, 8 - Character.getNumericValue(pos.charAt(1)));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public String toSAN() {
 | 
						public String toLAN() {
 | 
				
			||||||
		return String.valueOf((char) (x + 97)) + String.valueOf(8 - y);
 | 
							return String.valueOf((char) (x + 97)) + String.valueOf(8 - y);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,9 @@ import java.util.List;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Queen.java</strong><br>
 | 
					 * File: <strong>Queen.java</strong><br>
 | 
				
			||||||
 * Created: <strong>01.07.2019</strong><br>
 | 
					 * Created: <strong>01.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class Queen extends Piece {
 | 
					public class Queen extends Piece {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -99,5 +101,5 @@ public class Queen extends Piece {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public Type getType() { return Type.QUEEN; }
 | 
						public int getValue() { return 90; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,9 @@ import java.util.List;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Rook.java</strong><br>
 | 
					 * File: <strong>Rook.java</strong><br>
 | 
				
			||||||
 * Created: <strong>01.07.2019</strong><br>
 | 
					 * Created: <strong>01.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class Rook extends Piece {
 | 
					public class Rook extends Piece {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -63,5 +65,5 @@ public class Rook extends Piece {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public Type getType() { return Type.ROOK; }
 | 
						public int getValue() { return 50; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,9 @@ package dev.kske.chess.event;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Event.java</strong><br>
 | 
					 * File: <strong>Event.java</strong><br>
 | 
				
			||||||
 * Created: <strong>7 Aug 2019</strong><br>
 | 
					 * Created: <strong>7 Aug 2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.4-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public interface Event<T> {
 | 
					public interface Event<T> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,9 @@ import java.util.List;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>EventBus.java</strong><br>
 | 
					 * File: <strong>EventBus.java</strong><br>
 | 
				
			||||||
 * Created: <strong>7 Aug 2019</strong><br>
 | 
					 * Created: <strong>7 Aug 2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.4-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class EventBus {
 | 
					public class EventBus {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										21
									
								
								src/dev/kske/chess/event/GameStartEvent.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/dev/kske/chess/event/GameStartEvent.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,21 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.game.Game;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>GameStartEvent.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>30 Oct 2019</strong><br>
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.5-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class GameStartEvent implements Event<Game> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Game game;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public GameStartEvent(Game source) { game = source; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public Game getData() { return game; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,21 +1,28 @@
 | 
				
			|||||||
package dev.kske.chess.event;
 | 
					package dev.kske.chess.event;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.BoardState;
 | 
				
			||||||
import dev.kske.chess.board.Move;
 | 
					import dev.kske.chess.board.Move;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>MoveEvent.java</strong><br>
 | 
					 * File: <strong>MoveEvent.java</strong><br>
 | 
				
			||||||
 * Created: <strong>7 Aug 2019</strong><br>
 | 
					 * Created: <strong>7 Aug 2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.4-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class MoveEvent implements Event<Move> {
 | 
					public class MoveEvent implements Event<Move> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final Move move;
 | 
						private final Move move;
 | 
				
			||||||
 | 
						private final BoardState	boardState;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public MoveEvent(Move move) {
 | 
						public MoveEvent(Move move, BoardState boardState) {
 | 
				
			||||||
		this.move = move;
 | 
							this.move = move;
 | 
				
			||||||
 | 
							this.boardState	= boardState;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public Move getData() { return move; }
 | 
						public Move getData() { return move; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public BoardState getBoardState() { return boardState; }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,9 @@ import java.util.Set;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Subscribable.java</strong><br>
 | 
					 * File: <strong>Subscribable.java</strong><br>
 | 
				
			||||||
 * Created: <strong>7 Aug 2019</strong><br>
 | 
					 * Created: <strong>7 Aug 2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.4-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public interface Subscribable {
 | 
					public interface Subscribable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										26
									
								
								src/dev/kske/chess/exception/ChessException.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								src/dev/kske/chess/exception/ChessException.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.exception;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>ChessException.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>22 Sep 2019</strong><br>
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.5-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class ChessException extends Exception {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final long serialVersionUID = -2208596063548245189L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public ChessException(String message, Throwable cause) {
 | 
				
			||||||
 | 
							super(message, cause);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public ChessException(String message) {
 | 
				
			||||||
 | 
							super(message);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public ChessException(Throwable cause) {
 | 
				
			||||||
 | 
							super(cause);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -6,23 +6,26 @@ import java.util.Map;
 | 
				
			|||||||
import javax.swing.JOptionPane;
 | 
					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.BoardState;
 | 
				
			||||||
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.EventBus;
 | 
				
			||||||
 | 
					import dev.kske.chess.event.GameStartEvent;
 | 
				
			||||||
import dev.kske.chess.event.MoveEvent;
 | 
					import dev.kske.chess.event.MoveEvent;
 | 
				
			||||||
import dev.kske.chess.game.ai.AIPlayer;
 | 
					import dev.kske.chess.game.ai.AIPlayer;
 | 
				
			||||||
 | 
					import dev.kske.chess.io.EngineUtil;
 | 
				
			||||||
 | 
					import dev.kske.chess.io.EngineUtil.EngineInfo;
 | 
				
			||||||
import dev.kske.chess.ui.BoardComponent;
 | 
					import dev.kske.chess.ui.BoardComponent;
 | 
				
			||||||
import dev.kske.chess.ui.BoardPane;
 | 
					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;
 | 
					import dev.kske.chess.ui.OverlayComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Game.java</strong><br>
 | 
					 * File: <strong>Game.java</strong><br>
 | 
				
			||||||
 * Created: <strong>06.07.2019</strong><br>
 | 
					 * Created: <strong>06.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class Game {
 | 
					public class Game {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -36,8 +39,8 @@ public class Game {
 | 
				
			|||||||
		init(boardPane, whiteName, blackName);
 | 
							init(boardPane, whiteName, blackName);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Game(BoardPane boardPane, String whiteName, String blackName, String fen) {
 | 
						public Game(BoardPane boardPane, String whiteName, String blackName, Board board) {
 | 
				
			||||||
		board = new Board(fen);
 | 
							this.board = board;
 | 
				
			||||||
		init(boardPane, whiteName, blackName);
 | 
							init(boardPane, whiteName, blackName);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -80,14 +83,12 @@ public class Game {
 | 
				
			|||||||
			// Run garbage collection
 | 
								// Run garbage collection
 | 
				
			||||||
			System.gc();
 | 
								System.gc();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			System.out.printf("%s: %s%n", player.color, move);
 | 
								BoardState boardState = board.getGameEventType(board.getDest(move).getColor().opposite());
 | 
				
			||||||
			System.out.println("FEN: " + board.toFEN());
 | 
								EventBus.getInstance().dispatch(new MoveEvent(move, boardState));
 | 
				
			||||||
			EventBus.getInstance().dispatch(new MoveEvent(move));
 | 
								switch (boardState) {
 | 
				
			||||||
			GameState eventType = board.getGameEventType(board.getDest(move).getColor().opposite());
 | 
					 | 
				
			||||||
			switch (eventType) {
 | 
					 | 
				
			||||||
				case CHECKMATE:
 | 
									case CHECKMATE:
 | 
				
			||||||
				case STALEMATE:
 | 
									case STALEMATE:
 | 
				
			||||||
					String result = String.format("%s in %s!%n", player.color.opposite(), eventType);
 | 
										String result = String.format("%s in %s!%n", player.color.opposite(), boardState);
 | 
				
			||||||
					System.out.print(result);
 | 
										System.out.print(result);
 | 
				
			||||||
					JOptionPane.showMessageDialog(boardComponent, result);
 | 
										JOptionPane.showMessageDialog(boardComponent, result);
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
@@ -100,6 +101,7 @@ public class Game {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void start() {
 | 
						public void start() {
 | 
				
			||||||
 | 
							EventBus.getInstance().dispatch(new GameStartEvent(this));
 | 
				
			||||||
		players.get(board.getLog().getActiveColor()).requestMove();
 | 
							players.get(board.getLog().getActiveColor()).requestMove();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,9 +5,11 @@ 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 javax.swing.JComboBox;
 | 
				
			||||||
 | 
					import javax.swing.JOptionPane;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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;
 | 
				
			||||||
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;
 | 
				
			||||||
@@ -16,14 +18,17 @@ import dev.kske.chess.ui.OverlayComponent;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>NaturalPlayer.java</strong><br>
 | 
					 * File: <strong>NaturalPlayer.java</strong><br>
 | 
				
			||||||
 * Created: <strong>06.07.2019</strong><br>
 | 
					 * Created: <strong>06.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class NaturalPlayer extends Player implements MouseListener {
 | 
					public class NaturalPlayer extends Player implements MouseListener {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final OverlayComponent overlayComponent;
 | 
						private final OverlayComponent overlayComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private boolean		moveRequested;
 | 
						private boolean		moveRequested;
 | 
				
			||||||
	private Position	pos;
 | 
						private Piece		selectedPiece;
 | 
				
			||||||
 | 
						private List<Move>	possibleMoves;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public NaturalPlayer(Color color, OverlayComponent overlayComponent) {
 | 
						public NaturalPlayer(Color color, OverlayComponent overlayComponent) {
 | 
				
			||||||
		super(color);
 | 
							super(color);
 | 
				
			||||||
@@ -35,43 +40,63 @@ public class NaturalPlayer extends Player implements MouseListener {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void requestMove() {
 | 
						public void requestMove() { moveRequested = true; }
 | 
				
			||||||
		moveRequested = true;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void cancelMove() {
 | 
						public void cancelMove() { moveRequested = false; }
 | 
				
			||||||
		moveRequested = false;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void disconnect() {
 | 
						public void disconnect() { overlayComponent.removeMouseListener(this); }
 | 
				
			||||||
		overlayComponent.removeMouseListener(this);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void mousePressed(MouseEvent evt) {
 | 
						public void mousePressed(MouseEvent evt) {
 | 
				
			||||||
		if (!moveRequested) return;
 | 
							if (!moveRequested) return;
 | 
				
			||||||
		if (pos == null) {
 | 
							if (selectedPiece == null) {
 | 
				
			||||||
			pos = new Position(evt.getPoint().x / overlayComponent.getTileSize(),
 | 
					 | 
				
			||||||
					evt.getPoint().y / overlayComponent.getTileSize());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			Board board = new Board(this.board);
 | 
								// Get selected Piece
 | 
				
			||||||
			if (board.get(pos) != null && board.get(pos).getColor() == color) {
 | 
								final Position pos = new Position(evt.getPoint().x / overlayComponent.getTileSize(), evt.getPoint().y / overlayComponent.getTileSize());
 | 
				
			||||||
				List<Position> positions = board.getMoves(pos)
 | 
								selectedPiece = board.get(pos);
 | 
				
			||||||
					.stream()
 | 
					
 | 
				
			||||||
					.map(move -> move.dest)
 | 
								// Check if a piece was selected
 | 
				
			||||||
					.collect(Collectors.toList());
 | 
								if (selectedPiece != null) {
 | 
				
			||||||
				overlayComponent.displayDots(positions);
 | 
					
 | 
				
			||||||
			} else pos = null;
 | 
									// Discard selection if the piece has the wrong color
 | 
				
			||||||
 | 
									if (selectedPiece.getColor() == color.opposite()) selectedPiece = null;
 | 
				
			||||||
 | 
									else {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Generate all moves possible with the selected piece and display their
 | 
				
			||||||
 | 
										// destinations
 | 
				
			||||||
 | 
										possibleMoves = selectedPiece.getMoves(pos);
 | 
				
			||||||
 | 
										overlayComponent.displayDots(possibleMoves.stream().map(move -> move.getDest()).collect(Collectors.toList()));
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			Position dest = new Position(evt.getPoint().x / overlayComponent.getTileSize(),
 | 
								Position dest = new Position(evt.getPoint().x / overlayComponent.getTileSize(), evt.getPoint().y / overlayComponent.getTileSize());
 | 
				
			||||||
					evt.getPoint().y / overlayComponent.getTileSize());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
			overlayComponent.clearDots();
 | 
								// Get all moves leading to the specified destination
 | 
				
			||||||
 | 
								List<Move> selectedMoves = possibleMoves.stream().filter(m -> m.getDest().equals(dest)).collect(Collectors.toList());
 | 
				
			||||||
 | 
								if (!selectedMoves.isEmpty()) {
 | 
				
			||||||
 | 
									Move move;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Process pawn promotion if necessary
 | 
				
			||||||
 | 
									if (selectedMoves.size() > 1) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// Let the user select a promotion piece
 | 
				
			||||||
 | 
										JComboBox<Move> comboBox = new JComboBox<Move>(selectedMoves.toArray(new Move[0]));
 | 
				
			||||||
 | 
										JOptionPane.showMessageDialog(overlayComponent, comboBox, "Select a promotion", JOptionPane.QUESTION_MESSAGE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										move = selectedMoves.get(comboBox.getSelectedIndex());
 | 
				
			||||||
 | 
									} else move = selectedMoves.get(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									// Tell the game to execute the move
 | 
				
			||||||
				moveRequested = false;
 | 
									moveRequested = false;
 | 
				
			||||||
			game.onMove(NaturalPlayer.this, new Move(pos, dest, Type.UNKNOWN));
 | 
									game.onMove(NaturalPlayer.this, move);
 | 
				
			||||||
			pos = null;
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Discard the selection
 | 
				
			||||||
 | 
								overlayComponent.clearDots();
 | 
				
			||||||
 | 
								selectedPiece	= null;
 | 
				
			||||||
 | 
								possibleMoves	= null;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,9 @@ import dev.kske.chess.board.Piece.Color;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>Player.java</strong><br>
 | 
					 * File: <strong>Player.java</strong><br>
 | 
				
			||||||
 * Created: <strong>06.07.2019</strong><br>
 | 
					 * Created: <strong>06.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public abstract class Player {
 | 
					public abstract class Player {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@ package dev.kske.chess.game;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.FENString;
 | 
				
			||||||
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.uci.UCIHandle;
 | 
					import dev.kske.chess.uci.UCIHandle;
 | 
				
			||||||
@@ -11,7 +12,9 @@ import dev.kske.chess.uci.UCIListener;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>UCIPlayer.java</strong><br>
 | 
					 * File: <strong>UCIPlayer.java</strong><br>
 | 
				
			||||||
 * Created: <strong>18.07.2019</strong><br>
 | 
					 * Created: <strong>18.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.3-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class UCIPlayer extends Player implements UCIListener {
 | 
					public class UCIPlayer extends Player implements UCIListener {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -30,7 +33,7 @@ public class UCIPlayer extends Player implements UCIListener {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void requestMove() {
 | 
						public void requestMove() {
 | 
				
			||||||
		handle.positionFEN(board.toFEN());
 | 
							handle.positionFEN(new FENString(board).toString());
 | 
				
			||||||
		handle.go();
 | 
							handle.go();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,7 +54,7 @@ public class UCIPlayer extends Player implements UCIListener {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public void onBestMove(String move) {
 | 
						public void onBestMove(String move) {
 | 
				
			||||||
		Move moveObj = Move.fromSAN(move);
 | 
							Move moveObj = Move.fromLAN(move);
 | 
				
			||||||
		game.onMove(this, moveObj);
 | 
							game.onMove(this, moveObj);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,9 @@ import dev.kske.chess.game.Player;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>AIPlayer.java</strong><br>
 | 
					 * File: <strong>AIPlayer.java</strong><br>
 | 
				
			||||||
 * Created: <strong>06.07.2019</strong><br>
 | 
					 * Created: <strong>06.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class AIPlayer extends Player {
 | 
					public class AIPlayer extends Player {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -50,7 +52,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	= new Board(this.board);
 | 
								Board		board	= new Board(this.board, false);
 | 
				
			||||||
			List<Move>	moves	= board.getMoves(color);
 | 
								List<Move>	moves	= board.getMoves(color);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			/*
 | 
								/*
 | 
				
			||||||
@@ -64,7 +66,7 @@ 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(new MoveProcessor(new Board(board), moves.subList(beginIndex, endIndex), color,
 | 
									processors.add(new MoveProcessor(new Board(board, false), moves.subList(beginIndex, endIndex), color,
 | 
				
			||||||
						maxDepth, alphaBetaThreshold));
 | 
											maxDepth, alphaBetaThreshold));
 | 
				
			||||||
				beginIndex = endIndex;
 | 
									beginIndex = endIndex;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,28 @@
 | 
				
			|||||||
package dev.kske.chess.game.ai;
 | 
					package dev.kske.chess.game.ai;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
import java.util.concurrent.Callable;
 | 
					import java.util.concurrent.Callable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Bishop;
 | 
				
			||||||
import dev.kske.chess.board.Board;
 | 
					import dev.kske.chess.board.Board;
 | 
				
			||||||
 | 
					import dev.kske.chess.board.King;
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Knight;
 | 
				
			||||||
import dev.kske.chess.board.Move;
 | 
					import dev.kske.chess.board.Move;
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Pawn;
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Piece;
 | 
				
			||||||
import dev.kske.chess.board.Piece.Color;
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Queen;
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Rook;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>MoveProcessor.java</strong><br>
 | 
					 * File: <strong>MoveProcessor.java</strong><br>
 | 
				
			||||||
 * Created: <strong>08.07.2019</strong><br>
 | 
					 * Created: <strong>08.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class MoveProcessor implements Callable<ProcessingResult> {
 | 
					public class MoveProcessor implements Callable<ProcessingResult> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -23,6 +34,40 @@ public class MoveProcessor implements Callable<ProcessingResult> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private Move bestMove;
 | 
						private Move bestMove;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final Map<Class<? extends Piece>, int[][]> positionScores;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						static {
 | 
				
			||||||
 | 
							positionScores = new HashMap<>();
 | 
				
			||||||
 | 
							positionScores.put(King.class,
 | 
				
			||||||
 | 
									new int[][] { new int[] { -3, -4, -4, -5, -5, -4, -4, -3 }, new int[] { -3, -4, -4, -5, -4, -4, -4, -3 },
 | 
				
			||||||
 | 
											new int[] { -3, -4, -4, -5, -4, -4, -4, -3 }, new int[] { -3, -4, -4, -5, -4, -4, -4, -3 },
 | 
				
			||||||
 | 
											new int[] { -2, -3, -3, -2, -2, -2, -2, -1 }, new int[] { -1, -2, -2, -2, -2, -2, -2, -1 },
 | 
				
			||||||
 | 
											new int[] { 2, 2, 0, 0, 0, 0, 2, 2 }, new int[] { 2, 3, 1, 0, 0, 1, 3, 2 } });
 | 
				
			||||||
 | 
							positionScores.put(Queen.class,
 | 
				
			||||||
 | 
									new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, -2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { 0, 0, 1, 1, 1, 1, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, 1, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 0, 0, 0, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
 | 
				
			||||||
 | 
							positionScores.put(Rook.class,
 | 
				
			||||||
 | 
									new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 1, 1, 1, 1, 1, 1, 1, 1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { 0, 0, 0, 1, 1, 0, 0, 0 } });
 | 
				
			||||||
 | 
							positionScores.put(Knight.class,
 | 
				
			||||||
 | 
									new int[][] { new int[] { -5, -4, -3, -3, -3, -3, -4, -5 }, new int[] { -4, -2, 0, 0, 0, 0, -2, -4 },
 | 
				
			||||||
 | 
											new int[] { -3, 0, 1, 2, 2, 1, 0, -3 }, new int[] { -3, 1, 2, 2, 2, 2, 1, -3 }, new int[] { -3, 0, 2, 2, 2, 2, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { -3, 1, 1, 2, 2, 1, 1, -3 }, new int[] { -4, -2, 0, 1, 1, 0, -2, -4 },
 | 
				
			||||||
 | 
											new int[] { -5, -4, -3, -3, -3, -3, -4, -5 } });
 | 
				
			||||||
 | 
							positionScores.put(Bishop.class,
 | 
				
			||||||
 | 
									new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, 2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 1, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 },
 | 
				
			||||||
 | 
											new int[] { -1, 1, 1, 1, 1, 1, 1, -1 }, new int[] { -1, 1, 0, 0, 0, 0, 1, -1 },
 | 
				
			||||||
 | 
											new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
 | 
				
			||||||
 | 
							positionScores.put(Pawn.class,
 | 
				
			||||||
 | 
									new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 5, 5, 5, 5, 5, 5, 5, 5 }, new int[] { 1, 1, 2, 3, 3, 2, 1, 1 },
 | 
				
			||||||
 | 
											new int[] { 0, 0, 1, 3, 3, 1, 0, 0 }, new int[] { 0, 0, 0, 2, 2, 0, 0, 0 }, new int[] { 0, 0, -1, 0, 0, -1, 0, 0 },
 | 
				
			||||||
 | 
											new int[] { 0, 1, 1, -2, -2, 1, 1, 0 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0 } });
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public MoveProcessor(Board board, List<Move> rootMoves, Color color, int maxDepth, int alphaBetaThreshold) {
 | 
						public MoveProcessor(Board board, List<Move> rootMoves, Color color, int maxDepth, int alphaBetaThreshold) {
 | 
				
			||||||
		this.board				= board;
 | 
							this.board				= board;
 | 
				
			||||||
		this.rootMoves			= rootMoves;
 | 
							this.rootMoves			= rootMoves;
 | 
				
			||||||
@@ -41,8 +86,8 @@ public class MoveProcessor implements Callable<ProcessingResult> {
 | 
				
			|||||||
		int bestValue = Integer.MIN_VALUE;
 | 
							int bestValue = Integer.MIN_VALUE;
 | 
				
			||||||
		for (Move move : moves) {
 | 
							for (Move move : moves) {
 | 
				
			||||||
			board.move(move);
 | 
								board.move(move);
 | 
				
			||||||
			int	teamValue	= board.evaluate(color);
 | 
								int	teamValue	= evaluate(board, color);
 | 
				
			||||||
			int	enemyValue	= board.evaluate(color.opposite());
 | 
								int	enemyValue	= evaluate(board, color.opposite());
 | 
				
			||||||
			int	valueChange	= teamValue - enemyValue;
 | 
								int	valueChange	= teamValue - enemyValue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if (depth < maxDepth && valueChange >= alphaBetaThreshold)
 | 
								if (depth < maxDepth && valueChange >= alphaBetaThreshold)
 | 
				
			||||||
@@ -57,4 +102,22 @@ public class MoveProcessor implements Callable<ProcessingResult> {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		return bestValue;
 | 
							return bestValue;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Evaluated a board.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param color The color to evaluate for
 | 
				
			||||||
 | 
						 * @return An positive number representing how good the position is
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						private int evaluate(Board board, Color color) {
 | 
				
			||||||
 | 
							int score = 0;
 | 
				
			||||||
 | 
							for (int i = 0; i < 8; i++)
 | 
				
			||||||
 | 
								for (int j = 0; j < 8; j++)
 | 
				
			||||||
 | 
									if (board.getBoardArr()[i][j] != null && board.getBoardArr()[i][j].getColor() == color) {
 | 
				
			||||||
 | 
										score += board.getBoardArr()[i][j].getValue();
 | 
				
			||||||
 | 
										if (positionScores.containsKey(board.getBoardArr()[i][j].getClass()))
 | 
				
			||||||
 | 
											score += positionScores.get(board.getBoardArr()[i][j].getClass())[i][color == Color.WHITE ? j : 7 - j];
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
							return score;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,9 @@ import dev.kske.chess.board.Move;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>ProcessingResult.java</strong><br>
 | 
					 * File: <strong>ProcessingResult.java</strong><br>
 | 
				
			||||||
 * Created: <strong>08.07.2019</strong><br>
 | 
					 * Created: <strong>08.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class ProcessingResult {
 | 
					public class ProcessingResult {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
package dev.kske.chess.ui;
 | 
					package dev.kske.chess.io;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.io.FileInputStream;
 | 
					import java.io.FileInputStream;
 | 
				
			||||||
import java.io.FileOutputStream;
 | 
					import java.io.FileOutputStream;
 | 
				
			||||||
@@ -16,7 +16,10 @@ import dev.kske.chess.uci.UCIListener;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>MenuBar.java</strong><br>
 | 
					 * File: <strong>MenuBar.java</strong><br>
 | 
				
			||||||
 * Created: <strong>23.07.2019</strong><br>
 | 
					 * Created: <strong>23.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Leon Hofmeister</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.2-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class EngineUtil {
 | 
					public class EngineUtil {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
package dev.kske.chess.ui;
 | 
					package dev.kske.chess.io;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.awt.Image;
 | 
					import java.awt.Image;
 | 
				
			||||||
import java.awt.image.BufferedImage;
 | 
					import java.awt.image.BufferedImage;
 | 
				
			||||||
@@ -15,7 +15,9 @@ import dev.kske.chess.board.Piece;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>TextureUtil.java</strong><br>
 | 
					 * File: <strong>TextureUtil.java</strong><br>
 | 
				
			||||||
 * Created: <strong>01.07.2019</strong><br>
 | 
					 * Created: <strong>01.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class TextureUtil {
 | 
					public class TextureUtil {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,23 +31,24 @@ public class TextureUtil {
 | 
				
			|||||||
	private TextureUtil() {}
 | 
						private TextureUtil() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Loads a piece texture fitting to a piece object
 | 
						 * Loads a piece texture fitting to a piece object.
 | 
				
			||||||
	 * 
 | 
						 * 
 | 
				
			||||||
	 * @param piece The piece from which the texture properties are taken
 | 
						 * @param piece The piece from which the texture properties are taken
 | 
				
			||||||
	 * @return The fitting texture
 | 
						 * @return The fitting texture
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	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.toString().toLowerCase() + "_" + piece.getColor().toString().toLowerCase();
 | 
				
			||||||
		return scaledTextures.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.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param tileSize the new width and height of the piece textures
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public static void scalePieceTextures(int scale) {
 | 
						public static void scalePieceTextures(int tileSize) {
 | 
				
			||||||
		scaledTextures.clear();
 | 
							scaledTextures.clear();
 | 
				
			||||||
		textures
 | 
							textures.forEach((key, img) -> scaledTextures.put(key, img.getScaledInstance(tileSize, tileSize, Image.SCALE_SMOOTH)));
 | 
				
			||||||
			.forEach((key, img) -> scaledTextures.put(key, img.getScaledInstance(scale, scale, Image.SCALE_SMOOTH)));
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -84,5 +87,4 @@ public class TextureUtil {
 | 
				
			|||||||
					"pawn_black")
 | 
										"pawn_black")
 | 
				
			||||||
			.forEach(name -> textures.put(name, loadImage("/pieces/" + name + ".png")));
 | 
								.forEach(name -> textures.put(name, loadImage("/pieces/" + name + ".png")));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										56
									
								
								src/dev/kske/chess/pgn/PGNDatabase.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/dev/kske/chess/pgn/PGNDatabase.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,56 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.pgn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.File;
 | 
				
			||||||
 | 
					import java.io.FileNotFoundException;
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.io.PrintWriter;
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Scanner;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.exception.ChessException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>PGNDatabase.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>4 Oct 2019</strong><br>
 | 
				
			||||||
 | 
					 * <br>
 | 
				
			||||||
 | 
					 * Contains a series of {@link PGNGame} objects that can be stored inside a PGN
 | 
				
			||||||
 | 
					 * file.
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.5-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class PGNDatabase {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final List<PGNGame> games = new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Loads PGN games from a file.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param pgnFile the file to load the games from
 | 
				
			||||||
 | 
						 * @throws FileNotFoundException if the specified file is not found
 | 
				
			||||||
 | 
						 * @throws ChessException        if an error occurs while parsing the file
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void load(File pgnFile) throws FileNotFoundException, ChessException {
 | 
				
			||||||
 | 
							Scanner sc = new Scanner(pgnFile);
 | 
				
			||||||
 | 
							while (sc.hasNext())
 | 
				
			||||||
 | 
								games.add(PGNGame.parse(sc));
 | 
				
			||||||
 | 
							sc.close();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Saves PGN games to a file.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param pgnFile the file to save the games to.
 | 
				
			||||||
 | 
						 * @throws IOException if the file could not be created
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void save(File pgnFile) throws IOException {
 | 
				
			||||||
 | 
							pgnFile.getParentFile().mkdirs();
 | 
				
			||||||
 | 
							PrintWriter pw = new PrintWriter(pgnFile);
 | 
				
			||||||
 | 
							games.forEach(g -> g.writePGN(pw));
 | 
				
			||||||
 | 
							pw.close();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public List<PGNGame> getGames() { return games; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										121
									
								
								src/dev/kske/chess/pgn/PGNGame.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								src/dev/kske/chess/pgn/PGNGame.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.pgn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.PrintWriter;
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.Scanner;
 | 
				
			||||||
 | 
					import java.util.regex.MatchResult;
 | 
				
			||||||
 | 
					import java.util.regex.Pattern;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Board;
 | 
				
			||||||
 | 
					import dev.kske.chess.board.FENString;
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Move;
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
 | 
					import dev.kske.chess.exception.ChessException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>PGNGame.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>22 Sep 2019</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @since Chess v0.5-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class PGNGame {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final Map<String, String>	tagPairs	= new HashMap<>(7);
 | 
				
			||||||
 | 
						private final Board					board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public PGNGame() { board = new Board(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public PGNGame(Board board) { this.board = board; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static PGNGame parse(Scanner sc) throws ChessException {
 | 
				
			||||||
 | 
							PGNGame game = new PGNGame();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							MatchResult	matchResult;
 | 
				
			||||||
 | 
							Pattern		tagPairPattern	= Pattern.compile("\\[(\\w+) \"(.*)\"]"),
 | 
				
			||||||
 | 
									movePattern = Pattern.compile("\\d+\\.\\s+(?:(?:(\\S+)\\s+(\\S+))|(?:O-O-O)|(?:O-O))(?:\\+{0,2}|\\#)"),
 | 
				
			||||||
 | 
									nagPattern = Pattern.compile("(\\$\\d{1,3})*"), terminationMarkerPattern = Pattern.compile("1-0|0-1|1\\/2-1\\/2|\\*");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Parse tag pairs
 | 
				
			||||||
 | 
							while (sc.findInLine(tagPairPattern) != null) {
 | 
				
			||||||
 | 
								matchResult = sc.match();
 | 
				
			||||||
 | 
								if (matchResult.groupCount() == 2) game.setTag(matchResult.group(1), matchResult.group(2));
 | 
				
			||||||
 | 
								else break;
 | 
				
			||||||
 | 
								sc.nextLine();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Parse movetext
 | 
				
			||||||
 | 
							while (true) {
 | 
				
			||||||
 | 
								// Skip NAG (Numeric Annotation Glyph)
 | 
				
			||||||
 | 
								sc.skip(nagPattern);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// TODO: Parse RAV (Recursive Annotation Variation)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (sc.findWithinHorizon(movePattern, 20) != null) {
 | 
				
			||||||
 | 
									matchResult = sc.match();
 | 
				
			||||||
 | 
									if (matchResult.groupCount() > 0) for (int i = 1; i < matchResult.groupCount() + 1; i++) {
 | 
				
			||||||
 | 
										game.board.move(matchResult.group(i));
 | 
				
			||||||
 | 
										System.out.println(game.getBoard().getLog().getLast().move.toLAN() + ": " + new FENString(game.board).toString());
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									else break;
 | 
				
			||||||
 | 
								} else break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Parse game termination marker
 | 
				
			||||||
 | 
							if (sc.findWithinHorizon(terminationMarkerPattern, 20) == null) System.err.println("Termination marker expected");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							return game;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void writePGN(PrintWriter pw) {
 | 
				
			||||||
 | 
							// Set the unknown result tag if no result tag is specified
 | 
				
			||||||
 | 
							tagPairs.putIfAbsent("Result", "*");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Write tag pairs
 | 
				
			||||||
 | 
							tagPairs.forEach((k, v) -> pw.printf("[%s \"%s\"]%n", k, v));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Insert newline if tags were printed
 | 
				
			||||||
 | 
							if (!tagPairs.isEmpty()) pw.println();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (!board.getLog().isEmpty()) {
 | 
				
			||||||
 | 
								// Collect SAN moves
 | 
				
			||||||
 | 
								Board			clone	= new Board(board, true);
 | 
				
			||||||
 | 
								List<String>	chunks	= new ArrayList<>();
 | 
				
			||||||
 | 
								boolean			flag	= true;
 | 
				
			||||||
 | 
								while (flag) {
 | 
				
			||||||
 | 
									Move move = clone.getLog().getLast().move;
 | 
				
			||||||
 | 
									flag = clone.getLog().hasParent();
 | 
				
			||||||
 | 
									clone.revert();
 | 
				
			||||||
 | 
									String chunk = clone.getLog().getActiveColor() == Color.WHITE ? String.format(" %d. ", clone.getLog().getFullmoveNumber()) : " ";
 | 
				
			||||||
 | 
									chunk += move.toSAN(clone);
 | 
				
			||||||
 | 
									chunks.add(chunk);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								Collections.reverse(chunks);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Write movetext
 | 
				
			||||||
 | 
								String line = "";
 | 
				
			||||||
 | 
								for (String chunk : chunks)
 | 
				
			||||||
 | 
									if (line.length() + chunk.length() <= 80) line += chunk;
 | 
				
			||||||
 | 
									else {
 | 
				
			||||||
 | 
										pw.println(line);
 | 
				
			||||||
 | 
										line = chunk;
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								if (!line.isEmpty()) pw.println(line);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Write game termination marker
 | 
				
			||||||
 | 
							pw.print(tagPairs.get("Result"));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public String getTag(String tagName) { return tagPairs.get(tagName); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public boolean hasTag(String tagName) { return tagPairs.containsKey(tagName); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void setTag(String tagName, String tagValue) { tagPairs.put(tagName, tagValue); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public Board getBoard() { return board; }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,12 +2,18 @@ package dev.kske.chess.uci;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.io.IOException;
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.io.PrintWriter;
 | 
					import java.io.PrintWriter;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.StringJoiner;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Move;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>UCIHandle.java</strong><br>
 | 
					 * File: <strong>UCIHandle.java</strong><br>
 | 
				
			||||||
 * Created: <strong>18.07.2019</strong><br>
 | 
					 * Created: <strong>18.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.3-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class UCIHandle {
 | 
					public class UCIHandle {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -29,34 +35,26 @@ public class UCIHandle {
 | 
				
			|||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Tells the engine to use UCI.
 | 
						 * Tells the engine to use UCI.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void uci() {
 | 
						public void uci() { out.println("uci"); }
 | 
				
			||||||
		out.println("uci");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Switches the debug mode of the engine on or off.
 | 
						 * Switches the debug mode of the engine on or off.
 | 
				
			||||||
	 * 
 | 
						 * 
 | 
				
			||||||
	 * @param debug Enables debugging if set to {@code true}, disables it otherwise
 | 
						 * @param debug Enables debugging if set to {@code true}, disables it otherwise
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void debug(boolean debug) {
 | 
						public void debug(boolean debug) { out.println("debug " + (debug ? "on" : "off")); }
 | 
				
			||||||
		out.println("debug " + (debug ? "on" : "off"));
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Synchronized the engine with the GUI
 | 
						 * Synchronized the engine with the GUI
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void isready() {
 | 
						public void isready() { out.println("isready"); }
 | 
				
			||||||
		out.println("isready");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Signifies a button press to the engine.
 | 
						 * Signifies a button press to the engine.
 | 
				
			||||||
	 * 
 | 
						 * 
 | 
				
			||||||
	 * @param name The name of the button
 | 
						 * @param name The name of the button
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setOption(String name) {
 | 
						public void setOption(String name) { out.println("setoption name " + name); }
 | 
				
			||||||
		out.println("setoption name " + name);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Changes an internal parameter of the engine.
 | 
						 * Changes an internal parameter of the engine.
 | 
				
			||||||
@@ -64,9 +62,7 @@ public class UCIHandle {
 | 
				
			|||||||
	 * @param name  The name of the parameter
 | 
						 * @param name  The name of the parameter
 | 
				
			||||||
	 * @param value The value of the parameter
 | 
						 * @param value The value of the parameter
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void setOption(String name, String value) {
 | 
						public void setOption(String name, String value) { out.printf("setoption name %s value %s%n", name, value); }
 | 
				
			||||||
		out.printf("setoption name %s value %s%n", name, value);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Registers the engine
 | 
						 * Registers the engine
 | 
				
			||||||
@@ -74,70 +70,131 @@ public class UCIHandle {
 | 
				
			|||||||
	 * @param name The name the engine should be registered with
 | 
						 * @param name The name the engine should be registered with
 | 
				
			||||||
	 * @param code The code the engine should be registered with
 | 
						 * @param code The code the engine should be registered with
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void register(String name, String code) {
 | 
						public void register(String name, String code) { out.printf("register %s %s%n", name, code); }
 | 
				
			||||||
		out.printf("register %s %s%n", name, code);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Tells the engine to postpone the registration.
 | 
						 * Tells the engine to postpone the registration.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void registerLater() {
 | 
						public void registerLater() { out.println("register later"); }
 | 
				
			||||||
		out.println("register later");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Tells the engine that the next search will be from a different game.
 | 
						 * Tells the engine that the next search will be from a different game.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void uciNewGame() {
 | 
						public void uciNewGame() { out.println("ucinewgame"); }
 | 
				
			||||||
		out.println("ucinewgame");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// TODO: position
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Sets up the position in its initial state.
 | 
						 * Sets up the position in its initial state.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void startPosition() {
 | 
						public void positionStartpos() { out.println("position startpos"); }
 | 
				
			||||||
		out.println("position startpos");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Sets up the position described in the FEN string.
 | 
						 * Sets up the position described in the FEN string.
 | 
				
			||||||
	 * 
 | 
						 * 
 | 
				
			||||||
	 * @param fen FEN representation of the current board
 | 
						 * @param fen FEN representation of the current board
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void positionFEN(String fen) {
 | 
						public void positionFEN(String fen) { out.println("position fen " + fen); }
 | 
				
			||||||
		out.println("position fen " + fen);
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Sets up the position described by a list of moves.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param moves the moves to execute from the starting position to reach the
 | 
				
			||||||
 | 
						 *              desired position
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void positionMoves(List<Move> moves) {
 | 
				
			||||||
 | 
							StringJoiner joiner = new StringJoiner(" ");
 | 
				
			||||||
 | 
							moves.forEach(m -> joiner.add(m.toLAN()));
 | 
				
			||||||
 | 
							out.println("position moves " + joiner);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO: go with parameters
 | 
						/**
 | 
				
			||||||
 | 
						 * Starts calculating on the current position.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void go() { out.println("go"); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void go() {
 | 
						/**
 | 
				
			||||||
		out.println("go");
 | 
						 * Starts calculating on the current position.
 | 
				
			||||||
 | 
						 * This command has multiple optional parameters which will only be included in
 | 
				
			||||||
 | 
						 * the call if they are not {@code null}, greater than zero or {@code true} for
 | 
				
			||||||
 | 
						 * {@code searchMoves}, all integer parameters and all boolean parameters
 | 
				
			||||||
 | 
						 * respectively.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param searchMoves restrict the search to these moves only
 | 
				
			||||||
 | 
						 * @param ponder      start the search in ponder mode
 | 
				
			||||||
 | 
						 * @param wTime       the amount of milliseconds left on white's clock
 | 
				
			||||||
 | 
						 * @param bTime       the amount of milliseconds left on black's clocks
 | 
				
			||||||
 | 
						 * @param wInc        white's increment per move in milliseconds
 | 
				
			||||||
 | 
						 * @param bInc        black's increment per move in milliseconds
 | 
				
			||||||
 | 
						 * @param movesToGo   the number of moves left until the next time control
 | 
				
			||||||
 | 
						 * @param depth       the maximal amount of plies to search
 | 
				
			||||||
 | 
						 * @param nodes       the maximal amount of nodes to search
 | 
				
			||||||
 | 
						 * @param mate        the amount of moves in which to search for a mate
 | 
				
			||||||
 | 
						 * @param moveTime    the exact search time
 | 
				
			||||||
 | 
						 * @param infinite    search until the {@code stop} command
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void go(List<Move> searchMoves, boolean ponder, int wTime, int bTime, int wInc, int bInc, int movesToGo, int depth, int nodes, int mate,
 | 
				
			||||||
 | 
								int moveTime, boolean infinite) {
 | 
				
			||||||
 | 
							StringJoiner joiner = new StringJoiner(" ");
 | 
				
			||||||
 | 
							joiner.add("go");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (searchMoves != null && !searchMoves.isEmpty()) {
 | 
				
			||||||
 | 
								joiner.add("searchmoves");
 | 
				
			||||||
 | 
								searchMoves.forEach(m -> joiner.add(m.toLAN()));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (ponder) joiner.add("ponder");
 | 
				
			||||||
 | 
							if (wTime > 0) {
 | 
				
			||||||
 | 
								joiner.add("wtime");
 | 
				
			||||||
 | 
								joiner.add(String.valueOf(wTime));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (bTime > 0) {
 | 
				
			||||||
 | 
								joiner.add("btime");
 | 
				
			||||||
 | 
								joiner.add(String.valueOf(bTime));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (wInc > 0) {
 | 
				
			||||||
 | 
								joiner.add("winc");
 | 
				
			||||||
 | 
								joiner.add(String.valueOf(wInc));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (bInc > 0) {
 | 
				
			||||||
 | 
								joiner.add("bind");
 | 
				
			||||||
 | 
								joiner.add(String.valueOf(bInc));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (movesToGo > 0) {
 | 
				
			||||||
 | 
								joiner.add("movestogo");
 | 
				
			||||||
 | 
								joiner.add(String.valueOf(movesToGo));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (depth > 0) {
 | 
				
			||||||
 | 
								joiner.add("depth");
 | 
				
			||||||
 | 
								joiner.add(String.valueOf(depth));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (nodes > 0) {
 | 
				
			||||||
 | 
								joiner.add("nodes");
 | 
				
			||||||
 | 
								joiner.add(String.valueOf(nodes));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (mate > 0) {
 | 
				
			||||||
 | 
								joiner.add("mate");
 | 
				
			||||||
 | 
								joiner.add(String.valueOf(mate));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (moveTime > 0) {
 | 
				
			||||||
 | 
								joiner.add("movetime");
 | 
				
			||||||
 | 
								joiner.add(String.valueOf(moveTime));
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (infinite) joiner.add("infinite");
 | 
				
			||||||
 | 
							out.println(joiner);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Stops calculation as soon as possible.
 | 
						 * Stops calculation as soon as possible.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void stop() {
 | 
						public void stop() { out.println("stop"); }
 | 
				
			||||||
		out.println("stop");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Tells the engine that the user has played the expected move.
 | 
						 * Tells the engine that the user has played the expected move.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void ponderHit() {
 | 
						public void ponderHit() { out.println("ponderhit"); }
 | 
				
			||||||
		out.println("ponderhit");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Quits the engine process as soon as possible.
 | 
						 * Quits the engine process as soon as possible.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void quit() {
 | 
						public void quit() { out.println("quit"); }
 | 
				
			||||||
		out.println("quit");
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void setListener(UCIListener listener) {
 | 
						public void setListener(UCIListener listener) { receiver.addListener(listener); }
 | 
				
			||||||
		receiver.addListener(listener);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,7 +2,9 @@ package dev.kske.chess.uci;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.chess.board.Move;
 | 
					import dev.kske.chess.board.Move;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -10,13 +12,15 @@ import dev.kske.chess.board.Move;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>UCIInfo.java</strong><br>
 | 
					 * File: <strong>UCIInfo.java</strong><br>
 | 
				
			||||||
 * Created: <strong>28.07.2019</strong><br>
 | 
					 * Created: <strong>28.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.3-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class UCIInfo {
 | 
					public class UCIInfo {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private int			depth, seldepth, time, nodes, multipv, currmovenumber, hashfull, nps, tbhits, sbhits, cpuload,
 | 
						private int							depth, seldepth, time, nodes, multipv, currmovenumber, hashfull, nps, tbhits, sbhits, cpuload, cpunr;
 | 
				
			||||||
			cpunr;
 | 
						private List<Move>					pv			= new ArrayList<>(), refutation = new ArrayList<>();
 | 
				
			||||||
	private List<Move>	pv, refutation, currline;
 | 
						private Map<Integer, List<Move>>	currline	= new HashMap<>();
 | 
				
			||||||
	private Move						currmove;
 | 
						private Move						currmove;
 | 
				
			||||||
	private Score						score;
 | 
						private Score						score;
 | 
				
			||||||
	private String						displayString;
 | 
						private String						displayString;
 | 
				
			||||||
@@ -44,9 +48,6 @@ public class UCIInfo {
 | 
				
			|||||||
			"currline");
 | 
								"currline");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public UCIInfo(String line) {
 | 
						public UCIInfo(String line) {
 | 
				
			||||||
		pv			= new ArrayList<>();
 | 
					 | 
				
			||||||
		refutation	= new ArrayList<>();
 | 
					 | 
				
			||||||
		currline	= new ArrayList<>();
 | 
					 | 
				
			||||||
		String[] tokens = line.split(" ");
 | 
							String[] tokens = line.split(" ");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for (int i = 0; i < tokens.length; i++)
 | 
							for (int i = 0; i < tokens.length; i++)
 | 
				
			||||||
@@ -68,7 +69,7 @@ public class UCIInfo {
 | 
				
			|||||||
					multipv = Integer.parseInt(tokens[++i]);
 | 
										multipv = Integer.parseInt(tokens[++i]);
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				case "currmove":
 | 
									case "currmove":
 | 
				
			||||||
					currmove = Move.fromSAN(tokens[++i]);
 | 
										currmove = Move.fromLAN(tokens[++i]);
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				case "currmovenumber":
 | 
									case "currmovenumber":
 | 
				
			||||||
					currmovenumber = Integer.parseInt(tokens[++i]);
 | 
										currmovenumber = Integer.parseInt(tokens[++i]);
 | 
				
			||||||
@@ -97,16 +98,19 @@ public class UCIInfo {
 | 
				
			|||||||
					break;
 | 
										break;
 | 
				
			||||||
				case "pv":
 | 
									case "pv":
 | 
				
			||||||
					while (++i < tokens.length && !params.contains(tokens[i]))
 | 
										while (++i < tokens.length && !params.contains(tokens[i]))
 | 
				
			||||||
						pv.add(Move.fromSAN(tokens[i]));
 | 
											pv.add(Move.fromLAN(tokens[i]));
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				case "refutation":
 | 
									case "refutation":
 | 
				
			||||||
					while (++i < tokens.length && !params.contains(tokens[i]))
 | 
										while (++i < tokens.length && !params.contains(tokens[i]))
 | 
				
			||||||
						refutation.add(Move.fromSAN(tokens[i]));
 | 
											refutation.add(Move.fromLAN(tokens[i]));
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				// TODO: currline
 | 
					 | 
				
			||||||
				case "currline":
 | 
									case "currline":
 | 
				
			||||||
					while (++i < tokens.length && !params.contains(tokens[i]))
 | 
										// A CPU number of 1 can be omitted
 | 
				
			||||||
						;
 | 
										final Integer cpu = tokens[i].matches("\\d+") ? Integer.valueOf(tokens[i++]) : 1;
 | 
				
			||||||
 | 
										final ArrayList<Move> moves = new ArrayList<>();
 | 
				
			||||||
 | 
										while (i < tokens.length && !params.contains(tokens[i]))
 | 
				
			||||||
 | 
											moves.add(Move.fromLAN(tokens[i++]));
 | 
				
			||||||
 | 
										currline.put(cpu, moves);
 | 
				
			||||||
					System.err.println("The parameter 'currline' for command 'info' is not yet implemented");
 | 
										System.err.println("The parameter 'currline' for command 'info' is not yet implemented");
 | 
				
			||||||
					break;
 | 
										break;
 | 
				
			||||||
				default:
 | 
									default:
 | 
				
			||||||
@@ -142,7 +146,7 @@ public class UCIInfo {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	public List<Move> getRefutation() { return refutation; }
 | 
						public List<Move> getRefutation() { return refutation; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public List<Move> getCurrline() { return currline; }
 | 
						public Map<Integer, List<Move>> getCurrline() { return currline; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public Move getCurrmove() { return currmove; }
 | 
						public Move getCurrmove() { return currmove; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,7 +6,9 @@ import dev.kske.chess.board.Move;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>UCIListener.java</strong><br>
 | 
					 * File: <strong>UCIListener.java</strong><br>
 | 
				
			||||||
 * Created: <strong>19.07.2019</strong><br>
 | 
					 * Created: <strong>19.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.3-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public interface UCIListener {
 | 
					public interface UCIListener {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -53,6 +55,7 @@ public interface UCIListener {
 | 
				
			|||||||
	 * The engine will check the copy protection now.
 | 
						 * The engine will check the copy protection now.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	default void onCopyProtectionChecking() {}
 | 
						default void onCopyProtectionChecking() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * The engine has successfully checked the copy protection.
 | 
						 * The engine has successfully checked the copy protection.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,9 @@ import java.util.StringJoiner;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>UCIOption.java</strong><br>
 | 
					 * File: <strong>UCIOption.java</strong><br>
 | 
				
			||||||
 * Created: <strong>22.07.2019</strong><br>
 | 
					 * Created: <strong>22.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.3-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class UCIOption {
 | 
					public class UCIOption {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,9 @@ import dev.kske.chess.board.Move;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>UCIReceiver.java</strong><br>
 | 
					 * File: <strong>UCIReceiver.java</strong><br>
 | 
				
			||||||
 * Created: <strong>19.07.2019</strong><br>
 | 
					 * Created: <strong>19.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.3-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class UCIReceiver implements Runnable {
 | 
					public class UCIReceiver implements Runnable {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -93,7 +95,7 @@ public class UCIReceiver implements Runnable {
 | 
				
			|||||||
		String		move	= tokens[0];
 | 
							String		move	= tokens[0];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Ponder move
 | 
							// Ponder move
 | 
				
			||||||
		if (tokens.length == 3) listeners.forEach(l -> l.onBestMove(move, Move.fromSAN(tokens[2])));
 | 
							if (tokens.length == 3) listeners.forEach(l -> l.onBestMove(move, Move.fromLAN(tokens[2])));
 | 
				
			||||||
		else listeners.forEach(l -> l.onBestMove(move));
 | 
							else listeners.forEach(l -> l.onBestMove(move));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -129,15 +131,9 @@ public class UCIReceiver implements Runnable {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void parseInfo(String line) {
 | 
						private void parseInfo(String line) { listeners.forEach(l -> l.onInfo(new UCIInfo(line))); }
 | 
				
			||||||
		listeners.forEach(l -> l.onInfo(new UCIInfo(line)));
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void parseOption(String line) {
 | 
						private void parseOption(String line) { listeners.forEach(l -> l.onOption(new UCIOption((line)))); }
 | 
				
			||||||
		listeners.forEach(l -> l.onOption(new UCIOption((line))));
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void addListener(UCIListener listener) {
 | 
						public void addListener(UCIListener listener) { listeners.add(listener); }
 | 
				
			||||||
		listeners.add(listener);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,8 +12,11 @@ import javax.swing.SpinnerNumberModel;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>AIConfigDialog.java</strong><br>
 | 
					 * File: <strong>AIConfigDialog.java</strong><br>
 | 
				
			||||||
 * Created: <strong>16.07.2019</strong><br>
 | 
					 * Created: <strong>16.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					@Deprecated
 | 
				
			||||||
public class AIConfigDialog extends JDialog {
 | 
					public class AIConfigDialog extends JDialog {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private static final long serialVersionUID = -8047984368152479992L;
 | 
						private static final long serialVersionUID = -8047984368152479992L;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,16 +6,19 @@ import java.awt.Graphics;
 | 
				
			|||||||
import javax.swing.JComponent;
 | 
					import javax.swing.JComponent;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.chess.board.Board;
 | 
					import dev.kske.chess.board.Board;
 | 
				
			||||||
 | 
					import dev.kske.chess.io.TextureUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>BoardComponent.java</strong><br>
 | 
					 * File: <strong>BoardComponent.java</strong><br>
 | 
				
			||||||
 * Created: <strong>01.07.2019</strong><br>
 | 
					 * Created: <strong>01.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong><br>
 | 
					 | 
				
			||||||
 * <br>
 | 
					 * <br>
 | 
				
			||||||
 * A square panel for rendering the chess board. To work correctly,
 | 
					 * A square panel for rendering the chess board. To work correctly,
 | 
				
			||||||
 * this must be added to a parent component that allows the child to decide the
 | 
					 * this must be added to a parent component that allows the child to decide the
 | 
				
			||||||
 * size.
 | 
					 * size.
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class BoardComponent extends JComponent {
 | 
					public class BoardComponent extends JComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -47,8 +50,8 @@ public class BoardComponent extends JComponent {
 | 
				
			|||||||
		// Draw the pieces if a board is present
 | 
							// Draw the pieces if a board is present
 | 
				
			||||||
		if (board != null) for (int i = 0; i < 8; i++)
 | 
							if (board != null) for (int i = 0; i < 8; i++)
 | 
				
			||||||
			for (int j = 0; j < 8; j++)
 | 
								for (int j = 0; j < 8; j++)
 | 
				
			||||||
				if (board.getBoardArr()[i][j] != null) g.drawImage(TextureUtil
 | 
									if (board.getBoardArr()[i][j] != null)
 | 
				
			||||||
					.getPieceTexture(board.getBoardArr()[i][j]), i * tileSize, j * tileSize, this);
 | 
										g.drawImage(TextureUtil.getPieceTexture(board.getBoardArr()[i][j]), i * tileSize, j * tileSize, this);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public int getTileSize() { return boardPane.getTileSize(); }
 | 
						public int getTileSize() { return boardPane.getTileSize(); }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,9 @@ import javax.swing.JLayeredPane;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>BoardPane.java</strong><br>
 | 
					 * File: <strong>BoardPane.java</strong><br>
 | 
				
			||||||
 * Created: <strong>08.07.2019</strong><br>
 | 
					 * Created: <strong>08.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class BoardPane extends JLayeredPane {
 | 
					public class BoardPane extends JLayeredPane {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										82
									
								
								src/dev/kske/chess/ui/DialogUtil.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/dev/kske/chess/ui/DialogUtil.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.awt.Component;
 | 
				
			||||||
 | 
					import java.awt.Font;
 | 
				
			||||||
 | 
					import java.io.File;
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					import java.util.function.BiConsumer;
 | 
				
			||||||
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.swing.DefaultComboBoxModel;
 | 
				
			||||||
 | 
					import javax.swing.JComboBox;
 | 
				
			||||||
 | 
					import javax.swing.JFileChooser;
 | 
				
			||||||
 | 
					import javax.swing.JLabel;
 | 
				
			||||||
 | 
					import javax.swing.JOptionPane;
 | 
				
			||||||
 | 
					import javax.swing.JPanel;
 | 
				
			||||||
 | 
					import javax.swing.filechooser.FileNameExtensionFilter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.io.EngineUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>DialogUtil.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>24.07.2019</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @since Chess v0.3-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class DialogUtil {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private DialogUtil() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static void showFileSelectionDialog(Component parent, Consumer<List<File>> action, Collection<FileNameExtensionFilter> filters) {
 | 
				
			||||||
 | 
							JFileChooser fileChooser = new JFileChooser();
 | 
				
			||||||
 | 
							fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
 | 
				
			||||||
 | 
							fileChooser.setAcceptAllFileFilterUsed(false);
 | 
				
			||||||
 | 
							filters.forEach(fileChooser::addChoosableFileFilter);
 | 
				
			||||||
 | 
							if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) action.accept(Arrays.asList(fileChooser.getSelectedFile()));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static void showFileSaveDialog(Component parent, Consumer<File> action, Collection<FileNameExtensionFilter> filters) {
 | 
				
			||||||
 | 
							JFileChooser fileChooser = new JFileChooser();
 | 
				
			||||||
 | 
							fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
 | 
				
			||||||
 | 
							fileChooser.setAcceptAllFileFilterUsed(false);
 | 
				
			||||||
 | 
							filters.forEach(fileChooser::addChoosableFileFilter);
 | 
				
			||||||
 | 
							if (fileChooser.showSaveDialog(parent) == JFileChooser.APPROVE_OPTION) action.accept(
 | 
				
			||||||
 | 
									new File(fileChooser.getSelectedFile().getAbsolutePath() + "."
 | 
				
			||||||
 | 
											+ ((FileNameExtensionFilter) fileChooser.getFileFilter()).getExtensions()[0]));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public static void showGameConfigurationDialog(Component parent, BiConsumer<String, String> action) {
 | 
				
			||||||
 | 
							JPanel dialogPanel = new JPanel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							List<String> options = new ArrayList<>(Arrays.asList("Natural Player", "AI Player"));
 | 
				
			||||||
 | 
							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);
 | 
				
			||||||
 | 
							dialogPanel.add(lblWhite);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JComboBox<Object> cbWhite = new JComboBox<>();
 | 
				
			||||||
 | 
							cbWhite.setModel(new DefaultComboBoxModel<>(options.toArray()));
 | 
				
			||||||
 | 
							cbWhite.setBounds(98, 9, 159, 22);
 | 
				
			||||||
 | 
							dialogPanel.add(cbWhite);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JLabel lblBlack = new JLabel("Black:");
 | 
				
			||||||
 | 
							lblBlack.setFont(new Font("Tahoma", Font.PLAIN, 14));
 | 
				
			||||||
 | 
							lblBlack.setBounds(10, 38, 49, 14);
 | 
				
			||||||
 | 
							dialogPanel.add(lblBlack);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JComboBox<Object> cbBlack = new JComboBox<>();
 | 
				
			||||||
 | 
							cbBlack.setModel(new DefaultComboBoxModel<>(options.toArray()));
 | 
				
			||||||
 | 
							cbBlack.setBounds(98, 36, 159, 22);
 | 
				
			||||||
 | 
							dialogPanel.add(cbBlack);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JOptionPane.showMessageDialog(parent, dialogPanel, "Game configuration", JOptionPane.QUESTION_MESSAGE);
 | 
				
			||||||
 | 
							action.accept(options.get(cbWhite.getSelectedIndex()), options.get(cbBlack.getSelectedIndex()));
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,55 +0,0 @@
 | 
				
			|||||||
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();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,76 +0,0 @@
 | 
				
			|||||||
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);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										37
									
								
								src/dev/kske/chess/ui/GameDropTarget.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/dev/kske/chess/ui/GameDropTarget.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					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.File;
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>GameDropTarget.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>13 Aug 2019</strong><br>
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.3-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class GameDropTarget extends DropTargetAdapter {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private MainWindow mainWindow;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public GameDropTarget(MainWindow mainWindow) { this.mainWindow = mainWindow; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@SuppressWarnings("unchecked")
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public void drop(DropTargetDropEvent evt) {
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
 | 
				
			||||||
 | 
								mainWindow.loadFiles((List<File>) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor));
 | 
				
			||||||
 | 
							} catch (UnsupportedFlavorException | IOException ex) {
 | 
				
			||||||
 | 
								ex.printStackTrace();
 | 
				
			||||||
 | 
								evt.rejectDrop();
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,13 +3,27 @@ package dev.kske.chess.ui;
 | 
				
			|||||||
import java.awt.GridBagConstraints;
 | 
					import java.awt.GridBagConstraints;
 | 
				
			||||||
import java.awt.GridBagLayout;
 | 
					import java.awt.GridBagLayout;
 | 
				
			||||||
import java.awt.GridLayout;
 | 
					import java.awt.GridLayout;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.HashSet;
 | 
				
			||||||
 | 
					import java.util.Set;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.swing.DefaultListModel;
 | 
				
			||||||
import javax.swing.JButton;
 | 
					import javax.swing.JButton;
 | 
				
			||||||
import javax.swing.JComponent;
 | 
					import javax.swing.JComponent;
 | 
				
			||||||
import javax.swing.JLabel;
 | 
					import javax.swing.JLabel;
 | 
				
			||||||
 | 
					import javax.swing.JList;
 | 
				
			||||||
import javax.swing.JPanel;
 | 
					import javax.swing.JPanel;
 | 
				
			||||||
 | 
					import javax.swing.JScrollPane;
 | 
				
			||||||
 | 
					import javax.swing.ListSelectionModel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.BoardState;
 | 
				
			||||||
 | 
					import dev.kske.chess.board.MoveNode;
 | 
				
			||||||
import dev.kske.chess.board.Piece.Color;
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
 | 
					import dev.kske.chess.event.Event;
 | 
				
			||||||
 | 
					import dev.kske.chess.event.EventBus;
 | 
				
			||||||
 | 
					import dev.kske.chess.event.GameStartEvent;
 | 
				
			||||||
 | 
					import dev.kske.chess.event.MoveEvent;
 | 
				
			||||||
 | 
					import dev.kske.chess.event.Subscribable;
 | 
				
			||||||
import dev.kske.chess.game.Game;
 | 
					import dev.kske.chess.game.Game;
 | 
				
			||||||
import dev.kske.chess.game.NaturalPlayer;
 | 
					import dev.kske.chess.game.NaturalPlayer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -17,7 +31,9 @@ import dev.kske.chess.game.NaturalPlayer;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>GamePane.java</strong><br>
 | 
					 * File: <strong>GamePane.java</strong><br>
 | 
				
			||||||
 * Created: <strong>23.08.2019</strong><br>
 | 
					 * Created: <strong>23.08.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.4-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class GamePane extends JComponent {
 | 
					public class GamePane extends JComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -25,9 +41,12 @@ public class GamePane extends JComponent {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private JButton		btnRestart, btnSwapColors;
 | 
						private JButton		btnRestart, btnSwapColors;
 | 
				
			||||||
	private BoardPane	boardPane;
 | 
						private BoardPane	boardPane;
 | 
				
			||||||
	private LogPanel	logPanel;
 | 
					 | 
				
			||||||
	private Game		game;
 | 
						private Game		game;
 | 
				
			||||||
	private Color		activeColor;
 | 
						private Color		activeColor;
 | 
				
			||||||
 | 
						private JPanel		moveSelectionPanel;
 | 
				
			||||||
 | 
						private JButton		btnNext;
 | 
				
			||||||
 | 
						private JButton		btnFirst;
 | 
				
			||||||
 | 
						private JButton		btnLast;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public GamePane() {
 | 
						public GamePane() {
 | 
				
			||||||
		activeColor = Color.WHITE;
 | 
							activeColor = Color.WHITE;
 | 
				
			||||||
@@ -35,13 +54,18 @@ public class GamePane extends JComponent {
 | 
				
			|||||||
		GridBagLayout gridBagLayout = new GridBagLayout();
 | 
							GridBagLayout gridBagLayout = new GridBagLayout();
 | 
				
			||||||
		gridBagLayout.columnWidths	= new int[] { 450, 1, 0 };
 | 
							gridBagLayout.columnWidths	= new int[] { 450, 1, 0 };
 | 
				
			||||||
		gridBagLayout.rowHeights	= new int[] { 33, 267, 1, 0 };
 | 
							gridBagLayout.rowHeights	= new int[] { 33, 267, 1, 0 };
 | 
				
			||||||
		gridBagLayout.columnWeights	= new double[] { 0.0, 0.0, Double.MIN_VALUE };
 | 
							gridBagLayout.columnWeights	= new double[] { 0.0, 1.0, 1.0 };
 | 
				
			||||||
		gridBagLayout.rowWeights	= new double[] { 0.0, 0.0, 0.0, Double.MIN_VALUE };
 | 
							gridBagLayout.rowWeights	= new double[] { 1.0, 1.0, 1.0, Double.MIN_VALUE };
 | 
				
			||||||
		setLayout(gridBagLayout);
 | 
							setLayout(gridBagLayout);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		JPanel toolPanel = new JPanel();
 | 
							JPanel toolPanel = new JPanel();
 | 
				
			||||||
		btnRestart = new JButton("Restart");
 | 
							btnRestart = new JButton("Restart");
 | 
				
			||||||
		btnRestart.addActionListener((evt) -> { if (game != null) game.reset(); game.start(); });
 | 
							btnRestart.addActionListener((evt) -> {
 | 
				
			||||||
 | 
								if (game != null) {
 | 
				
			||||||
 | 
									game.reset();
 | 
				
			||||||
 | 
									game.start();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		btnSwapColors = new JButton("Play as black");
 | 
							btnSwapColors = new JButton("Play as black");
 | 
				
			||||||
		btnSwapColors.addActionListener((evt) -> {
 | 
							btnSwapColors.addActionListener((evt) -> {
 | 
				
			||||||
@@ -58,7 +82,31 @@ public class GamePane extends JComponent {
 | 
				
			|||||||
		gbc_toolPanel.fill		= GridBagConstraints.HORIZONTAL;
 | 
							gbc_toolPanel.fill		= GridBagConstraints.HORIZONTAL;
 | 
				
			||||||
		gbc_toolPanel.gridx		= 0;
 | 
							gbc_toolPanel.gridx		= 0;
 | 
				
			||||||
		gbc_toolPanel.gridy		= 0;
 | 
							gbc_toolPanel.gridy		= 0;
 | 
				
			||||||
 | 
							gbc_toolPanel.gridwidth	= 2;
 | 
				
			||||||
		add(toolPanel, gbc_toolPanel);
 | 
							add(toolPanel, gbc_toolPanel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							moveSelectionPanel = new JPanel();
 | 
				
			||||||
 | 
							GridBagConstraints gbc_moveSelectionPanel = new GridBagConstraints();
 | 
				
			||||||
 | 
							gbc_moveSelectionPanel.fill		= GridBagConstraints.BOTH;
 | 
				
			||||||
 | 
							gbc_moveSelectionPanel.gridx	= 2;
 | 
				
			||||||
 | 
							gbc_moveSelectionPanel.gridy	= 0;
 | 
				
			||||||
 | 
							add(moveSelectionPanel, gbc_moveSelectionPanel);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							btnFirst = new JButton("First");
 | 
				
			||||||
 | 
							btnFirst.setEnabled(false);
 | 
				
			||||||
 | 
							moveSelectionPanel.add(btnFirst);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JButton btnPreviousMove = new JButton("Previous");
 | 
				
			||||||
 | 
							btnPreviousMove.setEnabled(false);
 | 
				
			||||||
 | 
							moveSelectionPanel.add(btnPreviousMove);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							btnNext = new JButton("Next");
 | 
				
			||||||
 | 
							btnNext.setEnabled(false);
 | 
				
			||||||
 | 
							moveSelectionPanel.add(btnNext);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							btnLast = new JButton("Last");
 | 
				
			||||||
 | 
							btnLast.setEnabled(false);
 | 
				
			||||||
 | 
							moveSelectionPanel.add(btnLast);
 | 
				
			||||||
		boardPane = new BoardPane();
 | 
							boardPane = new BoardPane();
 | 
				
			||||||
		GridBagConstraints gbc_boardPane = new GridBagConstraints();
 | 
							GridBagConstraints gbc_boardPane = new GridBagConstraints();
 | 
				
			||||||
		gbc_boardPane.fill	= GridBagConstraints.BOTH;
 | 
							gbc_boardPane.fill	= GridBagConstraints.BOTH;
 | 
				
			||||||
@@ -90,14 +138,42 @@ public class GamePane extends JComponent {
 | 
				
			|||||||
			letterPanel.add(letterLabel);
 | 
								letterPanel.add(letterLabel);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Initialize LogPanel
 | 
							JScrollPane			scrollPane		= new JScrollPane();
 | 
				
			||||||
		logPanel = new LogPanel();
 | 
							GridBagConstraints	gbc_scrollPane	= new GridBagConstraints();
 | 
				
			||||||
		GridBagConstraints gbc_logPanel = new GridBagConstraints();
 | 
							gbc_scrollPane.fill		= GridBagConstraints.BOTH;
 | 
				
			||||||
		gbc_logPanel.anchor	= GridBagConstraints.EAST;
 | 
							gbc_scrollPane.gridx	= 2;
 | 
				
			||||||
		gbc_logPanel.fill	= GridBagConstraints.VERTICAL;
 | 
							gbc_scrollPane.gridy	= 1;
 | 
				
			||||||
		gbc_logPanel.gridx	= 2;
 | 
							add(scrollPane, gbc_scrollPane);
 | 
				
			||||||
		gbc_logPanel.gridy	= 1;
 | 
					
 | 
				
			||||||
		add(logPanel, gbc_logPanel);
 | 
							JList<MoveNode> pgnList = new JList<>();
 | 
				
			||||||
 | 
							pgnList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
 | 
				
			||||||
 | 
							pgnList.setLayoutOrientation(JList.HORIZONTAL_WRAP);
 | 
				
			||||||
 | 
							pgnList.setVisibleRowCount(0);
 | 
				
			||||||
 | 
							pgnList.setCellRenderer(new MoveNodeRenderer());
 | 
				
			||||||
 | 
							scrollPane.setViewportView(pgnList);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Listen to moves and game (re-)starts and update the move list or disable the
 | 
				
			||||||
 | 
							// color switching buttons if necessary
 | 
				
			||||||
 | 
							EventBus.getInstance().register(new Subscribable() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public void handle(Event<?> event) {
 | 
				
			||||||
 | 
									if (event instanceof MoveEvent && (((MoveEvent) event).getBoardState() == BoardState.CHECKMATE
 | 
				
			||||||
 | 
											|| ((MoveEvent) event).getBoardState() == BoardState.STALEMATE))
 | 
				
			||||||
 | 
										btnSwapColors.setEnabled(false);
 | 
				
			||||||
 | 
									else if (event instanceof GameStartEvent) btnSwapColors.setEnabled(
 | 
				
			||||||
 | 
											game.getPlayers().get(Color.WHITE) instanceof NaturalPlayer ^ game.getPlayers().get(Color.BLACK) instanceof NaturalPlayer);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									if (game.getBoard().getLog() == null) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									DefaultListModel<MoveNode> model = new DefaultListModel<>();
 | 
				
			||||||
 | 
									game.getBoard().getLog().forEach(node -> model.addElement(node));
 | 
				
			||||||
 | 
									pgnList.setModel(model);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								@Override
 | 
				
			||||||
 | 
								public Set<Class<?>> supports() { return new HashSet<>(Arrays.asList(MoveEvent.class, GameStartEvent.class)); }
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -119,8 +195,5 @@ public class GamePane extends JComponent {
 | 
				
			|||||||
	public void setGame(Game game) {
 | 
						public void setGame(Game game) {
 | 
				
			||||||
		if (this.game != null) this.game.stop();
 | 
							if (this.game != null) this.game.stop();
 | 
				
			||||||
		this.game = game;
 | 
							this.game = game;
 | 
				
			||||||
		btnSwapColors.setEnabled(game.getPlayers().get(Color.WHITE) instanceof NaturalPlayer
 | 
					 | 
				
			||||||
				^ game.getPlayers().get(Color.BLACK) instanceof NaturalPlayer);
 | 
					 | 
				
			||||||
		logPanel.setLog(game.getBoard().getLog());
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,76 +0,0 @@
 | 
				
			|||||||
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,17 +1,34 @@
 | 
				
			|||||||
package dev.kske.chess.ui;
 | 
					package dev.kske.chess.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.awt.Desktop;
 | 
				
			||||||
import java.awt.EventQueue;
 | 
					import java.awt.EventQueue;
 | 
				
			||||||
import java.awt.Toolkit;
 | 
					import java.awt.Toolkit;
 | 
				
			||||||
import java.awt.dnd.DropTarget;
 | 
					import java.awt.dnd.DropTarget;
 | 
				
			||||||
 | 
					import java.io.File;
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.nio.charset.StandardCharsets;
 | 
				
			||||||
 | 
					import java.nio.file.Files;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.swing.JComboBox;
 | 
				
			||||||
import javax.swing.JFrame;
 | 
					import javax.swing.JFrame;
 | 
				
			||||||
 | 
					import javax.swing.JOptionPane;
 | 
				
			||||||
import javax.swing.JTabbedPane;
 | 
					import javax.swing.JTabbedPane;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Board;
 | 
				
			||||||
 | 
					import dev.kske.chess.board.FENString;
 | 
				
			||||||
 | 
					import dev.kske.chess.exception.ChessException;
 | 
				
			||||||
 | 
					import dev.kske.chess.game.Game;
 | 
				
			||||||
 | 
					import dev.kske.chess.pgn.PGNDatabase;
 | 
				
			||||||
 | 
					import dev.kske.chess.pgn.PGNGame;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>MainWindow.java</strong><br>
 | 
					 * File: <strong>MainWindow.java</strong><br>
 | 
				
			||||||
 * Created: <strong>01.07.2019</strong><br>
 | 
					 * Created: <strong>01.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class MainWindow extends JFrame {
 | 
					public class MainWindow extends JFrame {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -55,7 +72,7 @@ public class MainWindow extends JFrame {
 | 
				
			|||||||
		getContentPane().add(tabbedPane);
 | 
							getContentPane().add(tabbedPane);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		setJMenuBar(new MenuBar(this));
 | 
							setJMenuBar(new MenuBar(this));
 | 
				
			||||||
		new DropTarget(this, new FENDropTarget(this));
 | 
							new DropTarget(this, new GameDropTarget(this));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Update position and dimensions
 | 
							// Update position and dimensions
 | 
				
			||||||
		pack();
 | 
							pack();
 | 
				
			||||||
@@ -70,22 +87,109 @@ public class MainWindow extends JFrame {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Creates a new {@link GamePane}, adds it to the tabbed pane and opens it.
 | 
						 * Creates a new {@link GamePane}, adds it to the tabbed pane and opens it.
 | 
				
			||||||
 | 
						 * The new tab has the title {@code Game n} where {@code n} is its number.
 | 
				
			||||||
	 * 
 | 
						 * 
 | 
				
			||||||
	 * @return The new {@link GamePane}
 | 
						 * @return The new {@link GamePane}
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public GamePane addGamePane() {
 | 
						public GamePane addGamePane() { return addGamePane("Game " + (tabbedPane.getComponentCount() + 1)); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Creates a new {@link GamePane}, adds it to the tabbed pane and opens it.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param title The title of the {@link GamePane}
 | 
				
			||||||
 | 
						 * @return The new {@link GamePane}
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public GamePane addGamePane(String title) {
 | 
				
			||||||
		GamePane gamePane = new GamePane();
 | 
							GamePane gamePane = new GamePane();
 | 
				
			||||||
		tabbedPane.add("Game " + (tabbedPane.getComponentCount() + 1), gamePane);
 | 
							tabbedPane.add(title, gamePane);
 | 
				
			||||||
		tabbedPane.setSelectedIndex(tabbedPane.getComponentCount() - 1);
 | 
							tabbedPane.setSelectedIndex(tabbedPane.getComponentCount() - 1);
 | 
				
			||||||
		return gamePane;
 | 
							return gamePane;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public GamePane addGamePane(String title, Board board) {
 | 
				
			||||||
 | 
							GamePane gamePane = addGamePane(title);
 | 
				
			||||||
 | 
							DialogUtil.showGameConfigurationDialog(this,
 | 
				
			||||||
 | 
									(whiteName, blackName) -> {
 | 
				
			||||||
 | 
										Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, board);
 | 
				
			||||||
 | 
										gamePane.setGame(game);
 | 
				
			||||||
 | 
										game.start();
 | 
				
			||||||
 | 
									});
 | 
				
			||||||
 | 
							return gamePane;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Removes a {@link GamePane} form the tabbed pane.
 | 
						 * Removes a {@link GamePane} form the tabbed pane.
 | 
				
			||||||
	 * 
 | 
						 * 
 | 
				
			||||||
	 * @param index The index of the {@link GamePane} to remove
 | 
						 * @param index The index of the {@link GamePane} to remove
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void removeGamePane(int index) {
 | 
						public void removeGamePane(int index) { tabbedPane.remove(index); }
 | 
				
			||||||
		tabbedPane.remove(index);
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Loads a game file (FEN or PGN) and adds it to a new {@link GamePane}.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @param files the files to load the game from
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public void loadFiles(List<File> files) {
 | 
				
			||||||
 | 
							files.forEach(file -> {
 | 
				
			||||||
 | 
								final String	name		= file.getName().substring(0, file.getName().lastIndexOf('.'));
 | 
				
			||||||
 | 
								final String	extension	= file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase();
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									Board board;
 | 
				
			||||||
 | 
									switch (extension) {
 | 
				
			||||||
 | 
										case ".fen":
 | 
				
			||||||
 | 
											board = new FENString(new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)).getBoard();
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										case ".pgn":
 | 
				
			||||||
 | 
											PGNDatabase pgnDB = new PGNDatabase();
 | 
				
			||||||
 | 
											pgnDB.load(file);
 | 
				
			||||||
 | 
											if (pgnDB.getGames().size() > 0) {
 | 
				
			||||||
 | 
												String[] gameNames = new String[pgnDB.getGames().size()];
 | 
				
			||||||
 | 
												for (int i = 0; i < gameNames.length; i++) {
 | 
				
			||||||
 | 
													final PGNGame game = pgnDB.getGames().get(i);
 | 
				
			||||||
 | 
													gameNames[i] = String.format("%s vs %s: %s", game.getTag("White"), game.getTag("Black"), game.getTag("Result"));
 | 
				
			||||||
 | 
												}
 | 
				
			||||||
 | 
												JComboBox<String> comboBox = new JComboBox<>(gameNames);
 | 
				
			||||||
 | 
												JOptionPane.showMessageDialog(this, comboBox, "Select a game", JOptionPane.QUESTION_MESSAGE);
 | 
				
			||||||
 | 
												board = pgnDB.getGames().get(comboBox.getSelectedIndex()).getBoard();
 | 
				
			||||||
 | 
											} else throw new ChessException("The PGN database '" + name + "' is empty!");
 | 
				
			||||||
 | 
											break;
 | 
				
			||||||
 | 
										default:
 | 
				
			||||||
 | 
											throw new ChessException("The file extension '" + extension + "' is not supported!");
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									addGamePane(name, board);
 | 
				
			||||||
 | 
								} catch (Exception e) {
 | 
				
			||||||
 | 
									e.printStackTrace();
 | 
				
			||||||
 | 
									JOptionPane.showMessageDialog(this,
 | 
				
			||||||
 | 
											"Failed to load the file " + file.getName() + ": " + e.toString(),
 | 
				
			||||||
 | 
											"File loading error",
 | 
				
			||||||
 | 
											JOptionPane.ERROR_MESSAGE);
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public void saveFile(File file) {
 | 
				
			||||||
 | 
							final int		dotIndex	= file.getName().lastIndexOf('.');
 | 
				
			||||||
 | 
							final String	extension	= file.getName().substring(dotIndex).toLowerCase();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (extension.equals(".pgn")) try {
 | 
				
			||||||
 | 
								PGNGame pgnGame = new PGNGame(getSelectedGamePane().getGame().getBoard());
 | 
				
			||||||
 | 
								pgnGame.setTag("Event", tabbedPane.getTitleAt(tabbedPane.getSelectedIndex()));
 | 
				
			||||||
 | 
								pgnGame.setTag("Result", "*");
 | 
				
			||||||
 | 
								PGNDatabase pgnDB = new PGNDatabase();
 | 
				
			||||||
 | 
								pgnDB.getGames().add(pgnGame);
 | 
				
			||||||
 | 
								pgnDB.save(file);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (JOptionPane.showConfirmDialog(this,
 | 
				
			||||||
 | 
										"Game export finished. Do you want to view the created file?",
 | 
				
			||||||
 | 
										"Game export finished",
 | 
				
			||||||
 | 
										JOptionPane.YES_NO_OPTION) == JOptionPane.YES_OPTION)
 | 
				
			||||||
 | 
									Desktop.getDesktop().open(file);
 | 
				
			||||||
 | 
							} catch (IOException e) {
 | 
				
			||||||
 | 
								e.printStackTrace();
 | 
				
			||||||
 | 
								JOptionPane.showMessageDialog(this,
 | 
				
			||||||
 | 
										"Failed to save the file " + file.getName() + ": " + e.toString(),
 | 
				
			||||||
 | 
										"File saving error",
 | 
				
			||||||
 | 
										JOptionPane.ERROR_MESSAGE);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,19 +2,26 @@ package dev.kske.chess.ui;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.awt.Toolkit;
 | 
					import java.awt.Toolkit;
 | 
				
			||||||
import java.awt.datatransfer.StringSelection;
 | 
					import java.awt.datatransfer.StringSelection;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.swing.JMenu;
 | 
					import javax.swing.JMenu;
 | 
				
			||||||
import javax.swing.JMenuBar;
 | 
					import javax.swing.JMenuBar;
 | 
				
			||||||
import javax.swing.JMenuItem;
 | 
					import javax.swing.JMenuItem;
 | 
				
			||||||
import javax.swing.JOptionPane;
 | 
					import javax.swing.JOptionPane;
 | 
				
			||||||
 | 
					import javax.swing.filechooser.FileNameExtensionFilter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.FENString;
 | 
				
			||||||
 | 
					import dev.kske.chess.exception.ChessException;
 | 
				
			||||||
import dev.kske.chess.game.Game;
 | 
					import dev.kske.chess.game.Game;
 | 
				
			||||||
 | 
					import dev.kske.chess.io.EngineUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>MenuBar.java</strong><br>
 | 
					 * File: <strong>MenuBar.java</strong><br>
 | 
				
			||||||
 * Created: <strong>16.07.2019</strong><br>
 | 
					 * Created: <strong>16.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class MenuBar extends JMenuBar {
 | 
					public class MenuBar extends JMenuBar {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -32,16 +39,29 @@ public class MenuBar extends JMenuBar {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private void initGameMenu() {
 | 
						private void initGameMenu() {
 | 
				
			||||||
		JMenu gameMenu = new JMenu("Game");
 | 
							JMenu gameMenu = new JMenu("Game");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		JMenuItem newGameMenuItem = new JMenuItem("New Game");
 | 
							JMenuItem newGameMenuItem = new JMenuItem("New Game");
 | 
				
			||||||
		newGameMenuItem.addActionListener((evt) -> {
 | 
							newGameMenuItem.addActionListener((evt) -> DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
 | 
				
			||||||
			GameConfigurationDialog.show((whiteName, blackName) -> {
 | 
					 | 
				
			||||||
			GamePane	gamePane	= mainWindow.addGamePane();
 | 
								GamePane	gamePane	= mainWindow.addGamePane();
 | 
				
			||||||
			Game		game		= new Game(gamePane.getBoardPane(), whiteName, blackName);
 | 
								Game		game		= new Game(gamePane.getBoardPane(), whiteName, blackName);
 | 
				
			||||||
			gamePane.setGame(game);
 | 
								gamePane.setGame(game);
 | 
				
			||||||
			game.start();
 | 
								game.start();
 | 
				
			||||||
			});
 | 
							}));
 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
		gameMenu.add(newGameMenuItem);
 | 
							gameMenu.add(newGameMenuItem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JMenuItem loadFileMenu = new JMenuItem("Load game file");
 | 
				
			||||||
 | 
							loadFileMenu.addActionListener((evt) -> DialogUtil
 | 
				
			||||||
 | 
								.showFileSelectionDialog(mainWindow,
 | 
				
			||||||
 | 
										mainWindow::loadFiles,
 | 
				
			||||||
 | 
										Arrays.asList(new FileNameExtensionFilter("FEN and PGN files", "fen", "pgn"))));
 | 
				
			||||||
 | 
							gameMenu.add(loadFileMenu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							JMenuItem saveFileMenu = new JMenuItem("Save game file");
 | 
				
			||||||
 | 
							saveFileMenu
 | 
				
			||||||
 | 
								.addActionListener((evt) -> DialogUtil
 | 
				
			||||||
 | 
									.showFileSaveDialog(mainWindow, mainWindow::saveFile, Arrays.asList(new FileNameExtensionFilter("PGN file", "pgn"))));
 | 
				
			||||||
 | 
							gameMenu.add(saveFileMenu);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		add(gameMenu);
 | 
							add(gameMenu);
 | 
				
			||||||
		newGameMenuItem.doClick();
 | 
							newGameMenuItem.doClick();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -51,10 +71,8 @@ public class MenuBar extends JMenuBar {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		JMenuItem addEngineMenuItem = new JMenuItem("Add engine");
 | 
							JMenuItem addEngineMenuItem = new JMenuItem("Add engine");
 | 
				
			||||||
		addEngineMenuItem.addActionListener((evt) -> {
 | 
							addEngineMenuItem.addActionListener((evt) -> {
 | 
				
			||||||
			String enginePath = JOptionPane.showInputDialog(getParent(),
 | 
								String enginePath = JOptionPane
 | 
				
			||||||
					"Enter the path to a UCI-compatible chess engine:",
 | 
									.showInputDialog(getParent(), "Enter the path to a UCI-compatible chess engine:", "Engine selection", JOptionPane.QUESTION_MESSAGE);
 | 
				
			||||||
					"Engine selection",
 | 
					 | 
				
			||||||
					JOptionPane.QUESTION_MESSAGE);
 | 
					 | 
				
			||||||
			if (enginePath != null) EngineUtil.addEngine(enginePath);
 | 
								if (enginePath != null) EngineUtil.addEngine(enginePath);
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -70,7 +88,7 @@ public class MenuBar extends JMenuBar {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		JMenuItem exportFENMenuItem = new JMenuItem("Export board to FEN");
 | 
							JMenuItem exportFENMenuItem = new JMenuItem("Export board to FEN");
 | 
				
			||||||
		exportFENMenuItem.addActionListener((evt) -> {
 | 
							exportFENMenuItem.addActionListener((evt) -> {
 | 
				
			||||||
			final String fen = mainWindow.getSelectedGamePane().getGame().getBoard().toFEN();
 | 
								final String fen = new FENString(mainWindow.getSelectedGamePane().getGame().getBoard()).toString();
 | 
				
			||||||
			Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(fen), null);
 | 
								Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(fen), null);
 | 
				
			||||||
			JOptionPane.showMessageDialog(mainWindow, String.format("FEN-string copied to clipboard!%n%s", fen));
 | 
								JOptionPane.showMessageDialog(mainWindow, String.format("FEN-string copied to clipboard!%n%s", fen));
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
@@ -80,10 +98,17 @@ public class MenuBar extends JMenuBar {
 | 
				
			|||||||
		loadFromFENMenuItem.addActionListener((evt) -> {
 | 
							loadFromFENMenuItem.addActionListener((evt) -> {
 | 
				
			||||||
			final GamePane	gamePane	= mainWindow.addGamePane();
 | 
								final GamePane	gamePane	= mainWindow.addGamePane();
 | 
				
			||||||
			final String	fen			= JOptionPane.showInputDialog("Enter a FEN string: ");
 | 
								final String	fen			= JOptionPane.showInputDialog("Enter a FEN string: ");
 | 
				
			||||||
			GameConfigurationDialog.show((whiteName, blackName) -> {
 | 
								DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> {
 | 
				
			||||||
				final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, fen);
 | 
									Game game;
 | 
				
			||||||
 | 
									try {
 | 
				
			||||||
 | 
										game = new Game(gamePane.getBoardPane(), whiteName, blackName, new FENString(fen).getBoard());
 | 
				
			||||||
					gamePane.setGame(game);
 | 
										gamePane.setGame(game);
 | 
				
			||||||
					game.start();
 | 
										game.start();
 | 
				
			||||||
 | 
									} catch (ChessException e) {
 | 
				
			||||||
 | 
										e.printStackTrace();
 | 
				
			||||||
 | 
										JOptionPane
 | 
				
			||||||
 | 
											.showMessageDialog(mainWindow, "Failed to load FEN string: " + e.toString(), "FEN loading error", JOptionPane.ERROR_MESSAGE);
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			});
 | 
								});
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
		toolsMenu.add(loadFromFENMenuItem);
 | 
							toolsMenu.add(loadFromFENMenuItem);
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										34
									
								
								src/dev/kske/chess/ui/MoveNodeRenderer.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/dev/kske/chess/ui/MoveNodeRenderer.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.ui;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.awt.Color;
 | 
				
			||||||
 | 
					import java.awt.Component;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javax.swing.JLabel;
 | 
				
			||||||
 | 
					import javax.swing.JList;
 | 
				
			||||||
 | 
					import javax.swing.ListCellRenderer;
 | 
				
			||||||
 | 
					import javax.swing.border.EmptyBorder;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.MoveNode;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>MoveNodeRenderer.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>9 Oct 2019</strong><br>
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.5-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class MoveNodeRenderer extends JLabel implements ListCellRenderer<MoveNode> {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private static final long serialVersionUID = 5242015788752442446L;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						@Override
 | 
				
			||||||
 | 
						public Component getListCellRendererComponent(JList<? extends MoveNode> list, MoveNode node, int index,
 | 
				
			||||||
 | 
								boolean isSelected, boolean cellHasFocus) {
 | 
				
			||||||
 | 
							setBorder(new EmptyBorder(5, 5, 5, 5));
 | 
				
			||||||
 | 
							setText(node.move.toLAN());
 | 
				
			||||||
 | 
							setBackground(isSelected ? Color.red : Color.white);
 | 
				
			||||||
 | 
							setOpaque(true);
 | 
				
			||||||
 | 
							return this;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -20,7 +20,9 @@ import dev.kske.chess.board.Position;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>OverlayComponent.java</strong><br>
 | 
					 * File: <strong>OverlayComponent.java</strong><br>
 | 
				
			||||||
 * Created: <strong>08.07.2019</strong><br>
 | 
					 * Created: <strong>08.07.2019</strong><br>
 | 
				
			||||||
 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
					 * 
 | 
				
			||||||
 | 
					 * @since Chess v0.1-alpha
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class OverlayComponent extends JComponent {
 | 
					public class OverlayComponent extends JComponent {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -46,15 +48,15 @@ public class OverlayComponent extends JComponent {
 | 
				
			|||||||
		// Draw an arrow representing the last move and mark its position and
 | 
							// Draw an arrow representing the last move and mark its position and
 | 
				
			||||||
		// destination
 | 
							// destination
 | 
				
			||||||
		if (arrow != null) {
 | 
							if (arrow != null) {
 | 
				
			||||||
			Point	pos		= new Point(arrow.pos.x * tileSize + tileSize / 2, arrow.pos.y * tileSize + tileSize / 2);
 | 
								Point	pos		= new Point(arrow.getPos().x * tileSize + tileSize / 2, arrow.getPos().y * tileSize + tileSize / 2);
 | 
				
			||||||
			Point	dest	= new Point(arrow.dest.x * tileSize + tileSize / 2, arrow.dest.y * tileSize + tileSize / 2);
 | 
								Point	dest	= new Point(arrow.getDest().x * tileSize + tileSize / 2, arrow.getDest().y * tileSize + tileSize / 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			Graphics2D g2d = (Graphics2D) g;
 | 
								Graphics2D g2d = (Graphics2D) g;
 | 
				
			||||||
			g2d.setStroke(new BasicStroke(3));
 | 
								g2d.setStroke(new BasicStroke(3));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			g2d.setColor(Color.yellow);
 | 
								g2d.setColor(Color.yellow);
 | 
				
			||||||
			g2d.drawRect(arrow.pos.x * tileSize, arrow.pos.y * tileSize, tileSize, tileSize);
 | 
								g2d.drawRect(arrow.getPos().x * tileSize, arrow.getPos().y * tileSize, tileSize, tileSize);
 | 
				
			||||||
			g2d.drawRect(arrow.dest.x * tileSize, arrow.dest.y * tileSize, tileSize, tileSize);
 | 
								g2d.drawRect(arrow.getDest().x * tileSize, arrow.getDest().y * tileSize, tileSize, tileSize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			Shape arrowShape = createArrowShape(pos, dest);
 | 
								Shape arrowShape = createArrowShape(pos, dest);
 | 
				
			||||||
			g.setColor(new Color(255, 0, 0, 127));
 | 
								g.setColor(new Color(255, 0, 0, 127));
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,7 @@ class BoardTest {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	void testClone() {
 | 
						void testClone() {
 | 
				
			||||||
		Board clone = new Board(board);
 | 
							Board clone = new Board(board, false);
 | 
				
			||||||
		assertNotSame(clone, board);
 | 
							assertNotSame(clone, board);
 | 
				
			||||||
		assertNotSame(clone.getBoardArr(), board.getBoardArr());
 | 
							assertNotSame(clone.getBoardArr(), board.getBoardArr());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										72
									
								
								test/dev/kske/chess/board/FENStringTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								test/dev/kske/chess/board/FENStringTest.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.assertEquals;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.BeforeEach;
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
 | 
					import dev.kske.chess.exception.ChessException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>FENStringTest.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>24 Oct 2019</strong><br>
 | 
				
			||||||
 | 
					 * 
 | 
				
			||||||
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class FENStringTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						List<String>	fenStrings	= new ArrayList<>();
 | 
				
			||||||
 | 
						List<Board>		boards		= new ArrayList<>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						void cleanBoard(Board board) {
 | 
				
			||||||
 | 
							for (int i = 0; i < 8; i++)
 | 
				
			||||||
 | 
								for (int j = 0; j < 8; j++)
 | 
				
			||||||
 | 
									board.getBoardArr()[i][j] = null;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * @throws java.lang.Exception
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@BeforeEach
 | 
				
			||||||
 | 
						void setUp() throws Exception {
 | 
				
			||||||
 | 
							fenStrings.addAll(Arrays.asList("rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2"));
 | 
				
			||||||
 | 
							Board board = new Board();
 | 
				
			||||||
 | 
							board.set(Position.fromLAN("c7"), null);
 | 
				
			||||||
 | 
							board.set(Position.fromLAN("c5"), new Pawn(Color.BLACK, board));
 | 
				
			||||||
 | 
							board.set(Position.fromLAN("e4"), new Pawn(Color.WHITE, board));
 | 
				
			||||||
 | 
							board.set(Position.fromLAN("f3"), new Knight(Color.WHITE, board));
 | 
				
			||||||
 | 
							board.set(Position.fromLAN("e2"), null);
 | 
				
			||||||
 | 
							board.set(Position.fromLAN("g1"), null);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							board.getLog().setActiveColor(Color.BLACK);
 | 
				
			||||||
 | 
							board.getLog().setHalfmoveClock(1);
 | 
				
			||||||
 | 
							board.getLog().setFullmoveNumber(2);
 | 
				
			||||||
 | 
							boards.add(board);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.FENString#toString()}.
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testToString() {
 | 
				
			||||||
 | 
							for (int i = 0; i < fenStrings.size(); i++)
 | 
				
			||||||
 | 
								assertEquals(fenStrings.get(i), new FENString(boards.get(i)).toString());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.board.FENString#getBoard()}.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @throws ChessException
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testGetBoard() throws ChessException {
 | 
				
			||||||
 | 
							for (int i = 0; i < boards.size(); i++)
 | 
				
			||||||
 | 
								assertEquals(boards.get(i), new FENString(fenStrings.get(i)).getBoard());
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -30,7 +30,7 @@ class LogTest {
 | 
				
			|||||||
		assertNull(log.getRoot());
 | 
							assertNull(log.getRoot());
 | 
				
			||||||
		assertEquals(log.getActiveColor(), Color.WHITE);
 | 
							assertEquals(log.getActiveColor(), Color.WHITE);
 | 
				
			||||||
		assertNull(log.getEnPassant());
 | 
							assertNull(log.getEnPassant());
 | 
				
			||||||
		assertEquals(log.getFullmoveCounter(), 1);
 | 
							assertEquals(log.getFullmoveNumber(), 1);
 | 
				
			||||||
		assertEquals(log.getHalfmoveClock(), 0);
 | 
							assertEquals(log.getHalfmoveClock(), 0);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -43,10 +43,10 @@ class LogTest {
 | 
				
			|||||||
		log.setActiveColor(Color.WHITE);
 | 
							log.setActiveColor(Color.WHITE);
 | 
				
			||||||
		other.setActiveColor(Color.BLACK);
 | 
							other.setActiveColor(Color.BLACK);
 | 
				
			||||||
		assertNotEquals(log.getActiveColor(), other.getActiveColor());
 | 
							assertNotEquals(log.getActiveColor(), other.getActiveColor());
 | 
				
			||||||
		log.add(Move.fromSAN("a2a4"), null, true);
 | 
							log.add(Move.fromLAN("a2a4"), new Pawn(Color.WHITE, null), null);
 | 
				
			||||||
		log.add(Move.fromSAN("a4a5"), null, true);
 | 
							log.add(Move.fromLAN("a4a5"), new Pawn(Color.WHITE, null), null);
 | 
				
			||||||
		other.add(Move.fromSAN("a2a4"), null, true);
 | 
							other.add(Move.fromLAN("a2a4"), new Pawn(Color.WHITE, null), null);
 | 
				
			||||||
		other.add(Move.fromSAN("a4a5"), null, true);
 | 
							other.add(Move.fromLAN("a4a5"), new Pawn(Color.WHITE, null), null);
 | 
				
			||||||
		assertNotEquals(log.getRoot(), other.getRoot());
 | 
							assertNotEquals(log.getRoot(), other.getRoot());
 | 
				
			||||||
		assertNotEquals(log.getRoot().getVariations(), other.getRoot().getVariations());
 | 
							assertNotEquals(log.getRoot().getVariations(), other.getRoot().getVariations());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -132,7 +132,7 @@ class LogTest {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Test method for {@link dev.kske.chess.board.Log#getFullmoveCounter()}.
 | 
						 * Test method for {@link dev.kske.chess.board.Log#getFullmoveNumber()}.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	void testGetFullmoveCounter() {
 | 
						void testGetFullmoveCounter() {
 | 
				
			||||||
@@ -140,7 +140,7 @@ class LogTest {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Test method for {@link dev.kske.chess.board.Log#setFullmoveCounter(int)}.
 | 
						 * Test method for {@link dev.kske.chess.board.Log#setFullmoveNumber(int)}.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	void testSetFullmoveCounter() {
 | 
						void testSetFullmoveCounter() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,21 +19,21 @@ class PositionTest {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Test method for
 | 
						 * Test method for
 | 
				
			||||||
	 * {@link dev.kske.chess.board.Position#fromSAN(java.lang.String)}.
 | 
						 * {@link dev.kske.chess.board.Position#fromLAN(java.lang.String)}.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	void testFromSAN() {
 | 
						void testFromSAN() {
 | 
				
			||||||
		for (int i = 0; i < n; i++)
 | 
							for (int i = 0; i < n; i++)
 | 
				
			||||||
			assertEquals(positions[i], Position.fromSAN(sans[i]));
 | 
								assertEquals(positions[i], Position.fromLAN(sans[i]));
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Test method for {@link dev.kske.chess.board.Position#toSAN()}.
 | 
						 * Test method for {@link dev.kske.chess.board.Position#toLAN()}.
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	void testToSAN() {
 | 
						void testToSAN() {
 | 
				
			||||||
		for (int i = 0; i < n; i++)
 | 
							for (int i = 0; i < n; i++)
 | 
				
			||||||
			assertEquals(sans[i], positions[i].toSAN());
 | 
								assertEquals(sans[i], positions[i].toLAN());
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										36
									
								
								test/dev/kske/chess/pgn/PGNDatabaseTest.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								test/dev/kske/chess/pgn/PGNDatabaseTest.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,36 @@
 | 
				
			|||||||
 | 
					package dev.kske.chess.pgn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import static org.junit.jupiter.api.Assertions.fail;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.File;
 | 
				
			||||||
 | 
					import java.io.FileNotFoundException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.Test;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import dev.kske.chess.exception.ChessException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>PGNDatabaseTest.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>4 Oct 2019</strong><br>
 | 
				
			||||||
 | 
					 * Author: <strong>Kai S. K. Engelbart</strong>
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					class PGNDatabaseTest {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Test method for {@link dev.kske.chess.pgn.PGNDatabase#load(java.io.File)}.
 | 
				
			||||||
 | 
						 * 
 | 
				
			||||||
 | 
						 * @throws ChessException
 | 
				
			||||||
 | 
						 * @throws FileNotFoundException
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						@Test
 | 
				
			||||||
 | 
						void testLoad() {
 | 
				
			||||||
 | 
							PGNDatabase db = new PGNDatabase();
 | 
				
			||||||
 | 
							try {
 | 
				
			||||||
 | 
								db.load(new File(getClass().getClassLoader().getResource("test.pgn").getFile()));
 | 
				
			||||||
 | 
							} catch (FileNotFoundException | ChessException e) {
 | 
				
			||||||
 | 
								e.printStackTrace();
 | 
				
			||||||
 | 
								fail(e);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										18
									
								
								test_res/test.pgn
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								test_res/test.pgn
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					[Event "Test Game"]
 | 
				
			||||||
 | 
					[Site "Test Environment"]
 | 
				
			||||||
 | 
					[Date "2019.10.04"]
 | 
				
			||||||
 | 
					[Round "1"]
 | 
				
			||||||
 | 
					[Result "1-0"]
 | 
				
			||||||
 | 
					[White "Kai Engelbart"]
 | 
				
			||||||
 | 
					[Black "Kai Engelbart 2"]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. e4 c5 2. Nf3 e6 3. d4 cxd4 4. Nxd4 a6 5. Bd3 Nf6 6. O-O d6
 | 
				
			||||||
 | 
					7. c4 Bd7 8. Nc3 Nc6 9. Be3 Be7 10. h3 Ne5 11. Be2 Rc8 12. Qb3
 | 
				
			||||||
 | 
					Qc7 13. Rac1 O-O 14. f4 Nc6 15. Nf3 Qb8 16. Qd1 Be8 17. Qd2
 | 
				
			||||||
 | 
					Na5 18. b3 b6 19. Bd3 Nc6 20. Qf2 b5 21. Rfd1 Nb4 22. Bf1 bxc4
 | 
				
			||||||
 | 
					23. bxc4 a5 24. Nd4 Qa8 25. Qf3 Na6 26. Ndb5 Nc5 27. e5 dxe5
 | 
				
			||||||
 | 
					28. Qxa8 Rxa8 29. fxe5 Nfe4 30. Nd6 Bc6 31. Ncxe4 Nxe4 32. c5
 | 
				
			||||||
 | 
					Ng3 33. Bc4 h5 34. Bf2 h4 35. Bxg3 hxg3 36. Bb5 Bxb5 37. Nxb5
 | 
				
			||||||
 | 
					f6 38. Rd7 Bd8 39. Rc3 fxe5 40. Rxg3 Rf7 41. Rxf7 Kxf7 42. c6
 | 
				
			||||||
 | 
					Bb6+ 43. Kf1 Kf8 44. c7 Rc8 45. a4 e4 46. Ke2 e5 47. Rg6 Bd4
 | 
				
			||||||
 | 
					48. h4 Bb2 1-0
 | 
				
			||||||
		Reference in New Issue
	
	Block a user