package chess.pgn; 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; import chess.controller.commands.MoveCommand; import chess.controller.commands.NewGameCommand; import chess.controller.commands.PromoteCommand; import chess.controller.event.EmptyGameDispatcher; import chess.model.Color; import chess.model.Coordinate; import chess.model.Game; import chess.model.Move; import chess.model.Piece; import chess.model.pieces.Pawn; /** * Export a game to PGN format. */ public class PgnExport { private static final PiecePgnName piecePgnName = new PiecePgnName(); private static Piece pieceAt(CommandExecutor commandExecutor, Coordinate coordinate) { GetPieceAtCommand cmd = new GetPieceAtCommand(coordinate); commandExecutor.executeCommand(cmd); return cmd.getPiece(); } private static List playerMoves(CommandExecutor commandExecutor) { GetPlayerMovesCommand cmd = new GetPlayerMovesCommand(); commandExecutor.executeCommand(cmd); return cmd.getMoves(); } private static String gameEnd(Game game) { switch (game.checkGameStatus(game.getPlayerTurn())) { case Draw: case Pat: return "1/2-1/2"; case CheckMate: if (game.getPlayerTurn() == Color.White) return "0-1"; return "1-0"; default: return ""; } } /** * Resolve PGN ambiguity in the case of two pieces of the same type able to move on the same square. * @param cmdExec the command executor attached to the game * @param pieceMove move of the piece * @return the character that solves the eventual ambiguity, empty if no ambiguity to start with */ private static String resolveAmbiguity(CommandExecutor cmdExec, Move pieceMove) { Piece movingPiece = pieceAt(cmdExec, pieceMove.getStart()); assert movingPiece != null; if (movingPiece instanceof Pawn) return ""; List moves = playerMoves(cmdExec); for (Move move : moves) { if (move.equals(pieceMove) || !move.getFinish().equals(pieceMove.getFinish())) continue; Piece otherPiece = pieceAt(cmdExec, move.getStart()); // checking type of piece if (!otherPiece.getClass().equals(movingPiece.getClass())) continue; String startPos = toString(pieceMove.getStart()); if (move.getStart().getX() != pieceMove.getStart().getX()) // not on the same column return Character.toString(startPos.charAt(0)); else return Character.toString(startPos.charAt(1)); } return ""; } /** * From a move, get the capture-part of the associated PGN string. * @param move the move * @param movingPiece the piece that is moving * @return the capture string of the PGN move */ private static String capture(MoveCommand move, Piece movingPiece) { String result = ""; if (move.getDeadPiece() != null) { if (movingPiece instanceof Pawn) { result += toString(move.getMove().getStart()).charAt(0); } result += "x"; } return result; } private static String promote(PlayerCommand nextCommand) { if (nextCommand != null && nextCommand instanceof PromoteCommand promoteCommand) { String result = "="; result += switch (promoteCommand.getPromoteType()) { case Bishop -> "B"; case Knight -> "N"; case Queen -> "Q"; case Rook -> "R"; }; return result; } return ""; } private static String castling(CastlingCommand castlingCommand) { String result = "O-O"; if (castlingCommand.isBigCastling()) result += "-O"; return result; } private static String checkCheckMate(Game game) { switch (game.checkGameStatus(game.getPlayerTurn())) { case CheckMate: return "#"; case Check: return "+"; default: return ""; } } 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) { Piece movingPiece = virtualGame.getBoard().pieceAt(move.getMove().getStart()); assert movingPiece != null; // piece name result += piecePgnName.visit(movingPiece); // ambiguious start result += resolveAmbiguity(executor, move.getMove()); // capture result += capture(move, movingPiece); // end cell result += toString(move.getMove().getFinish()); // promote result += promote(nextCommand); } else if (cmd instanceof CastlingCommand castlingCommand) { result += castling(castlingCommand); } CommandResult commandResult = executor.executeCommand(cmd); // check or checkmate result += checkCheckMate(virtualGame); result += " "; return new MoveResult(result, commandResult); } public static String exportGame(Game game) { Game virtualGame = new Game(); CommandExecutor executor = new CommandExecutor(virtualGame, new EmptyGameDispatcher()); executor.executeCommand(new NewGameCommand()); List commands = game.getMoves(); String result = ""; 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); } MoveResult moveResult = printMove(cmd, nextCommand, virtualGame, executor); if (moveResult.commandResult() == CommandResult.Moved && virtualGame.getPlayerTurn() == Color.Black) { result += tour + "."; tour++; } 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); } public static String toString(Coordinate coordinate) { String letters = "abcdefgh"; String numbers = "87654321"; return Character.toString(letters.charAt(coordinate.getX())) + numbers.charAt(coordinate.getY()); } }