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

@ -34,9 +34,9 @@ public class Board {
* 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,7 +45,11 @@ 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);
} }
/** /**

View File

@ -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;
} }
@ -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

@ -64,11 +64,11 @@ 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);
}); }
} }
} }

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,7 +11,7 @@ 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;
/** /**
@ -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

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