Files
3DChess/app/src/main/java/chess/pgn/PgnImport.java
fl.du.pr Grens c741044469
All checks were successful
Linux arm64 / Build (push) Successful in 34s
doc, fin
2025-05-18 23:30:30 +02:00

199 lines
5.5 KiB
Java

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<String, Class<? extends Piece>> pieceMap = Map.of(
"K", King.class,
"Q", Queen.class,
"R", Rook.class,
"B", Bishop.class,
"N", Knight.class);
private static final Map<String, PromoteType> promoteMap = Map.of(
"Q", PromoteType.Queen,
"R", PromoteType.Rook,
"B", PromoteType.Bishop,
"N", PromoteType.Knight);
public static List<PlayerCommand> 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<PlayerCommand> 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<PlayerCommand> 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<PlayerCommand> 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<PlayerCommand> 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<? extends Piece> 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<PlayerCommand> 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<? extends Piece> pieceType, Coordinate ambiguity, Game game) {
final ChessBoard board = game.getBoard();
List<Coordinate> 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));
}
}