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; /** * Import a game from PGN format. */ 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 final int COORDINATE_ANY = -1; /** * Parse the moves from a PGN string. * @param unparsedMoves * @return */ 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; } /** * Parse a move from the PGN format and plays it in the given game. * @param move * @param game * @return */ 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(COORDINATE_ANY, COORDINATE_ANY); // 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; } /** * Get the start coordinate of a piece. * @param dest the end position of the moving piece * @param pieceType the type of the piece * @param ambiguity coordinates of the moving piece, indicated with the constant COORDINATE_ANY. * @param game the game * @see COORDINATE_ANY * @see getAmbiguityPattern * @return the start coordinate of the piece */ 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() != COORDINATE_ANY && coord.getX() != pattern.getX()) return false; if (pattern.getY() != COORDINATE_ANY && coord.getY() != pattern.getY()) return false; return true; } private static Coordinate getAmbiguityPattern(char amb) { if (Character.isDigit(amb)) return new Coordinate(COORDINATE_ANY, getYCoord(amb)); return new Coordinate(getXCoord(amb), COORDINATE_ANY); } private static Coordinate stringToCoordinate(String coordinates) { char xPos = coordinates.charAt(0); char yPos = coordinates.charAt(1); return new Coordinate(getXCoord(xPos), getYCoord(yPos)); } }