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 86b696ef4c
commit cbf110c28d
Signed by: kske
GPG Key ID: 8BEB13EC5DF7EF13
7 changed files with 79 additions and 45 deletions

View File

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

View File

@ -32,12 +32,12 @@ public class Game {
private BoardComponent boardComponent;
public Game(BoardPane boardPane, String whiteName, String blackName) {
board = new Board();
board = new Board();
init(boardPane, whiteName, blackName);
}
public Game(BoardPane boardPane, String whiteName, String blackName, String fen) {
board = new Board(fen);
public Game(BoardPane boardPane, String whiteName, String blackName, Board board) {
this.board = board;
init(boardPane, whiteName, blackName);
}

View File

@ -18,12 +18,14 @@ public class PGNDatabase {
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)) {
while (sc.hasNext())
games.add(PGNGame.parse(sc));
} 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;
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})*"),
terminationMarkerPattern = Pattern.compile("1-0|0-1|1\\/2-1\\/2|\\*");
// Parse tag pairs
while (true) {
if (sc.findInLine(tagPairPattern) != null) {
matchResult = sc.match();
if (matchResult.groupCount() == 2) game.setTag(matchResult.group(0), matchResult.group(1));
else break;
} else break;
while (sc.findInLine(tagPairPattern) != null) {
matchResult = sc.match();
if (matchResult.groupCount() == 2) game.setTag(matchResult.group(1), matchResult.group(2));
else break;
sc.nextLine();
}
@ -46,19 +44,19 @@ public class PGNGame {
// TODO: Parse RAV (Recursive Annotation Variation)
sc.findWithinHorizon(movePattern, 20);
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.board.toFEN());
}
else break;
if (sc.findWithinHorizon(movePattern, 20) != null) {
matchResult = sc.match();
if (matchResult.groupCount() > 0) for (int i = 1; i < matchResult.groupCount() + 1; i++) {
game.board.move(matchResult.group(i));
System.out.println(game.getBoard().getLog().getLast().move.toLAN() + ": " + game.board.toFEN());
}
else break;
} else break;
}
// Parse game termination marker
if (sc.hasNext(terminationMarkerPattern)) {
sc.next(terminationMarkerPattern);
} else throw new ChessException("Game termination marker expected");
if (sc.findWithinHorizon(terminationMarkerPattern, 20) == null)
System.err.println("Termination marker expected");
return game;
}

View File

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

View File

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

View File

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