375 lines
9.4 KiB
Java
375 lines
9.4 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;
|
|
}
|
|
}
|