From 1ed43dd180404828226b989521e2766f1642b772 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Sun, 20 Oct 2019 17:27:52 +0200 Subject: [PATCH 1/7] Created io package, moved IO-related classes --- src/dev/kske/chess/game/Game.java | 4 ++-- src/dev/kske/chess/{ui => io}/EngineUtil.java | 2 +- src/dev/kske/chess/{ui => io}/TextureUtil.java | 2 +- src/dev/kske/chess/ui/BoardComponent.java | 1 + src/dev/kske/chess/ui/DialogUtil.java | 2 ++ src/dev/kske/chess/ui/MenuBar.java | 1 + 6 files changed, 8 insertions(+), 4 deletions(-) rename src/dev/kske/chess/{ui => io}/EngineUtil.java (95%) rename src/dev/kske/chess/{ui => io}/TextureUtil.java (95%) diff --git a/src/dev/kske/chess/game/Game.java b/src/dev/kske/chess/game/Game.java index 13cf92b..feb8781 100644 --- a/src/dev/kske/chess/game/Game.java +++ b/src/dev/kske/chess/game/Game.java @@ -12,10 +12,10 @@ import dev.kske.chess.board.Piece.Color; import dev.kske.chess.event.EventBus; import dev.kske.chess.event.MoveEvent; import dev.kske.chess.game.ai.AIPlayer; +import dev.kske.chess.io.EngineUtil; +import dev.kske.chess.io.EngineUtil.EngineInfo; import dev.kske.chess.ui.BoardComponent; import dev.kske.chess.ui.BoardPane; -import dev.kske.chess.ui.EngineUtil; -import dev.kske.chess.ui.EngineUtil.EngineInfo; import dev.kske.chess.ui.OverlayComponent; /** diff --git a/src/dev/kske/chess/ui/EngineUtil.java b/src/dev/kske/chess/io/EngineUtil.java similarity index 95% rename from src/dev/kske/chess/ui/EngineUtil.java rename to src/dev/kske/chess/io/EngineUtil.java index b56c416..608006e 100644 --- a/src/dev/kske/chess/ui/EngineUtil.java +++ b/src/dev/kske/chess/io/EngineUtil.java @@ -1,4 +1,4 @@ -package dev.kske.chess.ui; +package dev.kske.chess.io; import java.io.FileInputStream; import java.io.FileOutputStream; diff --git a/src/dev/kske/chess/ui/TextureUtil.java b/src/dev/kske/chess/io/TextureUtil.java similarity index 95% rename from src/dev/kske/chess/ui/TextureUtil.java rename to src/dev/kske/chess/io/TextureUtil.java index d7e6910..58fc621 100644 --- a/src/dev/kske/chess/ui/TextureUtil.java +++ b/src/dev/kske/chess/io/TextureUtil.java @@ -1,4 +1,4 @@ -package dev.kske.chess.ui; +package dev.kske.chess.io; import java.awt.Image; import java.awt.image.BufferedImage; diff --git a/src/dev/kske/chess/ui/BoardComponent.java b/src/dev/kske/chess/ui/BoardComponent.java index 09b97d7..802af23 100644 --- a/src/dev/kske/chess/ui/BoardComponent.java +++ b/src/dev/kske/chess/ui/BoardComponent.java @@ -6,6 +6,7 @@ import java.awt.Graphics; import javax.swing.JComponent; import dev.kske.chess.board.Board; +import dev.kske.chess.io.TextureUtil; /** * Project: Chess
diff --git a/src/dev/kske/chess/ui/DialogUtil.java b/src/dev/kske/chess/ui/DialogUtil.java index a25711d..e69f3d7 100644 --- a/src/dev/kske/chess/ui/DialogUtil.java +++ b/src/dev/kske/chess/ui/DialogUtil.java @@ -16,6 +16,8 @@ import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; +import dev.kske.chess.io.EngineUtil; + /** * Project: Chess
* File: DialogUtil.java
diff --git a/src/dev/kske/chess/ui/MenuBar.java b/src/dev/kske/chess/ui/MenuBar.java index c7c74d0..1d995b1 100644 --- a/src/dev/kske/chess/ui/MenuBar.java +++ b/src/dev/kske/chess/ui/MenuBar.java @@ -14,6 +14,7 @@ import javax.swing.JOptionPane; import dev.kske.chess.board.Board; import dev.kske.chess.game.Game; +import dev.kske.chess.io.EngineUtil; import dev.kske.chess.pgn.PGNDatabase; import dev.kske.chess.pgn.PGNGame; From 73f5e174050bbe9b40b12e1833c023995d2ab44f Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Tue, 22 Oct 2019 21:25:06 +0200 Subject: [PATCH 2/7] Renamed FEN string fullmove counter to fullmove number --- src/dev/kske/chess/board/Board.java | 2 +- src/dev/kske/chess/board/Log.java | 20 ++++++++++---------- test/dev/kske/chess/board/LogTest.java | 6 +++--- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index 435b371..e353a35 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -561,7 +561,7 @@ public class Board { log.setHalfmoveClock(Integer.parseInt(parts[4])); // Fullmove counter - log.setFullmoveCounter(Integer.parseInt(parts[5])); + log.setFullmoveNumber(Integer.parseInt(parts[5])); } /** diff --git a/src/dev/kske/chess/board/Log.java b/src/dev/kske/chess/board/Log.java index afa5f00..d5c1794 100644 --- a/src/dev/kske/chess/board/Log.java +++ b/src/dev/kske/chess/board/Log.java @@ -16,7 +16,7 @@ public class Log implements Iterable { private Position enPassant; private Color activeColor; - private int fullmoveCounter, halfmoveClock; + private int fullmoveNumber, halfmoveClock; public Log() { reset(); @@ -34,14 +34,14 @@ public class Log implements Iterable { public Log(Log other, boolean copyVariations) { enPassant = other.enPassant; activeColor = other.activeColor; - fullmoveCounter = other.fullmoveCounter; + fullmoveNumber = other.fullmoveNumber; halfmoveClock = other.halfmoveClock; // The new root is the current node of the copied instance if (!other.isEmpty()) { - root = new MoveNode(other.current, copyVariations); + root = new MoveNode(other.current, copyVariations); root.setParent(null); - current = root; + current = root; } } @@ -76,11 +76,11 @@ public class Log implements Iterable { */ public void add(Move move, Piece capturedPiece, boolean pawnMove) { enPassant = pawnMove && move.yDist == 2 ? new Position(move.pos.x, move.pos.y + move.ySign) : null; - if (activeColor == Color.BLACK) ++fullmoveCounter; + if (activeColor == Color.BLACK) ++fullmoveNumber; if (pawnMove || capturedPiece != null) halfmoveClock = 0; else++halfmoveClock; activeColor = activeColor.opposite(); - final MoveNode leaf = new MoveNode(move, capturedPiece, enPassant, activeColor, fullmoveCounter, halfmoveClock); + final MoveNode leaf = new MoveNode(move, capturedPiece, enPassant, activeColor, fullmoveNumber, halfmoveClock); if (isEmpty()) { root = leaf; @@ -118,7 +118,7 @@ public class Log implements Iterable { current = null; enPassant = null; activeColor = Color.WHITE; - fullmoveCounter = 1; + fullmoveNumber = 1; halfmoveClock = 0; } @@ -156,7 +156,7 @@ public class Log implements Iterable { private void update() { activeColor = current.activeColor; enPassant = current.enPassant; - fullmoveCounter = current.fullmoveCounter; + fullmoveNumber = current.fullmoveCounter; halfmoveClock = current.halfmoveClock; } @@ -178,9 +178,9 @@ public class Log implements Iterable { 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 fullmoveCounter) { this.fullmoveNumber = fullmoveCounter; } public int getHalfmoveClock() { return halfmoveClock; } diff --git a/test/dev/kske/chess/board/LogTest.java b/test/dev/kske/chess/board/LogTest.java index 4e682d4..204b6be 100644 --- a/test/dev/kske/chess/board/LogTest.java +++ b/test/dev/kske/chess/board/LogTest.java @@ -30,7 +30,7 @@ class LogTest { assertNull(log.getRoot()); assertEquals(log.getActiveColor(), Color.WHITE); assertNull(log.getEnPassant()); - assertEquals(log.getFullmoveCounter(), 1); + assertEquals(log.getFullmoveNumber(), 1); assertEquals(log.getHalfmoveClock(), 0); } @@ -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 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 void testSetFullmoveCounter() { From 47db284b9bdfc383f80a74ded1ae500e834433fe Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Thu, 24 Oct 2019 06:09:16 +0200 Subject: [PATCH 3/7] Added FENString class --- src/dev/kske/chess/board/FENString.java | 194 ++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 src/dev/kske/chess/board/FENString.java diff --git a/src/dev/kske/chess/board/FENString.java b/src/dev/kske/chess/board/FENString.java new file mode 100644 index 0000000..5e53511 --- /dev/null +++ b/src/dev/kske/chess/board/FENString.java @@ -0,0 +1,194 @@ +package dev.kske.chess.board; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import dev.kske.chess.board.Piece.Color; +import dev.kske.chess.exception.ChessException; + +/** + * Project: Chess
+ * File: FENString.java
+ * Created: 20 Oct 2019
+ *
+ * Represents a FEN string and enables parsing an existing FEN string or + * serializing a {@link Board} to one. + * + * @author Kai S. K. Engelbart + * @since Chess v0.4-alpha + */ +public class FENString { + + private Board board; + private Map fields = new LinkedHashMap<>(); + + public static enum FENField { + PIECE_PLACEMENT, ACTIVE_COLOR, CASTLING_AVAILABILITY, EN_PASSANT_TARGET_SQUARE, HALFMOVE_CLOCK, FULLMOVE_NUMBER + } + + /** + * 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(); + fields.put(FENField.PIECE_PLACEMENT, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"); + fields.put(FENField.ACTIVE_COLOR, "w"); + fields.put(FENField.CASTLING_AVAILABILITY, "KQkq"); + fields.put(FENField.EN_PASSANT_TARGET_SQUARE, "-"); + fields.put(FENField.HALFMOVE_CLOCK, "0"); + fields.put(FENField.FULLMOVE_NUMBER, "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( + "^(?(?:[1-8nbrqkpNBRQKP]{1,8}\\/){7}[1-8nbrqkpNBRQKP]{1,8}) (?[wb]) (?-|[KQkq]{1,4}) (?-|[a-h][1-8]) (?\\d) (?\\d)$"); + Matcher matcher = fenPattern.matcher(fen); + if (!matcher.find()) throw new ChessException("FEN string does not match pattern " + fenPattern.pattern()); + for (FENField field : FENField.values()) + fields.put(field, matcher.group(field.toString())); + + // 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 = fields.get(FENField.PIECE_PLACEMENT).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; + switch (Character.toUpperCase(c)) { + case 'K': + board.getBoardArr()[i][j] = new King(color, board); + break; + case 'Q': + board.getBoardArr()[i][j] = new Queen(color, board); + break; + case 'R': + board.getBoardArr()[i][j] = new Rook(color, board); + break; + case 'N': + board.getBoardArr()[i][j] = new Knight(color, board); + break; + case 'B': + board.getBoardArr()[i][j] = new Bishop(color, board); + break; + case 'P': + board.getBoardArr()[i][j] = new Pawn(color, board); + break; + } + ++j; + } + } + } + + // Active color + board.getLog().setActiveColor(Color.fromFirstChar(fields.get(FENField.ACTIVE_COLOR).charAt(0))); + + // TODO: Castling availability + + // En passant square + if (!fields.get(FENField.EN_PASSANT_TARGET_SQUARE).equals("-")) + board.getLog().setEnPassant(Position.fromLAN(fields.get(FENField.EN_PASSANT_TARGET_SQUARE))); + + // Halfmove clock + board.getLog().setHalfmoveClock(Integer.parseInt(fields.get(FENField.HALFMOVE_CLOCK))); + + // Fullmove number + board.getLog().setFullmoveNumber(Integer.parseInt(fields.get(FENField.FULLMOVE_NUMBER))); + } + + /** + * 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()[i][j]; + + if (piece == null) ++empty; + else { + + // Write empty field count + if (empty > 0) { + sb.append(empty); + empty = 0; + } + + // Write piece character + char p = piece.getType().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('/'); + } + fields.put(FENField.PIECE_PLACEMENT, sb.toString()); + + // Active color + fields.put(FENField.ACTIVE_COLOR, String.valueOf(board.getLog().getActiveColor().firstChar())); + + // TODO: Castling availability + + // En passant availability + final Position enPassantPosition = board.getLog().getEnPassant(); + fields.put(FENField.EN_PASSANT_TARGET_SQUARE, enPassantPosition == null ? "-" : enPassantPosition.toLAN()); + + // Halfmove clock + fields.put(FENField.HALFMOVE_CLOCK, String.valueOf(board.getLog().getHalfmoveClock())); + + // Fullmove counter + fields.put(FENField.FULLMOVE_NUMBER, String.valueOf(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.join(" ", fields.values()); + } + + /** + * @return a {@link Board} object corresponding to this {@link FENString} + */ + public Board getBoard() { return board; } +} From de18ec57595444fb8ee179d92b5315a1521d7ad6 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Thu, 24 Oct 2019 19:54:59 +0200 Subject: [PATCH 4/7] Enhanced FENString class, added unit test and Board#equals() --- .classpath | 5 ++ src/dev/kske/chess/board/Board.java | 23 +++++- src/dev/kske/chess/board/FENString.java | 83 +++++++++++--------- src/dev/kske/chess/board/Log.java | 17 ++++ src/dev/kske/chess/board/Move.java | 17 ++++ src/dev/kske/chess/board/MoveNode.java | 19 +++++ src/dev/kske/chess/board/Piece.java | 15 ++++ test/dev/kske/chess/board/FENStringTest.java | 72 +++++++++++++++++ 8 files changed, 212 insertions(+), 39 deletions(-) create mode 100644 test/dev/kske/chess/board/FENStringTest.java diff --git a/.classpath b/.classpath index e4beaea..8469573 100644 --- a/.classpath +++ b/.classpath @@ -7,6 +7,11 @@ + + + + + diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index e353a35..94b481b 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -1,9 +1,11 @@ package dev.kske.chess.board; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -576,7 +578,7 @@ public class Board { for (int j = 0; j < 8; j++) { final Piece piece = boardArr[j][i]; if (piece == null) ++emptyCount; - else { // TODO: rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b - - 1 2 error + else { if (emptyCount != 0) { sb.append(emptyCount); emptyCount = 0; @@ -616,6 +618,25 @@ public class Board { return sb.toString(); } + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.deepHashCode(boardArr); + result = prime * result + Objects.hash(castlingRights, kingPos, log); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Board other = (Board) obj; + return Arrays.deepEquals(boardArr, other.boardArr) && Objects.equals(castlingRights, other.castlingRights) + && Objects.equals(kingPos, other.kingPos) && Objects.equals(log, other.log); + } + /** * @param pos The position from which to return a piece * @return The piece at the position diff --git a/src/dev/kske/chess/board/FENString.java b/src/dev/kske/chess/board/FENString.java index 5e53511..6183015 100644 --- a/src/dev/kske/chess/board/FENString.java +++ b/src/dev/kske/chess/board/FENString.java @@ -1,7 +1,5 @@ package dev.kske.chess.board; -import java.util.LinkedHashMap; -import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -21,25 +19,23 @@ import dev.kske.chess.exception.ChessException; */ public class FENString { - private Board board; - private Map fields = new LinkedHashMap<>(); - - public static enum FENField { - PIECE_PLACEMENT, ACTIVE_COLOR, CASTLING_AVAILABILITY, EN_PASSANT_TARGET_SQUARE, HALFMOVE_CLOCK, FULLMOVE_NUMBER - } + 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(); - fields.put(FENField.PIECE_PLACEMENT, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"); - fields.put(FENField.ACTIVE_COLOR, "w"); - fields.put(FENField.CASTLING_AVAILABILITY, "KQkq"); - fields.put(FENField.EN_PASSANT_TARGET_SQUARE, "-"); - fields.put(FENField.HALFMOVE_CLOCK, "0"); - fields.put(FENField.FULLMOVE_NUMBER, "1"); + board = new Board(); + piecePlacement = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR"; + activeColor = Color.WHITE; + castlingAvailability = "KQkq"; + halfmoveClock = 0; + fullmoveNumber = 1; } /** @@ -51,11 +47,18 @@ public class FENString { public FENString(String fen) throws ChessException { // Check fen string against regex Pattern fenPattern = Pattern.compile( - "^(?(?:[1-8nbrqkpNBRQKP]{1,8}\\/){7}[1-8nbrqkpNBRQKP]{1,8}) (?[wb]) (?-|[KQkq]{1,4}) (?-|[a-h][1-8]) (?\\d) (?\\d)$"); + "^(?(?:[1-8nbrqkpNBRQKP]{1,8}\\/){7}[1-8nbrqkpNBRQKP]{1,8}) (?[wb]) (?-|[KQkq]{1,4}) (?-|[a-h][1-8]) (?\\d) (?\\d)$"); Matcher matcher = fenPattern.matcher(fen); if (!matcher.find()) throw new ChessException("FEN string does not match pattern " + fenPattern.pattern()); - for (FENField field : FENField.values()) - fields.put(field, matcher.group(field.toString())); + + // 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(); @@ -66,7 +69,7 @@ public class FENString { // Parse individual fields // Piece placement - final String[] rows = fields.get(FENField.PIECE_PLACEMENT).split("/"); + 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(); @@ -80,22 +83,22 @@ public class FENString { Color color = Character.isUpperCase(c) ? Color.WHITE : Color.BLACK; switch (Character.toUpperCase(c)) { case 'K': - board.getBoardArr()[i][j] = new King(color, board); + board.getBoardArr()[j][i] = new King(color, board); break; case 'Q': - board.getBoardArr()[i][j] = new Queen(color, board); + board.getBoardArr()[j][i] = new Queen(color, board); break; case 'R': - board.getBoardArr()[i][j] = new Rook(color, board); + board.getBoardArr()[j][i] = new Rook(color, board); break; case 'N': - board.getBoardArr()[i][j] = new Knight(color, board); + board.getBoardArr()[j][i] = new Knight(color, board); break; case 'B': - board.getBoardArr()[i][j] = new Bishop(color, board); + board.getBoardArr()[j][i] = new Bishop(color, board); break; case 'P': - board.getBoardArr()[i][j] = new Pawn(color, board); + board.getBoardArr()[j][i] = new Pawn(color, board); break; } ++j; @@ -104,19 +107,18 @@ public class FENString { } // Active color - board.getLog().setActiveColor(Color.fromFirstChar(fields.get(FENField.ACTIVE_COLOR).charAt(0))); + board.getLog().setActiveColor(activeColor); // TODO: Castling availability // En passant square - if (!fields.get(FENField.EN_PASSANT_TARGET_SQUARE).equals("-")) - board.getLog().setEnPassant(Position.fromLAN(fields.get(FENField.EN_PASSANT_TARGET_SQUARE))); + board.getLog().setEnPassant(enPassantTargetSquare); // Halfmove clock - board.getLog().setHalfmoveClock(Integer.parseInt(fields.get(FENField.HALFMOVE_CLOCK))); + board.getLog().setHalfmoveClock(halfmoveClock); // Fullmove number - board.getLog().setFullmoveNumber(Integer.parseInt(fields.get(FENField.FULLMOVE_NUMBER))); + board.getLog().setFullmoveNumber(fullmoveNumber); } /** @@ -134,7 +136,7 @@ public class FENString { for (int i = 0; i < 8; i++) { int empty = 0; for (int j = 0; j < 8; j++) { - final Piece piece = board.getBoardArr()[i][j]; + final Piece piece = board.getBoardArr()[j][i]; if (piece == null) ++empty; else { @@ -159,22 +161,21 @@ public class FENString { if (i < 7) sb.append('/'); } - fields.put(FENField.PIECE_PLACEMENT, sb.toString()); + piecePlacement = sb.toString(); // Active color - fields.put(FENField.ACTIVE_COLOR, String.valueOf(board.getLog().getActiveColor().firstChar())); + activeColor = board.getLog().getActiveColor(); // TODO: Castling availability // En passant availability - final Position enPassantPosition = board.getLog().getEnPassant(); - fields.put(FENField.EN_PASSANT_TARGET_SQUARE, enPassantPosition == null ? "-" : enPassantPosition.toLAN()); + enPassantTargetSquare = board.getLog().getEnPassant(); // Halfmove clock - fields.put(FENField.HALFMOVE_CLOCK, String.valueOf(board.getLog().getHalfmoveClock())); + halfmoveClock = board.getLog().getHalfmoveClock(); // Fullmove counter - fields.put(FENField.FULLMOVE_NUMBER, String.valueOf(board.getLog().getFullmoveNumber())); + fullmoveNumber = board.getLog().getFullmoveNumber(); } /** @@ -184,7 +185,13 @@ public class FENString { */ @Override public String toString() { - return String.join(" ", fields.values()); + return String.format("%s %c %s %s %d %d", + piecePlacement, + activeColor.firstChar(), + castlingAvailability, + enPassantTargetSquare == null ? "-" : enPassantTargetSquare.toLAN(), + halfmoveClock, + fullmoveNumber); } /** diff --git a/src/dev/kske/chess/board/Log.java b/src/dev/kske/chess/board/Log.java index d5c1794..133d42a 100644 --- a/src/dev/kske/chess/board/Log.java +++ b/src/dev/kske/chess/board/Log.java @@ -1,6 +1,7 @@ package dev.kske.chess.board; import java.util.Iterator; +import java.util.Objects; import dev.kske.chess.board.Piece.Color; @@ -160,6 +161,22 @@ public class Log implements Iterable { halfmoveClock = current.halfmoveClock; } + @Override + public int hashCode() { + return Objects.hash(activeColor, current, enPassant, fullmoveNumber, halfmoveClock, root); + } + + @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 && Objects.equals(current, other.current) + && Objects.equals(enPassant, other.enPassant) && fullmoveNumber == other.fullmoveNumber + && halfmoveClock == other.halfmoveClock && Objects.equals(root, other.root); + } + /** * @return The first logged move, or {@code null} if there is none */ diff --git a/src/dev/kske/chess/board/Move.java b/src/dev/kske/chess/board/Move.java index 74f8cb7..34cacf9 100644 --- a/src/dev/kske/chess/board/Move.java +++ b/src/dev/kske/chess/board/Move.java @@ -1,5 +1,7 @@ package dev.kske.chess.board; +import java.util.Objects; + /** * Project: Chess
* File: Move.java
@@ -50,6 +52,21 @@ public class Move { return String.format("%s -> %s", pos, dest); } + @Override + public int hashCode() { + return Objects.hash(dest, pos, type, xDist, xSign, yDist, ySign); + } + + @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(dest, other.dest) && Objects.equals(pos, other.pos) && type == other.type + && xDist == other.xDist && xSign == other.xSign && yDist == other.yDist && ySign == other.ySign; + } + public static enum Type { NORMAL, PAWN_PROMOTION, CASTLING, EN_PASSANT, UNKNOWN } diff --git a/src/dev/kske/chess/board/MoveNode.java b/src/dev/kske/chess/board/MoveNode.java index 6d4afad..e71c4b8 100644 --- a/src/dev/kske/chess/board/MoveNode.java +++ b/src/dev/kske/chess/board/MoveNode.java @@ -2,6 +2,7 @@ package dev.kske.chess.board; import java.util.ArrayList; import java.util.List; +import java.util.Objects; import dev.kske.chess.board.Piece.Color; @@ -93,4 +94,22 @@ public class MoveNode { public boolean hasParent() { return parent != null; } + + @Override + public int hashCode() { + return Objects + .hash(activeColor, capturedPiece, enPassant, fullmoveCounter, halfmoveClock, move, parent, variations); + } + + @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) + && Objects.equals(enPassant, other.enPassant) && fullmoveCounter == other.fullmoveCounter + && halfmoveClock == other.halfmoveClock && Objects.equals(move, other.move) + && Objects.equals(parent, other.parent) && Objects.equals(variations, other.variations); + } } \ No newline at end of file diff --git a/src/dev/kske/chess/board/Piece.java b/src/dev/kske/chess/board/Piece.java index e9ca766..79cb5d6 100644 --- a/src/dev/kske/chess/board/Piece.java +++ b/src/dev/kske/chess/board/Piece.java @@ -2,6 +2,7 @@ package dev.kske.chess.board; import java.util.Iterator; import java.util.List; +import java.util.Objects; /** * Project: Chess
@@ -84,6 +85,20 @@ public abstract class Piece implements Cloneable { --moveCounter; } + @Override + public int hashCode() { + return Objects.hash(color, moveCounter); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + Piece other = (Piece) obj; + return color == other.color && moveCounter == other.moveCounter; + } + public static enum Type { KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN; diff --git a/test/dev/kske/chess/board/FENStringTest.java b/test/dev/kske/chess/board/FENStringTest.java new file mode 100644 index 0000000..8d2810f --- /dev/null +++ b/test/dev/kske/chess/board/FENStringTest.java @@ -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: Chess
+ * File: FENStringTest.java
+ * Created: 24 Oct 2019
+ * + * @author Kai S. K. Engelbart + */ +class FENStringTest { + + List fenStrings = new ArrayList<>(); + List 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 - - 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()); + } +} From 208f585c11ecf2478444d705176685d5d698debe Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Fri, 25 Oct 2019 11:34:07 +0200 Subject: [PATCH 5/7] Moved castling right logging to Log * Removed move counter from Piece * Added castling right array to MoveNode and Log * Removed castling right map from Board * Added castling right serialization and deserialization to FENString * Modified LogTest --- src/dev/kske/chess/board/Board.java | 183 +++++++----------------- src/dev/kske/chess/board/FENString.java | 26 +++- src/dev/kske/chess/board/King.java | 14 +- src/dev/kske/chess/board/Log.java | 62 +++++--- src/dev/kske/chess/board/MoveNode.java | 27 ++-- src/dev/kske/chess/board/Piece.java | 15 +- test/dev/kske/chess/board/LogTest.java | 8 +- 7 files changed, 143 insertions(+), 192 deletions(-) diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index 94b481b..5c2a8ac 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -20,63 +20,55 @@ import dev.kske.chess.board.Piece.Type; */ public class Board { - private Piece[][] boardArr = new Piece[8][8]; - private Map kingPos = new HashMap<>(); - private Map> castlingRights = new HashMap<>(); - private Log log = new Log(); + private Piece[][] boardArr = new Piece[8][8]; + private Map kingPos = new HashMap<>(); + private Log log = new Log(); private static final Map positionScores; static { positionScores = new HashMap<>(); positionScores.put(Type.KING, - new int[][] { new int[] { -3, -4, -4, -5, -5, -4, -4, -3 }, + 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[] { -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 } }); + 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 } }); + 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[][] { 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 } }); + 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 } }); + 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[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 5, 5, 5, 5, 5, 5, 5, 5 }, new int[] { 1, 1, 2, 3, 3, 2, 1, 1 }, + new int[] { 0, 0, 1, 3, 3, 1, 0, 0 }, new int[] { 0, 0, 0, 2, 2, 0, 0, 0 }, new int[] { 0, 0, -1, 0, 0, -1, 0, 0 }, new int[] { 0, 1, 1, -2, -2, 1, 1, 0 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0 } }); } /** * Initializes the board with the default chess starting position. */ - public Board() { - initDefaultPositions(); - } + public Board() { initDefaultPositions(); } /** * Initializes the board with data from a FEN-string. * * @param fen The FEN-string to initialize the board from */ - public Board(String fen) { - initFromFEN(fen); - } + public Board(String fen) { initFromFEN(fen); } /** * Creates a copy of another {@link Board} instance.
@@ -95,10 +87,6 @@ public class Board { } kingPos.putAll(other.kingPos); - Map whiteCastling = new HashMap<>(other.castlingRights.get(Color.WHITE)), - blackCastling = new HashMap<>(other.castlingRights.get(Color.BLACK)); - castlingRights.put(Color.WHITE, whiteCastling); - castlingRights.put(Color.BLACK, blackCastling); log = new Log(other.log, false); } @@ -158,8 +146,6 @@ public class Board { : new Move(0, move.pos.y, 3, move.pos.y); // Queenside setDest(rookMove, getPos(rookMove)); setPos(rookMove, null); - - getDest(rookMove).incMoveCounter(); break; case UNKNOWN: System.err.printf("Move of unknown type %s found!%n", move); @@ -171,17 +157,11 @@ public class Board { 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 + // Update the king's position if the moved piece is the king if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest); // Update log - log.add(move, capturePiece, piece.getType() == Type.PAWN); - - updateCastlingRights(); + log.add(move, piece, capturePiece); } /** @@ -195,9 +175,7 @@ public class Board { Pattern.compile( "^(?[NBRQK])(?:(?[a-h])|(?[1-8])|(?[a-h][1-8]))?x?(?[a-h][1-8])(?:\\+{0,2}|\\#)$")); patterns.put("pawnCapture", - Pattern - .compile( - "^(?[a-h])(?[1-8])?x(?[a-h][1-8])(?[NBRQK])?(?:\\+{0,2}|\\#)?$")); + Pattern.compile("^(?[a-h])(?[1-8])?x(?[a-h][1-8])(?[NBRQK])?(?:\\+{0,2}|\\#)?$")); patterns.put("pawnPush", Pattern.compile("^(?[a-h][1-8])(?[NBRQK])?(?:\\+{0,2}|\\#)$")); patterns.put("castling", Pattern.compile("^(?O-O-O)|(?O-O)(?:\\+{0,2}|\\#)?$")); @@ -228,8 +206,7 @@ public class Board { case "pawnCapture": dest = Position.fromLAN(m.group("toSquare")); char file = m.group("fromFile").charAt(0); - int rank = m.group("fromRank") == null ? get(Type.PAWN, file) - : Integer.parseInt(m.group("fromRank")); + int rank = m.group("fromRank") == null ? get(Type.PAWN, file) : Integer.parseInt(m.group("fromRank")); pos = Position.fromLAN(String.format("%c%d", file, rank)); break; case "pawnPush": @@ -282,8 +259,6 @@ public class Board { : 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); @@ -295,38 +270,11 @@ public class Board { 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 if (getPos(move).getType() == Type.KING) kingPos.put(getPos(move).getColor(), move.pos); // Update log log.removeLast(); - - updateCastlingRights(); - } - - private void updateCastlingRights() { - // White - if (new Position(4, 7).equals(kingPos.get(Color.WHITE))) { - final King king = (King) get(kingPos.get(Color.WHITE)); - castlingRights.get(Color.WHITE).put(Type.KING, king.canCastleKingside()); - castlingRights.get(Color.WHITE).put(Type.QUEEN, king.canCastleQueenside()); - } else { - castlingRights.get(Color.WHITE).put(Type.KING, false); - castlingRights.get(Color.WHITE).put(Type.QUEEN, false); - } - - // Black - if (new Position(4, 0).equals(kingPos.get(Color.BLACK))) { - final King king = (King) get(kingPos.get(Color.BLACK)); - castlingRights.get(Color.BLACK).put(Type.KING, king.canCastleKingside()); - castlingRights.get(Color.BLACK).put(Type.QUEEN, king.canCastleQueenside()); - } else { - castlingRights.get(Color.BLACK).put(Type.KING, false); - castlingRights.get(Color.BLACK).put(Type.QUEEN, false); - } } /** @@ -339,14 +287,11 @@ public class Board { List moves = new ArrayList<>(); for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) - if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) - moves.addAll(boardArr[i][j].getMoves(new Position(i, j))); + if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) moves.addAll(boardArr[i][j].getMoves(new Position(i, j))); return moves; } - public List getMoves(Position pos) { - return get(pos).getMoves(pos); - } + public List getMoves(Position pos) { return get(pos).getMoves(pos); } /** * Checks, if the king is in check. @@ -358,9 +303,7 @@ public class Board { for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) { Position pos = new Position(i, j); - if (get(pos) != null && get(pos).getColor() != color - && get(pos).isValidMove(new Move(pos, kingPos.get(color)))) - return true; + if (get(pos) != null && get(pos).getColor() != color && get(pos).isValidMove(new Move(pos, kingPos.get(color)))) return true; } return false; } @@ -388,8 +331,7 @@ public class Board { public GameState getGameEventType(Color color) { return checkCheck(color) ? checkCheckmate(color) ? GameState.CHECKMATE : GameState.CHECK - : getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? GameState.STALEMATE - : GameState.NORMAL; + : getMoves(color).isEmpty() || log.getLast().halfmoveClock >= 50 ? GameState.STALEMATE : GameState.NORMAL; } /** @@ -471,15 +413,6 @@ public class Board { for (int j = 2; j < 6; j++) boardArr[i][j] = null; - // Initialize castling rights - Map 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(); } @@ -524,9 +457,7 @@ public class Board { 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); + System.err.printf("Unknown character '%c' in board declaration of FEN string '%s'%n", places[k], fen); } } } @@ -550,11 +481,10 @@ public class Board { case '-': break; default: - System.err - .printf("Unknown character '%c' in castling rights declaration of FEN string '%s'", c, fen); + 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); + // castlingRights.put(Color.WHITE, whiteCastling); + // castlingRights.put(Color.BLACK, blackCastling); // En passant availability if (!parts[3].equals("-")) log.setEnPassant(Position.fromLAN(parts[3])); @@ -597,10 +527,10 @@ public class Board { // Castling Rights sb.append(' '); StringBuilder castlingSb = new StringBuilder(); - if (castlingRights.get(Color.WHITE).get(Type.KING)) castlingSb.append('K'); - if (castlingRights.get(Color.WHITE).get(Type.QUEEN)) castlingSb.append('Q'); - if (castlingRights.get(Color.BLACK).get(Type.KING)) castlingSb.append('k'); - if (castlingRights.get(Color.BLACK).get(Type.QUEEN)) castlingSb.append('q'); + // if (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); @@ -623,7 +553,7 @@ public class Board { final int prime = 31; int result = 1; result = prime * result + Arrays.deepHashCode(boardArr); - result = prime * result + Objects.hash(castlingRights, kingPos, log); + result = prime * result + Objects.hash(kingPos, log); return result; } @@ -633,17 +563,14 @@ public class Board { if (obj == null) return false; if (getClass() != obj.getClass()) return false; Board other = (Board) obj; - return Arrays.deepEquals(boardArr, other.boardArr) && Objects.equals(castlingRights, other.castlingRights) - && Objects.equals(kingPos, other.kingPos) && Objects.equals(log, other.log); + return Arrays.deepEquals(boardArr, other.boardArr) && Objects.equals(kingPos, other.kingPos) && Objects.equals(log, other.log); } /** * @param pos The position from which to return a piece * @return The piece at the position */ - public Piece get(Position pos) { - return boardArr[pos.x][pos.y]; - } + public Piece get(Position pos) { return boardArr[pos.x][pos.y]; } /** * Searches for a {@link Piece} inside a file (A - H). @@ -656,9 +583,7 @@ public class Board { public int get(Type type, char file) { int x = file - 97; for (int i = 0; i < 8; i++) - if (boardArr[x][i] != null && boardArr[x][i].getType() == type - && boardArr[x][i].getColor() == log.getActiveColor()) - return 8 - i; + if (boardArr[x][i] != null && boardArr[x][i].getType() == type && boardArr[x][i].getColor() == log.getActiveColor()) return 8 - i; return -1; } @@ -673,8 +598,7 @@ public class Board { public char get(Type type, int rank) { int y = rank - 1; for (int i = 0; i < 8; i++) - if (boardArr[i][y] != null && boardArr[i][y].getType() == type - && boardArr[i][y].getColor() == log.getActiveColor()) + if (boardArr[i][y] != null && boardArr[i][y].getType() == type && boardArr[i][y].getColor() == log.getActiveColor()) return (char) (i + 97); return '-'; } @@ -689,8 +613,7 @@ public class Board { public Position get(Type type, Position dest) { for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) - if (boardArr[i][j] != null && boardArr[i][j].getType() == type - && boardArr[i][j].getColor() == log.getActiveColor()) { + if (boardArr[i][j] != null && boardArr[i][j].getType() == type && boardArr[i][j].getColor() == log.getActiveColor()) { Position pos = new Position(i, j); if (boardArr[i][j].isValidMove(new Move(pos, dest))) return pos; } @@ -703,25 +626,19 @@ public class Board { * @param pos The position to place the piece at * @param piece The piece to place */ - public void set(Position pos, Piece piece) { - boardArr[pos.x][pos.y] = piece; - } + public void set(Position pos, Piece piece) { boardArr[pos.x][pos.y] = piece; } /** * @param move The move from which position to return a piece * @return The piece at the position of the move */ - public Piece getPos(Move move) { - return get(move.pos); - } + public Piece getPos(Move move) { return get(move.pos); } /** * @param move The move from which destination to return a piece * @return The piece at the destination of the move */ - public Piece getDest(Move move) { - return get(move.dest); - } + public Piece getDest(Move move) { return get(move.dest); } /** * Places a piece at the position of a move. @@ -729,9 +646,7 @@ public class Board { * @param move The move at which position to place the piece * @param piece The piece to place */ - public void setPos(Move move, Piece piece) { - set(move.pos, piece); - } + public void setPos(Move move, Piece piece) { set(move.pos, piece); } /** * Places a piece at the destination of a move. @@ -739,9 +654,7 @@ public class Board { * @param move The move at which destination to place the piece * @param piece The piece to place */ - public void setDest(Move move, Piece piece) { - set(move.dest, piece); - } + public void setDest(Move move, Piece piece) { set(move.dest, piece); } /** * @return The board array diff --git a/src/dev/kske/chess/board/FENString.java b/src/dev/kske/chess/board/FENString.java index 6183015..4811fcc 100644 --- a/src/dev/kske/chess/board/FENString.java +++ b/src/dev/kske/chess/board/FENString.java @@ -109,7 +109,24 @@ public class FENString { // Active color board.getLog().setActiveColor(activeColor); - // TODO: Castling availability + // 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); @@ -166,7 +183,12 @@ public class FENString { // Active color activeColor = board.getLog().getActiveColor(); - // TODO: Castling availability + // 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(); diff --git a/src/dev/kske/chess/board/King.java b/src/dev/kske/chess/board/King.java index 78f672d..de90fbf 100644 --- a/src/dev/kske/chess/board/King.java +++ b/src/dev/kske/chess/board/King.java @@ -11,9 +11,7 @@ import java.util.List; */ public class King extends Piece { - public King(Color color, Board board) { - super(color, board); - } + public King(Color color, Board board) { super(color, board); } @Override public boolean isValidMove(Move move) { @@ -33,22 +31,20 @@ public class King extends Piece { } public boolean canCastleKingside() { - if (getMoveCounter() == 0) { + if (board.getLog().getCastlingRights()[getColor() == Color.WHITE ? MoveNode.WHITE_KINGSIDE : MoveNode.BLACK_KINGSIDE]) { 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))); + return rook != null && rook.getType() == Type.ROOK && isFreePath(new Move(new Position(4, y), new Position(6, y))); } else return false; } public boolean canCastleQueenside() { - if (getMoveCounter() == 0) { + if (board.getLog().getCastlingRights()[getColor() == Color.WHITE ? MoveNode.WHITE_QUEENSIDE : MoveNode.BLACK_QUEENSIDE]) { 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))); + return rook != null && rook.getType() == Type.ROOK && isFreePath(new Move(new Position(4, y), new Position(1, y))); } else return false; } diff --git a/src/dev/kske/chess/board/Log.java b/src/dev/kske/chess/board/Log.java index 133d42a..3ae372e 100644 --- a/src/dev/kske/chess/board/Log.java +++ b/src/dev/kske/chess/board/Log.java @@ -1,9 +1,11 @@ package dev.kske.chess.board; +import java.util.Arrays; import java.util.Iterator; import java.util.Objects; import dev.kske.chess.board.Piece.Color; +import dev.kske.chess.board.Piece.Type; /** * Project: Chess
@@ -15,13 +17,12 @@ public class Log implements Iterable { private MoveNode root, current; - private Position enPassant; private Color activeColor; + private boolean[] castlingRights; + private Position enPassant; private int fullmoveNumber, halfmoveClock; - public Log() { - reset(); - } + public Log() { reset(); } /** * Creates a (partially deep) copy of another {@link Log} instance which begins @@ -34,6 +35,7 @@ public class Log implements Iterable { */ public Log(Log other, boolean copyVariations) { enPassant = other.enPassant; + castlingRights = other.castlingRights.clone(); activeColor = other.activeColor; fullmoveNumber = other.fullmoveNumber; halfmoveClock = other.halfmoveClock; @@ -54,9 +56,7 @@ public class Log implements Iterable { private boolean hasNext = true; @Override - public boolean hasNext() { - return hasNext; - } + public boolean hasNext() { return hasNext; } @Override public MoveNode next() { @@ -72,16 +72,20 @@ public class Log implements Iterable { * Adds a move to the move history and adjusts the log to the new position. * * @param move The move to log + * @param piece The piece that performed 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) { - enPassant = pawnMove && move.yDist == 2 ? new Position(move.pos.x, move.pos.y + move.ySign) : null; + public void add(Move move, Piece piece, Piece capturedPiece) { + enPassant = piece.getType() == Type.PAWN && move.yDist == 2 ? new Position(move.pos.x, move.pos.y + move.ySign) : null; if (activeColor == Color.BLACK) ++fullmoveNumber; - if (pawnMove || capturedPiece != null) halfmoveClock = 0; + if (piece.getType() == Type.PAWN || capturedPiece != null) halfmoveClock = 0; else++halfmoveClock; activeColor = activeColor.opposite(); - final MoveNode leaf = new MoveNode(move, capturedPiece, enPassant, activeColor, fullmoveNumber, halfmoveClock); + + // Disable castling rights if a king or a rook has been moved + if (piece.getType() == Type.KING || piece.getType() == Type.ROOK) disableCastlingRights(piece, move.pos); + + final MoveNode leaf = new MoveNode(move, capturedPiece, castlingRights.clone(), enPassant, activeColor, fullmoveNumber, halfmoveClock); if (isEmpty()) { root = leaf; @@ -106,9 +110,7 @@ public class Log implements Iterable { public boolean isEmpty() { return root == null; } - public boolean hasParent() { - return !isEmpty() && current.hasParent(); - } + public boolean hasParent() { return !isEmpty() && current.hasParent(); } /** * Reverts the log to its initial state corresponding to the default board @@ -117,6 +119,7 @@ public class Log implements Iterable { public void reset() { root = null; current = null; + castlingRights = new boolean[] { true, true, true, true }; enPassant = null; activeColor = Color.WHITE; fullmoveNumber = 1; @@ -124,8 +127,9 @@ public class Log implements Iterable { } /** + * Changes the current node to one of its children (variations). * - * @param index + * @param index the index of the variation to select */ public void selectNextNode(int index) { if (!isEmpty() && current.hasVariations() && index < current.getVariations().size()) { @@ -156,14 +160,29 @@ public class Log implements Iterable { private void update() { activeColor = current.activeColor; + castlingRights = current.castlingRights.clone(); enPassant = current.enPassant; fullmoveNumber = current.fullmoveCounter; halfmoveClock = current.halfmoveClock; } + private void disableCastlingRights(Piece piece, Position initialPosition) { + // Kingside + if (piece.getType() == Type.KING || piece.getType() == Type.ROOK && initialPosition.x == 7) + castlingRights[piece.getColor() == Color.WHITE ? MoveNode.WHITE_KINGSIDE : MoveNode.BLACK_KINGSIDE] = false; + + // Queenside + if (piece.getType() == Type.KING || piece.getType() == Type.ROOK && initialPosition.x == 0) + castlingRights[piece.getColor() == Color.WHITE ? MoveNode.WHITE_QUEENSIDE : MoveNode.BLACK_QUEENSIDE] = false; + } + @Override public int hashCode() { - return Objects.hash(activeColor, current, enPassant, fullmoveNumber, halfmoveClock, root); + 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 @@ -172,9 +191,8 @@ public class Log implements Iterable { if (obj == null) return false; if (getClass() != obj.getClass()) return false; Log other = (Log) obj; - return activeColor == other.activeColor && Objects.equals(current, other.current) - && Objects.equals(enPassant, other.enPassant) && fullmoveNumber == other.fullmoveNumber - && halfmoveClock == other.halfmoveClock && Objects.equals(root, other.root); + 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; } /** @@ -187,6 +205,10 @@ public class Log implements Iterable { */ public MoveNode getLast() { return current; } + public boolean[] getCastlingRights() { return castlingRights; } + + public void setCastlingRights(boolean[] castlingRights) { this.castlingRights = castlingRights; } + public Position getEnPassant() { return enPassant; } public void setEnPassant(Position enPassant) { this.enPassant = enPassant; } diff --git a/src/dev/kske/chess/board/MoveNode.java b/src/dev/kske/chess/board/MoveNode.java index e71c4b8..600a0c9 100644 --- a/src/dev/kske/chess/board/MoveNode.java +++ b/src/dev/kske/chess/board/MoveNode.java @@ -1,6 +1,7 @@ package dev.kske.chess.board; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -14,8 +15,11 @@ import dev.kske.chess.board.Piece.Color; */ 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; @@ -34,10 +38,11 @@ public class MoveNode { * @param fullmoveCounter * @param halfmoveClock */ - public MoveNode(Move move, Piece capturedPiece, Position enPassant, Color activeColor, int fullmoveCounter, - int 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; @@ -53,8 +58,8 @@ public class MoveNode { * considers subsequent variations */ public MoveNode(MoveNode other, boolean copyVariations) { - this(other.move, other.capturedPiece, other.enPassant, other.activeColor, other.fullmoveCounter, - other.halfmoveClock); + this(other.move, other.capturedPiece, other.castlingRights.clone(), other.enPassant, other.activeColor, + other.fullmoveCounter, other.halfmoveClock); if (copyVariations && other.variations != null) { if (variations == null) variations = new ArrayList<>(); other.variations.forEach(variation -> { @@ -97,8 +102,12 @@ public class MoveNode { @Override public int hashCode() { - return Objects - .hash(activeColor, capturedPiece, enPassant, fullmoveCounter, halfmoveClock, move, parent, variations); + 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 @@ -108,8 +117,8 @@ public class MoveNode { if (getClass() != obj.getClass()) return false; MoveNode other = (MoveNode) obj; return activeColor == other.activeColor && Objects.equals(capturedPiece, other.capturedPiece) - && Objects.equals(enPassant, other.enPassant) && fullmoveCounter == other.fullmoveCounter - && halfmoveClock == other.halfmoveClock && Objects.equals(move, other.move) - && Objects.equals(parent, other.parent) && Objects.equals(variations, other.variations); + && Arrays.equals(castlingRights, other.castlingRights) && Objects.equals(enPassant, other.enPassant) + && fullmoveCounter == other.fullmoveCounter && halfmoveClock == other.halfmoveClock + && Objects.equals(move, other.move); } } \ No newline at end of file diff --git a/src/dev/kske/chess/board/Piece.java b/src/dev/kske/chess/board/Piece.java index 79cb5d6..ef4ea90 100644 --- a/src/dev/kske/chess/board/Piece.java +++ b/src/dev/kske/chess/board/Piece.java @@ -14,7 +14,6 @@ public abstract class Piece implements Cloneable { private final Color color; protected Board board; - private int moveCounter; public Piece(Color color, Board board) { this.color = color; @@ -75,19 +74,9 @@ public abstract class Piece implements Cloneable { public Color getColor() { return color; } - public int getMoveCounter() { return moveCounter; } - - public void incMoveCounter() { - ++moveCounter; - } - - public void decMoveCounter() { - --moveCounter; - } - @Override public int hashCode() { - return Objects.hash(color, moveCounter); + return Objects.hash(color); } @Override @@ -96,7 +85,7 @@ public abstract class Piece implements Cloneable { if (obj == null) return false; if (getClass() != obj.getClass()) return false; Piece other = (Piece) obj; - return color == other.color && moveCounter == other.moveCounter; + return color == other.color; } public static enum Type { diff --git a/test/dev/kske/chess/board/LogTest.java b/test/dev/kske/chess/board/LogTest.java index 204b6be..fafc6dc 100644 --- a/test/dev/kske/chess/board/LogTest.java +++ b/test/dev/kske/chess/board/LogTest.java @@ -43,10 +43,10 @@ class LogTest { log.setActiveColor(Color.WHITE); other.setActiveColor(Color.BLACK); assertNotEquals(log.getActiveColor(), other.getActiveColor()); - log.add(Move.fromLAN("a2a4"), null, true); - log.add(Move.fromLAN("a4a5"), null, true); - other.add(Move.fromLAN("a2a4"), null, true); - other.add(Move.fromLAN("a4a5"), null, true); + log.add(Move.fromLAN("a2a4"), new Pawn(Color.WHITE, null), null); + log.add(Move.fromLAN("a4a5"), new Pawn(Color.WHITE, null), null); + other.add(Move.fromLAN("a2a4"), new Pawn(Color.WHITE, null), null); + other.add(Move.fromLAN("a4a5"), new Pawn(Color.WHITE, null), null); assertNotEquals(log.getRoot(), other.getRoot()); assertNotEquals(log.getRoot().getVariations(), other.getRoot().getVariations()); } From 5f6e2e514f8f76fa62416ba39af9ee95890d2717 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Fri, 25 Oct 2019 11:52:48 +0200 Subject: [PATCH 6/7] Removed old FEN string methods, fixed FEN regex --- src/dev/kske/chess/board/Board.java | 139 ------------------- src/dev/kske/chess/board/FENString.java | 5 +- src/dev/kske/chess/game/UCIPlayer.java | 3 +- src/dev/kske/chess/pgn/PGNGame.java | 21 +-- src/dev/kske/chess/ui/FENDropTarget.java | 24 +++- src/dev/kske/chess/ui/MenuBar.java | 50 ++++--- test/dev/kske/chess/board/FENStringTest.java | 2 +- 7 files changed, 59 insertions(+), 185 deletions(-) diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index 5c2a8ac..fbab10d 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -63,13 +63,6 @@ public class Board { */ public Board() { initDefaultPositions(); } - /** - * Initializes the board with data from a FEN-string. - * - * @param fen The FEN-string to initialize the board from - */ - public Board(String fen) { initFromFEN(fen); } - /** * Creates a copy of another {@link Board} instance.
* The created object is a deep copy, but does not contain any move history @@ -416,138 +409,6 @@ public class Board { log.reset(); } - /** - * Initialized the board with a position specified in a FEN-encoded string. - * - * @param fen The FEN-encoded string representing target state of the board - */ - public void initFromFEN(String fen) { - String[] parts = fen.split(" "); - 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 - log.setActiveColor(Color.fromFirstChar(parts[1].charAt(0))); - - // Castling rights - Map whiteCastling = new HashMap<>(), blackCastling = new HashMap<>(); - for (char c : parts[2].toCharArray()) - switch (c) { - case 'K': - whiteCastling.put(Type.KING, true); - case 'Q': - whiteCastling.put(Type.QUEEN, true); - case 'k': - blackCastling.put(Type.KING, true); - case 'q': - blackCastling.put(Type.QUEEN, true); - case '-': - break; - default: - System.err.printf("Unknown character '%c' in castling rights declaration of FEN string '%s'", c, fen); - } - // castlingRights.put(Color.WHITE, whiteCastling); - // castlingRights.put(Color.BLACK, blackCastling); - - // En passant availability - if (!parts[3].equals("-")) log.setEnPassant(Position.fromLAN(parts[3])); - - // Halfmove clock - log.setHalfmoveClock(Integer.parseInt(parts[4])); - - // Fullmove counter - log.setFullmoveNumber(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.toLAN())); - - // 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(); - } - @Override public int hashCode() { final int prime = 31; diff --git a/src/dev/kske/chess/board/FENString.java b/src/dev/kske/chess/board/FENString.java index 4811fcc..536cecf 100644 --- a/src/dev/kske/chess/board/FENString.java +++ b/src/dev/kske/chess/board/FENString.java @@ -47,7 +47,7 @@ public class FENString { public FENString(String fen) throws ChessException { // Check fen string against regex Pattern fenPattern = Pattern.compile( - "^(?(?:[1-8nbrqkpNBRQKP]{1,8}\\/){7}[1-8nbrqkpNBRQKP]{1,8}) (?[wb]) (?-|[KQkq]{1,4}) (?-|[a-h][1-8]) (?\\d) (?\\d)$"); + "^(?(?:[1-8nbrqkpNBRQKP]{1,8}\\/){7}[1-8nbrqkpNBRQKP]{1,8}) (?[wb]) (?-|[KQkq]{1,4}) (?-|[a-h][1-8]) (?\\d+) (?\\d+)$"); Matcher matcher = fenPattern.matcher(fen); if (!matcher.find()) throw new ChessException("FEN string does not match pattern " + fenPattern.pattern()); @@ -55,8 +55,7 @@ public class FENString { 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")); + if (!matcher.group("enPassantTargetSquare").equals("-")) enPassantTargetSquare = Position.fromLAN(matcher.group("enPassantTargetSquare")); halfmoveClock = Integer.parseInt(matcher.group("halfmoveClock")); fullmoveNumber = Integer.parseInt(matcher.group("fullmoveNumber")); diff --git a/src/dev/kske/chess/game/UCIPlayer.java b/src/dev/kske/chess/game/UCIPlayer.java index 12b73d3..369c16d 100644 --- a/src/dev/kske/chess/game/UCIPlayer.java +++ b/src/dev/kske/chess/game/UCIPlayer.java @@ -2,6 +2,7 @@ package dev.kske.chess.game; import java.io.IOException; +import dev.kske.chess.board.FENString; import dev.kske.chess.board.Move; import dev.kske.chess.board.Piece.Color; import dev.kske.chess.uci.UCIHandle; @@ -30,7 +31,7 @@ public class UCIPlayer extends Player implements UCIListener { @Override public void requestMove() { - handle.positionFEN(board.toFEN()); + handle.positionFEN(new FENString(board).toString()); handle.go(); } diff --git a/src/dev/kske/chess/pgn/PGNGame.java b/src/dev/kske/chess/pgn/PGNGame.java index 9b56b8f..6293976 100644 --- a/src/dev/kske/chess/pgn/PGNGame.java +++ b/src/dev/kske/chess/pgn/PGNGame.java @@ -7,6 +7,7 @@ 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.exception.ChessException; /** @@ -26,8 +27,7 @@ public class 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|\\*"); + 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) { @@ -48,30 +48,23 @@ public class PGNGame { 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() + ": " + game.board.toFEN()); + System.out.println(game.getBoard().getLog().getLast().move.toLAN() + ": " + new FENString(game.board).toString()); } else break; } else break; } // Parse game termination marker - if (sc.findWithinHorizon(terminationMarkerPattern, 20) == null) - System.err.println("Termination marker expected"); + if (sc.findWithinHorizon(terminationMarkerPattern, 20) == null) System.err.println("Termination marker expected"); return game; } - 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); } - public void setTag(String tagName, String tagValue) { - tagPairs.put(tagName, tagValue); - } + public void setTag(String tagName, String tagValue) { tagPairs.put(tagName, tagValue); } public Board getBoard() { return board; } } diff --git a/src/dev/kske/chess/ui/FENDropTarget.java b/src/dev/kske/chess/ui/FENDropTarget.java index b8ffc6d..aea43b3 100644 --- a/src/dev/kske/chess/ui/FENDropTarget.java +++ b/src/dev/kske/chess/ui/FENDropTarget.java @@ -11,7 +11,10 @@ import java.io.FileReader; import java.io.IOException; import java.util.List; -import dev.kske.chess.board.Board; +import javax.swing.JOptionPane; + +import dev.kske.chess.board.FENString; +import dev.kske.chess.exception.ChessException; import dev.kske.chess.game.Game; /** @@ -24,9 +27,7 @@ public class FENDropTarget extends DropTargetAdapter { private MainWindow mainWindow; - public FENDropTarget(MainWindow mainWindow) { - this.mainWindow = mainWindow; - } + public FENDropTarget(MainWindow mainWindow) { this.mainWindow = mainWindow; } @SuppressWarnings("unchecked") @Override @@ -38,9 +39,18 @@ public class FENDropTarget extends DropTargetAdapter { final GamePane gamePane = mainWindow.addGamePane(); final String fen = br.readLine(); DialogUtil.showGameConfigurationDialog(null, (whiteName, blackName) -> { - final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, new Board(fen)); - gamePane.setGame(game); - game.start(); + Game game; + try { + game = new Game(gamePane.getBoardPane(), whiteName, blackName, new FENString(fen).getBoard()); + gamePane.setGame(game); + game.start(); + } catch (ChessException e) { + e.printStackTrace(); + JOptionPane.showMessageDialog(mainWindow, + "Failed to load FEN string: " + e.toString(), + "FEN loading error", + JOptionPane.ERROR_MESSAGE); + } }); evt.dropComplete(true); } catch (IOException e) { diff --git a/src/dev/kske/chess/ui/MenuBar.java b/src/dev/kske/chess/ui/MenuBar.java index 1d995b1..8e796db 100644 --- a/src/dev/kske/chess/ui/MenuBar.java +++ b/src/dev/kske/chess/ui/MenuBar.java @@ -12,7 +12,8 @@ import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; -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.io.EngineUtil; import dev.kske.chess.pgn.PGNDatabase; @@ -42,8 +43,7 @@ public class MenuBar extends JMenuBar { JMenu gameMenu = new JMenu("Game"); JMenuItem newGameMenuItem = new JMenuItem("New Game"); - newGameMenuItem - .addActionListener((evt) -> DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> { + newGameMenuItem.addActionListener((evt) -> DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> { GamePane gamePane = mainWindow.addGamePane(); Game game = new Game(gamePane.getBoardPane(), whiteName, blackName); gamePane.setGame(game); @@ -64,10 +64,8 @@ public class MenuBar extends JMenuBar { JMenuItem addEngineMenuItem = new JMenuItem("Add engine"); addEngineMenuItem.addActionListener((evt) -> { - String enginePath = JOptionPane.showInputDialog(getParent(), - "Enter the path to a UCI-compatible chess engine:", - "Engine selection", - JOptionPane.QUESTION_MESSAGE); + String enginePath = JOptionPane + .showInputDialog(getParent(), "Enter the path to a UCI-compatible chess engine:", "Engine selection", JOptionPane.QUESTION_MESSAGE); if (enginePath != null) EngineUtil.addEngine(enginePath); }); @@ -83,7 +81,7 @@ public class MenuBar extends JMenuBar { JMenuItem exportFENMenuItem = new JMenuItem("Export board to FEN"); 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); JOptionPane.showMessageDialog(mainWindow, String.format("FEN-string copied to clipboard!%n%s", fen)); }); @@ -94,9 +92,16 @@ public class MenuBar extends JMenuBar { final GamePane gamePane = mainWindow.addGamePane(); final String fen = JOptionPane.showInputDialog("Enter a FEN string: "); DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> { - final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, new Board(fen)); - gamePane.setGame(game); - game.start(); + Game game; + try { + game = new Game(gamePane.getBoardPane(), whiteName, blackName, new FENString(fen).getBoard()); + gamePane.setGame(game); + game.start(); + } catch (ChessException e) { + e.printStackTrace(); + JOptionPane + .showMessageDialog(mainWindow, "Failed to load FEN string: " + e.toString(), "FEN loading error", JOptionPane.ERROR_MESSAGE); + } }); }); toolsMenu.add(loadFromFENMenuItem); @@ -113,9 +118,18 @@ public class MenuBar extends JMenuBar { final GamePane gamePane = mainWindow.addGamePane(name); final String fen = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> { - final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, new Board(fen)); - gamePane.setGame(game); - game.start(); + Game game; + try { + game = new Game(gamePane.getBoardPane(), whiteName, blackName, new FENString(fen).getBoard()); + gamePane.setGame(game); + game.start(); + } catch (ChessException e) { + e.printStackTrace(); + JOptionPane.showMessageDialog(mainWindow, + "Failed to load FEN string: " + e.toString(), + "FEN loading error", + JOptionPane.ERROR_MESSAGE); + } }); } catch (Exception e) { e.printStackTrace(); @@ -134,14 +148,10 @@ public class MenuBar extends JMenuBar { String[] gameNames = new String[pgnDB.getGames().size()]; for (int i = 0; i < gameNames.length; i++) { final PGNGame game = pgnDB.getGames().get(i); - gameNames[i] = String.format("%s vs %s: %s", - game.getTag("White"), - game.getTag("Black"), - game.getTag("Result")); + gameNames[i] = String.format("%s vs %s: %s", game.getTag("White"), game.getTag("Black"), game.getTag("Result")); } JComboBox comboBox = new JComboBox<>(gameNames); - JOptionPane - .showInputDialog(mainWindow, comboBox, "Select a game", JOptionPane.QUESTION_MESSAGE); + JOptionPane.showInputDialog(mainWindow, comboBox, "Select a game", JOptionPane.QUESTION_MESSAGE); DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> { final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, pgnDB.getGames().get(comboBox.getSelectedIndex()).getBoard()); diff --git a/test/dev/kske/chess/board/FENStringTest.java b/test/dev/kske/chess/board/FENStringTest.java index 8d2810f..b2c1e43 100644 --- a/test/dev/kske/chess/board/FENStringTest.java +++ b/test/dev/kske/chess/board/FENStringTest.java @@ -35,7 +35,7 @@ class FENStringTest { */ @BeforeEach void setUp() throws Exception { - fenStrings.addAll(Arrays.asList("rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R b - - 1 2")); + 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)); From ada382aee632680219f18e1fb75eb25b90819c99 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Fri, 25 Oct 2019 16:58:18 +0200 Subject: [PATCH 7/7] Moved loadFile method to MainWindow, removed redundancies --- src/dev/kske/chess/ui/DialogUtil.java | 21 ++++++- src/dev/kske/chess/ui/FENDropTarget.java | 66 ------------------- src/dev/kske/chess/ui/GameDropTarget.java | 35 +++++++++++ src/dev/kske/chess/ui/MainWindow.java | 75 ++++++++++++++++++++-- src/dev/kske/chess/ui/MenuBar.java | 77 +---------------------- 5 files changed, 123 insertions(+), 151 deletions(-) delete mode 100644 src/dev/kske/chess/ui/FENDropTarget.java create mode 100644 src/dev/kske/chess/ui/GameDropTarget.java diff --git a/src/dev/kske/chess/ui/DialogUtil.java b/src/dev/kske/chess/ui/DialogUtil.java index e69f3d7..f591881 100644 --- a/src/dev/kske/chess/ui/DialogUtil.java +++ b/src/dev/kske/chess/ui/DialogUtil.java @@ -15,6 +15,7 @@ import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.filechooser.FileFilter; import dev.kske.chess.io.EngineUtil; @@ -28,11 +29,25 @@ public class DialogUtil { private DialogUtil() {} - public static void showFileSelectionDialog(Component parent, Consumer action) { + public static void showFileSelectionDialog(Component parent, Consumer> action) { JFileChooser fileChooser = new JFileChooser(); fileChooser.setCurrentDirectory(new File(System.getProperty("user.home"))); - if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) - action.accept(fileChooser.getSelectedFile()); + fileChooser.setAcceptAllFileFilterUsed(false); + fileChooser.addChoosableFileFilter(new FileFilter() { + + @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())); } public static void showGameConfigurationDialog(Component parent, BiConsumer action) { diff --git a/src/dev/kske/chess/ui/FENDropTarget.java b/src/dev/kske/chess/ui/FENDropTarget.java deleted file mode 100644 index aea43b3..0000000 --- a/src/dev/kske/chess/ui/FENDropTarget.java +++ /dev/null @@ -1,66 +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 javax.swing.JOptionPane; - -import dev.kske.chess.board.FENString; -import dev.kske.chess.exception.ChessException; -import dev.kske.chess.game.Game; - -/** - * Project: Chess
- * File: FENDropTarget.java
- * Created: 13 Aug 2019
- * Author: Kai S. K. Engelbart - */ -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) 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(); - DialogUtil.showGameConfigurationDialog(null, (whiteName, blackName) -> { - Game game; - try { - game = new Game(gamePane.getBoardPane(), whiteName, blackName, new FENString(fen).getBoard()); - gamePane.setGame(game); - game.start(); - } catch (ChessException e) { - e.printStackTrace(); - JOptionPane.showMessageDialog(mainWindow, - "Failed to load FEN string: " + e.toString(), - "FEN loading error", - JOptionPane.ERROR_MESSAGE); - } - }); - evt.dropComplete(true); - } catch (IOException e) { - e.printStackTrace(); - evt.rejectDrop(); - } - }); - } catch (UnsupportedFlavorException | IOException ex) { - ex.printStackTrace(); - evt.rejectDrop(); - } - } -} diff --git a/src/dev/kske/chess/ui/GameDropTarget.java b/src/dev/kske/chess/ui/GameDropTarget.java new file mode 100644 index 0000000..c6da82b --- /dev/null +++ b/src/dev/kske/chess/ui/GameDropTarget.java @@ -0,0 +1,35 @@ +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: Chess
+ * File: GameDropTarget.java
+ * Created: 13 Aug 2019
+ * 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) evt.getTransferable().getTransferData(DataFlavor.javaFileListFlavor)); + } catch (UnsupportedFlavorException | IOException ex) { + ex.printStackTrace(); + evt.rejectDrop(); + } + } +} diff --git a/src/dev/kske/chess/ui/MainWindow.java b/src/dev/kske/chess/ui/MainWindow.java index edaa113..cb6271f 100644 --- a/src/dev/kske/chess/ui/MainWindow.java +++ b/src/dev/kske/chess/ui/MainWindow.java @@ -3,10 +3,23 @@ package dev.kske.chess.ui; import java.awt.EventQueue; import java.awt.Toolkit; import java.awt.dnd.DropTarget; +import java.io.File; +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.JOptionPane; 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: Chess
* File: MainWindow.java
@@ -55,7 +68,7 @@ public class MainWindow extends JFrame { getContentPane().add(tabbedPane); setJMenuBar(new MenuBar(this)); - new DropTarget(this, new FENDropTarget(this)); + new DropTarget(this, new GameDropTarget(this)); // Update position and dimensions pack(); @@ -74,9 +87,7 @@ public class MainWindow extends JFrame { * * @return The new {@link GamePane} */ - public GamePane addGamePane() { - return addGamePane("Game " + (tabbedPane.getComponentCount() + 1)); - } + public GamePane addGamePane() { return addGamePane("Game " + (tabbedPane.getComponentCount() + 1)); } /** * Creates a new {@link GamePane}, adds it to the tabbed pane and opens it. @@ -91,12 +102,64 @@ public class MainWindow extends JFrame { 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. * * @param index The index of the {@link GamePane} to remove */ - public void removeGamePane(int index) { - tabbedPane.remove(index); + public void removeGamePane(int index) { tabbedPane.remove(index); } + + /** + * Loads a game file (FEN or PGN) and adds it to a new {@link GamePane}. + * + * @param files the files to load the game from + */ + public void loadFiles(List 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 comboBox = new JComboBox<>(gameNames); + JOptionPane.showInputDialog(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); + } + }); } } diff --git a/src/dev/kske/chess/ui/MenuBar.java b/src/dev/kske/chess/ui/MenuBar.java index 8e796db..a224268 100644 --- a/src/dev/kske/chess/ui/MenuBar.java +++ b/src/dev/kske/chess/ui/MenuBar.java @@ -2,11 +2,7 @@ package dev.kske.chess.ui; import java.awt.Toolkit; import java.awt.datatransfer.StringSelection; -import java.io.File; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import javax.swing.JComboBox; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; @@ -16,8 +12,6 @@ import dev.kske.chess.board.FENString; import dev.kske.chess.exception.ChessException; import dev.kske.chess.game.Game; import dev.kske.chess.io.EngineUtil; -import dev.kske.chess.pgn.PGNDatabase; -import dev.kske.chess.pgn.PGNGame; /** * Project: Chess
@@ -52,7 +46,7 @@ public class MenuBar extends JMenuBar { gameMenu.add(newGameMenuItem); JMenuItem loadFileMenu = new JMenuItem("Load game file"); - loadFileMenu.addActionListener((evt) -> DialogUtil.showFileSelectionDialog(mainWindow, this::loadFile)); + loadFileMenu.addActionListener((evt) -> DialogUtil.showFileSelectionDialog(mainWindow, mainWindow::loadFiles)); gameMenu.add(loadFileMenu); add(gameMenu); @@ -108,73 +102,4 @@ public class MenuBar extends JMenuBar { add(toolsMenu); } - - private void loadFile(File file) { - final String name = file.getName().substring(0, file.getName().lastIndexOf('.')); - final String extension = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase(); - switch (extension) { - case ".fen": - try { - final GamePane gamePane = mainWindow.addGamePane(name); - final String fen = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); - DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> { - Game game; - try { - game = new Game(gamePane.getBoardPane(), whiteName, blackName, new FENString(fen).getBoard()); - gamePane.setGame(game); - game.start(); - } catch (ChessException e) { - e.printStackTrace(); - JOptionPane.showMessageDialog(mainWindow, - "Failed to load FEN string: " + e.toString(), - "FEN loading error", - JOptionPane.ERROR_MESSAGE); - } - }); - } catch (Exception e) { - e.printStackTrace(); - JOptionPane.showMessageDialog(mainWindow, - "Failed to load the file " + file.getName() + ": " + e.toString(), - "File loading error", - JOptionPane.ERROR_MESSAGE); - } - break; - case ".pgn": - try { - final GamePane gamePane = mainWindow.addGamePane(name); - 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 comboBox = new JComboBox<>(gameNames); - JOptionPane.showInputDialog(mainWindow, comboBox, "Select a game", JOptionPane.QUESTION_MESSAGE); - DialogUtil.showGameConfigurationDialog(mainWindow, (whiteName, blackName) -> { - final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, - pgnDB.getGames().get(comboBox.getSelectedIndex()).getBoard()); - game.start(); - }); - } - } catch (Exception e) { - e.printStackTrace(); - JOptionPane.showMessageDialog(mainWindow, - "Failed to load the file " + file.getName() + ": " + e.toString(), - "File loading error", - JOptionPane.ERROR_MESSAGE); - } - break; - default: - JOptionPane.showMessageDialog(mainWindow, - "The file extension '" + extension + "' is not supported!", - "File loading error", - JOptionPane.ERROR_MESSAGE); - } - } - - public void loadFENFile(File file) { - - } }