From 966c959786603cdca349a92a1ec3f43e22ef14a3 Mon Sep 17 00:00:00 2001 From: kske Date: Fri, 25 Oct 2019 11:52:48 +0200 Subject: [PATCH] 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));