Improved PGN parsing, added PGN file loading

This commit is contained in:
Kai S. K. Engelbart 2019-10-20 08:52:51 +02:00
parent 02a4b3ee32
commit 84158c2dac
7 changed files with 79 additions and 45 deletions

View File

@ -154,8 +154,6 @@ public class Board {
// Move the rook // Move the rook
Move rookMove = move.dest.x == 6 ? new Move(7, move.pos.y, 5, move.pos.y) // Kingside Move rookMove = move.dest.x == 6 ? new Move(7, move.pos.y, 5, move.pos.y) // Kingside
: new Move(0, move.pos.y, 3, move.pos.y); // Queenside : new Move(0, move.pos.y, 3, move.pos.y); // Queenside
// Move the rook
setDest(rookMove, getPos(rookMove)); setDest(rookMove, getPos(rookMove));
setPos(rookMove, null); setPos(rookMove, null);
@ -193,18 +191,22 @@ public class Board {
Map<String, Pattern> patterns = new HashMap<>(); Map<String, Pattern> patterns = new HashMap<>();
patterns.put("pieceMove", patterns.put("pieceMove",
Pattern.compile( Pattern.compile(
"^(?<pieceType>[NBRQK])(?:(?<fromFile>[a-h])|(?<fromRank>[1-8])|(?<fromSquare>[a-h][1-8]))?x?(?<toSquare>[a-h][1-8])$")); "^(?<pieceType>[NBRQK])(?:(?<fromFile>[a-h])|(?<fromRank>[1-8])|(?<fromSquare>[a-h][1-8]))?x?(?<toSquare>[a-h][1-8])(?:\\+{0,2}|\\#)$"));
patterns.put("pawnCapture", patterns.put("pawnCapture",
Pattern Pattern
.compile("^(?<fromFile>[a-h])(?<fromRank>[1-8])?x(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQK])?$")); .compile(
patterns.put("pawnPush", Pattern.compile("^(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQK])?$")); "^(?<fromFile>[a-h])(?<fromRank>[1-8])?x(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQK])?(?:\\+{0,2}|\\#)?$"));
patterns.put("pawnPush", Pattern.compile("^(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQK])?(?:\\+{0,2}|\\#)$"));
patterns.put("castling", Pattern.compile("^(?<queenside>O-O-O)|(?<kingside>O-O)(?:\\+{0,2}|\\#)?$"));
patterns.forEach((patternName, pattern) -> { patterns.forEach((patternName, pattern) -> {
Matcher m = pattern.matcher(sanMove); Matcher m = pattern.matcher(sanMove);
if (m.find()) { if (m.find()) {
Position pos = null, dest = Position.fromLAN(m.group("toSquare")); Position pos = null, dest = null;
Move.Type moveType = Move.Type.NORMAL;
switch (patternName) { switch (patternName) {
case "pieceMove": case "pieceMove":
dest = Position.fromLAN(m.group("toSquare"));
if (m.group("fromSquare") != null) pos = Position.fromLAN(m.group("fromSquare")); if (m.group("fromSquare") != null) pos = Position.fromLAN(m.group("fromSquare"));
else { else {
Type type = Type.fromFirstChar(m.group("pieceType").charAt(0)); Type type = Type.fromFirstChar(m.group("pieceType").charAt(0));
@ -222,12 +224,14 @@ public class Board {
} }
break; break;
case "pawnCapture": case "pawnCapture":
dest = Position.fromLAN(m.group("toSquare"));
char file = m.group("fromFile").charAt(0); char file = m.group("fromFile").charAt(0);
int rank = m.group("fromRank") == null ? get(Type.PAWN, file) int rank = m.group("fromRank") == null ? get(Type.PAWN, file)
: Integer.parseInt(m.group("fromRank")); : Integer.parseInt(m.group("fromRank"));
pos = Position.fromLAN(String.format("%c%d", file, rank)); pos = Position.fromLAN(String.format("%c%d", file, rank));
break; break;
case "pawnPush": case "pawnPush":
dest = Position.fromLAN(m.group("toSquare"));
// TODO: Pawn promotion // TODO: Pawn promotion
int step = log.getActiveColor() == Color.WHITE ? 1 : -1; int step = log.getActiveColor() == Color.WHITE ? 1 : -1;
@ -236,8 +240,13 @@ public class Board {
// Double step forward // Double step forward
else pos = new Position(dest.x, dest.y + 2 * step); else pos = new Position(dest.x, dest.y + 2 * step);
break; break;
case "castling":
pos = new Position(4, log.getActiveColor() == Color.WHITE ? 7 : 0);
dest = new Position(m.group("kingside") != null ? 6 : 2, pos.y);
moveType = Move.Type.CASTLING;
break;
} }
move(new Move(pos, dest)); move(new Move(pos, dest, moveType));
return; return;
} }
}); });

View File

@ -36,8 +36,8 @@ public class Game {
init(boardPane, whiteName, blackName); init(boardPane, whiteName, blackName);
} }
public Game(BoardPane boardPane, String whiteName, String blackName, String fen) { public Game(BoardPane boardPane, String whiteName, String blackName, Board board) {
board = new Board(fen); this.board = board;
init(boardPane, whiteName, blackName); init(boardPane, whiteName, blackName);
} }

View File

@ -18,12 +18,14 @@ public class PGNDatabase {
private final List<PGNGame> games = new ArrayList<>(); private final List<PGNGame> games = new ArrayList<>();
public void load(File pgnFile) { public void load(File pgnFile) throws FileNotFoundException, ChessException {
try (Scanner sc = new Scanner(pgnFile)) { try (Scanner sc = new Scanner(pgnFile)) {
while (sc.hasNext()) while (sc.hasNext())
games.add(PGNGame.parse(sc)); games.add(PGNGame.parse(sc));
} catch (FileNotFoundException | ChessException e) { } catch (FileNotFoundException | ChessException e) {
e.printStackTrace(); throw e;
} }
} }
public List<PGNGame> getGames() { return games; }
} }

View File

@ -25,17 +25,15 @@ public class PGNGame {
MatchResult matchResult; MatchResult matchResult;
Pattern tagPairPattern = Pattern.compile("\\[(\\w+) \"(.*)\"]"), Pattern tagPairPattern = Pattern.compile("\\[(\\w+) \"(.*)\"]"),
movePattern = Pattern.compile("\\d+\\. (\\S+)\\s(\\S+)"), movePattern = Pattern.compile("\\d+\\.\\s+(?:(?:(\\S+)\\s+(\\S+))|(?:O-O-O)|(?:O-O))(?:\\+{0,2}|\\#)"),
nagPattern = Pattern.compile("(\\$\\d{1,3})*"), nagPattern = Pattern.compile("(\\$\\d{1,3})*"),
terminationMarkerPattern = Pattern.compile("1-0|0-1|1\\/2-1\\/2|\\*"); terminationMarkerPattern = Pattern.compile("1-0|0-1|1\\/2-1\\/2|\\*");
// Parse tag pairs // Parse tag pairs
while (true) { while (sc.findInLine(tagPairPattern) != null) {
if (sc.findInLine(tagPairPattern) != null) {
matchResult = sc.match(); matchResult = sc.match();
if (matchResult.groupCount() == 2) game.setTag(matchResult.group(0), matchResult.group(1)); if (matchResult.groupCount() == 2) game.setTag(matchResult.group(1), matchResult.group(2));
else break; else break;
} else break;
sc.nextLine(); sc.nextLine();
} }
@ -46,19 +44,19 @@ public class PGNGame {
// TODO: Parse RAV (Recursive Annotation Variation) // TODO: Parse RAV (Recursive Annotation Variation)
sc.findWithinHorizon(movePattern, 20); if (sc.findWithinHorizon(movePattern, 20) != null) {
matchResult = sc.match(); matchResult = sc.match();
if (matchResult.groupCount() > 0) for (int i = 1; i < matchResult.groupCount() + 1; i++) { if (matchResult.groupCount() > 0) for (int i = 1; i < matchResult.groupCount() + 1; i++) {
game.board.move(matchResult.group(i)); game.board.move(matchResult.group(i));
System.out.println(game.board.toFEN()); System.out.println(game.getBoard().getLog().getLast().move.toLAN() + ": " + game.board.toFEN());
} }
else break; else break;
} else break;
} }
// Parse game termination marker // Parse game termination marker
if (sc.hasNext(terminationMarkerPattern)) { if (sc.findWithinHorizon(terminationMarkerPattern, 20) == null)
sc.next(terminationMarkerPattern); System.err.println("Termination marker expected");
} else throw new ChessException("Game termination marker expected");
return game; return game;
} }

View File

@ -11,6 +11,7 @@ import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import dev.kske.chess.board.Board;
import dev.kske.chess.game.Game; import dev.kske.chess.game.Game;
/** /**
@ -37,7 +38,7 @@ public class FENDropTarget extends DropTargetAdapter {
final GamePane gamePane = mainWindow.addGamePane(); final GamePane gamePane = mainWindow.addGamePane();
final String fen = br.readLine(); final String fen = br.readLine();
DialogUtil.showGameConfigurationDialog((whiteName, blackName) -> { DialogUtil.showGameConfigurationDialog((whiteName, blackName) -> {
final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, fen); final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, new Board(fen));
gamePane.setGame(game); gamePane.setGame(game);
game.start(); game.start();
}); });

View File

@ -3,7 +3,6 @@ package dev.kske.chess.ui;
import java.awt.GridBagConstraints; import java.awt.GridBagConstraints;
import java.awt.GridBagLayout; import java.awt.GridBagLayout;
import java.awt.GridLayout; import java.awt.GridLayout;
import java.awt.Insets;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -70,7 +69,6 @@ public class GamePane extends JComponent {
toolPanel.add(btnSwapColors); toolPanel.add(btnSwapColors);
GridBagConstraints gbc_toolPanel = new GridBagConstraints(); GridBagConstraints gbc_toolPanel = new GridBagConstraints();
gbc_toolPanel.insets = new Insets(0, 0, 5, 5);
gbc_toolPanel.anchor = GridBagConstraints.NORTH; gbc_toolPanel.anchor = GridBagConstraints.NORTH;
gbc_toolPanel.fill = GridBagConstraints.HORIZONTAL; gbc_toolPanel.fill = GridBagConstraints.HORIZONTAL;
gbc_toolPanel.gridx = 0; gbc_toolPanel.gridx = 0;
@ -80,7 +78,6 @@ public class GamePane extends JComponent {
moveSelectionPanel = new JPanel(); moveSelectionPanel = new JPanel();
GridBagConstraints gbc_moveSelectionPanel = new GridBagConstraints(); GridBagConstraints gbc_moveSelectionPanel = new GridBagConstraints();
gbc_moveSelectionPanel.insets = new Insets(0, 0, 5, 0);
gbc_moveSelectionPanel.fill = GridBagConstraints.BOTH; gbc_moveSelectionPanel.fill = GridBagConstraints.BOTH;
gbc_moveSelectionPanel.gridx = 2; gbc_moveSelectionPanel.gridx = 2;
gbc_moveSelectionPanel.gridy = 0; gbc_moveSelectionPanel.gridy = 0;
@ -103,7 +100,6 @@ public class GamePane extends JComponent {
moveSelectionPanel.add(btnLast); moveSelectionPanel.add(btnLast);
boardPane = new BoardPane(); boardPane = new BoardPane();
GridBagConstraints gbc_boardPane = new GridBagConstraints(); GridBagConstraints gbc_boardPane = new GridBagConstraints();
gbc_boardPane.insets = new Insets(0, 0, 5, 5);
gbc_boardPane.fill = GridBagConstraints.BOTH; gbc_boardPane.fill = GridBagConstraints.BOTH;
gbc_boardPane.gridx = 0; gbc_boardPane.gridx = 0;
gbc_boardPane.gridy = 1; gbc_boardPane.gridy = 1;
@ -111,7 +107,6 @@ public class GamePane extends JComponent {
JPanel numberPanel = new JPanel(new GridLayout(8, 1)); JPanel numberPanel = new JPanel(new GridLayout(8, 1));
GridBagConstraints gbc_numberPanel = new GridBagConstraints(); GridBagConstraints gbc_numberPanel = new GridBagConstraints();
gbc_numberPanel.insets = new Insets(0, 0, 5, 5);
gbc_numberPanel.anchor = GridBagConstraints.WEST; gbc_numberPanel.anchor = GridBagConstraints.WEST;
gbc_numberPanel.fill = GridBagConstraints.VERTICAL; gbc_numberPanel.fill = GridBagConstraints.VERTICAL;
gbc_numberPanel.gridx = 1; gbc_numberPanel.gridx = 1;
@ -120,7 +115,6 @@ public class GamePane extends JComponent {
JPanel letterPanel = new JPanel(new GridLayout(1, 8)); JPanel letterPanel = new JPanel(new GridLayout(1, 8));
GridBagConstraints gbc_letterPanel = new GridBagConstraints(); GridBagConstraints gbc_letterPanel = new GridBagConstraints();
gbc_letterPanel.insets = new Insets(0, 0, 0, 5);
gbc_letterPanel.anchor = GridBagConstraints.NORTH; gbc_letterPanel.anchor = GridBagConstraints.NORTH;
gbc_letterPanel.fill = GridBagConstraints.HORIZONTAL; gbc_letterPanel.fill = GridBagConstraints.HORIZONTAL;
gbc_letterPanel.gridx = 0; gbc_letterPanel.gridx = 0;
@ -137,7 +131,6 @@ public class GamePane extends JComponent {
JScrollPane scrollPane = new JScrollPane(); JScrollPane scrollPane = new JScrollPane();
GridBagConstraints gbc_scrollPane = new GridBagConstraints(); GridBagConstraints gbc_scrollPane = new GridBagConstraints();
gbc_scrollPane.insets = new Insets(0, 0, 5, 0);
gbc_scrollPane.fill = GridBagConstraints.BOTH; gbc_scrollPane.fill = GridBagConstraints.BOTH;
gbc_scrollPane.gridx = 2; gbc_scrollPane.gridx = 2;
gbc_scrollPane.gridy = 1; gbc_scrollPane.gridy = 1;

View File

@ -2,16 +2,19 @@ package dev.kske.chess.ui;
import java.awt.Toolkit; import java.awt.Toolkit;
import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.StringSelection;
import java.io.IOException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files; import java.nio.file.Files;
import javax.swing.JComboBox;
import javax.swing.JMenu; import javax.swing.JMenu;
import javax.swing.JMenuBar; import javax.swing.JMenuBar;
import javax.swing.JMenuItem; import javax.swing.JMenuItem;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import dev.kske.chess.board.Board;
import dev.kske.chess.game.Game; import dev.kske.chess.game.Game;
import dev.kske.chess.pgn.PGNDatabase;
import dev.kske.chess.pgn.PGNGame;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
@ -47,20 +50,20 @@ public class MenuBar extends JMenuBar {
JMenuItem loadFileMenu = new JMenuItem("Load game file"); JMenuItem loadFileMenu = new JMenuItem("Load game file");
loadFileMenu.addActionListener((evt) -> DialogUtil.showFileSelectionDialog(mainWindow, (file) -> { loadFileMenu.addActionListener((evt) -> DialogUtil.showFileSelectionDialog(mainWindow, (file) -> {
final String name = file.getName().substring(0, file.getName().lastIndexOf('.'));
final String extension = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase(); final String extension = file.getName().substring(file.getName().lastIndexOf('.')).toLowerCase();
switch (extension) { switch (extension) {
case ".fen": case ".fen":
try { try {
final GamePane gamePane = mainWindow final GamePane gamePane = mainWindow.addGamePane(name);
.addGamePane(file.getName().substring(0, file.getName().lastIndexOf('.')));
final String fen = new String(Files.readAllBytes(file.toPath()), final String fen = new String(Files.readAllBytes(file.toPath()),
StandardCharsets.UTF_8); StandardCharsets.UTF_8);
DialogUtil.showGameConfigurationDialog((whiteName, blackName) -> { DialogUtil.showGameConfigurationDialog((whiteName, blackName) -> {
final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, fen); final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, new Board(fen));
gamePane.setGame(game); gamePane.setGame(game);
game.start(); game.start();
}); });
} catch (IOException e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
JOptionPane.showMessageDialog(mainWindow, JOptionPane.showMessageDialog(mainWindow,
"Failed to load the file " + file.getName() + ": " + e.toString(), "Failed to load the file " + file.getName() + ": " + e.toString(),
@ -69,7 +72,35 @@ public class MenuBar extends JMenuBar {
} }
break; break;
case ".pgn": case ".pgn":
// TODO: Load board from 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<String> comboBox = new JComboBox<>(gameNames);
JOptionPane
.showMessageDialog(mainWindow, comboBox, "Select a game", JOptionPane.QUESTION_MESSAGE);
DialogUtil.showGameConfigurationDialog((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; break;
default: default:
JOptionPane.showMessageDialog(mainWindow, JOptionPane.showMessageDialog(mainWindow,
@ -119,7 +150,7 @@ public class MenuBar extends JMenuBar {
final GamePane gamePane = mainWindow.addGamePane(); final GamePane gamePane = mainWindow.addGamePane();
final String fen = JOptionPane.showInputDialog("Enter a FEN string: "); final String fen = JOptionPane.showInputDialog("Enter a FEN string: ");
DialogUtil.showGameConfigurationDialog((whiteName, blackName) -> { DialogUtil.showGameConfigurationDialog((whiteName, blackName) -> {
final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, fen); final Game game = new Game(gamePane.getBoardPane(), whiteName, blackName, new Board(fen));
gamePane.setGame(game); gamePane.setGame(game);
game.start(); game.start();
}); });