Implemented game serialization to the PGN format #16

Merged
CyB3RC0nN0R merged 9 commits from f/pgn_save into master 2019-12-11 07:53:46 +01:00
8 changed files with 65 additions and 45 deletions
Showing only changes of commit 719e4f99ef - Show all commits

View File

@ -13,7 +13,7 @@ import dev.kske.chess.board.Piece.Color;
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
* File: <strong>Board.java</strong><br> * File: <strong>Board.java</strong><br>
* Created: <strong>01.07.2019</strong><br> * Created: <strong>01.07.2019</strong><br>
* *
* @since Chess v0.1-alpha * @since Chess v0.1-alpha
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
*/ */
@ -32,11 +32,11 @@ public class Board {
* Creates a copy of another {@link Board} instance.<br> * Creates a copy of another {@link Board} instance.<br>
* The created object is a deep copy, but does not contain any move history * The created object is a deep copy, but does not contain any move history
* apart from the current {@link MoveNode}. * apart from the current {@link MoveNode}.
* *
* @param other The {@link Board} instance to copy * @param other The {@link Board} instance to copy
* @param copyVariations TODO
*/ */
public Board(Board other) { public Board(Board other, boolean copyVariations) {
boardArr = new Piece[8][8];
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++) { for (int j = 0; j < 8; j++) {
if (other.boardArr[i][j] == null) continue; if (other.boardArr[i][j] == null) continue;
@ -45,12 +45,16 @@ public class Board {
} }
kingPos.putAll(other.kingPos); kingPos.putAll(other.kingPos);
log = new Log(other.log, false); log = new Log(other.log, copyVariations);
// Synchronize the current move node with the board
while (log.getLast().hasVariations())
log.selectNextNode(0);
} }
/** /**
* Moves a piece across the board if the move is legal. * Moves a piece across the board if the move is legal.
* *
* @param move The move to execute * @param move The move to execute
* @return {@code true}, if the attempted move was legal and thus executed * @return {@code true}, if the attempted move was legal and thus executed
*/ */
@ -73,7 +77,7 @@ public class Board {
/** /**
* Moves a piece across the board without checking if the move is legal. * Moves a piece across the board without checking if the move is legal.
* *
* @param move The move to execute * @param move The move to execute
*/ */
public void move(Move move) { public void move(Move move) {
@ -92,7 +96,7 @@ public class Board {
/** /**
* Moves a piece across the board without checking if the move is legal. * Moves a piece across the board without checking if the move is legal.
* *
* @param sanMove The move to execute in SAN (Standard Algebraic Notation) * @param sanMove The move to execute in SAN (Standard Algebraic Notation)
*/ */
public void move(String sanMove) { public void move(String sanMove) {
@ -118,7 +122,7 @@ public class Board {
/** /**
* Generated every legal move for one color * Generated every legal move for one color
* *
* @param color The color to generate the moves for * @param color The color to generate the moves for
* @return A list of all legal moves * @return A list of all legal moves
*/ */
@ -134,7 +138,7 @@ public class Board {
/** /**
* Checks, if the king is in check. * Checks, if the king is in check.
* *
* @param color The color of the king to check * @param color The color of the king to check
* @return {@code true}, if the king is in check * @return {@code true}, if the king is in check
*/ */
@ -142,7 +146,7 @@ public class Board {
/** /**
* Checks, if a field can be attacked by pieces of a certain color. * Checks, if a field can be attacked by pieces of a certain color.
* *
* @param dest the field to check * @param dest the field to check
* @param color the color of a potential attacker piece * @param color the color of a potential attacker piece
* @return {@code true} if a move with the destination {@code dest} * @return {@code true} if a move with the destination {@code dest}
@ -159,7 +163,7 @@ public class Board {
/** /**
* Checks, if the king is in checkmate. * Checks, if the king is in checkmate.
* This requires the king to already be in check! * This requires the king to already be in check!
* *
* @param color The color of the king to check * @param color The color of the king to check
* @return {@code true}, if the king is in checkmate * @return {@code true}, if the king is in checkmate
*/ */
@ -256,7 +260,7 @@ public class Board {
/** /**
* Searches for a {@link Piece} inside a file (A - H). * Searches for a {@link Piece} inside a file (A - H).
* *
* @param pieceClass The class of the piece to search for * @param pieceClass The class of the piece to search for
* @param file The file in which to search for the piece * @param file The file in which to search for the piece
* @return The rank (1 - 8) of the first piece with the specified type and * @return The rank (1 - 8) of the first piece with the specified type and
@ -271,7 +275,7 @@ public class Board {
/** /**
* Searches for a {@link Piece} inside a rank (1 - 8). * Searches for a {@link Piece} inside a rank (1 - 8).
* *
* @param pieceClass The class of the piece to search for * @param pieceClass The class of the piece to search for
* @param rank The rank in which to search for the piece * @param rank The rank in which to search for the piece
* @return The file (A - H) of the first piece with the specified type and * @return The file (A - H) of the first piece with the specified type and
@ -287,7 +291,7 @@ public class Board {
/** /**
* Searches for a {@link Piece} that can move to a {@link Position}. * Searches for a {@link Piece} that can move to a {@link Position}.
* *
* @param pieceClass The class of the piece to search for * @param pieceClass The class of the piece to search for
* @param dest The destination that the piece is required to reach * @param dest The destination that the piece is required to reach
* @return The position of a piece that can move to the specified destination * @return The position of a piece that can move to the specified destination
@ -304,7 +308,7 @@ public class Board {
/** /**
* Places a piece at a position. * Places a piece at a position.
* *
* @param pos The position to place the piece at * @param pos The position to place the piece at
* @param piece The piece to place * @param piece The piece to place
*/ */
@ -324,7 +328,7 @@ public class Board {
/** /**
* Places a piece at the position of a move. * Places a piece at the position of a move.
* *
* @param move The move at which position to place the piece * @param move The move at which position to place the piece
* @param piece The piece to place * @param piece The piece to place
*/ */
@ -332,7 +336,7 @@ public class Board {
/** /**
* Places a piece at the destination of a move. * Places a piece at the destination of a move.
* *
* @param move The move at which destination to place the piece * @param move The move at which destination to place the piece
* @param piece The piece to place * @param piece The piece to place
*/ */

View File

@ -10,7 +10,7 @@ import dev.kske.chess.board.Piece.Color;
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
* File: <strong>Log.java</strong><br> * File: <strong>Log.java</strong><br>
* Created: <strong>09.07.2019</strong><br> * Created: <strong>09.07.2019</strong><br>
* *
* @since Chess v0.1-alpha * @since Chess v0.1-alpha
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
*/ */
@ -28,7 +28,7 @@ public class Log implements Iterable<MoveNode> {
/** /**
* Creates a (partially deep) copy of another {@link Log} instance which begins * Creates a (partially deep) copy of another {@link Log} instance which begins
* with the current {@link MoveNode}. * with the current {@link MoveNode}.
* *
* @param other The {@link Log} instance to copy * @param other The {@link Log} instance to copy
* @param copyVariations If set to {@code true}, subsequent variations of the * @param copyVariations If set to {@code true}, subsequent variations of the
* current {@link MoveNode} are copied with the * current {@link MoveNode} are copied with the
@ -43,7 +43,7 @@ public class Log implements Iterable<MoveNode> {
// The new root is the current node of the copied instance // The new root is the current node of the copied instance
if (!other.isEmpty()) { if (!other.isEmpty()) {
root = new MoveNode(other.current, copyVariations); root = new MoveNode(other.root, copyVariations);
root.setParent(null); root.setParent(null);
current = root; current = root;
} }
@ -76,7 +76,7 @@ public class Log implements Iterable<MoveNode> {
/** /**
* Adds a move to the move history and adjusts the log to the new position. * Adds a move to the move history and adjusts the log to the new position.
* *
* @param move The move to log * @param move The move to log
* @param piece The piece that performed the move * @param piece The piece that performed the move
* @param capturedPiece The piece captured with the move * @param capturedPiece The piece captured with the move
@ -140,7 +140,7 @@ public class Log implements Iterable<MoveNode> {
/** /**
* Changes the current node to one of its children (variations). * Changes the current node to one of its children (variations).
* *
* @param index the index of the variation to select * @param index the index of the variation to select
*/ */
public void selectNextNode(int index) { public void selectNextNode(int index) {
@ -186,7 +186,7 @@ public class Log implements Iterable<MoveNode> {
* Removed the castling rights bound to a rook or king for the rest of the game. * Removed the castling rights bound to a rook or king for the rest of the game.
* This method should be called once the piece has been moved, as a castling * This method should be called once the piece has been moved, as a castling
* move involving this piece is forbidden afterwards. * move involving this piece is forbidden afterwards.
* *
* @param piece the rook or king to disable the castling rights for * @param piece the rook or king to disable the castling rights for
* @param initialPosition the initial position of the piece during the start of * @param initialPosition the initial position of the piece during the start of
* the game * the game
@ -244,7 +244,7 @@ public class Log implements Iterable<MoveNode> {
public int getFullmoveNumber() { return fullmoveNumber; } public int getFullmoveNumber() { return fullmoveNumber; }
public void setFullmoveNumber(int fullmoveCounter) { this.fullmoveNumber = fullmoveCounter; } public void setFullmoveNumber(int fullmoveCounter) { fullmoveNumber = fullmoveCounter; }
public int getHalfmoveClock() { return halfmoveClock; } public int getHalfmoveClock() { return halfmoveClock; }

View File

@ -157,8 +157,8 @@ public class Move {
// Position // Position
// TODO: Deconstruct position into optional file or rank // TODO: Deconstruct position into optional file or rank
// TODO: Omit if the move is a pawn push // Omit position if the move is a pawn push
sb.append(pos.toLAN()); if (!(piece instanceof Pawn && xDist == 0)) sb.append(pos.toLAN());
// Capture indicator // Capture indicator
if (board.get(dest) != null) sb.append('x'); if (board.get(dest) != null) sb.append('x');

View File

@ -11,7 +11,7 @@ import dev.kske.chess.board.Piece.Color;
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
* File: <strong>MoveNode.java</strong><br> * File: <strong>MoveNode.java</strong><br>
* Created: <strong>02.10.2019</strong><br> * Created: <strong>02.10.2019</strong><br>
* *
* @since Chess v0.5-alpha * @since Chess v0.5-alpha
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
*/ */
@ -31,7 +31,7 @@ public class MoveNode {
/** /**
* Creates a new {@link MoveNode}. * Creates a new {@link MoveNode}.
* *
* @param move The logged {@link Move} * @param move The logged {@link Move}
* @param capturedPiece The {@link Piece} captures by the logged {@link Move} * @param capturedPiece The {@link Piece} captures by the logged {@link Move}
* @param enPassant The en passant {@link Position} valid after the logged * @param enPassant The en passant {@link Position} valid after the logged
@ -53,7 +53,7 @@ public class MoveNode {
/** /**
* Creates a (deep) copy of another {@link MoveNode}. * Creates a (deep) copy of another {@link MoveNode}.
* *
* @param other The {@link MoveNode} to copy * @param other The {@link MoveNode} to copy
* @param copyVariations When this is set to {@code true} a deep copy is * @param copyVariations When this is set to {@code true} a deep copy is
* created, which * created, which
@ -64,17 +64,17 @@ public class MoveNode {
other.fullmoveCounter, other.halfmoveClock); other.fullmoveCounter, other.halfmoveClock);
if (copyVariations && other.variations != null) { if (copyVariations && other.variations != null) {
if (variations == null) variations = new ArrayList<>(); if (variations == null) variations = new ArrayList<>();
other.variations.forEach(variation -> { for (MoveNode variation : other.variations) {
MoveNode copy = new MoveNode(variation, true); MoveNode copy = new MoveNode(variation, true);
copy.parent = this; copy.parent = this;
variations.add(copy); variations.add(copy);
}); }
} }
} }
/** /**
* Adds another {@link MoveNode} as a child node. * Adds another {@link MoveNode} as a child node.
* *
* @param variation The {@link MoveNode} to append to this {@link MoveNode} * @param variation The {@link MoveNode} to append to this {@link MoveNode}
*/ */
public void addVariation(MoveNode variation) { public void addVariation(MoveNode variation) {

View File

@ -52,7 +52,7 @@ public class AIPlayer extends Player {
/* /*
* Get a copy of the board and the available moves. * Get a copy of the board and the available moves.
*/ */
Board board = new Board(this.board); Board board = new Board(this.board, false);
List<Move> moves = board.getMoves(color); List<Move> moves = board.getMoves(color);
/* /*
@ -66,7 +66,7 @@ public class AIPlayer extends Player {
for (int i = 0; i < numThreads; i++) { for (int i = 0; i < numThreads; i++) {
if (rem-- > 0) ++endIndex; if (rem-- > 0) ++endIndex;
endIndex += step; endIndex += step;
processors.add(new MoveProcessor(new Board(board), moves.subList(beginIndex, endIndex), color, processors.add(new MoveProcessor(new Board(board, false), moves.subList(beginIndex, endIndex), color,
maxDepth, alphaBetaThreshold)); maxDepth, alphaBetaThreshold));
beginIndex = endIndex; beginIndex = endIndex;
} }

View File

@ -1,7 +1,9 @@
package dev.kske.chess.pgn; package dev.kske.chess.pgn;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import java.util.regex.MatchResult; import java.util.regex.MatchResult;
@ -9,14 +11,14 @@ import java.util.regex.Pattern;
import dev.kske.chess.board.Board; import dev.kske.chess.board.Board;
import dev.kske.chess.board.FENString; import dev.kske.chess.board.FENString;
import dev.kske.chess.board.Piece.Color; import dev.kske.chess.board.Move;
import dev.kske.chess.exception.ChessException; import dev.kske.chess.exception.ChessException;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
* File: <strong>PGNGame.java</strong><br> * File: <strong>PGNGame.java</strong><br>
* Created: <strong>22 Sep 2019</strong><br> * Created: <strong>22 Sep 2019</strong><br>
* *
* @since Chess v0.5-alpha * @since Chess v0.5-alpha
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
*/ */
@ -78,11 +80,25 @@ public class PGNGame {
// Insert newline if tags were printed // Insert newline if tags were printed
if (!tagPairs.isEmpty()) pw.println(); if (!tagPairs.isEmpty()) pw.println();
// Collect SAN moves
Board clone = new Board(board, true);
List<String> sanMoves = new ArrayList<>();
while (clone.getLog().hasParent()) {
Move move = clone.getLog().getLast().move;
clone.revert();
sanMoves.add(move.toSAN(clone));
}
// Write movetext // Write movetext
board.getLog().forEach(m -> { for (int i = sanMoves.size() - 1; i >= 0; i--)
if (m.activeColor == Color.BLACK) pw.printf("%d. ", m.fullmoveCounter); pw.printf("%s ", sanMoves.get(i));
pw.printf("%s ", m.move); // TODO: Convert to SAN
}); // Write movetext
// board.getLog().forEach(m -> {
// if (m.activeColor == Color.BLACK) pw.printf("%d. ", m.fullmoveCounter);
// pw.printf("%s ", m.move.toSAN(board));
// });
// Write game termination marker // Write game termination marker
pw.print(tagPairs.get("Result")); pw.print(tagPairs.get("Result"));

View File

@ -24,7 +24,7 @@ import dev.kske.chess.io.EngineUtil;
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
* File: <strong>DialogUtil.java</strong><br> * File: <strong>DialogUtil.java</strong><br>
* Created: <strong>24.07.2019</strong><br> * Created: <strong>24.07.2019</strong><br>
* *
* @since Chess v0.3-alpha * @since Chess v0.3-alpha
* @author Kai S. K. Engelbart * @author Kai S. K. Engelbart
*/ */
@ -62,7 +62,7 @@ public class DialogUtil {
dialogPanel.add(lblWhite); dialogPanel.add(lblWhite);
JComboBox<Object> cbWhite = new JComboBox<>(); JComboBox<Object> cbWhite = new JComboBox<>();
cbWhite.setModel(new DefaultComboBoxModel<Object>(options.toArray())); cbWhite.setModel(new DefaultComboBoxModel<>(options.toArray()));
cbWhite.setBounds(98, 9, 159, 22); cbWhite.setBounds(98, 9, 159, 22);
dialogPanel.add(cbWhite); dialogPanel.add(cbWhite);
@ -72,7 +72,7 @@ public class DialogUtil {
dialogPanel.add(lblBlack); dialogPanel.add(lblBlack);
JComboBox<Object> cbBlack = new JComboBox<>(); JComboBox<Object> cbBlack = new JComboBox<>();
cbBlack.setModel(new DefaultComboBoxModel<Object>(options.toArray())); cbBlack.setModel(new DefaultComboBoxModel<>(options.toArray()));
cbBlack.setBounds(98, 36, 159, 22); cbBlack.setBounds(98, 36, 159, 22);
dialogPanel.add(cbBlack); dialogPanel.add(cbBlack);

View File

@ -31,7 +31,7 @@ class BoardTest {
*/ */
@Test @Test
void testClone() { void testClone() {
Board clone = new Board(board); Board clone = new Board(board, false);
assertNotSame(clone, board); assertNotSame(clone, board);
assertNotSame(clone.getBoardArr(), board.getBoardArr()); assertNotSame(clone.getBoardArr(), board.getBoardArr());