Implemented saving to PGN file

+ copyVariations parameter in copy constructors of Board and Log

This procedure still required work in the form of efficiently rewinding
the board to the first position for SAN move extraction and shortening
SAN moves to the smallest possible representation.
This commit is contained in:
Kai S. K. Engelbart 2019-12-06 23:54:11 +01:00
parent 927d5ed254
commit 719e4f99ef
8 changed files with 65 additions and 45 deletions

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());