2019-10-09 21:03:39 +02:00
|
|
|
package dev.kske.chess.pgn;
|
|
|
|
|
2019-11-07 05:53:28 +01:00
|
|
|
import java.io.PrintWriter;
|
2020-01-19 22:12:33 +01:00
|
|
|
import java.util.*;
|
2019-10-09 21:03:39 +02:00
|
|
|
import java.util.regex.MatchResult;
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
|
|
import dev.kske.chess.board.Board;
|
2019-10-25 11:52:48 +02:00
|
|
|
import dev.kske.chess.board.FENString;
|
2019-12-06 23:54:11 +01:00
|
|
|
import dev.kske.chess.board.Move;
|
2019-12-08 22:15:29 +01:00
|
|
|
import dev.kske.chess.board.Piece.Color;
|
2019-10-09 21:03:39 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Project: <strong>Chess</strong><br>
|
|
|
|
* File: <strong>PGNGame.java</strong><br>
|
|
|
|
* Created: <strong>22 Sep 2019</strong><br>
|
2019-12-06 23:54:11 +01:00
|
|
|
*
|
2019-10-26 07:55:21 +02:00
|
|
|
* @since Chess v0.5-alpha
|
|
|
|
* @author Kai S. K. Engelbart
|
2019-10-09 21:03:39 +02:00
|
|
|
*/
|
|
|
|
public class PGNGame {
|
|
|
|
|
|
|
|
private final Map<String, String> tagPairs = new HashMap<>(7);
|
2019-11-08 15:22:12 +01:00
|
|
|
private final Board board;
|
|
|
|
|
2020-01-19 22:12:33 +01:00
|
|
|
/**
|
|
|
|
* Creates an instance of {@link PGNGame}. A new default {@link Board} will be
|
|
|
|
* created.
|
|
|
|
*/
|
2019-11-08 15:22:12 +01:00
|
|
|
public PGNGame() { board = new Board(); }
|
|
|
|
|
2020-01-19 22:12:33 +01:00
|
|
|
/**
|
|
|
|
* Creates an instance of {@link PGNGame}.
|
|
|
|
*
|
|
|
|
* @param board the board associated with the game
|
|
|
|
*/
|
2019-11-08 15:22:12 +01:00
|
|
|
public PGNGame(Board board) { this.board = board; }
|
2019-10-09 21:03:39 +02:00
|
|
|
|
2020-01-19 22:12:33 +01:00
|
|
|
/**
|
|
|
|
* Parses a game in {@code PGN} format from a {@link Scanner} instance
|
|
|
|
*
|
|
|
|
* @param sc the {@link Scanner} to parse the game from, which is not closed
|
|
|
|
* after this process
|
|
|
|
* @return the parsed {@link PGNGame}
|
|
|
|
*/
|
|
|
|
public static PGNGame parse(Scanner sc) {
|
2019-10-09 21:03:39 +02:00
|
|
|
PGNGame game = new PGNGame();
|
|
|
|
|
|
|
|
MatchResult matchResult;
|
|
|
|
Pattern tagPairPattern = Pattern.compile("\\[(\\w+) \"(.*)\"]"),
|
2019-10-20 08:52:51 +02:00
|
|
|
movePattern = Pattern.compile("\\d+\\.\\s+(?:(?:(\\S+)\\s+(\\S+))|(?:O-O-O)|(?:O-O))(?:\\+{0,2}|\\#)"),
|
2019-10-25 11:52:48 +02:00
|
|
|
nagPattern = Pattern.compile("(\\$\\d{1,3})*"), terminationMarkerPattern = Pattern.compile("1-0|0-1|1\\/2-1\\/2|\\*");
|
2019-10-09 21:03:39 +02:00
|
|
|
|
|
|
|
// Parse tag pairs
|
2019-10-20 08:52:51 +02:00
|
|
|
while (sc.findInLine(tagPairPattern) != null) {
|
|
|
|
matchResult = sc.match();
|
|
|
|
if (matchResult.groupCount() == 2) game.setTag(matchResult.group(1), matchResult.group(2));
|
|
|
|
else break;
|
2019-10-09 21:03:39 +02:00
|
|
|
sc.nextLine();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse movetext
|
|
|
|
while (true) {
|
|
|
|
// Skip NAG (Numeric Annotation Glyph)
|
|
|
|
sc.skip(nagPattern);
|
|
|
|
|
|
|
|
// TODO: Parse RAV (Recursive Annotation Variation)
|
|
|
|
|
2019-10-20 08:52:51 +02:00
|
|
|
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));
|
2019-10-25 11:52:48 +02:00
|
|
|
System.out.println(game.getBoard().getLog().getLast().move.toLAN() + ": " + new FENString(game.board).toString());
|
2019-10-20 08:52:51 +02:00
|
|
|
}
|
|
|
|
else break;
|
|
|
|
} else break;
|
2019-10-09 21:03:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Parse game termination marker
|
2019-10-25 11:52:48 +02:00
|
|
|
if (sc.findWithinHorizon(terminationMarkerPattern, 20) == null) System.err.println("Termination marker expected");
|
2019-10-09 21:03:39 +02:00
|
|
|
|
|
|
|
return game;
|
|
|
|
}
|
|
|
|
|
2020-01-19 22:12:33 +01:00
|
|
|
/**
|
|
|
|
* Serializes this game to {@code PGN} format.
|
|
|
|
*
|
|
|
|
* @param pw the writer to write the game to
|
|
|
|
*/
|
2019-11-07 05:53:28 +01:00
|
|
|
public void writePGN(PrintWriter pw) {
|
2019-11-08 15:22:12 +01:00
|
|
|
// Set the unknown result tag if no result tag is specified
|
|
|
|
tagPairs.putIfAbsent("Result", "*");
|
|
|
|
|
2019-11-07 05:53:28 +01:00
|
|
|
// Write tag pairs
|
|
|
|
tagPairs.forEach((k, v) -> pw.printf("[%s \"%s\"]%n", k, v));
|
|
|
|
|
2019-11-08 15:22:12 +01:00
|
|
|
// Insert newline if tags were printed
|
|
|
|
if (!tagPairs.isEmpty()) pw.println();
|
|
|
|
|
2019-12-10 21:27:06 +01:00
|
|
|
if (!board.getLog().isEmpty()) {
|
|
|
|
// Collect SAN moves
|
|
|
|
Board clone = new Board(board, true);
|
|
|
|
List<String> chunks = new ArrayList<>();
|
|
|
|
boolean flag = true;
|
|
|
|
while (flag) {
|
|
|
|
Move move = clone.getLog().getLast().move;
|
|
|
|
flag = clone.getLog().hasParent();
|
|
|
|
clone.revert();
|
|
|
|
String chunk = clone.getLog().getActiveColor() == Color.WHITE ? String.format(" %d. ", clone.getLog().getFullmoveNumber()) : " ";
|
|
|
|
chunk += move.toSAN(clone);
|
|
|
|
chunks.add(chunk);
|
2019-12-08 22:15:29 +01:00
|
|
|
}
|
2019-12-10 21:27:06 +01:00
|
|
|
Collections.reverse(chunks);
|
|
|
|
|
|
|
|
// Write movetext
|
|
|
|
String line = "";
|
|
|
|
for (String chunk : chunks)
|
|
|
|
if (line.length() + chunk.length() <= 80) line += chunk;
|
|
|
|
else {
|
|
|
|
pw.println(line);
|
2019-12-11 07:45:39 +01:00
|
|
|
line = chunk;
|
2019-12-10 21:27:06 +01:00
|
|
|
}
|
|
|
|
if (!line.isEmpty()) pw.println(line);
|
|
|
|
}
|
2019-11-08 15:22:12 +01:00
|
|
|
// Write game termination marker
|
|
|
|
pw.print(tagPairs.get("Result"));
|
2019-11-07 05:53:28 +01:00
|
|
|
}
|
|
|
|
|
2020-01-19 22:12:33 +01:00
|
|
|
/**
|
|
|
|
* @param tagName the name of a game tag
|
|
|
|
* @return the value of the game tag
|
|
|
|
*/
|
2019-10-25 11:52:48 +02:00
|
|
|
public String getTag(String tagName) { return tagPairs.get(tagName); }
|
2019-10-09 21:03:39 +02:00
|
|
|
|
2020-01-19 22:12:33 +01:00
|
|
|
/**
|
|
|
|
* @param tagName the name of a game tag
|
|
|
|
* @return {@code true} if the tag is present
|
|
|
|
*/
|
2019-10-25 11:52:48 +02:00
|
|
|
public boolean hasTag(String tagName) { return tagPairs.containsKey(tagName); }
|
2019-10-09 21:03:39 +02:00
|
|
|
|
2020-01-19 22:12:33 +01:00
|
|
|
/**
|
|
|
|
* Sets a game tag.
|
|
|
|
*
|
|
|
|
* @param tagName the name of the tag
|
|
|
|
* @param tagValue the value of the tag
|
|
|
|
*/
|
2019-10-25 11:52:48 +02:00
|
|
|
public void setTag(String tagName, String tagValue) { tagPairs.put(tagName, tagValue); }
|
2019-10-14 06:31:03 +02:00
|
|
|
|
2020-01-19 22:12:33 +01:00
|
|
|
/**
|
|
|
|
* @return the board associated with this game
|
|
|
|
*/
|
2019-10-14 06:31:03 +02:00
|
|
|
public Board getBoard() { return board; }
|
2019-10-09 21:03:39 +02:00
|
|
|
}
|