Merge pull request #11 from CyB3RC0nN0R/f/pawn_promotion

Added pawn promotion
This commit is contained in:
Kai S. K. Engelbart 2019-11-05 05:44:11 +01:00 committed by GitHub
commit f70cd85f2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 424 additions and 317 deletions

View File

@ -65,5 +65,5 @@ public class Bishop extends Piece {
}
@Override
public Type getType() { return Type.BISHOP; }
public int getValue() { return 30; }
}

View File

@ -10,7 +10,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.board.Piece.Type;
/**
* Project: <strong>Chess</strong><br>
@ -61,9 +60,6 @@ public class Board {
Piece piece = getPos(move);
if (piece == null || !piece.isValidMove(move)) return false;
else {
// Set type after validation
if (move.type == Move.Type.UNKNOWN) move.type = Move.Type.NORMAL;
// Move piece
move(move);
@ -86,40 +82,11 @@ public class Board {
Piece piece = getPos(move);
Piece capturePiece = getDest(move);
switch (move.type) {
case PAWN_PROMOTION:
setPos(move, null);
// TODO: Select promotion
setDest(move, new Queen(piece.getColor(), this));
break;
case EN_PASSANT:
setDest(move, piece);
setPos(move, null);
boardArr[move.dest.x][move.dest.y - move.ySign] = null;
break;
case CASTLING:
// Move the king
setDest(move, piece);
setPos(move, null);
// Move the rook
Move rookMove = move.dest.x == 6 ? new Move(7, move.pos.y, 5, move.pos.y) // Kingside
: new Move(0, move.pos.y, 3, move.pos.y); // Queenside
setDest(rookMove, getPos(rookMove));
setPos(rookMove, null);
break;
case UNKNOWN:
System.err.printf("Move of unknown type %s found!%n", move);
case NORMAL:
setDest(move, piece);
setPos(move, null);
break;
default:
System.err.printf("Move %s of unimplemented type found!%n", move);
}
// Execute the move
move.execute(this);
// Update the king's position if the moved piece is the king
if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest);
if (piece instanceof King) kingPos.put(piece.getColor(), move.getDest());
// Update log
log.add(move, piece, capturePiece);
@ -136,57 +103,75 @@ public class Board {
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>[NBRQK])?(?:\\+{0,2}|\\#)?$"));
patterns.put("pawnPush", Pattern.compile("^(?<toSquare>[a-h][1-8])(?<promotedTo>[NBRQK])?(?:\\+{0,2}|\\#)$"));
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}|\\#)?$"));
patterns.forEach((patternName, pattern) -> {
Matcher m = pattern.matcher(sanMove);
if (m.find()) {
Position pos = null, dest = null;
Move.Type moveType = Move.Type.NORMAL;
Position pos = null, dest = null;
Move move = null;
switch (patternName) {
case "pieceMove":
dest = Position.fromLAN(m.group("toSquare"));
if (m.group("fromSquare") != null) pos = Position.fromLAN(m.group("fromSquare"));
else {
Type type = Type.fromFirstChar(m.group("pieceType").charAt(0));
char file;
int rank;
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 = get(type, file);
rank = 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 = get(type, rank);
file = get(pieceClass, rank);
pos = Position.fromLAN(String.format("%c%d", file, rank));
} else pos = get(type, dest);
} else pos = get(pieceClass, dest);
}
move = new Move(pos, dest);
break;
case "pawnCapture":
dest = Position.fromLAN(m.group("toSquare"));
char file = m.group("fromFile").charAt(0);
int rank = m.group("fromRank") == null ? get(Type.PAWN, file) : Integer.parseInt(m.group("fromRank"));
int rank = m.group("fromRank") == null ? 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 (NoSuchMethodException | SecurityException | IllegalArgumentException e) {
e.printStackTrace();
}
} else move = new Move(pos, dest);
break;
case "pawnPush":
dest = Position.fromLAN(m.group("toSquare"));
// TODO: Pawn promotion
int step = log.getActiveColor() == Color.WHITE ? 1 : -1;
// One step forward
if (boardArr[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 (NoSuchMethodException | SecurityException | IllegalArgumentException e) {
e.printStackTrace();
}
} else move = new Move(pos, dest);
break;
case "castling":
pos = new Position(4, log.getActiveColor() == Color.WHITE ? 7 : 0);
dest = new Position(m.group("kingside") != null ? 6 : 2, pos.y);
moveType = Move.Type.CASTLING;
move = new Castling(pos, dest);
break;
}
move(new Move(pos, dest, moveType));
move(move);
return;
}
});
@ -196,43 +181,14 @@ public class Board {
* Reverts the last move and removes it from the log.
*/
public void revert() {
MoveNode moveNode = log.getLast();
Move move = moveNode.move;
Piece capturedPiece = moveNode.capturedPiece;
MoveNode moveNode = log.getLast();
Move move = moveNode.move;
switch (move.type) {
case PAWN_PROMOTION:
setPos(move, new Pawn(getDest(move).getColor(), this));
setDest(move, capturedPiece);
break;
case EN_PASSANT:
setPos(move, getDest(move));
setDest(move, null);
boardArr[move.dest.x][move.dest.y - move.ySign] = new Pawn(getPos(move).getColor().opposite(), this);
break;
case CASTLING:
// Move the king
setPos(move, getDest(move));
setDest(move, null);
// Move the rook
Move rookMove = move.dest.x == 6 ? new Move(5, move.pos.y, 7, move.pos.y) // Kingside
: new Move(3, move.pos.y, 0, move.pos.y); // Queenside
setDest(rookMove, getPos(rookMove));
setPos(rookMove, null);
break;
case UNKNOWN:
System.err.printf("Move of unknown type %s found!%n", move);
case NORMAL:
setPos(move, getDest(move));
setDest(move, capturedPiece);
break;
default:
System.err.printf("Move %s of unimplemented type found!%n", move);
}
// Revert the move
move.revert(this, moveNode.capturedPiece);
// Update the king's position if the moved piece is the king
if (getPos(move).getType() == Type.KING) kingPos.put(getPos(move).getColor(), move.pos);
if (getPos(move) instanceof King) kingPos.put(getPos(move).getColor(), move.getPos());
// Update log
log.removeLast();
@ -379,30 +335,30 @@ public class Board {
/**
* Searches for a {@link Piece} inside a file (A - H).
*
* @param type The {@link Type} of the piece to search for
* @param file The file in which to search for the piece
* @param pieceClass The class of the piece to search for
* @param file The file in which to search for the piece
* @return The rank (1 - 8) of the first piece with the specified type and
* current color in the file, or {@code -1} if there isn't any
*/
public int get(Type type, char file) {
public int get(Class<? extends Piece> pieceClass, char file) {
int x = file - 97;
for (int i = 0; i < 8; i++)
if (boardArr[x][i] != null && boardArr[x][i].getType() == type && boardArr[x][i].getColor() == log.getActiveColor()) return 8 - i;
if (boardArr[x][i] != null && boardArr[x][i].getClass() == pieceClass && boardArr[x][i].getColor() == log.getActiveColor()) return 8 - i;
return -1;
}
/**
* Searches for a {@link Piece} inside a rank (1 - 8).
*
* @param type The {@link Type} of the piece to search for
* @param rank The rank in which to search for the piece
* @param pieceClass The class of the piece to search for
* @param rank The rank in which to search for the piece
* @return The file (A - H) of the first piece with the specified type and
* current color in the file, or {@code -} if there isn't any
*/
public char get(Type type, int rank) {
public char get(Class<? extends Piece> pieceClass, int rank) {
int y = rank - 1;
for (int i = 0; i < 8; i++)
if (boardArr[i][y] != null && boardArr[i][y].getType() == type && boardArr[i][y].getColor() == log.getActiveColor())
if (boardArr[i][y] != null && boardArr[i][y].getClass() == pieceClass && boardArr[i][y].getColor() == log.getActiveColor())
return (char) (i + 97);
return '-';
}
@ -410,14 +366,14 @@ public class Board {
/**
* Searches for a {@link Piece} that can move to a {@link Position}.
*
* @param type The {@link Type} of the piece to search for
* @param dest The destination that the piece is required to reach
* @param pieceClass The class of the piece to search for
* @param dest The destination that the piece is required to reach
* @return The position of a piece that can move to the specified destination
*/
public Position get(Type type, Position dest) {
public Position get(Class<? extends Piece> pieceClass, Position dest) {
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
if (boardArr[i][j] != null && boardArr[i][j].getType() == type && boardArr[i][j].getColor() == log.getActiveColor()) {
if (boardArr[i][j] != null && boardArr[i][j].getClass() == pieceClass && boardArr[i][j].getColor() == log.getActiveColor()) {
Position pos = new Position(i, j);
if (boardArr[i][j].isValidMove(new Move(pos, dest))) return pos;
}
@ -436,13 +392,13 @@ public class Board {
* @param move The move from which position to return a piece
* @return The piece at the position of the move
*/
public Piece getPos(Move move) { return get(move.pos); }
public Piece getPos(Move move) { return get(move.getPos()); }
/**
* @param move The move from which destination to return a piece
* @return The piece at the destination of the move
*/
public Piece getDest(Move move) { return get(move.dest); }
public Piece getDest(Move move) { return get(move.getDest()); }
/**
* Places a piece at the position of a move.
@ -450,7 +406,7 @@ public class Board {
* @param move The move at which position to place the piece
* @param piece The piece to place
*/
public void setPos(Move move, Piece piece) { set(move.pos, piece); }
public void setPos(Move move, Piece piece) { set(move.getPos(), piece); }
/**
* Places a piece at the destination of a move.
@ -458,7 +414,7 @@ public class Board {
* @param move The move at which destination to place the piece
* @param piece The piece to place
*/
public void setDest(Move move, Piece piece) { set(move.dest, piece); }
public void setDest(Move move, Piece piece) { set(move.getDest(), piece); }
/**
* @return The board array

View File

@ -0,0 +1,35 @@
package dev.kske.chess.board;
/**
* Project: <strong>Chess</strong><br>
* File: <strong>Castling.java</strong><br>
* Created: <strong>2 Nov 2019</strong><br>
*
* @since Chess v0.5-alpha
* @author Kai S. K. Engelbart
*/
public class Castling extends Move {
private final Move rookMove;
public Castling(Position pos, Position dest) {
super(pos, dest);
rookMove = dest.x == 6 ? new Move(7, pos.y, 5, pos.y) : new Move(0, pos.y, 3, pos.y);
}
public Castling(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); }
@Override
public void execute(Board board) {
// Move the king and the rook
super.execute(board);
rookMove.execute(board);
}
@Override
public void revert(Board board, Piece capturedPiece) {
// Move the king and the rook
super.revert(board, capturedPiece);
rookMove.revert(board, null);
}
}

View File

@ -0,0 +1,35 @@
package dev.kske.chess.board;
/**
* Project: <strong>Chess</strong><br>
* File: <strong>EnPassant.java</strong><br>
* Created: <strong>2 Nov 2019</strong><br>
*
* @since Chess v0.5-alpha
* @author Kai S. K. Engelbart
*/
public class EnPassant extends Move {
private final Position capturePos;
public EnPassant(Position pos, Position dest) {
super(pos, dest);
capturePos = new Position(dest.x, dest.y - ySign);
}
public EnPassant(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); }
@Override
public void execute(Board board) {
super.execute(board);
board.set(capturePos, null);
}
@Override
public void revert(Board board, Piece capturedPiece) {
super.revert(board, capturedPiece);
board.set(capturePos, new Pawn(board.get(pos).getColor().opposite(), board));
}
public Position getCapturePos() { return capturePos; }
}

View File

@ -164,7 +164,7 @@ public class FENString {
}
// Write piece character
char p = piece.getType().firstChar();
char p = piece.firstChar();
sb.append(piece.getColor() == Color.WHITE ? Character.toUpperCase(p) : p);
}
}

View File

@ -17,18 +17,9 @@ public class King extends Piece {
@Override
public boolean isValidMove(Move move) {
// Castling
if (move.xDist == 2 && move.yDist == 0) {
if (canCastleKingside()) {
move.type = Move.Type.CASTLING;
return true;
}
if (canCastleQueenside()) {
move.type = Move.Type.CASTLING;
return true;
}
}
return move.xDist <= 1 && move.yDist <= 1 && checkDestination(move);
return (move.getxDist() == 2 && move.getyDist() == 0
&& (move.getDest().x == 6 && canCastleKingside() || move.getDest().x == 2 && canCastleQueenside()))
|| move.getxDist() <= 1 && move.getyDist() <= 1 && checkDestination(move);
}
@Override
@ -42,8 +33,8 @@ public class King extends Piece {
}
// Castling
if (canCastleKingside()) moves.add(new Move(pos, new Position(6, pos.y), Move.Type.CASTLING));
if (canCastleQueenside()) moves.add(new Move(pos, new Position(2, pos.y), Move.Type.CASTLING));
if (canCastleKingside()) moves.add(new Castling(pos, new Position(6, pos.y)));
if (canCastleQueenside()) moves.add(new Castling(pos, new Position(2, pos.y)));
return moves;
}
@ -72,10 +63,10 @@ public class King extends Piece {
private boolean canCastle(Position kingPos, Position freeDest, Position rookPos, Position jumpPos) {
Piece rook = board.get(rookPos);
return rook != null && rook.getType() == Type.ROOK && isFreePath(new Move(kingPos, freeDest))
&& !board.isAttacked(kingPos, getColor().opposite()) && !board.isAttacked(jumpPos, getColor().opposite());
return rook != null && rook instanceof Rook && isFreePath(new Move(kingPos, freeDest)) && !board.isAttacked(kingPos, getColor().opposite())
&& !board.isAttacked(jumpPos, getColor().opposite());
}
@Override
public Type getType() { return Type.KING; }
}
public int getValue() { return 0; }
}

View File

@ -19,8 +19,8 @@ public class Knight extends Piece {
@Override
public boolean isValidMove(Move move) {
return Math.abs(move.xDist - move.yDist) == 1
&& (move.xDist == 1 && move.yDist == 2 || move.xDist == 2 && move.yDist == 1) && checkDestination(move);
return Math.abs(move.getxDist() - move.getyDist()) == 1
&& (move.getxDist() == 1 && move.getyDist() == 2 || move.getxDist() == 2 && move.getyDist() == 1) && checkDestination(move);
}
private void checkAndInsertMove(List<Move> moves, Position pos, int offsetX, int offsetY) {
@ -45,5 +45,8 @@ public class Knight extends Piece {
}
@Override
public Type getType() { return Type.KNIGHT; }
public int getValue() { return 35; }
@Override
public char firstChar() { return 'n'; }
}

View File

@ -5,7 +5,6 @@ import java.util.Iterator;
import java.util.Objects;
import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.board.Piece.Type;
/**
* Project: <strong>Chess</strong><br>
@ -78,14 +77,14 @@ public class Log implements Iterable<MoveNode> {
* @param capturedPiece The piece captured with the move
*/
public void add(Move move, Piece piece, Piece capturedPiece) {
enPassant = piece.getType() == Type.PAWN && move.yDist == 2 ? new Position(move.pos.x, move.pos.y + move.ySign) : null;
enPassant = piece instanceof Pawn && move.getyDist() == 2 ? new Position(move.getPos().x, move.getPos().y + move.getySign()) : null;
if (activeColor == Color.BLACK) ++fullmoveNumber;
if (piece.getType() == Type.PAWN || capturedPiece != null) halfmoveClock = 0;
if (piece instanceof Pawn || capturedPiece != null) halfmoveClock = 0;
else++halfmoveClock;
activeColor = activeColor.opposite();
// Disable castling rights if a king or a rook has been moved
if (piece.getType() == Type.KING || piece.getType() == Type.ROOK) disableCastlingRights(piece, move.pos);
if (piece instanceof King || piece instanceof Rook) disableCastlingRights(piece, move.getPos());
final MoveNode leaf = new MoveNode(move, capturedPiece, castlingRights.clone(), enPassant, activeColor, fullmoveNumber, halfmoveClock);
@ -170,11 +169,11 @@ public class Log implements Iterable<MoveNode> {
private void disableCastlingRights(Piece piece, Position initialPosition) {
// Kingside
if (piece.getType() == Type.KING || piece.getType() == Type.ROOK && initialPosition.x == 7)
if (piece instanceof King || piece instanceof Rook && initialPosition.x == 7)
castlingRights[piece.getColor() == Color.WHITE ? MoveNode.WHITE_KINGSIDE : MoveNode.BLACK_KINGSIDE] = false;
// Queenside
if (piece.getType() == Type.KING || piece.getType() == Type.ROOK && initialPosition.x == 0)
if (piece instanceof King || piece instanceof Rook && initialPosition.x == 0)
castlingRights[piece.getColor() == Color.WHITE ? MoveNode.WHITE_QUEENSIDE : MoveNode.BLACK_QUEENSIDE] = false;
}

View File

@ -12,52 +12,58 @@ import java.util.Objects;
*/
public class Move {
public final Position pos, dest;
public final int xDist, yDist, xSign, ySign;
public Type type;
protected final Position pos, dest;
protected final int xDist, yDist, xSign, ySign;
public Move(Position pos, Position dest, Type type) {
public Move(Position pos, Position dest) {
this.pos = pos;
this.dest = dest;
this.type = type;
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);
}
public Move(Position pos, Position dest) {
this(pos, dest, Type.NORMAL);
public Move(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); }
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);
}
public Move(int xPos, int yPos, int xDest, int yDest) {
this(new Position(xPos, yPos), new Position(xDest, yDest));
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);
}
public static Move fromLAN(String move) {
return new Move(Position.fromLAN(move.substring(0, 2)),
Position.fromLAN(move.substring(2)));
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 (NoSuchMethodException | SecurityException | IllegalArgumentException e) {
e.printStackTrace();
return null;
}
} else return new Move(pos, dest);
}
public String toLAN() {
return pos.toLAN() + dest.toLAN();
}
public String toLAN() { return getPos().toLAN() + getDest().toLAN(); }
public boolean isHorizontal() { return yDist == 0; }
public boolean isHorizontal() { return getyDist() == 0; }
public boolean isVertical() { return xDist == 0; }
public boolean isVertical() { return getxDist() == 0; }
public boolean isDiagonal() { return xDist == yDist; }
public boolean isDiagonal() { return getxDist() == getyDist(); }
@Override
public String toString() {
return String.format("%s -> %s", pos, dest);
}
public String toString() { return toLAN(); }
@Override
public int hashCode() {
return Objects.hash(dest, pos, type, xDist, xSign, yDist, ySign);
}
public int hashCode() { return Objects.hash(getDest(), getPos(), getxDist(), getxSign(), getyDist(), getySign()); }
@Override
public boolean equals(Object obj) {
@ -65,11 +71,19 @@ public class Move {
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
Move other = (Move) obj;
return Objects.equals(dest, other.dest) && Objects.equals(pos, other.pos) && type == other.type
&& xDist == other.xDist && xSign == other.xSign && yDist == other.yDist && ySign == other.ySign;
return Objects.equals(getDest(), other.getDest()) && Objects.equals(getPos(), other.getPos()) && getxDist() == other.getxDist()
&& getxSign() == other.getxSign() && getyDist() == other.getyDist() && getySign() == other.getySign();
}
public static enum Type {
NORMAL, PAWN_PROMOTION, CASTLING, EN_PASSANT, UNKNOWN
}
public Position getPos() { return pos; }
public Position getDest() { return dest; }
public int getxDist() { return xDist; }
public int getyDist() { return yDist; }
public int getxSign() { return xSign; }
public int getySign() { return ySign; }
}

View File

@ -13,87 +13,74 @@ import java.util.List;
*/
public class Pawn extends Piece {
public Pawn(Color color, Board board) {
super(color, board);
}
public Pawn(Color color, Board board) { super(color, board); }
@Override
public boolean isValidMove(Move move) {
boolean step = move.isVertical() && move.yDist == 1;
boolean doubleStep = move.isVertical() && move.yDist == 2;
boolean strafe = move.isDiagonal() && move.xDist == 1;
boolean enPassant = false;
if (getColor() == Color.WHITE) doubleStep &= move.pos.y == 6;
else doubleStep &= move.pos.y == 1;
boolean step = move.isVertical() && move.getyDist() == 1;
boolean doubleStep = move.isVertical() && move.getyDist() == 2;
boolean strafe = move.isDiagonal() && move.getxDist() == 1;
boolean enPassant = strafe && move.getDest().equals(board.getLog().getEnPassant());
if (getColor() == Color.WHITE) doubleStep &= move.getPos().y == 6;
else doubleStep &= move.getPos().y == 1;
// Mark move as pawn promotion if necessary
if (move.ySign == 1 && move.pos.y == 6 || move.ySign == -1 && move.pos.y == 1)
move.type = Move.Type.PAWN_PROMOTION;
// Mark the move as en passant if necessary
if (strafe && move.dest.equals(board.getLog().getEnPassant())) {
enPassant = true;
move.type = Move.Type.EN_PASSANT;
}
return enPassant || (step ^ doubleStep ^ strafe) && move.ySign == (getColor() == Color.WHITE ? -1 : 1)
&& isFreePath(move);
return enPassant || (step ^ doubleStep ^ strafe) && move.getySign() == (getColor() == Color.WHITE ? -1 : 1) && isFreePath(move);
}
@Override
protected boolean isFreePath(Move move) {
// Two steps forward
if (move.yDist == 2)
return board.getBoardArr()[move.pos.x][move.dest.y - move.ySign] == null && board.getDest(move) == null;
if (move.getyDist() == 2)
return board.getBoardArr()[move.getPos().x][move.getDest().y - move.getySign()] == null && board.getDest(move) == null;
// One step forward
else if (move.xDist == 0) return board.getDest(move) == null;
else if (move.getxDist() == 0) return board.getDest(move) == null;
// Capture move
else return board.getDest(move) != null && board.getDest(move).getColor() != getColor();
}
@Override
protected List<Move> getPseudolegalMoves(Position pos) {
List<Move> moves = new ArrayList<>();
int sign = getColor() == Color.WHITE ? -1 : 1;
List<Move> moves = new ArrayList<>();
int sign = getColor() == Color.WHITE ? -1 : 1;
boolean pawnPromotion = sign == 1 && pos.y == 6 || sign == -1 && pos.y == 1;
// Strafe left
if (pos.x > 0) {
Move move = new Move(pos, new Position(pos.x - 1, pos.y + sign));
if (isFreePath(move)) moves.add(move);
}
if (pos.x > 0) addMoveIfValid(moves, pos, new Position(pos.x - 1, pos.y + sign), pawnPromotion);
// Strafe right
if (pos.x < 7) {
Move move = new Move(pos, new Position(pos.x + 1, pos.y + sign));
if (isFreePath(move)) moves.add(move);
}
if (pos.x < 7) addMoveIfValid(moves, pos, new Position(pos.x + 1, pos.y + sign), pawnPromotion);
// Step forward
if (sign == 1 && pos.y < 7 || sign == -1 && pos.y > 0) {
Move move = new Move(pos, new Position(pos.x, pos.y + sign));
if (isFreePath(move)) moves.add(move);
}
if (sign == 1 && pos.y < 7 || sign == -1 && pos.y > 0) addMoveIfValid(moves, pos, new Position(pos.x, pos.y + sign), pawnPromotion);
// Double step forward
if (sign == 1 && pos.y == 1 || sign == -1 && pos.y == 6) {
Move move = new Move(pos, new Position(pos.x, pos.y + 2 * sign));
if (isFreePath(move)) moves.add(move);
}
// Mark moves as pawn promotion if necessary
if (sign == 1 && pos.y == 6 || sign == -1 && pos.y == 1)
moves.parallelStream().forEach(m -> m.type = Move.Type.PAWN_PROMOTION);
if (sign == 1 && pos.y == 1 || sign == -1 && pos.y == 6) addMoveIfValid(moves, pos, new Position(pos.x, pos.y + 2 * sign), pawnPromotion);
// Add en passant move if necessary
if (board.getLog().getEnPassant() != null) {
Move move = new Move(pos, board.getLog().getEnPassant(), Move.Type.EN_PASSANT);
if (move.isDiagonal() && move.xDist == 1) moves.add(move);
Move move = new EnPassant(pos, board.getLog().getEnPassant());
if (move.isDiagonal() && move.getxDist() == 1) moves.add(move);
}
return moves;
}
private void addMoveIfValid(List<Move> moves, Position pos, Position dest, boolean pawnPromotion) {
Move move = new Move(pos, dest);
if (isFreePath(move)) {
if (pawnPromotion) {
try {
moves.add(new PawnPromotion(pos, dest, Queen.class));
moves.add(new PawnPromotion(pos, dest, Rook.class));
moves.add(new PawnPromotion(pos, dest, Knight.class));
moves.add(new PawnPromotion(pos, dest, Bishop.class));
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException e) {
e.printStackTrace();
}
} else moves.add(move);
}
}
@Override
public Type getType() { return Type.PAWN; }
public int getValue() { return 10; }
}

View File

@ -0,0 +1,81 @@
package dev.kske.chess.board;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Objects;
import dev.kske.chess.board.Piece.Color;
/**
* Project: <strong>Chess</strong><br>
* File: <strong>PawnPromotion.java</strong><br>
* Created: <strong>2 Nov 2019</strong><br>
*
* @since Chess v0.5-alpha
* @author Kai S. K. Engelbart
*/
public class PawnPromotion extends Move {
private final Class<? extends Piece> promotionPieceClass;
private final Constructor<? extends Piece> promotionPieceConstructor;
public PawnPromotion(Position pos, Position dest, Class<? extends Piece> promotionPieceClass) throws NoSuchMethodException, SecurityException {
super(pos, dest);
this.promotionPieceClass = promotionPieceClass;
// Cache piece constructor
promotionPieceConstructor = promotionPieceClass.getDeclaredConstructor(Color.class, Board.class);
promotionPieceConstructor.setAccessible(true);
}
public PawnPromotion(int xPos, int yPos, int xDest, int yDest, Class<? extends Piece> promotionPiece)
throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException,
InstantiationException {
this(new Position(xPos, yPos), new Position(xDest, yDest), promotionPiece);
}
@Override
public void execute(Board board) {
try {
board.set(pos, promotionPieceConstructor.newInstance(board.get(pos).getColor(), board));
super.execute(board);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | SecurityException e) {
e.printStackTrace();
}
}
@Override
public void revert(Board board, Piece capturedPiece) {
board.set(dest, new Pawn(board.get(dest).getColor(), board));
super.revert(board, capturedPiece);
}
@Override
public String toLAN() {
char promotionPieceChar = '-';
try {
promotionPieceChar = (char) promotionPieceClass.getMethod("firstChar").invoke(promotionPieceConstructor.newInstance(null, null));
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException
| InstantiationException e) {
e.printStackTrace();
}
return pos.toLAN() + dest.toLAN() + promotionPieceChar;
}
@Override
public int hashCode() {
final int prime = 31;
int result = super.hashCode();
result = prime * result + Objects.hash(promotionPieceClass);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!super.equals(obj)) return false;
if (getClass() != obj.getClass()) return false;
PawnPromotion other = (PawnPromotion) obj;
return Objects.equals(promotionPieceClass, other.promotionPieceClass);
}
}

View File

@ -44,8 +44,8 @@ public abstract class Piece implements Cloneable {
* @param move The move to check
*/
protected boolean isFreePath(Move move) {
for (int i = move.pos.x + move.xSign, j = move.pos.y + move.ySign; i != move.dest.x
|| j != move.dest.y; i += move.xSign, j += move.ySign)
for (int i = move.getPos().x + move.getxSign(), j = move.getPos().y + move.getySign(); i != move.getDest().x
|| j != move.getDest().y; i += move.getxSign(), j += move.getySign())
if (board.getBoardArr()[i][j] != null) return false;
return checkDestination(move);
}
@ -57,9 +57,7 @@ public abstract class Piece implements Cloneable {
* @param move The move to check
* @return {@code false} if the move's destination is from the same team
*/
protected final boolean checkDestination(Move move) {
return board.getDest(move) == null || board.getDest(move).getColor() != getColor();
}
protected final boolean checkDestination(Move move) { return board.getDest(move) == null || board.getDest(move).getColor() != getColor(); }
@Override
public Object clone() {
@ -72,14 +70,11 @@ public abstract class Piece implements Cloneable {
return piece;
}
public abstract Type getType();
public Color getColor() { return color; }
@Override
public String toString() { return getClass().getSimpleName(); }
@Override
public int hashCode() {
return Objects.hash(color);
}
public int hashCode() { return Objects.hash(color); }
@Override
public boolean equals(Object obj) {
@ -90,52 +85,50 @@ public abstract class Piece implements Cloneable {
return color == other.color;
}
public static enum Type {
/**
* @return the standard value of this {@link Piece} that can be used for board
* evaluation
*/
public abstract int getValue();
KING, QUEEN, ROOK, KNIGHT, BISHOP, PAWN;
/**
* @return The first character of this {@link Piece} in algebraic notation and
* lower case
*/
public char firstChar() { return Character.toLowerCase(toString().charAt(0)); }
/**
* @return The first character of this {@link Type} in algebraic notation and
* lower case
*/
public char firstChar() {
return this == KNIGHT ? 'n' : Character.toLowerCase(this.toString().charAt(0));
}
public static Type fromFirstChar(char firstChar) {
switch (Character.toLowerCase(firstChar)) {
case 'k':
return KING;
case 'q':
return QUEEN;
case 'r':
return ROOK;
case 'n':
return KNIGHT;
case 'b':
return BISHOP;
case 'p':
return PAWN;
default:
return null;
}
public static Class<? extends Piece> fromFirstChar(char firstChar) {
switch (Character.toLowerCase(firstChar)) {
case 'k':
return King.class;
case 'q':
return Queen.class;
case 'r':
return Rook.class;
case 'n':
return Knight.class;
case 'b':
return Bishop.class;
case 'p':
return Pawn.class;
default:
return null;
}
}
/**
* @return the {@link Color} of this {@link Piece}
*/
public Color getColor() { return color; }
public static enum Color {
WHITE, BLACK;
public static Color fromFirstChar(char c) {
return Character.toLowerCase(c) == 'w' ? WHITE : BLACK;
}
public static Color fromFirstChar(char c) { return Character.toLowerCase(c) == 'w' ? WHITE : BLACK; }
public char firstChar() {
return this == WHITE ? 'w' : 'b';
}
public char firstChar() { return this == WHITE ? 'w' : 'b'; }
public Color opposite() {
return this == WHITE ? BLACK : WHITE;
}
public Color opposite() { return this == WHITE ? BLACK : WHITE; }
}
}

View File

@ -101,5 +101,5 @@ public class Queen extends Piece {
}
@Override
public Type getType() { return Type.QUEEN; }
public int getValue() { return 90; }
}

View File

@ -65,5 +65,5 @@ public class Rook extends Piece {
}
@Override
public Type getType() { return Type.ROOK; }
public int getValue() { return 50; }
}

View File

@ -5,9 +5,11 @@ import java.awt.event.MouseListener;
import java.util.List;
import java.util.stream.Collectors;
import dev.kske.chess.board.Board;
import javax.swing.JComboBox;
import javax.swing.JOptionPane;
import dev.kske.chess.board.Move;
import dev.kske.chess.board.Move.Type;
import dev.kske.chess.board.Piece;
import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.board.Position;
import dev.kske.chess.ui.OverlayComponent;
@ -25,7 +27,8 @@ public class NaturalPlayer extends Player implements MouseListener {
private final OverlayComponent overlayComponent;
private boolean moveRequested;
private Position pos;
private Piece selectedPiece;
private List<Move> possibleMoves;
public NaturalPlayer(Color color, OverlayComponent overlayComponent) {
super(color);
@ -37,43 +40,63 @@ public class NaturalPlayer extends Player implements MouseListener {
}
@Override
public void requestMove() {
moveRequested = true;
}
public void requestMove() { moveRequested = true; }
@Override
public void cancelMove() {
moveRequested = false;
}
public void cancelMove() { moveRequested = false; }
@Override
public void disconnect() {
overlayComponent.removeMouseListener(this);
}
public void disconnect() { overlayComponent.removeMouseListener(this); }
@Override
public void mousePressed(MouseEvent evt) {
if (!moveRequested) return;
if (pos == null) {
pos = new Position(evt.getPoint().x / overlayComponent.getTileSize(),
evt.getPoint().y / overlayComponent.getTileSize());
if (selectedPiece == null) {
Board board = new Board(this.board);
if (board.get(pos) != null && board.get(pos).getColor() == color) {
List<Position> positions = board.getMoves(pos)
.stream()
.map(move -> move.dest)
.collect(Collectors.toList());
overlayComponent.displayDots(positions);
} else pos = null;
// Get selected Piece
final Position pos = new Position(evt.getPoint().x / overlayComponent.getTileSize(), evt.getPoint().y / overlayComponent.getTileSize());
selectedPiece = board.get(pos);
// Check if a piece was selected
if (selectedPiece != null) {
// Discard selection if the piece has the wrong color
if (selectedPiece.getColor() == color.opposite()) selectedPiece = null;
else {
// Generate all moves possible with the selected piece and display their
// destinations
possibleMoves = selectedPiece.getMoves(pos);
overlayComponent.displayDots(possibleMoves.stream().map(move -> move.getDest()).collect(Collectors.toList()));
}
}
} else {
Position dest = new Position(evt.getPoint().x / overlayComponent.getTileSize(),
evt.getPoint().y / overlayComponent.getTileSize());
Position dest = new Position(evt.getPoint().x / overlayComponent.getTileSize(), evt.getPoint().y / overlayComponent.getTileSize());
// Get all moves leading to the specified destination
List<Move> selectedMoves = possibleMoves.stream().filter(m -> m.getDest().equals(dest)).collect(Collectors.toList());
if (!selectedMoves.isEmpty()) {
Move move;
// Process pawn promotion if necessary
if (selectedMoves.size() > 1) {
// Let the user select a promotion piece
JComboBox<Move> comboBox = new JComboBox<Move>(selectedMoves.toArray(new Move[0]));
JOptionPane.showMessageDialog(overlayComponent, comboBox, "Select a promotion", JOptionPane.QUESTION_MESSAGE);
move = selectedMoves.get(comboBox.getSelectedIndex());
} else move = selectedMoves.get(0);
// Tell the game to execute the move
moveRequested = false;
game.onMove(NaturalPlayer.this, move);
}
// Discard the selection
overlayComponent.clearDots();
moveRequested = false;
game.onMove(NaturalPlayer.this, new Move(pos, dest, Type.UNKNOWN));
pos = null;
selectedPiece = null;
possibleMoves = null;
}
}

View File

@ -5,10 +5,16 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import dev.kske.chess.board.Bishop;
import dev.kske.chess.board.Board;
import dev.kske.chess.board.King;
import dev.kske.chess.board.Knight;
import dev.kske.chess.board.Move;
import dev.kske.chess.board.Pawn;
import dev.kske.chess.board.Piece;
import dev.kske.chess.board.Piece.Color;
import dev.kske.chess.board.Piece.Type;
import dev.kske.chess.board.Queen;
import dev.kske.chess.board.Rook;
/**
* Project: <strong>Chess</strong><br>
@ -28,35 +34,35 @@ public class MoveProcessor implements Callable<ProcessingResult> {
private Move bestMove;
private static final Map<Type, int[][]> positionScores;
private static final Map<Class<? extends Piece>, int[][]> positionScores;
static {
positionScores = new HashMap<>();
positionScores.put(Type.KING,
positionScores.put(King.class,
new int[][] { new int[] { -3, -4, -4, -5, -5, -4, -4, -3 }, new int[] { -3, -4, -4, -5, -4, -4, -4, -3 },
new int[] { -3, -4, -4, -5, -4, -4, -4, -3 }, new int[] { -3, -4, -4, -5, -4, -4, -4, -3 },
new int[] { -2, -3, -3, -2, -2, -2, -2, -1 }, new int[] { -1, -2, -2, -2, -2, -2, -2, -1 },
new int[] { 2, 2, 0, 0, 0, 0, 2, 2 }, new int[] { 2, 3, 1, 0, 0, 1, 3, 2 } });
positionScores.put(Type.QUEEN,
positionScores.put(Queen.class,
new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, -2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { 0, 0, 1, 1, 1, 1, 0, -1 },
new int[] { -1, 1, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 0, 1, 0, 0, 0, 0, -1 },
new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
positionScores.put(Type.ROOK,
positionScores.put(Rook.class,
new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 1, 1, 1, 1, 1, 1, 1, 1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
new int[] { -1, 0, 0, 0, 0, 0, 0, -1 }, new int[] { 0, 0, 0, 1, 1, 0, 0, 0 } });
positionScores.put(Type.KNIGHT,
positionScores.put(Knight.class,
new int[][] { new int[] { -5, -4, -3, -3, -3, -3, -4, -5 }, new int[] { -4, -2, 0, 0, 0, 0, -2, -4 },
new int[] { -3, 0, 1, 2, 2, 1, 0, -3 }, new int[] { -3, 1, 2, 2, 2, 2, 1, -3 }, new int[] { -3, 0, 2, 2, 2, 2, 0, -1 },
new int[] { -3, 1, 1, 2, 2, 1, 1, -3 }, new int[] { -4, -2, 0, 1, 1, 0, -2, -4 },
new int[] { -5, -4, -3, -3, -3, -3, -4, -5 } });
positionScores.put(Type.BISHOP,
positionScores.put(Bishop.class,
new int[][] { new int[] { -2, -1, -1, -1, -1, -1, -1, 2 }, new int[] { -1, 0, 0, 0, 0, 0, 0, -1 },
new int[] { -1, 0, 1, 1, 1, 1, 0, -1 }, new int[] { -1, 1, 1, 1, 1, 1, 1, -1 }, new int[] { -1, 0, 1, 1, 1, 1, 0, -1 },
new int[] { -1, 1, 1, 1, 1, 1, 1, -1 }, new int[] { -1, 1, 0, 0, 0, 0, 1, -1 },
new int[] { -2, -1, -1, -1, -1, -1, -1, -2 } });
positionScores.put(Type.PAWN,
positionScores.put(Pawn.class,
new int[][] { new int[] { 0, 0, 0, 0, 0, 0, 0, 0 }, new int[] { 5, 5, 5, 5, 5, 5, 5, 5 }, new int[] { 1, 1, 2, 3, 3, 2, 1, 1 },
new int[] { 0, 0, 1, 3, 3, 1, 0, 0 }, new int[] { 0, 0, 0, 2, 2, 0, 0, 0 }, new int[] { 0, 0, -1, 0, 0, -1, 0, 0 },
new int[] { 0, 1, 1, -2, -2, 1, 1, 0 }, new int[] { 0, 0, 0, 0, 0, 0, 0, 0 } });
@ -108,25 +114,9 @@ public class MoveProcessor implements Callable<ProcessingResult> {
for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++)
if (board.getBoardArr()[i][j] != null && board.getBoardArr()[i][j].getColor() == color) {
switch (board.getBoardArr()[i][j].getType()) {
case QUEEN:
score += 90;
break;
case ROOK:
score += 50;
break;
case KNIGHT:
score += 30;
break;
case BISHOP:
score += 30;
break;
case PAWN:
score += 10;
break;
}
if (positionScores.containsKey(board.getBoardArr()[i][j].getType()))
score += positionScores.get(board.getBoardArr()[i][j].getType())[i][color == Color.WHITE ? j : 7 - j];
score += board.getBoardArr()[i][j].getValue();
if (positionScores.containsKey(board.getBoardArr()[i][j].getClass()))
score += positionScores.get(board.getBoardArr()[i][j].getClass())[i][color == Color.WHITE ? j : 7 - j];
}
return score;
}

View File

@ -37,7 +37,7 @@ public class TextureUtil {
* @return The fitting texture
*/
public static Image getPieceTexture(Piece piece) {
String key = piece.getType().toString().toLowerCase() + "_" + piece.getColor().toString().toLowerCase();
String key = piece.toString().toLowerCase() + "_" + piece.getColor().toString().toLowerCase();
return scaledTextures.get(key);
}

View File

@ -48,15 +48,15 @@ public class OverlayComponent extends JComponent {
// Draw an arrow representing the last move and mark its position and
// destination
if (arrow != null) {
Point pos = new Point(arrow.pos.x * tileSize + tileSize / 2, arrow.pos.y * tileSize + tileSize / 2);
Point dest = new Point(arrow.dest.x * tileSize + tileSize / 2, arrow.dest.y * tileSize + tileSize / 2);
Point pos = new Point(arrow.getPos().x * tileSize + tileSize / 2, arrow.getPos().y * tileSize + tileSize / 2);
Point dest = new Point(arrow.getDest().x * tileSize + tileSize / 2, arrow.getDest().y * tileSize + tileSize / 2);
Graphics2D g2d = (Graphics2D) g;
g2d.setStroke(new BasicStroke(3));
g2d.setColor(Color.yellow);
g2d.drawRect(arrow.pos.x * tileSize, arrow.pos.y * tileSize, tileSize, tileSize);
g2d.drawRect(arrow.dest.x * tileSize, arrow.dest.y * tileSize, tileSize, tileSize);
g2d.drawRect(arrow.getPos().x * tileSize, arrow.getPos().y * tileSize, tileSize, tileSize);
g2d.drawRect(arrow.getDest().x * tileSize, arrow.getDest().y * tileSize, tileSize, tileSize);
Shape arrowShape = createArrowShape(pos, dest);
g.setColor(new Color(255, 0, 0, 127));