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 6af213ed4f
commit 44f91591b4
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>
* File: <strong>Board.java</strong><br>
* Created: <strong>01.07.2019</strong><br>
*
*
* @since Chess v0.1-alpha
* @author Kai S. K. Engelbart
*/
@ -32,11 +32,11 @@ public class Board {
* Creates a copy of another {@link Board} instance.<br>
* The created object is a deep copy, but does not contain any move history
* apart from the current {@link MoveNode}.
*
*
* @param other The {@link Board} instance to copy
* @param copyVariations TODO
*/
public Board(Board other) {
boardArr = new Piece[8][8];
public Board(Board other, boolean copyVariations) {
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++) {
if (other.boardArr[i][j] == null) continue;
@ -45,12 +45,16 @@ public class Board {
}
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.
*
*
* @param move The move to execute
* @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.
*
*
* @param move The move to execute
*/
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.
*
*
* @param sanMove The move to execute in SAN (Standard Algebraic Notation)
*/
public void move(String sanMove) {
@ -118,7 +122,7 @@ public class Board {
/**
* Generated every legal move for one color
*
*
* @param color The color to generate the moves for
* @return A list of all legal moves
*/
@ -134,7 +138,7 @@ public class Board {
/**
* Checks, if the king is in check.
*
*
* @param color The color of the king to 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.
*
*
* @param dest the field to check
* @param color the color of a potential attacker piece
* @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.
* This requires the king to already be in check!
*
*
* @param color The color of the king to check
* @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).
*
*
* @param pieceClass The class of the piece to search for
* @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
@ -271,7 +275,7 @@ public class Board {
/**
* Searches for a {@link Piece} inside a rank (1 - 8).
*
*
* @param pieceClass The class of the piece to search for
* @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
@ -287,7 +291,7 @@ public class Board {
/**
* Searches for a {@link Piece} that can move to a {@link Position}.
*
*
* @param pieceClass The class of the piece to search for
* @param dest The destination that the piece is required to reach
* @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.
*
*
* @param pos The position to place the piece at
* @param piece The piece to place
*/
@ -324,7 +328,7 @@ public class Board {
/**
* Places a piece at the position of a move.
*
*
* @param move The move at which position to place the piece
* @param piece The piece to place
*/
@ -332,7 +336,7 @@ public class Board {
/**
* Places a piece at the destination of a move.
*
*
* @param move The move at which destination to place the piece
* @param piece The piece to place
*/

View File

@ -10,7 +10,7 @@ import dev.kske.chess.board.Piece.Color;
* Project: <strong>Chess</strong><br>
* File: <strong>Log.java</strong><br>
* Created: <strong>09.07.2019</strong><br>
*
*
* @since Chess v0.1-alpha
* @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
* with the current {@link MoveNode}.
*
*
* @param other The {@link Log} instance to copy
* @param copyVariations If set to {@code true}, subsequent variations of 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
if (!other.isEmpty()) {
root = new MoveNode(other.current, copyVariations);
root = new MoveNode(other.root, copyVariations);
root.setParent(null);
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.
*
*
* @param move The move to log
* @param piece The piece that performed 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).
*
*
* @param index the index of the variation to select
*/
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.
* This method should be called once the piece has been moved, as a castling
* move involving this piece is forbidden afterwards.
*
*
* @param piece the rook or king to disable the castling rights for
* @param initialPosition the initial position of the piece during the start of
* the game
@ -244,7 +244,7 @@ public class Log implements Iterable<MoveNode> {
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; }

View File

@ -157,8 +157,8 @@ public class Move {
// Position
// TODO: Deconstruct position into optional file or rank
// TODO: Omit if the move is a pawn push
sb.append(pos.toLAN());
// Omit position if the move is a pawn push
if (!(piece instanceof Pawn && xDist == 0)) sb.append(pos.toLAN());
// Capture indicator
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>
* File: <strong>MoveNode.java</strong><br>
* Created: <strong>02.10.2019</strong><br>
*
*
* @since Chess v0.5-alpha
* @author Kai S. K. Engelbart
*/
@ -31,7 +31,7 @@ public class MoveNode {
/**
* Creates a new {@link MoveNode}.
*
*
* @param move 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
@ -53,7 +53,7 @@ public class MoveNode {
/**
* Creates a (deep) copy of another {@link MoveNode}.
*
*
* @param other The {@link MoveNode} to copy
* @param copyVariations When this is set to {@code true} a deep copy is
* created, which
@ -64,17 +64,17 @@ public class MoveNode {
other.fullmoveCounter, other.halfmoveClock);
if (copyVariations && other.variations != null) {
if (variations == null) variations = new ArrayList<>();
other.variations.forEach(variation -> {
for (MoveNode variation : other.variations) {
MoveNode copy = new MoveNode(variation, true);
copy.parent = this;
variations.add(copy);
});
}
}
}
/**
* Adds another {@link MoveNode} as a child node.
*
*
* @param variation The {@link MoveNode} to append to this {@link MoveNode}
*/
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.
*/
Board board = new Board(this.board);
Board board = new Board(this.board, false);
List<Move> moves = board.getMoves(color);
/*
@ -66,7 +66,7 @@ public class AIPlayer extends Player {
for (int i = 0; i < numThreads; i++) {
if (rem-- > 0) ++endIndex;
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));
beginIndex = endIndex;
}

View File

@ -1,7 +1,9 @@
package dev.kske.chess.pgn;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
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.FENString;
import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.board.Move;
import dev.kske.chess.exception.ChessException;
/**
* Project: <strong>Chess</strong><br>
* File: <strong>PGNGame.java</strong><br>
* Created: <strong>22 Sep 2019</strong><br>
*
*
* @since Chess v0.5-alpha
* @author Kai S. K. Engelbart
*/
@ -78,11 +80,25 @@ public class PGNGame {
// Insert newline if tags were printed
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
board.getLog().forEach(m -> {
if (m.activeColor == Color.BLACK) pw.printf("%d. ", m.fullmoveCounter);
pw.printf("%s ", m.move); // TODO: Convert to SAN
});
for (int i = sanMoves.size() - 1; i >= 0; i--)
pw.printf("%s ", sanMoves.get(i));
// 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
pw.print(tagPairs.get("Result"));

View File

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

View File

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