Merge pull request #16 from CyB3RC0nN0R/f/pgn_save
Implemented game serialization to the PGN format
This commit is contained in:
		@@ -6,8 +6,6 @@ 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 java.util.Objects;
 | 
				
			||||||
import java.util.regex.Matcher;
 | 
					 | 
				
			||||||
import java.util.regex.Pattern;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.chess.board.Piece.Color;
 | 
					import dev.kske.chess.board.Piece.Color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -36,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;
 | 
				
			||||||
@@ -47,7 +45,11 @@ public class Board {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		kingPos.putAll(other.kingPos);
 | 
							kingPos.putAll(other.kingPos);
 | 
				
			||||||
		log = new Log(other.log, false);
 | 
							log = new Log(other.log, copyVariations);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Synchronize the current move node with the board
 | 
				
			||||||
 | 
							while (log.getLast().hasVariations())
 | 
				
			||||||
 | 
								log.selectNextNode(0);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -98,83 +100,7 @@ public class Board {
 | 
				
			|||||||
	 * @param sanMove The move to execute in SAN (Standard Algebraic Notation)
 | 
						 * @param sanMove The move to execute in SAN (Standard Algebraic Notation)
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void move(String sanMove) {
 | 
						public void move(String sanMove) {
 | 
				
			||||||
		Map<String, Pattern> patterns = new HashMap<>();
 | 
							move(Move.fromSAN(sanMove, this));
 | 
				
			||||||
		patterns.put("pieceMove",
 | 
					 | 
				
			||||||
				Pattern.compile(
 | 
					 | 
				
			||||||
						"^(?<pieceType>[NBRQK])(?:(?<fromFile>[a-h])|(?<fromRank>[1-8])|(?<fromSquare>[a-h][1-8]))?x?(?<toSquare>[a-h][1-8])(?:\\+{0,2}|\\#)$"));
 | 
					 | 
				
			||||||
		patterns.put("pawnCapture",
 | 
					 | 
				
			||||||
				Pattern.compile("^(?<fromFile>[a-h])(?<fromRank>[1-8])?x(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQ])?(?:\\+{0,2}|\\#)?$"));
 | 
					 | 
				
			||||||
		patterns.put("pawnPush", Pattern.compile("^(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQ])?(?:\\+{0,2}|\\#)$"));
 | 
					 | 
				
			||||||
		patterns.put("castling", Pattern.compile("^(?<queenside>O-O-O)|(?<kingside>O-O)(?:\\+{0,2}|\\#)?$"));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		patterns.forEach((patternName, pattern) -> {
 | 
					 | 
				
			||||||
			Matcher m = pattern.matcher(sanMove);
 | 
					 | 
				
			||||||
			if (m.find()) {
 | 
					 | 
				
			||||||
				Position	pos		= null, dest = null;
 | 
					 | 
				
			||||||
				Move		move	= null;
 | 
					 | 
				
			||||||
				switch (patternName) {
 | 
					 | 
				
			||||||
					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	= 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	= get(pieceClass, rank);
 | 
					 | 
				
			||||||
								pos		= Position.fromLAN(String.format("%c%d", file, rank));
 | 
					 | 
				
			||||||
							} else pos = get(pieceClass, dest);
 | 
					 | 
				
			||||||
						}
 | 
					 | 
				
			||||||
						move = new Move(pos, dest);
 | 
					 | 
				
			||||||
						break;
 | 
					 | 
				
			||||||
					case "pawnCapture":
 | 
					 | 
				
			||||||
						char file = m.group("fromFile").charAt(0);
 | 
					 | 
				
			||||||
						int rank = m.group("fromRank") == null ? 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 (NoSuchMethodException | SecurityException | IllegalArgumentException e) {
 | 
					 | 
				
			||||||
								e.printStackTrace();
 | 
					 | 
				
			||||||
							}
 | 
					 | 
				
			||||||
						} else move = new Move(pos, dest);
 | 
					 | 
				
			||||||
						break;
 | 
					 | 
				
			||||||
					case "pawnPush":
 | 
					 | 
				
			||||||
						dest = Position.fromLAN(m.group("toSquare"));
 | 
					 | 
				
			||||||
						int step = log.getActiveColor() == Color.WHITE ? 1 : -1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
						// One step forward
 | 
					 | 
				
			||||||
						if (boardArr[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 (NoSuchMethodException | SecurityException | IllegalArgumentException e) {
 | 
					 | 
				
			||||||
								e.printStackTrace();
 | 
					 | 
				
			||||||
							}
 | 
					 | 
				
			||||||
						} else move = new Move(pos, dest);
 | 
					 | 
				
			||||||
						break;
 | 
					 | 
				
			||||||
					case "castling":
 | 
					 | 
				
			||||||
						pos = new Position(4, log.getActiveColor() == Color.WHITE ? 7 : 0);
 | 
					 | 
				
			||||||
						dest = new Position(m.group("kingside") != null ? 6 : 2, pos.y);
 | 
					 | 
				
			||||||
						move = new Castling(pos, dest);
 | 
					 | 
				
			||||||
						break;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
				move(move);
 | 
					 | 
				
			||||||
				return;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -32,4 +32,13 @@ public class Castling extends Move {
 | 
				
			|||||||
		super.revert(board, capturedPiece);
 | 
							super.revert(board, capturedPiece);
 | 
				
			||||||
		rookMove.revert(board, null);
 | 
							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";
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -43,12 +43,17 @@ public class Log implements Iterable<MoveNode> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// 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.setParent(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
 | 
						@Override
 | 
				
			||||||
	public Iterator<MoveNode> iterator() {
 | 
						public Iterator<MoveNode> iterator() {
 | 
				
			||||||
		return new Iterator<MoveNode>() {
 | 
							return new Iterator<MoveNode>() {
 | 
				
			||||||
@@ -98,7 +103,7 @@ public class Log implements Iterable<MoveNode> {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * 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() {
 | 
				
			||||||
@@ -109,8 +114,14 @@ public class Log implements Iterable<MoveNode> {
 | 
				
			|||||||
		} 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(); }
 | 
						public boolean hasParent() { return !isEmpty() && current.hasParent(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -159,6 +170,10 @@ public class Log implements Iterable<MoveNode> {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * 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() {
 | 
						private void update() {
 | 
				
			||||||
		activeColor		= current.activeColor;
 | 
							activeColor		= current.activeColor;
 | 
				
			||||||
		castlingRights	= current.castlingRights.clone();
 | 
							castlingRights	= current.castlingRights.clone();
 | 
				
			||||||
@@ -167,6 +182,15 @@ public class Log implements Iterable<MoveNode> {
 | 
				
			|||||||
		halfmoveClock	= current.halfmoveClock;
 | 
							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) {
 | 
						private void disableCastlingRights(Piece piece, Position initialPosition) {
 | 
				
			||||||
		// Kingside
 | 
							// Kingside
 | 
				
			||||||
		if (piece instanceof King || piece instanceof Rook && initialPosition.x == 7)
 | 
							if (piece instanceof King || piece instanceof Rook && initialPosition.x == 7)
 | 
				
			||||||
@@ -220,7 +244,7 @@ public class Log implements Iterable<MoveNode> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	public int getFullmoveNumber() { return fullmoveNumber; }
 | 
						public int getFullmoveNumber() { return fullmoveNumber; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public void setFullmoveNumber(int fullmoveCounter) { this.fullmoveNumber = fullmoveCounter; }
 | 
						public void setFullmoveNumber(int fullmoveNumber) { this.fullmoveNumber = fullmoveNumber; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public int getHalfmoveClock() { return halfmoveClock; }
 | 
						public int getHalfmoveClock() { return halfmoveClock; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,12 @@
 | 
				
			|||||||
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.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>
 | 
				
			||||||
@@ -44,7 +50,7 @@ public class Move {
 | 
				
			|||||||
		if (move.length() == 5) {
 | 
							if (move.length() == 5) {
 | 
				
			||||||
			try {
 | 
								try {
 | 
				
			||||||
				return new PawnPromotion(pos, dest, Piece.fromFirstChar(move.charAt(4)));
 | 
									return new PawnPromotion(pos, dest, Piece.fromFirstChar(move.charAt(4)));
 | 
				
			||||||
			} catch (NoSuchMethodException | SecurityException | IllegalArgumentException e) {
 | 
								} catch (Exception e) {
 | 
				
			||||||
				e.printStackTrace();
 | 
									e.printStackTrace();
 | 
				
			||||||
				return null;
 | 
									return null;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -53,6 +59,116 @@ public class Move {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	public String toLAN() { return getPos().toLAN() + getDest().toLAN(); }
 | 
						public String toLAN() { return getPos().toLAN() + getDest().toLAN(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Converts a move string from standard algebraic notation to a {@link Move}
 | 
				
			||||||
 | 
						 * 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 String toSAN(Board board) {
 | 
				
			||||||
 | 
							final Piece		piece	= board.get(pos);
 | 
				
			||||||
 | 
							StringBuilder	sb		= new StringBuilder(8);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Piece symbol
 | 
				
			||||||
 | 
							if(!(piece instanceof Pawn))
 | 
				
			||||||
 | 
								sb.append(Character.toUpperCase(piece.firstChar()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Position
 | 
				
			||||||
 | 
							// TODO: Deconstruct position into optional file or rank
 | 
				
			||||||
 | 
							// Omit position if the move is a pawn push
 | 
				
			||||||
 | 
							if (!(piece instanceof Pawn && xDist == 0)) sb.append(pos.toLAN());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 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 isHorizontal() { return getyDist() == 0; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public boolean isVertical() { return getxDist() == 0; }
 | 
						public boolean isVertical() { return getxDist() == 0; }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -64,11 +64,11 @@ public class MoveNode {
 | 
				
			|||||||
				other.fullmoveCounter, other.halfmoveClock);
 | 
									other.fullmoveCounter, other.halfmoveClock);
 | 
				
			||||||
		if (copyVariations && other.variations != null) {
 | 
							if (copyVariations && other.variations != null) {
 | 
				
			||||||
			if (variations == null) variations = new ArrayList<>();
 | 
								if (variations == null) variations = new ArrayList<>();
 | 
				
			||||||
			other.variations.forEach(variation -> {
 | 
								for (MoveNode variation : other.variations) {
 | 
				
			||||||
				MoveNode copy = new MoveNode(variation, true);
 | 
									MoveNode copy = new MoveNode(variation, true);
 | 
				
			||||||
				copy.parent = this;
 | 
									copy.parent = this;
 | 
				
			||||||
				variations.add(copy);
 | 
									variations.add(copy);
 | 
				
			||||||
			});
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -74,7 +74,7 @@ public class Pawn extends Piece {
 | 
				
			|||||||
					moves.add(new PawnPromotion(pos, dest, Rook.class));
 | 
										moves.add(new PawnPromotion(pos, dest, Rook.class));
 | 
				
			||||||
					moves.add(new PawnPromotion(pos, dest, Knight.class));
 | 
										moves.add(new PawnPromotion(pos, dest, Knight.class));
 | 
				
			||||||
					moves.add(new PawnPromotion(pos, dest, Bishop.class));
 | 
										moves.add(new PawnPromotion(pos, dest, Bishop.class));
 | 
				
			||||||
				} catch (NoSuchMethodException | SecurityException | IllegalArgumentException e) {
 | 
									} catch (Exception e) {
 | 
				
			||||||
					e.printStackTrace();
 | 
										e.printStackTrace();
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
			} else moves.add(move);
 | 
								} else moves.add(move);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,21 +16,23 @@ import dev.kske.chess.board.Piece.Color;
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public class PawnPromotion extends Move {
 | 
					public class PawnPromotion extends Move {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final Class<? extends Piece>		promotionPieceClass;
 | 
					 | 
				
			||||||
	private final Constructor<? extends Piece>	promotionPieceConstructor;
 | 
						private final Constructor<? extends Piece>	promotionPieceConstructor;
 | 
				
			||||||
 | 
						private final char							promotionPieceChar;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public PawnPromotion(Position pos, Position dest, Class<? extends Piece> promotionPieceClass) throws NoSuchMethodException, SecurityException {
 | 
						public PawnPromotion(Position pos, Position dest, Class<? extends Piece> promotionPieceClass)
 | 
				
			||||||
 | 
								throws ReflectiveOperationException, RuntimeException {
 | 
				
			||||||
		super(pos, dest);
 | 
							super(pos, dest);
 | 
				
			||||||
		this.promotionPieceClass = promotionPieceClass;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Cache piece constructor
 | 
							// Cache piece constructor
 | 
				
			||||||
		promotionPieceConstructor = promotionPieceClass.getDeclaredConstructor(Color.class, Board.class);
 | 
							promotionPieceConstructor = promotionPieceClass.getDeclaredConstructor(Color.class, Board.class);
 | 
				
			||||||
		promotionPieceConstructor.setAccessible(true);
 | 
							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)
 | 
						public PawnPromotion(int xPos, int yPos, int xDest, int yDest, Class<? extends Piece> promotionPiece)
 | 
				
			||||||
			throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
 | 
								throws ReflectiveOperationException, RuntimeException {
 | 
				
			||||||
			InstantiationException {
 | 
					 | 
				
			||||||
		this(new Position(xPos, yPos), new Position(xDest, yDest), promotionPiece);
 | 
							this(new Position(xPos, yPos), new Position(xDest, yDest), promotionPiece);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,22 +53,19 @@ public class PawnPromotion extends Move {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public String toLAN() {
 | 
						public String toLAN() { return pos.toLAN() + dest.toLAN() + promotionPieceChar; }
 | 
				
			||||||
		char promotionPieceChar = '-';
 | 
					
 | 
				
			||||||
		try {
 | 
						@Override
 | 
				
			||||||
			promotionPieceChar = (char) promotionPieceClass.getMethod("firstChar").invoke(promotionPieceConstructor.newInstance(null, null));
 | 
						public String toSAN(Board board) {
 | 
				
			||||||
		} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException
 | 
							String san = super.toSAN(board);
 | 
				
			||||||
				| InstantiationException e) {
 | 
							return san + Character.toUpperCase(promotionPieceChar);
 | 
				
			||||||
			e.printStackTrace();
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return pos.toLAN() + dest.toLAN() + promotionPieceChar;
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Override
 | 
						@Override
 | 
				
			||||||
	public int hashCode() {
 | 
						public int hashCode() {
 | 
				
			||||||
		final int	prime	= 31;
 | 
							final int	prime	= 31;
 | 
				
			||||||
		int			result	= super.hashCode();
 | 
							int			result	= super.hashCode();
 | 
				
			||||||
		result = prime * result + Objects.hash(promotionPieceClass);
 | 
							result = prime * result + Objects.hash(promotionPieceChar, promotionPieceConstructor);
 | 
				
			||||||
		return result;
 | 
							return result;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -74,8 +73,8 @@ public class PawnPromotion extends Move {
 | 
				
			|||||||
	public boolean equals(Object obj) {
 | 
						public boolean equals(Object obj) {
 | 
				
			||||||
		if (this == obj) return true;
 | 
							if (this == obj) return true;
 | 
				
			||||||
		if (!super.equals(obj)) return false;
 | 
							if (!super.equals(obj)) return false;
 | 
				
			||||||
		if (getClass() != obj.getClass()) return false;
 | 
							if (!(obj instanceof PawnPromotion)) return false;
 | 
				
			||||||
		PawnPromotion other = (PawnPromotion) obj;
 | 
							PawnPromotion other = (PawnPromotion) obj;
 | 
				
			||||||
		return Objects.equals(promotionPieceClass, other.promotionPieceClass);
 | 
							return promotionPieceChar == other.promotionPieceChar && Objects.equals(promotionPieceConstructor, other.promotionPieceConstructor);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			/*
 | 
								/*
 | 
				
			||||||
@@ -66,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;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,8 @@ package dev.kske.chess.pgn;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import java.io.File;
 | 
					import java.io.File;
 | 
				
			||||||
import java.io.FileNotFoundException;
 | 
					import java.io.FileNotFoundException;
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
 | 
					import java.io.PrintWriter;
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Scanner;
 | 
					import java.util.Scanner;
 | 
				
			||||||
@@ -12,21 +14,42 @@ import dev.kske.chess.exception.ChessException;
 | 
				
			|||||||
 * Project: <strong>Chess</strong><br>
 | 
					 * Project: <strong>Chess</strong><br>
 | 
				
			||||||
 * File: <strong>PGNDatabase.java</strong><br>
 | 
					 * File: <strong>PGNDatabase.java</strong><br>
 | 
				
			||||||
 * Created: <strong>4 Oct 2019</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
 | 
					 * @since Chess v0.5-alpha
 | 
				
			||||||
 * @author Kai S. K. Engelbart
 | 
					 * @author Kai S. K. Engelbart
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public class PGNDatabase {
 | 
					public class PGNDatabase {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final List<PGNGame>	games	= new ArrayList<>();
 | 
						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 {
 | 
						public void load(File pgnFile) throws FileNotFoundException, ChessException {
 | 
				
			||||||
		try (Scanner sc = new Scanner(pgnFile)) {
 | 
							Scanner sc = new Scanner(pgnFile);
 | 
				
			||||||
			while (sc.hasNext())
 | 
							while (sc.hasNext())
 | 
				
			||||||
				games.add(PGNGame.parse(sc));
 | 
								games.add(PGNGame.parse(sc));
 | 
				
			||||||
		} catch (FileNotFoundException | ChessException e) {
 | 
							sc.close();
 | 
				
			||||||
			throw e;
 | 
						}
 | 
				
			||||||
		}
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * 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; }
 | 
						public List<PGNGame> getGames() { return games; }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,10 @@
 | 
				
			|||||||
package dev.kske.chess.pgn;
 | 
					package dev.kske.chess.pgn;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.io.PrintWriter;
 | 
				
			||||||
 | 
					import java.util.ArrayList;
 | 
				
			||||||
 | 
					import java.util.Collections;
 | 
				
			||||||
import java.util.HashMap;
 | 
					import java.util.HashMap;
 | 
				
			||||||
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
import java.util.Scanner;
 | 
					import java.util.Scanner;
 | 
				
			||||||
import java.util.regex.MatchResult;
 | 
					import java.util.regex.MatchResult;
 | 
				
			||||||
@@ -8,6 +12,8 @@ import java.util.regex.Pattern;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import dev.kske.chess.board.Board;
 | 
					import dev.kske.chess.board.Board;
 | 
				
			||||||
import dev.kske.chess.board.FENString;
 | 
					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;
 | 
					import dev.kske.chess.exception.ChessException;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -21,7 +27,11 @@ import dev.kske.chess.exception.ChessException;
 | 
				
			|||||||
public class PGNGame {
 | 
					public class PGNGame {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private final Map<String, String>	tagPairs	= new HashMap<>(7);
 | 
						private final Map<String, String>	tagPairs	= new HashMap<>(7);
 | 
				
			||||||
	private final Board					board		= new Board();
 | 
						private final Board					board;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public PGNGame() { board = new Board(); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						public PGNGame(Board board) { this.board = board; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static PGNGame parse(Scanner sc) throws ChessException {
 | 
						public static PGNGame parse(Scanner sc) throws ChessException {
 | 
				
			||||||
		PGNGame game = new PGNGame();
 | 
							PGNGame game = new PGNGame();
 | 
				
			||||||
@@ -62,6 +72,45 @@ public class PGNGame {
 | 
				
			|||||||
		return game;
 | 
							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 String getTag(String tagName) { return tagPairs.get(tagName); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public boolean hasTag(String tagName) { return tagPairs.containsKey(tagName); }
 | 
						public boolean hasTag(String tagName) { return tagPairs.containsKey(tagName); }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ import java.awt.Font;
 | 
				
			|||||||
import java.io.File;
 | 
					import java.io.File;
 | 
				
			||||||
import java.util.ArrayList;
 | 
					import java.util.ArrayList;
 | 
				
			||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.Collection;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
import java.util.function.BiConsumer;
 | 
					import java.util.function.BiConsumer;
 | 
				
			||||||
import java.util.function.Consumer;
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
@@ -15,7 +16,7 @@ import javax.swing.JFileChooser;
 | 
				
			|||||||
import javax.swing.JLabel;
 | 
					import javax.swing.JLabel;
 | 
				
			||||||
import javax.swing.JOptionPane;
 | 
					import javax.swing.JOptionPane;
 | 
				
			||||||
import javax.swing.JPanel;
 | 
					import javax.swing.JPanel;
 | 
				
			||||||
import javax.swing.filechooser.FileFilter;
 | 
					import javax.swing.filechooser.FileNameExtensionFilter;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import dev.kske.chess.io.EngineUtil;
 | 
					import dev.kske.chess.io.EngineUtil;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -31,27 +32,24 @@ public class DialogUtil {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	private DialogUtil() {}
 | 
						private DialogUtil() {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	public static void showFileSelectionDialog(Component parent, Consumer<List<File>> action) {
 | 
						public static void showFileSelectionDialog(Component parent, Consumer<List<File>> action, Collection<FileNameExtensionFilter> filters) {
 | 
				
			||||||
		JFileChooser fileChooser = new JFileChooser();
 | 
							JFileChooser fileChooser = new JFileChooser();
 | 
				
			||||||
		fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
 | 
							fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
 | 
				
			||||||
		fileChooser.setAcceptAllFileFilterUsed(false);
 | 
							fileChooser.setAcceptAllFileFilterUsed(false);
 | 
				
			||||||
		fileChooser.addChoosableFileFilter(new FileFilter() {
 | 
							filters.forEach(fileChooser::addChoosableFileFilter);
 | 
				
			||||||
 | 
					 | 
				
			||||||
			@Override
 | 
					 | 
				
			||||||
			public boolean accept(File f) {
 | 
					 | 
				
			||||||
				int dotIndex = f.getName().lastIndexOf('.');
 | 
					 | 
				
			||||||
				if (dotIndex >= 0) {
 | 
					 | 
				
			||||||
					String extension = f.getName().substring(dotIndex).toLowerCase();
 | 
					 | 
				
			||||||
					return extension.equals(".fen") || extension.equals(".pgn");
 | 
					 | 
				
			||||||
				} else return f.isDirectory();
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
			@Override
 | 
					 | 
				
			||||||
			public String getDescription() { return "FEN and PGN files"; }
 | 
					 | 
				
			||||||
		});
 | 
					 | 
				
			||||||
		if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) action.accept(Arrays.asList(fileChooser.getSelectedFile()));
 | 
							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) {
 | 
						public static void showGameConfigurationDialog(Component parent, BiConsumer<String, String> action) {
 | 
				
			||||||
		JPanel dialogPanel = new JPanel();
 | 
							JPanel dialogPanel = new JPanel();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -64,7 +62,7 @@ public class DialogUtil {
 | 
				
			|||||||
		dialogPanel.add(lblWhite);
 | 
							dialogPanel.add(lblWhite);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		JComboBox<Object> cbWhite = new JComboBox<>();
 | 
							JComboBox<Object> cbWhite = new JComboBox<>();
 | 
				
			||||||
		cbWhite.setModel(new DefaultComboBoxModel<Object>(options.toArray()));
 | 
							cbWhite.setModel(new DefaultComboBoxModel<>(options.toArray()));
 | 
				
			||||||
		cbWhite.setBounds(98, 9, 159, 22);
 | 
							cbWhite.setBounds(98, 9, 159, 22);
 | 
				
			||||||
		dialogPanel.add(cbWhite);
 | 
							dialogPanel.add(cbWhite);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -74,7 +72,7 @@ public class DialogUtil {
 | 
				
			|||||||
		dialogPanel.add(lblBlack);
 | 
							dialogPanel.add(lblBlack);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		JComboBox<Object> cbBlack = new JComboBox<>();
 | 
							JComboBox<Object> cbBlack = new JComboBox<>();
 | 
				
			||||||
		cbBlack.setModel(new DefaultComboBoxModel<Object>(options.toArray()));
 | 
							cbBlack.setModel(new DefaultComboBoxModel<>(options.toArray()));
 | 
				
			||||||
		cbBlack.setBounds(98, 36, 159, 22);
 | 
							cbBlack.setBounds(98, 36, 159, 22);
 | 
				
			||||||
		dialogPanel.add(cbBlack);
 | 
							dialogPanel.add(cbBlack);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,11 @@
 | 
				
			|||||||
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.File;
 | 
				
			||||||
 | 
					import java.io.IOException;
 | 
				
			||||||
import java.nio.charset.StandardCharsets;
 | 
					import java.nio.charset.StandardCharsets;
 | 
				
			||||||
import java.nio.file.Files;
 | 
					import java.nio.file.Files;
 | 
				
			||||||
import java.util.List;
 | 
					import java.util.List;
 | 
				
			||||||
@@ -164,4 +166,30 @@ public class MainWindow extends JFrame {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
		});
 | 
							});
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						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,11 +2,13 @@ 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.board.FENString;
 | 
				
			||||||
import dev.kske.chess.exception.ChessException;
 | 
					import dev.kske.chess.exception.ChessException;
 | 
				
			||||||
@@ -48,9 +50,18 @@ public class MenuBar extends JMenuBar {
 | 
				
			|||||||
		gameMenu.add(newGameMenuItem);
 | 
							gameMenu.add(newGameMenuItem);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		JMenuItem loadFileMenu = new JMenuItem("Load game file");
 | 
							JMenuItem loadFileMenu = new JMenuItem("Load game file");
 | 
				
			||||||
		loadFileMenu.addActionListener((evt) -> DialogUtil.showFileSelectionDialog(mainWindow, mainWindow::loadFiles));
 | 
							loadFileMenu.addActionListener((evt) -> DialogUtil
 | 
				
			||||||
 | 
								.showFileSelectionDialog(mainWindow,
 | 
				
			||||||
 | 
										mainWindow::loadFiles,
 | 
				
			||||||
 | 
										Arrays.asList(new FileNameExtensionFilter("FEN and PGN files", "fen", "pgn"))));
 | 
				
			||||||
		gameMenu.add(loadFileMenu);
 | 
							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();
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user