From b18b53f195ba0d2901af4c851a3a07bbb4b28e71 Mon Sep 17 00:00:00 2001 From: Persson-dev Date: Sat, 3 May 2025 20:46:20 +0200 Subject: [PATCH] feat: pgn parser --- .gitignore | 3 +- app/build.gradle | 12 +- .../chess/controller/CommandExecutor.java | 9 + app/src/main/java/chess/model/ChessBoard.java | 36 ++-- app/src/main/java/chess/pgn/PgnExport.java | 32 +++- .../main/java/chess/pgn/PgnFileSimulator.java | 22 +++ app/src/main/java/chess/pgn/PgnImport.java | 172 ++++++++++++++++++ app/src/main/java/chess/pgn/PgnSimulator.java | 28 +++ .../java/chess/simulator/CastlingTest.java | 31 ---- .../java/chess/simulator/EnPassantTest.java | 29 --- .../java/chess/simulator/FoolCheckMate.java | 28 --- .../java/chess/simulator/PromoteTest.java | 40 ---- .../main/java/chess/simulator/Simulator.java | 39 ---- .../java/chess/view/simplerender/Window.java | 6 +- app/src/main/resources/games/CastlingTest.pgn | 1 + .../main/resources/games/EnPassantTest.pgn | 1 + .../main/resources/games/FoolCheckmate.pgn | 1 + app/src/main/resources/games/PromoteTest.pgn | 1 + app/src/main/resources/games/akopian.pgn | 15 -- app/src/main/resources/games/wendy.pgn | 20 +- app/src/test/java/chess/PgnTest.java | 77 ++++++++ 21 files changed, 384 insertions(+), 219 deletions(-) create mode 100644 app/src/main/java/chess/pgn/PgnFileSimulator.java create mode 100644 app/src/main/java/chess/pgn/PgnImport.java create mode 100644 app/src/main/java/chess/pgn/PgnSimulator.java delete mode 100644 app/src/main/java/chess/simulator/CastlingTest.java delete mode 100644 app/src/main/java/chess/simulator/EnPassantTest.java delete mode 100644 app/src/main/java/chess/simulator/FoolCheckMate.java delete mode 100644 app/src/main/java/chess/simulator/PromoteTest.java delete mode 100644 app/src/main/java/chess/simulator/Simulator.java create mode 100644 app/src/main/resources/games/CastlingTest.pgn create mode 100644 app/src/main/resources/games/EnPassantTest.pgn create mode 100644 app/src/main/resources/games/FoolCheckmate.pgn create mode 100644 app/src/main/resources/games/PromoteTest.pgn delete mode 100644 app/src/main/resources/games/akopian.pgn create mode 100644 app/src/test/java/chess/PgnTest.java diff --git a/.gitignore b/.gitignore index 7b727db..f3001a4 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ app/bin .vscode -audio/*.wav \ No newline at end of file +audio/*.wav +app/audio/*.wav \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 2db934f..dd8380c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -43,16 +43,20 @@ run { standardInput = System.in } + +// Apply a specific Java toolchain to ease working on different environments. +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + jar { manifest { attributes 'Main-Class': application.mainClass } } -run { - standardInput = System.in -} - tasks.named('test') { // Use JUnit Platform for unit tests. useJUnitPlatform() diff --git a/app/src/main/java/chess/controller/CommandExecutor.java b/app/src/main/java/chess/controller/CommandExecutor.java index 2b11eaa..25b5fed 100644 --- a/app/src/main/java/chess/controller/CommandExecutor.java +++ b/app/src/main/java/chess/controller/CommandExecutor.java @@ -1,5 +1,7 @@ package chess.controller; +import java.util.List; + import chess.controller.Command.CommandResult; import chess.controller.commands.UndoCommand; import chess.controller.event.AsyncGameDispatcher; @@ -42,6 +44,13 @@ public class CommandExecutor { return result; } + public void executeCommands(List commands) { + for (Command command : commands) { + CommandResult result = executeCommand(command); + assert result != CommandResult.NotAllowed; + } + } + private void processResult(Command command, CommandResult result) { switch (result) { case NotAllowed: diff --git a/app/src/main/java/chess/model/ChessBoard.java b/app/src/main/java/chess/model/ChessBoard.java index adaf385..a4dad60 100644 --- a/app/src/main/java/chess/model/ChessBoard.java +++ b/app/src/main/java/chess/model/ChessBoard.java @@ -112,21 +112,33 @@ public class ChessBoard { } public Coordinate findKing(Color color) { - // KingIdentifier kingIdentifier = new KingIdentifier(color); - // for (int i = 0; i < Coordinate.VALUE_MAX; i++) { - // for (int j = 0; j < Coordinate.VALUE_MAX; j++) { - // Coordinate coordinate = new Coordinate(i, j); - // Piece piece = pieceAt(coordinate); - // if (kingIdentifier.isKing(piece)) { - // return coordinate; - // } - // } - // } - // assert false : "No king found ?!"; - // return null; return kingPos[color.ordinal()]; } + public List getAllowedStarts(Coordinate finish, Color color) { + List starts = new ArrayList<>(); + for (int i = 0; i < Coordinate.VALUE_MAX; i++) { + for (int j = 0; j < Coordinate.VALUE_MAX; j++) { + Coordinate attackCoords = new Coordinate(i, j); + Piece attackPiece = pieceAt(attackCoords); + if (attackPiece == null || attackPiece.getColor() != color) + continue; + + Move move = new Move(attackCoords, finish); + PiecePathChecker piecePathChecker = new PiecePathChecker(this, + move); + if (!piecePathChecker.isValid()) + continue; + + applyMove(move); + if (!isKingInCheck(color)) + starts.add(attackCoords); + undoLastMove(); + } + } + return starts; + } + public boolean isKingInCheck(Color color) { Coordinate kingPos = findKing(color); assert kingPos.isValid() : "King position is invalid!"; diff --git a/app/src/main/java/chess/pgn/PgnExport.java b/app/src/main/java/chess/pgn/PgnExport.java index e4fb6d4..f3ad6ab 100644 --- a/app/src/main/java/chess/pgn/PgnExport.java +++ b/app/src/main/java/chess/pgn/PgnExport.java @@ -4,6 +4,7 @@ import java.util.List; import chess.controller.CommandExecutor; import chess.controller.PlayerCommand; +import chess.controller.Command.CommandResult; import chess.controller.commands.CastlingCommand; import chess.controller.commands.GetPieceAtCommand; import chess.controller.commands.GetPlayerMovesCommand; @@ -88,7 +89,7 @@ public class PgnExport { Piece otherPiece = pieceAt(cmdExec, move.getStart()); // checking type of piece - if (otherPiece.hashCode() != movingPiece.hashCode()) + if (!otherPiece.getClass().equals(movingPiece.getClass())) continue; String startPos = toString(pieceMove.getStart()); @@ -149,7 +150,10 @@ public class PgnExport { } } - private static String printMove(PlayerCommand cmd, PlayerCommand nextCommand, Game virtualGame, + private record MoveResult(String move, CommandResult commandResult) { + } + + private static MoveResult printMove(PlayerCommand cmd, PlayerCommand nextCommand, Game virtualGame, CommandExecutor executor) { String result = ""; if (cmd instanceof MoveCommand move) { @@ -177,14 +181,14 @@ public class PgnExport { result += castling(castlingCommand); } - executor.executeCommand(cmd); + CommandResult commandResult = executor.executeCommand(cmd); // check or checkmate result += checkCheckMate(virtualGame); result += " "; - return result; + return new MoveResult(result, commandResult); } public static String exportGame(Game game) { @@ -199,17 +203,33 @@ public class PgnExport { int tour = 1; + String lastMove = null; + for (int i = 0; i < commands.size(); i++) { PlayerCommand cmd = commands.get(i); PlayerCommand nextCommand = null; + if (i != commands.size() - 1) { nextCommand = commands.get(i + 1); } - if (virtualGame.getPlayerTurn() == Color.White) { + MoveResult moveResult = printMove(cmd, nextCommand, virtualGame, executor); + if (moveResult.commandResult() == CommandResult.Moved && virtualGame.getPlayerTurn() == Color.Black) { result += tour + "."; tour++; } - result += printMove(cmd, nextCommand, virtualGame, executor); + + if (moveResult.commandResult() == CommandResult.ActionNeeded) { + lastMove = moveResult.move(); + continue; + } + + if (lastMove != null && moveResult.commandResult() == CommandResult.Moved){ + result += lastMove; + lastMove = null; + continue; + } + result += moveResult.move(); + } return result + " " + gameEnd(virtualGame); } diff --git a/app/src/main/java/chess/pgn/PgnFileSimulator.java b/app/src/main/java/chess/pgn/PgnFileSimulator.java new file mode 100644 index 0000000..6bd272f --- /dev/null +++ b/app/src/main/java/chess/pgn/PgnFileSimulator.java @@ -0,0 +1,22 @@ +package chess.pgn; + +import java.io.BufferedReader; +import java.io.InputStreamReader; + +import chess.controller.CommandExecutor; +import chess.view.AssetManager; + +public class PgnFileSimulator extends PgnSimulator{ + + private static String readResource(String path) { + StringBuilder builder = new StringBuilder(); + BufferedReader reader = new BufferedReader(new InputStreamReader(AssetManager.getResource(path))); + reader.lines().forEach((line) -> builder.append(line + "\n")); + return builder.toString(); + } + + public PgnFileSimulator(CommandExecutor commandExecutor, String fileName) { + super(commandExecutor, readResource(fileName)); + } + +} diff --git a/app/src/main/java/chess/pgn/PgnImport.java b/app/src/main/java/chess/pgn/PgnImport.java new file mode 100644 index 0000000..a12e73f --- /dev/null +++ b/app/src/main/java/chess/pgn/PgnImport.java @@ -0,0 +1,172 @@ +package chess.pgn; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import chess.controller.CommandExecutor; +import chess.controller.PlayerCommand; +import chess.controller.commands.CastlingCommand; +import chess.controller.commands.MoveCommand; +import chess.controller.commands.NewGameCommand; +import chess.controller.commands.PromoteCommand; +import chess.controller.commands.PromoteCommand.PromoteType; +import chess.controller.event.EmptyGameDispatcher; +import chess.model.ChessBoard; +import chess.model.Coordinate; +import chess.model.Game; +import chess.model.Move; +import chess.model.Piece; +import chess.model.pieces.Bishop; +import chess.model.pieces.King; +import chess.model.pieces.Knight; +import chess.model.pieces.Pawn; +import chess.model.pieces.Queen; +import chess.model.pieces.Rook; + +public class PgnImport { + + private static final Map> pieceMap = Map.of( + "K", King.class, + "Q", Queen.class, + "R", Rook.class, + "B", Bishop.class, + "N", Knight.class); + + private static final Map promoteMap = Map.of( + "Q", PromoteType.Queen, + "R", PromoteType.Rook, + "B", PromoteType.Bishop, + "N", PromoteType.Knight); + + public static List importGame(String pgnContent) { + String[] parts = pgnContent.split("\n\n"); + // we just ignore headers + return getMoves(parts[parts.length - 1]); + } + + private static List getMoves(String unparsedMoves) { + String[] moves = unparsedMoves.replaceAll("\\{.*?\\}", "") // Remove comments + .replaceAll("\\n", " ") // Remove new lines + .split("[\\s.]+"); // Split by whitespace and dots (trimming it also) + + Game virtualGame = new Game(); + CommandExecutor commandExecutor = new CommandExecutor(virtualGame, new EmptyGameDispatcher()); + List instructions = new ArrayList<>(); + + commandExecutor.executeCommand(new NewGameCommand()); + + for (int i = 0; i < moves.length; i++) { + if (i % 3 == 0) + continue; + + String move = moves[i]; + + if (move.equals("1-0") || move.equals("0-1") || move.equals("1/2-1/2")) { + break; // End of the game + } + + List cmds = parseMove(move, virtualGame); + commandExecutor.executeCommands(cmds); + + instructions.addAll(cmds); + } + return instructions; + } + + private static List parseMove(String move, Game game) { + if (move.equals("O-O-O")) + return Arrays.asList(new CastlingCommand(true)); + if (move.equals("O-O")) + return Arrays.asList(new CastlingCommand(false)); + + move = move.replaceAll("[x|#|\\+]", ""); + + PromoteCommand promoteCommand = null; + + if (move.contains("=")) { + String promoteString = move.substring(move.length() - 1); + promoteCommand = new PromoteCommand(promoteMap.get(promoteString)); + move = move.substring(0, move.length() - 2); + } + + Class pieceType = pieceMap.get(move.substring(0, 1)); + + if (pieceType == null) + pieceType = Pawn.class; + else + move = move.substring(1); + + assert move.length() == 3 || move.length() == 2; + + Coordinate ambiguity = new Coordinate(-1, -1); + + // ambiguity + if (move.length() == 3) { + ambiguity = getAmbiguityPattern(move.charAt(0)); + move = move.substring(1); + } + + Coordinate dest = stringToCoordinate(move); + + Coordinate start = getStartCoord(dest, pieceType, ambiguity, game); + + List cmds = new ArrayList<>(); + cmds.add(new MoveCommand(new Move(start, dest))); + if (promoteCommand != null) + cmds.add(promoteCommand); + + return cmds; + } + + private static Coordinate getStartCoord(Coordinate dest, Class pieceType, Coordinate ambiguity, + Game game) { + final ChessBoard board = game.getBoard(); + + List starts = board.getAllowedStarts(dest, game.getPlayerTurn()); + + assert !starts.isEmpty() : "No moves allowed!"; + + for (Coordinate start : starts) { + Piece piece = board.pieceAt(start); + if (piece.getClass().equals(pieceType) && coordPatternMatch(start, ambiguity)) + return start; + } + + assert false : "There is a small problem ..."; + + return null; + } + + private static int getXCoord(char xPos) { + return xPos - 'a'; + } + + private static int getYCoord(char yPos) { + return Coordinate.VALUE_MAX - 1 - (yPos - '1'); + } + + private static boolean coordPatternMatch(Coordinate coord, Coordinate pattern) { + if (pattern.getX() != -1 && coord.getX() != pattern.getX()) + return false; + + if (pattern.getY() != -1 && coord.getY() != pattern.getY()) + return false; + + return true; + } + + private static Coordinate getAmbiguityPattern(char amb) { + if (Character.isDigit(amb)) + return new Coordinate(-1, getYCoord(amb)); + return new Coordinate(getXCoord(amb), -1); + } + + private static Coordinate stringToCoordinate(String coordinates) { + char xPos = coordinates.charAt(0); + char yPos = coordinates.charAt(1); + + return new Coordinate(getXCoord(xPos), getYCoord(yPos)); + } +} diff --git a/app/src/main/java/chess/pgn/PgnSimulator.java b/app/src/main/java/chess/pgn/PgnSimulator.java new file mode 100644 index 0000000..47824a1 --- /dev/null +++ b/app/src/main/java/chess/pgn/PgnSimulator.java @@ -0,0 +1,28 @@ +package chess.pgn; + + +import java.util.List; + +import chess.controller.CommandExecutor; +import chess.controller.PlayerCommand; +import chess.controller.commands.NewGameCommand; +import chess.controller.event.GameAdaptator; +import chess.model.Game; + +public class PgnSimulator extends GameAdaptator { + + private final CommandExecutor commandExecutor; + private final String pgn; + + public PgnSimulator(CommandExecutor commandExecutor, String pgn) { + this.commandExecutor = commandExecutor; + this.pgn = pgn; + } + + @Override + public void onGameStart() { + List cmds = PgnImport.importGame(this.pgn); + this.commandExecutor.executeCommands(cmds); + } + +} diff --git a/app/src/main/java/chess/simulator/CastlingTest.java b/app/src/main/java/chess/simulator/CastlingTest.java deleted file mode 100644 index 2c13c71..0000000 --- a/app/src/main/java/chess/simulator/CastlingTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package chess.simulator; - -import chess.controller.CommandExecutor; -import chess.model.Coordinate; -import chess.model.Move; - -import java.util.Arrays; -import java.util.List; - -public class CastlingTest extends Simulator { - public CastlingTest(CommandExecutor commandExecutor) { - super(commandExecutor); - } - - @Override - protected List getMoves() { - return Arrays.asList( - // white pawn - new Move(new Coordinate(6, 6), new Coordinate(6, 4)), - // black knight - new Move(new Coordinate(1, 0), new Coordinate(0, 2)), - // white bishop - new Move(new Coordinate(5, 7), new Coordinate(7, 5)), - // black pawn - new Move(new Coordinate(1, 1), new Coordinate(1, 2)), - // white knight - new Move(new Coordinate(6, 7), new Coordinate(5, 5)), - // black pawn, bis - new Move(new Coordinate(2, 1), new Coordinate(2, 2))); - } -} diff --git a/app/src/main/java/chess/simulator/EnPassantTest.java b/app/src/main/java/chess/simulator/EnPassantTest.java deleted file mode 100644 index ac7209b..0000000 --- a/app/src/main/java/chess/simulator/EnPassantTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package chess.simulator; - -import chess.controller.CommandExecutor; -import chess.model.Coordinate; -import chess.model.Move; - -import java.util.Arrays; -import java.util.List; - -public class EnPassantTest extends Simulator{ - - public EnPassantTest(CommandExecutor commandExecutor) { - super(commandExecutor); - } - - - @Override - protected List getMoves() { - return Arrays.asList( - // white pawn - new Move(new Coordinate(4, 6), new Coordinate(4, 4)), - // black pawn 1 - new Move(new Coordinate(4, 1), new Coordinate(4, 2)), - // white pawn - new Move(new Coordinate(4, 4), new Coordinate(4, 3)), - // black pawn #2 - new Move(new Coordinate(3, 1), new Coordinate(3, 3))); - } -} diff --git a/app/src/main/java/chess/simulator/FoolCheckMate.java b/app/src/main/java/chess/simulator/FoolCheckMate.java deleted file mode 100644 index 5cacbdf..0000000 --- a/app/src/main/java/chess/simulator/FoolCheckMate.java +++ /dev/null @@ -1,28 +0,0 @@ -package chess.simulator; - -import java.util.Arrays; -import java.util.List; - -import chess.controller.CommandExecutor; -import chess.model.Coordinate; -import chess.model.Move; - -public class FoolCheckMate extends Simulator { - - public FoolCheckMate(CommandExecutor commandExecutor) { - super(commandExecutor); - } - - @Override - public List getMoves() { - return Arrays.asList( - // white pawn - new Move(new Coordinate(5, 6), new Coordinate(5, 5)), - // black pawn - new Move(new Coordinate(4, 1), new Coordinate(4, 3)), - // 2nd white pawn - new Move(new Coordinate(6, 6), new Coordinate(6, 4)), - // black queen - new Move(new Coordinate(3, 0), new Coordinate(7, 4))); - } -} diff --git a/app/src/main/java/chess/simulator/PromoteTest.java b/app/src/main/java/chess/simulator/PromoteTest.java deleted file mode 100644 index b4a0d88..0000000 --- a/app/src/main/java/chess/simulator/PromoteTest.java +++ /dev/null @@ -1,40 +0,0 @@ -package chess.simulator; - -import java.util.Arrays; -import java.util.List; - -import chess.controller.CommandExecutor; -import chess.model.Coordinate; -import chess.model.Move; - -public class PromoteTest extends Simulator{ - - public PromoteTest(CommandExecutor commandExecutor) { - super(commandExecutor); - } - - @Override - protected List getMoves() { - return Arrays.asList( - // white pawn - new Move(new Coordinate(5, 6), new Coordinate(5, 4)), - // black pawn - new Move(new Coordinate(4, 1), new Coordinate(4, 3)), - // white pawn capture - new Move(new Coordinate(5, 4), new Coordinate(4, 3)), - // black king - new Move(new Coordinate(4, 0), new Coordinate(4, 1)), - // white pawn moves - new Move(new Coordinate(4, 3), new Coordinate(4, 2)), - // black king - new Move(new Coordinate(4, 1), new Coordinate(5, 2)), - // white pawn moves - new Move(new Coordinate(4, 2), new Coordinate(4, 1)), - // black king - new Move(new Coordinate(5, 2), new Coordinate(6, 2)) - // white pawn moves - // new Move(new Coordinate(4, 1), new Coordinate(4, 0)) - ); - } - -} diff --git a/app/src/main/java/chess/simulator/Simulator.java b/app/src/main/java/chess/simulator/Simulator.java deleted file mode 100644 index fb9ef8c..0000000 --- a/app/src/main/java/chess/simulator/Simulator.java +++ /dev/null @@ -1,39 +0,0 @@ -package chess.simulator; - -import java.util.List; - -import chess.controller.CommandExecutor; -import chess.controller.commands.MoveCommand; -import chess.controller.event.GameAdaptator; -import chess.model.Move; -import common.Signal0; - -public abstract class Simulator extends GameAdaptator { - - protected final CommandExecutor commandExecutor; - - public final Signal0 onComplete = new Signal0(); - private int currentMove = 0; - - public Simulator(CommandExecutor commandExecutor) { - this.commandExecutor = commandExecutor; - } - - @Override - public void onGameStart() { - for (Move move : getMoves()) { - this.commandExecutor.executeCommand(new MoveCommand(move)); - } - } - - @Override - public void onBoardUpdate() { - currentMove++; - if (currentMove == getMoves().size()) { - onComplete.emit(); - } - } - - protected abstract List getMoves(); - -} diff --git a/app/src/main/java/chess/view/simplerender/Window.java b/app/src/main/java/chess/view/simplerender/Window.java index af4f8ee..8d0695f 100644 --- a/app/src/main/java/chess/view/simplerender/Window.java +++ b/app/src/main/java/chess/view/simplerender/Window.java @@ -74,15 +74,15 @@ public class Window extends JFrame implements GameListener { } private void buildButtons(JPanel bottom) { - castlingButton.addActionListener((_) -> { + castlingButton.addActionListener((event) -> { sendCommand(new CastlingCommand(false)); }); - bigCastlingButton.addActionListener((_) -> { + bigCastlingButton.addActionListener((event) -> { sendCommand(new CastlingCommand(true)); }); - undoButton.addActionListener((_) -> { + undoButton.addActionListener((event) -> { sendCommand(new UndoCommand()); }); diff --git a/app/src/main/resources/games/CastlingTest.pgn b/app/src/main/resources/games/CastlingTest.pgn new file mode 100644 index 0000000..c571a84 --- /dev/null +++ b/app/src/main/resources/games/CastlingTest.pgn @@ -0,0 +1 @@ +1.g4 Na6 2.Bh3 b6 3.Nf3 c6 \ No newline at end of file diff --git a/app/src/main/resources/games/EnPassantTest.pgn b/app/src/main/resources/games/EnPassantTest.pgn new file mode 100644 index 0000000..7b59c18 --- /dev/null +++ b/app/src/main/resources/games/EnPassantTest.pgn @@ -0,0 +1 @@ +1.e4 e6 2.e5 d5 \ No newline at end of file diff --git a/app/src/main/resources/games/FoolCheckmate.pgn b/app/src/main/resources/games/FoolCheckmate.pgn new file mode 100644 index 0000000..28216fd --- /dev/null +++ b/app/src/main/resources/games/FoolCheckmate.pgn @@ -0,0 +1 @@ +1.f3 e5 2.g4 Qh4# 0-1 \ No newline at end of file diff --git a/app/src/main/resources/games/PromoteTest.pgn b/app/src/main/resources/games/PromoteTest.pgn new file mode 100644 index 0000000..8f990e1 --- /dev/null +++ b/app/src/main/resources/games/PromoteTest.pgn @@ -0,0 +1 @@ +1.f4 e5 2.fxe5 Ke7 3.e6 Kf6 4.e7 Kg6 \ No newline at end of file diff --git a/app/src/main/resources/games/akopian.pgn b/app/src/main/resources/games/akopian.pgn deleted file mode 100644 index 281c577..0000000 --- a/app/src/main/resources/games/akopian.pgn +++ /dev/null @@ -1,15 +0,0 @@ -[Event "URS-chT"] -[Site "Moscow"] -[Date "1963.??.??"] -[Round "?"] -[White "Listergarten, Leonid B"] -[Black "Akopian, Vladimir"] -[Result "1-0"] -[WhiteElo ""] -[BlackElo ""] -[ECO "B48"] - -1.e4 c5 2.Nf3 e6 3.d4 cxd4 4.Nxd4 a6 5.Nc3 Qc7 6.Bd3 Nc6 7.Be3 b5 8.a3 Bb7 -9.O-O Rc8 10.Nxc6 Qxc6 11.Qg4 Nf6 12.Qg3 h5 13.e5 Nd5 14.Ne4 h4 15.Qh3 Qc7 -16.f4 Nxe3 17.Qxe3 h3 18.gxh3 f5 19.exf6 d5 20.Nf2 Kf7 21.Rae1 Re8 22.Qg3 g5 -23.fxg5 Qxg3+ 24.hxg3 e5 25.g6+ Kxf6 26.Ng4+ Kg5 27.Rf5+ 1-0 \ No newline at end of file diff --git a/app/src/main/resources/games/wendy.pgn b/app/src/main/resources/games/wendy.pgn index 50a9e72..04d98bc 100644 --- a/app/src/main/resources/games/wendy.pgn +++ b/app/src/main/resources/games/wendy.pgn @@ -1,12 +1,10 @@ -1. e4 {1.e4 $1 A fiery start $1} 1... e5 {I like how this game is starting $1} 2. Nc3 -Bc5 {J'aime un feu chaud.} 3. Nf3 {It's too cold in here for my liking.} 3... -Qf6 4. Nd5 Qd6 5. d3 c6 {A fiery position is what I seek $1} 6. Nc3 h6 7. a3 Qg6 -8. Nxe5 {Things are beginning to heat up, non $2} 8... Qd6 9. Nc4 Qe6 10. d4 Be7 -11. Ne3 b5 12. Nf5 d5 13. Nxg7+ {That's not very nice.} 13... Kd7 14. Nxe6 -{Brrrrrr. It is getting cold in here.} 14... fxe6 15. exd5 cxd5 16. Bf4 Nf6 17. -Bxb5+ Kd8 18. Qf3 Bd7 19. Be5 {My attack is getting cold, I need to go get some -more firewood $1} 19... a6 20. Bxf6 Re8 21. Bxe7+ Kxe7 22. Nxd5+ exd5 23. Qxd5 -Kf8+ {It's getting toasty in here $1} 24. Be2 Bc6 25. Qd6+ Re7 26. Kf1 Ba4 27. b3 +1. e4 e5 2. Nc3 +Bc5 3. Nf3 +Qf6 4. Nd5 Qd6 5. d3 c6 6. Nc3 h6 7. a3 Qg6 +8. Nxe5 Qd6 9. Nc4 Qe6 10. d4 Be7 +11. Ne3 b5 12. Nf5 d5 13. Nxg7+ Kd7 14. Nxe6 +fxe6 15. exd5 cxd5 16. Bf4 Nf6 17. +Bxb5+ Kd8 18. Qf3 Bd7 19. Be5 a6 20. Bxf6 Re8 21. Bxe7+ Kxe7 22. Nxd5+ exd5 23. Qxd5 +Kf8+ 24. Be2 Bc6 25. Qd6+ Re7 26. Kf1 Ba4 27. b3 Nc6 28. bxa4 a5 29. Qxc6 Rd8 30. Qxh6+ Kg8 31. Bc4+ Rf7 32. Qg5+ Kh8 33. Bxf7 -{C'est très, très mauvais $1} 33... Kh7 34. Qg6+ Kh8 35. Ra2 Rxd4 36. Qg8# {Good -play $1 I'll have to throw another log on the fire and try again.} 1-0 \ No newline at end of file +Kh7 34. Qg6+ Kh8 35. Ra2 Rxd4 36. Qg8# 1-0 \ No newline at end of file diff --git a/app/src/test/java/chess/PgnTest.java b/app/src/test/java/chess/PgnTest.java new file mode 100644 index 0000000..3369c18 --- /dev/null +++ b/app/src/test/java/chess/PgnTest.java @@ -0,0 +1,77 @@ +package chess; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import org.junit.jupiter.api.Test; + +import chess.ai.DumbAI; +import chess.controller.CommandExecutor; +import chess.controller.PlayerCommand; +import chess.controller.commands.NewGameCommand; +import chess.controller.event.GameAdaptator; +import chess.model.Color; +import chess.model.Game; +import chess.pgn.PgnExport; +import chess.pgn.PgnImport; + +public class PgnTest { + + private Game getRandomGame() { + Game game = new Game(); + CommandExecutor commandExecutor = new CommandExecutor(game); + + commandExecutor.addListener(new DumbAI(commandExecutor, Color.White)); + commandExecutor.addListener(new DumbAI(commandExecutor, Color.Black)); + + commandExecutor.addListener(new GameAdaptator() { + @Override + public void onGameEnd() { + synchronized (game) { + game.notifyAll(); + } + } + }); + + commandExecutor.executeCommand(new NewGameCommand()); + + synchronized (game) { + try { + game.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + commandExecutor.close(); + + return game; + } + + private void importExport() { + Game game = getRandomGame(); + + String pgnContent = PgnExport.exportGame(game); + + List moves = PgnImport.importGame(pgnContent); + + Game game2 = new Game(); + CommandExecutor commandExecutor = new CommandExecutor(game2); + commandExecutor.executeCommand(new NewGameCommand()); + + commandExecutor.executeCommands(moves); + + String pgnContent2 = PgnExport.exportGame(game2); + + commandExecutor.close(); + + assertEquals(pgnContent, pgnContent2); + } + + @Test void importExports() { + for (int i = 0; i < 50; i++) { + importExport(); + } + } +}