This repository has been archived on 2021-02-18. You can view files and clone it, but cannot push or open issues or pull requests.
chess/src/dev/kske/chess/board/Move.java

285 lines
8.9 KiB
Java

package dev.kske.chess.board;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import dev.kske.chess.board.Piece.Color;
/**
* Project: <strong>Chess</strong><br>
* File: <strong>Move.java</strong><br>
* Created: <strong>02.07.2019</strong><br>
*
* @since Chess v0.1-alpha
* @author Kai S. K. Engelbart
*/
public class Move {
protected final Position pos, dest;
protected final int xDist, yDist, xSign, ySign;
/**
* Creates an instance of {@link Move}.
*
* @param pos the position of this move
* @param dest the destination of this move
*/
public Move(Position pos, Position dest) {
this.pos = pos;
this.dest = dest;
xDist = Math.abs(dest.x - pos.x);
yDist = Math.abs(dest.y - pos.y);
xSign = (int) Math.signum(dest.x - pos.x);
ySign = (int) Math.signum(dest.y - pos.y);
}
/**
* Creates an instance of {@link Move}.
*
* @param xPos the horizontal position of this move
* @param yPos the vertical position of this move
* @param xDest the horizontal destination of this move
* @param yDest the vertical destination of this move
*/
public Move(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); }
/**
* Executed this move on a board.
*
* @param board the board to execute this move on.
*/
public void execute(Board board) {
// Move the piece to the move's destination square and clean the old position
board.set(dest, board.get(pos));
board.set(pos, null);
}
/**
* Reverts this move on a board.
*
* @param board the board to revert this move on
* @param capturedPiece the piece to place at the destination of this move (used
* for reinstating captured pieces)
*/
public void revert(Board board, Piece capturedPiece) {
// Move the piece to the move's position square and clean the destination
board.set(pos, board.get(dest));
board.set(dest, capturedPiece);
}
/**
* @return a new move containing this move's destination as its position and
* this move's position as its destination
*/
public Move invert() { return new Move(dest, pos); }
/**
* Constructs a move from a string representation in Long Algebraic Notation
* (LAN).
*
* @param move the LAN string to construct the move from
* @return the constructed move
*/
public static Move fromLAN(String move) {
Position pos = Position.fromLAN(move.substring(0, 2));
Position dest = Position.fromLAN(move.substring(2));
if (move.length() == 5) {
try {
return new PawnPromotion(pos, dest, Piece.fromFirstChar(move.charAt(4)));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
return new Move(pos, dest);
}
/**
* Generates a string representation of this move in Long Algebraic Notation
* (LAN).
*
* @return the LAN string
*/
public String toLAN() { return getPos().toLAN() + getDest().toLAN(); }
/**
* Converts a move string from standard algebraic notation to a {@link Move}
* object.
*
* @param sanMove the move string to convert from
* @param board the board on which the move has to be executed
* @return the converted {@link Move} object
*/
public static Move fromSAN(String sanMove, Board board) {
Map<String, Pattern> patterns = new HashMap<>();
patterns.put("pieceMove",
Pattern.compile(
"^(?<pieceType>[NBRQK])(?:(?<fromFile>[a-h])|(?<fromRank>[1-8])|(?<fromSquare>[a-h][1-8]))?x?(?<toSquare>[a-h][1-8])(?:\\+{0,2}|\\#)$"));
patterns.put("pawnCapture",
Pattern.compile("^(?<fromFile>[a-h])(?<fromRank>[1-8])?x(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQ])?(?:\\+{0,2}|\\#)?$"));
patterns.put("pawnPush", Pattern.compile("^(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQ])?(?:\\+{0,2}|\\#)$"));
patterns.put("castling", Pattern.compile("^(?<queenside>O-O-O)|(?<kingside>O-O)(?:\\+{0,2}|\\#)?$"));
for (Map.Entry<String, Pattern> entry : patterns.entrySet()) {
Matcher m = entry.getValue().matcher(sanMove);
if (m.find()) {
Position pos = null, dest = null;
Move move = null;
switch (entry.getKey()) {
case "pieceMove":
dest = Position.fromLAN(m.group("toSquare"));
if (m.group("fromSquare") != null) pos = Position.fromLAN(m.group("fromSquare"));
else {
Class<? extends Piece> pieceClass = Piece.fromFirstChar(m.group("pieceType").charAt(0));
char file;
int rank;
if (m.group("fromFile") != null) {
file = m.group("fromFile").charAt(0);
rank = board.get(pieceClass, file);
pos = Position.fromLAN(String.format("%c%d", file, rank));
} else if (m.group("fromRank") != null) {
rank = Integer.parseInt(m.group("fromRank").substring(0, 1));
file = board.get(pieceClass, rank);
pos = Position.fromLAN(String.format("%c%d", file, rank));
} else pos = board.get(pieceClass, dest);
}
move = new Move(pos, dest);
break;
case "pawnCapture":
char file = m.group("fromFile").charAt(0);
int rank = m.group("fromRank") == null ? board.get(Pawn.class, file) : Integer.parseInt(m.group("fromRank"));
dest = Position.fromLAN(m.group("toSquare"));
pos = Position.fromLAN(String.format("%c%d", file, rank));
if (m.group("promotedTo") != null) {
try {
move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0)));
} catch (Exception e) {
e.printStackTrace();
}
} else move = new Move(pos, dest);
break;
case "pawnPush":
dest = Position.fromLAN(m.group("toSquare"));
int step = board.getLog().getActiveColor() == Color.WHITE ? 1 : -1;
// One step forward
if (board.getBoardArr()[dest.x][dest.y + step] != null) pos = new Position(dest.x, dest.y + step);
// Double step forward
else pos = new Position(dest.x, dest.y + 2 * step);
if (m.group("promotedTo") != null) {
try {
move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0)));
} catch (Exception e) {
e.printStackTrace();
}
} else move = new Move(pos, dest);
break;
case "castling":
pos = new Position(4, board.getLog().getActiveColor() == Color.WHITE ? 7 : 0);
dest = new Position(m.group("kingside") != null ? 6 : 2, pos.y);
move = new Castling(pos, dest);
break;
}
return move;
}
}
return null;
}
/**
* Generates a string representation of this move in Standard Algebraic Notation
* (SAN).
*
* @param board the {@link Board} providing the context of this move
* @return the SAN string
*/
public String toSAN(Board board) {
final Piece piece = board.get(pos);
StringBuilder sb = new StringBuilder(8);
// Piece symbol
if (!(piece instanceof Pawn)) sb.append(Character.toUpperCase(piece.firstChar()));
// Position
// TODO: Deconstruct position into optional file or rank
// 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');
// Destination
sb.append(dest.toLAN());
return sb.toString();
}
/**
* @return {@code true} if the move is purely horizontal
*/
public boolean isHorizontal() { return getyDist() == 0; }
/**
* @return {@code true} if the move is purely vertical
*/
public boolean isVertical() { return getxDist() == 0; }
/**
* @return {@code true} if the move is diagonal
*/
public boolean isDiagonal() { return getxDist() == getyDist(); }
@Override
public String toString() { return toLAN(); }
@Override
public int hashCode() { return Objects.hash(getDest(), getPos(), getxDist(), getxSign(), getyDist(), getySign()); }
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Move other = (Move) obj;
return Objects.equals(getDest(), other.getDest()) && Objects.equals(getPos(), other.getPos()) && getxDist() == other.getxDist()
&& getxSign() == other.getxSign() && getyDist() == other.getyDist() && getySign() == other.getySign();
}
/**
* @return the position
*/
public Position getPos() { return pos; }
/**
* @return the destination
*/
public Position getDest() { return dest; }
/**
* @return the x distance
*/
public int getxDist() { return xDist; }
/**
* @return the y distance
*/
public int getyDist() { return yDist; }
/**
* @return the sign of the x distance
*/
public int getxSign() { return xSign; }
/**
* @return the sign of the y distance
*/
public int getySign() { return ySign; }
}