From 719e4f99efc19b20835d14517e7076e8352472c9 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Fri, 6 Dec 2019 23:54:11 +0100 Subject: [PATCH] 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. --- src/dev/kske/chess/board/Board.java | 40 +++++++++++++----------- src/dev/kske/chess/board/Log.java | 14 ++++----- src/dev/kske/chess/board/Move.java | 4 +-- src/dev/kske/chess/board/MoveNode.java | 12 +++---- src/dev/kske/chess/game/ai/AIPlayer.java | 4 +-- src/dev/kske/chess/pgn/PGNGame.java | 28 +++++++++++++---- src/dev/kske/chess/ui/DialogUtil.java | 6 ++-- test/dev/kske/chess/board/BoardTest.java | 2 +- 8 files changed, 65 insertions(+), 45 deletions(-) diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index 3288e2b..114f32e 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -13,7 +13,7 @@ import dev.kske.chess.board.Piece.Color; * Project: Chess
* File: Board.java
* Created: 01.07.2019
- * + * * @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.
* 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 */ diff --git a/src/dev/kske/chess/board/Log.java b/src/dev/kske/chess/board/Log.java index 95b1433..d8421e4 100644 --- a/src/dev/kske/chess/board/Log.java +++ b/src/dev/kske/chess/board/Log.java @@ -10,7 +10,7 @@ import dev.kske.chess.board.Piece.Color; * Project: Chess
* File: Log.java
* Created: 09.07.2019
- * + * * @since Chess v0.1-alpha * @author Kai S. K. Engelbart */ @@ -28,7 +28,7 @@ public class Log implements Iterable { /** * 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 { // 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 { /** * 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 { /** * 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 { * 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 { 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; } diff --git a/src/dev/kske/chess/board/Move.java b/src/dev/kske/chess/board/Move.java index d3841c8..2ff63e2 100644 --- a/src/dev/kske/chess/board/Move.java +++ b/src/dev/kske/chess/board/Move.java @@ -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'); diff --git a/src/dev/kske/chess/board/MoveNode.java b/src/dev/kske/chess/board/MoveNode.java index a8bf2b8..e618716 100644 --- a/src/dev/kske/chess/board/MoveNode.java +++ b/src/dev/kske/chess/board/MoveNode.java @@ -11,7 +11,7 @@ import dev.kske.chess.board.Piece.Color; * Project: Chess
* File: MoveNode.java
* Created: 02.10.2019
- * + * * @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) { diff --git a/src/dev/kske/chess/game/ai/AIPlayer.java b/src/dev/kske/chess/game/ai/AIPlayer.java index 7e8c015..9e53326 100644 --- a/src/dev/kske/chess/game/ai/AIPlayer.java +++ b/src/dev/kske/chess/game/ai/AIPlayer.java @@ -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 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; } diff --git a/src/dev/kske/chess/pgn/PGNGame.java b/src/dev/kske/chess/pgn/PGNGame.java index 5d443c8..9308b47 100644 --- a/src/dev/kske/chess/pgn/PGNGame.java +++ b/src/dev/kske/chess/pgn/PGNGame.java @@ -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: Chess
* File: PGNGame.java
* Created: 22 Sep 2019
- * + * * @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 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")); diff --git a/src/dev/kske/chess/ui/DialogUtil.java b/src/dev/kske/chess/ui/DialogUtil.java index 5c884b5..0621666 100644 --- a/src/dev/kske/chess/ui/DialogUtil.java +++ b/src/dev/kske/chess/ui/DialogUtil.java @@ -24,7 +24,7 @@ import dev.kske.chess.io.EngineUtil; * Project: Chess
* File: DialogUtil.java
* Created: 24.07.2019
- * + * * @since Chess v0.3-alpha * @author Kai S. K. Engelbart */ @@ -62,7 +62,7 @@ public class DialogUtil { dialogPanel.add(lblWhite); JComboBox cbWhite = new JComboBox<>(); - cbWhite.setModel(new DefaultComboBoxModel(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 cbBlack = new JComboBox<>(); - cbBlack.setModel(new DefaultComboBoxModel(options.toArray())); + cbBlack.setModel(new DefaultComboBoxModel<>(options.toArray())); cbBlack.setBounds(98, 36, 159, 22); dialogPanel.add(cbBlack); diff --git a/test/dev/kske/chess/board/BoardTest.java b/test/dev/kske/chess/board/BoardTest.java index b647632..776d221 100644 --- a/test/dev/kske/chess/board/BoardTest.java +++ b/test/dev/kske/chess/board/BoardTest.java @@ -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());