Improved PGN parsing, added PGN file loading
This commit is contained in:
parent
02a4b3ee32
commit
84158c2dac
@ -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;
|
||||
}
|
||||
});
|
||||
|
@ -36,8 +36,8 @@ public class Game {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -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; }
|
||||
}
|
||||
|
@ -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) {
|
||||
while (sc.findInLine(tagPairPattern) != null) {
|
||||
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;
|
||||
sc.nextLine();
|
||||
}
|
||||
|
||||
@ -46,19 +44,19 @@ public class PGNGame {
|
||||
|
||||
// TODO: Parse RAV (Recursive Annotation Variation)
|
||||
|
||||
sc.findWithinHorizon(movePattern, 20);
|
||||
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.board.toFEN());
|
||||
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;
|
||||
}
|
||||
|
@ -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();
|
||||
});
|
||||
|
@ -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;
|
||||
|
@ -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 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();
|
||||
});
|
||||
|
Reference in New Issue
Block a user