From 0edb83087bf80ebb4dd6f0b8cf97c58c0eaf9b8f Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Thu, 31 Oct 2019 19:09:43 +0100 Subject: [PATCH 1/5] Created getters for all fields in Move --- src/dev/kske/chess/board/Board.java | 30 ++++++------ src/dev/kske/chess/board/King.java | 4 +- src/dev/kske/chess/board/Knight.java | 4 +- src/dev/kske/chess/board/Log.java | 4 +- src/dev/kske/chess/board/Move.java | 54 +++++++++++---------- src/dev/kske/chess/board/Pawn.java | 27 ++++++----- src/dev/kske/chess/board/Piece.java | 4 +- src/dev/kske/chess/game/NaturalPlayer.java | 2 +- src/dev/kske/chess/ui/OverlayComponent.java | 8 +-- 9 files changed, 70 insertions(+), 67 deletions(-) diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index 60f0014..35f0b82 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -62,7 +62,7 @@ public class Board { if (piece == null || !piece.isValidMove(move)) return false; else { // Set type after validation - if (move.type == Move.Type.UNKNOWN) move.type = Move.Type.NORMAL; + if (move.getType() == Move.Type.UNKNOWN) move.type = Move.Type.NORMAL; // Move piece move(move); @@ -86,7 +86,7 @@ public class Board { Piece piece = getPos(move); Piece capturePiece = getDest(move); - switch (move.type) { + switch (move.getType()) { case PAWN_PROMOTION: setPos(move, null); // TODO: Select promotion @@ -95,7 +95,7 @@ public class Board { case EN_PASSANT: setDest(move, piece); setPos(move, null); - boardArr[move.dest.x][move.dest.y - move.ySign] = null; + boardArr[move.getDest().x][move.getDest().y - move.getySign()] = null; break; case CASTLING: // Move the king @@ -103,8 +103,8 @@ public class Board { 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 + Move rookMove = move.getDest().x == 6 ? new Move(7, move.getPos().y, 5, move.getPos().y) // Kingside + : new Move(0, move.getPos().y, 3, move.getPos().y); // Queenside setDest(rookMove, getPos(rookMove)); setPos(rookMove, null); break; @@ -119,7 +119,7 @@ public class Board { } // 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.getType() == Type.KING) kingPos.put(piece.getColor(), move.getDest()); // Update log log.add(move, piece, capturePiece); @@ -200,7 +200,7 @@ public class Board { Move move = moveNode.move; Piece capturedPiece = moveNode.capturedPiece; - switch (move.type) { + switch (move.getType()) { case PAWN_PROMOTION: setPos(move, new Pawn(getDest(move).getColor(), this)); setDest(move, capturedPiece); @@ -208,7 +208,7 @@ public class Board { 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); + boardArr[move.getDest().x][move.getDest().y - move.getySign()] = new Pawn(getPos(move).getColor().opposite(), this); break; case CASTLING: // Move the king @@ -216,8 +216,8 @@ public class Board { 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 + Move rookMove = move.getDest().x == 6 ? new Move(5, move.getPos().y, 7, move.getPos().y) // Kingside + : new Move(3, move.getPos().y, 0, move.getPos().y); // Queenside setDest(rookMove, getPos(rookMove)); setPos(rookMove, null); break; @@ -232,7 +232,7 @@ public class Board { } // 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).getType() == Type.KING) kingPos.put(getPos(move).getColor(), move.getPos()); // Update log log.removeLast(); @@ -436,13 +436,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 +450,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 +458,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 diff --git a/src/dev/kske/chess/board/King.java b/src/dev/kske/chess/board/King.java index a864171..f539e14 100644 --- a/src/dev/kske/chess/board/King.java +++ b/src/dev/kske/chess/board/King.java @@ -18,7 +18,7 @@ public class King extends Piece { @Override public boolean isValidMove(Move move) { // Castling - if (move.xDist == 2 && move.yDist == 0) { + if (move.getxDist() == 2 && move.getyDist() == 0) { if (canCastleKingside()) { move.type = Move.Type.CASTLING; return true; @@ -28,7 +28,7 @@ public class King extends Piece { return true; } } - return move.xDist <= 1 && move.yDist <= 1 && checkDestination(move); + return move.getxDist() <= 1 && move.getyDist() <= 1 && checkDestination(move); } @Override diff --git a/src/dev/kske/chess/board/Knight.java b/src/dev/kske/chess/board/Knight.java index aba0499..cbaf67a 100644 --- a/src/dev/kske/chess/board/Knight.java +++ b/src/dev/kske/chess/board/Knight.java @@ -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 moves, Position pos, int offsetX, int offsetY) { diff --git a/src/dev/kske/chess/board/Log.java b/src/dev/kske/chess/board/Log.java index d7754ff..a54b3bb 100644 --- a/src/dev/kske/chess/board/Log.java +++ b/src/dev/kske/chess/board/Log.java @@ -78,14 +78,14 @@ public class Log implements Iterable { * @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.getType() == Type.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; 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.getType() == Type.KING || piece.getType() == Type.ROOK) disableCastlingRights(piece, move.getPos()); final MoveNode leaf = new MoveNode(move, capturedPiece, castlingRights.clone(), enPassant, activeColor, fullmoveNumber, halfmoveClock); diff --git a/src/dev/kske/chess/board/Move.java b/src/dev/kske/chess/board/Move.java index f1312f4..e6e9ae0 100644 --- a/src/dev/kske/chess/board/Move.java +++ b/src/dev/kske/chess/board/Move.java @@ -12,8 +12,8 @@ import java.util.Objects; */ public class Move { - public final Position pos, dest; - public final int xDist, yDist, xSign, ySign; + protected final Position pos, dest; + protected final int xDist, yDist, xSign, ySign; public Type type; public Move(Position pos, Position dest, Type type) { @@ -26,38 +26,25 @@ public class Move { ySign = (int) Math.signum(dest.y - pos.y); } - public Move(Position pos, Position dest) { - this(pos, dest, Type.NORMAL); - } + 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 Move(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); } - public static Move fromLAN(String move) { - return new Move(Position.fromLAN(move.substring(0, 2)), - Position.fromLAN(move.substring(2))); - } + public static Move fromLAN(String move) { return new Move(Position.fromLAN(move.substring(0, 2)), Position.fromLAN(move.substring(2))); } - 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 String.format("%s -> %s", getPos(), getDest()); } @Override - public int hashCode() { - return Objects.hash(dest, pos, type, xDist, xSign, yDist, ySign); - } + public int hashCode() { return Objects.hash(getDest(), getPos(), getType(), getxDist(), getxSign(), getyDist(), getySign()); } @Override public boolean equals(Object obj) { @@ -65,10 +52,25 @@ 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()) && getType() == other.getType() + && getxDist() == other.getxDist() && getxSign() == other.getxSign() && getyDist() == other.getyDist() + && getySign() == other.getySign(); } + 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; } + + public Type getType() { return type; } + public static enum Type { NORMAL, PAWN_PROMOTION, CASTLING, EN_PASSANT, UNKNOWN } diff --git a/src/dev/kske/chess/board/Pawn.java b/src/dev/kske/chess/board/Pawn.java index b83a625..90fb7a2 100644 --- a/src/dev/kske/chess/board/Pawn.java +++ b/src/dev/kske/chess/board/Pawn.java @@ -19,34 +19,34 @@ public class Pawn extends Piece { @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 step = move.isVertical() && move.getyDist() == 1; + boolean doubleStep = move.isVertical() && move.getyDist() == 2; + boolean strafe = move.isDiagonal() && move.getxDist() == 1; boolean enPassant = false; - if (getColor() == Color.WHITE) doubleStep &= move.pos.y == 6; - else doubleStep &= move.pos.y == 1; + 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; + if (move.getySign() == 1 && move.getPos().y == 6 || move.getySign() == -1 && move.getPos().y == 1) + move.type = Move.Type.PAWN_PROMOTION; // TODO: Remove // Mark the move as en passant if necessary - if (strafe && move.dest.equals(board.getLog().getEnPassant())) { + if (strafe && move.getDest().equals(board.getLog().getEnPassant())) { enPassant = true; move.type = Move.Type.EN_PASSANT; } - return enPassant || (step ^ doubleStep ^ strafe) && move.ySign == (getColor() == Color.WHITE ? -1 : 1) + 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(); } @@ -81,6 +81,7 @@ public class Pawn extends Piece { if (isFreePath(move)) moves.add(move); } + // TODO: Check against instance // 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); @@ -88,7 +89,7 @@ public class Pawn extends Piece { // 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); + if (move.isDiagonal() && move.getxDist() == 1) moves.add(move); } return moves; diff --git a/src/dev/kske/chess/board/Piece.java b/src/dev/kske/chess/board/Piece.java index 865fb9f..5fc1fd6 100644 --- a/src/dev/kske/chess/board/Piece.java +++ b/src/dev/kske/chess/board/Piece.java @@ -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); } diff --git a/src/dev/kske/chess/game/NaturalPlayer.java b/src/dev/kske/chess/game/NaturalPlayer.java index 8670a0e..9455b41 100644 --- a/src/dev/kske/chess/game/NaturalPlayer.java +++ b/src/dev/kske/chess/game/NaturalPlayer.java @@ -62,7 +62,7 @@ public class NaturalPlayer extends Player implements MouseListener { if (board.get(pos) != null && board.get(pos).getColor() == color) { List positions = board.getMoves(pos) .stream() - .map(move -> move.dest) + .map(move -> move.getDest()) .collect(Collectors.toList()); overlayComponent.displayDots(positions); } else pos = null; diff --git a/src/dev/kske/chess/ui/OverlayComponent.java b/src/dev/kske/chess/ui/OverlayComponent.java index b011281..cee5449 100644 --- a/src/dev/kske/chess/ui/OverlayComponent.java +++ b/src/dev/kske/chess/ui/OverlayComponent.java @@ -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)); From 71f48895df5d9999da88c6f93dfe603177851a20 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Sun, 3 Nov 2019 15:46:08 +0100 Subject: [PATCH 2/5] Implemented proper pawn promotion * Moved move execution and reversion to the Move class * Removed Move.Type enumeration * Added Move subclasses Castling, EnPassant and PawnPromotion * Generating all four possible pawn promotions in the Pawn class * Temporarily removed special move support from NaturalPlayer --- src/dev/kske/chess/board/Board.java | 89 ++++----------------- src/dev/kske/chess/board/Castling.java | 35 ++++++++ src/dev/kske/chess/board/EnPassant.java | 35 ++++++++ src/dev/kske/chess/board/King.java | 19 ++--- src/dev/kske/chess/board/Move.java | 32 ++++---- src/dev/kske/chess/board/Pawn.java | 68 +++++++--------- src/dev/kske/chess/board/PawnPromotion.java | 46 +++++++++++ src/dev/kske/chess/game/NaturalPlayer.java | 27 ++----- 8 files changed, 189 insertions(+), 162 deletions(-) create mode 100644 src/dev/kske/chess/board/Castling.java create mode 100644 src/dev/kske/chess/board/EnPassant.java create mode 100644 src/dev/kske/chess/board/PawnPromotion.java diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index 35f0b82..7304f64 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -61,9 +61,6 @@ public class Board { Piece piece = getPos(move); if (piece == null || !piece.isValidMove(move)) return false; else { - // Set type after validation - if (move.getType() == Move.Type.UNKNOWN) move.type = Move.Type.NORMAL; - // Move piece move(move); @@ -86,37 +83,8 @@ public class Board { Piece piece = getPos(move); Piece capturePiece = getDest(move); - switch (move.getType()) { - 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.getDest().x][move.getDest().y - move.getySign()] = null; - break; - case CASTLING: - // Move the king - setDest(move, piece); - setPos(move, null); - - // Move the rook - Move rookMove = move.getDest().x == 6 ? new Move(7, move.getPos().y, 5, move.getPos().y) // Kingside - : new Move(0, move.getPos().y, 3, move.getPos().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.getDest()); @@ -136,15 +104,15 @@ public class Board { Pattern.compile( "^(?[NBRQK])(?:(?[a-h])|(?[1-8])|(?[a-h][1-8]))?x?(?[a-h][1-8])(?:\\+{0,2}|\\#)$")); patterns.put("pawnCapture", - Pattern.compile("^(?[a-h])(?[1-8])?x(?[a-h][1-8])(?[NBRQK])?(?:\\+{0,2}|\\#)?$")); - patterns.put("pawnPush", Pattern.compile("^(?[a-h][1-8])(?[NBRQK])?(?:\\+{0,2}|\\#)$")); + Pattern.compile("^(?[a-h])(?[1-8])?x(?[a-h][1-8])(?[NBRQ])?(?:\\+{0,2}|\\#)?$")); + patterns.put("pawnPush", Pattern.compile("^(?[a-h][1-8])(?[NBRQ])?(?:\\+{0,2}|\\#)$")); patterns.put("castling", Pattern.compile("^(?O-O-O)|(?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")); @@ -163,12 +131,16 @@ public class Board { pos = Position.fromLAN(String.format("%c%d", file, rank)); } else pos = get(type, 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")); pos = Position.fromLAN(String.format("%c%d", file, rank)); + if (m.group("promotedTo") != null) { + + } break; case "pawnPush": dest = Position.fromLAN(m.group("toSquare")); @@ -183,10 +155,10 @@ public class Board { 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,40 +168,11 @@ 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.getType()) { - 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.getDest().x][move.getDest().y - move.getySign()] = 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.getDest().x == 6 ? new Move(5, move.getPos().y, 7, move.getPos().y) // Kingside - : new Move(3, move.getPos().y, 0, move.getPos().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.getPos()); diff --git a/src/dev/kske/chess/board/Castling.java b/src/dev/kske/chess/board/Castling.java new file mode 100644 index 0000000..e631a24 --- /dev/null +++ b/src/dev/kske/chess/board/Castling.java @@ -0,0 +1,35 @@ +package dev.kske.chess.board; + +/** + * Project: Chess
+ * File: Castling.java
+ * Created: 2 Nov 2019
+ * + * @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); + } +} diff --git a/src/dev/kske/chess/board/EnPassant.java b/src/dev/kske/chess/board/EnPassant.java new file mode 100644 index 0000000..69c48d9 --- /dev/null +++ b/src/dev/kske/chess/board/EnPassant.java @@ -0,0 +1,35 @@ +package dev.kske.chess.board; + +/** + * Project: Chess
+ * File: EnPassant.java
+ * Created: 2 Nov 2019
+ * + * @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; } +} diff --git a/src/dev/kske/chess/board/King.java b/src/dev/kske/chess/board/King.java index f539e14..cde5a98 100644 --- a/src/dev/kske/chess/board/King.java +++ b/src/dev/kske/chess/board/King.java @@ -17,18 +17,9 @@ public class King extends Piece { @Override public boolean isValidMove(Move move) { - // Castling - if (move.getxDist() == 2 && move.getyDist() == 0) { - if (canCastleKingside()) { - move.type = Move.Type.CASTLING; - return true; - } - if (canCastleQueenside()) { - move.type = Move.Type.CASTLING; - return true; - } - } - return move.getxDist() <= 1 && move.getyDist() <= 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; } diff --git a/src/dev/kske/chess/board/Move.java b/src/dev/kske/chess/board/Move.java index e6e9ae0..f71cf37 100644 --- a/src/dev/kske/chess/board/Move.java +++ b/src/dev/kske/chess/board/Move.java @@ -14,24 +14,33 @@ public class Move { protected final Position pos, dest; protected final int xDist, yDist, xSign, ySign; - public Type type; - 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)); } + // TODO: Pawn promotion public static Move fromLAN(String move) { return new Move(Position.fromLAN(move.substring(0, 2)), Position.fromLAN(move.substring(2))); } + 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 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 String toLAN() { return getPos().toLAN() + getDest().toLAN(); } public boolean isHorizontal() { return getyDist() == 0; } @@ -44,7 +53,7 @@ public class Move { public String toString() { return String.format("%s -> %s", getPos(), getDest()); } @Override - public int hashCode() { return Objects.hash(getDest(), getPos(), getType(), getxDist(), getxSign(), getyDist(), getySign()); } + public int hashCode() { return Objects.hash(getDest(), getPos(), getxDist(), getxSign(), getyDist(), getySign()); } @Override public boolean equals(Object obj) { @@ -52,9 +61,8 @@ public class Move { 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()) && getType() == other.getType() - && getxDist() == other.getxDist() && getxSign() == other.getxSign() && getyDist() == other.getyDist() - && getySign() == other.getySign(); + return Objects.equals(getDest(), other.getDest()) && Objects.equals(getPos(), other.getPos()) && getxDist() == other.getxDist() + && getxSign() == other.getxSign() && getyDist() == other.getyDist() && getySign() == other.getySign(); } public Position getPos() { return pos; } @@ -68,10 +76,4 @@ public class Move { public int getxSign() { return xSign; } public int getySign() { return ySign; } - - public Type getType() { return type; } - - public static enum Type { - NORMAL, PAWN_PROMOTION, CASTLING, EN_PASSANT, UNKNOWN - } } diff --git a/src/dev/kske/chess/board/Pawn.java b/src/dev/kske/chess/board/Pawn.java index 90fb7a2..3f7ddf9 100644 --- a/src/dev/kske/chess/board/Pawn.java +++ b/src/dev/kske/chess/board/Pawn.java @@ -13,31 +13,18 @@ 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.getyDist() == 1; boolean doubleStep = move.isVertical() && move.getyDist() == 2; boolean strafe = move.isDiagonal() && move.getxDist() == 1; - boolean enPassant = false; + 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.getySign() == 1 && move.getPos().y == 6 || move.getySign() == -1 && move.getPos().y == 1) - move.type = Move.Type.PAWN_PROMOTION; // TODO: Remove - - // Mark the move as en passant if necessary - if (strafe && move.getDest().equals(board.getLog().getEnPassant())) { - enPassant = true; - move.type = Move.Type.EN_PASSANT; - } - - return enPassant || (step ^ doubleStep ^ strafe) && move.getySign() == (getColor() == Color.WHITE ? -1 : 1) - && isFreePath(move); + return enPassant || (step ^ doubleStep ^ strafe) && move.getySign() == (getColor() == Color.WHITE ? -1 : 1) && isFreePath(move); } @Override @@ -53,48 +40,47 @@ public class Pawn extends Piece { @Override protected List getPseudolegalMoves(Position pos) { - List moves = new ArrayList<>(); - - int sign = getColor() == Color.WHITE ? -1 : 1; + List 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); - } - - // TODO: Check against instance - // 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); + Move move = new EnPassant(pos, board.getLog().getEnPassant()); if (move.isDiagonal() && move.getxDist() == 1) moves.add(move); } return moves; } + private void addMoveIfValid(List 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 e) { + e.printStackTrace(); + } + } else moves.add(move); + } + } + @Override public Type getType() { return Type.PAWN; } } diff --git a/src/dev/kske/chess/board/PawnPromotion.java b/src/dev/kske/chess/board/PawnPromotion.java new file mode 100644 index 0000000..f8fa22b --- /dev/null +++ b/src/dev/kske/chess/board/PawnPromotion.java @@ -0,0 +1,46 @@ +package dev.kske.chess.board; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +import dev.kske.chess.board.Piece.Color; + +/** + * Project: Chess
+ * File: PawnPromotion.java
+ * Created: 2 Nov 2019
+ * + * @since Chess v0.5-alpha + * @author Kai S. K. Engelbart + */ +public class PawnPromotion extends Move { + + private Constructor promotionPieceConstructor; + + public PawnPromotion(Position pos, Position dest, Class promotionPiece) throws NoSuchMethodException, SecurityException { + super(pos, dest); + promotionPieceConstructor = promotionPiece.getDeclaredConstructor(Color.class, Board.class); + promotionPieceConstructor.setAccessible(true); + } + + public PawnPromotion(int xPos, int yPos, int xDest, int yDest, Class promotionPiece) + throws NoSuchMethodException, SecurityException { + 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) { + super.revert(board, capturedPiece); + board.set(pos, new Pawn(board.get(dest).getColor(), board)); + } +} diff --git a/src/dev/kske/chess/game/NaturalPlayer.java b/src/dev/kske/chess/game/NaturalPlayer.java index 9455b41..4e12c04 100644 --- a/src/dev/kske/chess/game/NaturalPlayer.java +++ b/src/dev/kske/chess/game/NaturalPlayer.java @@ -7,7 +7,6 @@ import java.util.stream.Collectors; import dev.kske.chess.board.Board; import dev.kske.chess.board.Move; -import dev.kske.chess.board.Move.Type; import dev.kske.chess.board.Piece.Color; import dev.kske.chess.board.Position; import dev.kske.chess.ui.OverlayComponent; @@ -37,42 +36,32 @@ 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()); + pos = new Position(evt.getPoint().x / overlayComponent.getTileSize(), evt.getPoint().y / overlayComponent.getTileSize()); Board board = new Board(this.board); if (board.get(pos) != null && board.get(pos).getColor() == color) { - List positions = board.getMoves(pos) - .stream() - .map(move -> move.getDest()) - .collect(Collectors.toList()); + List positions = board.getMoves(pos).stream().map(move -> move.getDest()).collect(Collectors.toList()); overlayComponent.displayDots(positions); } else pos = null; } 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()); overlayComponent.clearDots(); moveRequested = false; - game.onMove(NaturalPlayer.this, new Move(pos, dest, Type.UNKNOWN)); + // TODO: Special moves + game.onMove(NaturalPlayer.this, new Move(pos, dest)); pos = null; } } From e955f05016249072f2321d8ff5687104e4205ca8 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Mon, 4 Nov 2019 05:51:11 +0100 Subject: [PATCH 3/5] Removed Type enumeration from Piece class --- src/dev/kske/chess/board/Bishop.java | 2 +- src/dev/kske/chess/board/Board.java | 45 +++++----- src/dev/kske/chess/board/FENString.java | 2 +- src/dev/kske/chess/board/King.java | 8 +- src/dev/kske/chess/board/Knight.java | 5 +- src/dev/kske/chess/board/Log.java | 11 ++- src/dev/kske/chess/board/Pawn.java | 2 +- src/dev/kske/chess/board/Piece.java | 83 +++++++++---------- src/dev/kske/chess/board/Queen.java | 2 +- src/dev/kske/chess/board/Rook.java | 2 +- src/dev/kske/chess/game/ai/MoveProcessor.java | 44 ++++------ src/dev/kske/chess/io/TextureUtil.java | 2 +- 12 files changed, 97 insertions(+), 111 deletions(-) diff --git a/src/dev/kske/chess/board/Bishop.java b/src/dev/kske/chess/board/Bishop.java index 4b2d8b0..9412a30 100644 --- a/src/dev/kske/chess/board/Bishop.java +++ b/src/dev/kske/chess/board/Bishop.java @@ -65,5 +65,5 @@ public class Bishop extends Piece { } @Override - public Type getType() { return Type.BISHOP; } + public int getValue() { return 30; } } diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index 7304f64..d6c440f 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -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: Chess
@@ -87,7 +86,7 @@ public class Board { 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.getDest()); + if (piece instanceof King) kingPos.put(piece.getColor(), move.getDest()); // Update log log.add(move, piece, capturePiece); @@ -118,29 +117,30 @@ public class Board { 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 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")); pos = Position.fromLAN(String.format("%c%d", file, rank)); if (m.group("promotedTo") != null) { } + move = new Move(pos, dest); break; case "pawnPush": dest = Position.fromLAN(m.group("toSquare")); @@ -151,6 +151,7 @@ public class Board { 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); + move = new Move(pos, dest); break; case "castling": pos = new Position(4, log.getActiveColor() == Color.WHITE ? 7 : 0); @@ -175,7 +176,7 @@ public class Board { 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.getPos()); + if (getPos(move) instanceof King) kingPos.put(getPos(move).getColor(), move.getPos()); // Update log log.removeLast(); @@ -322,30 +323,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 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 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 '-'; } @@ -353,14 +354,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 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; } diff --git a/src/dev/kske/chess/board/FENString.java b/src/dev/kske/chess/board/FENString.java index 0dbb3e5..8f27140 100644 --- a/src/dev/kske/chess/board/FENString.java +++ b/src/dev/kske/chess/board/FENString.java @@ -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); } } diff --git a/src/dev/kske/chess/board/King.java b/src/dev/kske/chess/board/King.java index cde5a98..6807bb3 100644 --- a/src/dev/kske/chess/board/King.java +++ b/src/dev/kske/chess/board/King.java @@ -63,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; } +} \ No newline at end of file diff --git a/src/dev/kske/chess/board/Knight.java b/src/dev/kske/chess/board/Knight.java index cbaf67a..9e54fd8 100644 --- a/src/dev/kske/chess/board/Knight.java +++ b/src/dev/kske/chess/board/Knight.java @@ -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'; } } diff --git a/src/dev/kske/chess/board/Log.java b/src/dev/kske/chess/board/Log.java index a54b3bb..9d60002 100644 --- a/src/dev/kske/chess/board/Log.java +++ b/src/dev/kske/chess/board/Log.java @@ -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: Chess
@@ -78,14 +77,14 @@ public class Log implements Iterable { * @param capturedPiece The piece captured with the move */ public void add(Move move, Piece piece, Piece capturedPiece) { - enPassant = piece.getType() == Type.PAWN && move.getyDist() == 2 ? new Position(move.getPos().x, move.getPos().y + move.getySign()) : 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.getPos()); + 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 { 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; } diff --git a/src/dev/kske/chess/board/Pawn.java b/src/dev/kske/chess/board/Pawn.java index 3f7ddf9..177abba 100644 --- a/src/dev/kske/chess/board/Pawn.java +++ b/src/dev/kske/chess/board/Pawn.java @@ -82,5 +82,5 @@ public class Pawn extends Piece { } @Override - public Type getType() { return Type.PAWN; } + public int getValue() { return 10; } } diff --git a/src/dev/kske/chess/board/Piece.java b/src/dev/kske/chess/board/Piece.java index 5fc1fd6..848dca5 100644 --- a/src/dev/kske/chess/board/Piece.java +++ b/src/dev/kske/chess/board/Piece.java @@ -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 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; } } } diff --git a/src/dev/kske/chess/board/Queen.java b/src/dev/kske/chess/board/Queen.java index 3932b95..b2482ce 100644 --- a/src/dev/kske/chess/board/Queen.java +++ b/src/dev/kske/chess/board/Queen.java @@ -101,5 +101,5 @@ public class Queen extends Piece { } @Override - public Type getType() { return Type.QUEEN; } + public int getValue() { return 90; } } diff --git a/src/dev/kske/chess/board/Rook.java b/src/dev/kske/chess/board/Rook.java index 590e49e..b2efd5c 100644 --- a/src/dev/kske/chess/board/Rook.java +++ b/src/dev/kske/chess/board/Rook.java @@ -65,5 +65,5 @@ public class Rook extends Piece { } @Override - public Type getType() { return Type.ROOK; } + public int getValue() { return 50; } } diff --git a/src/dev/kske/chess/game/ai/MoveProcessor.java b/src/dev/kske/chess/game/ai/MoveProcessor.java index 2c73ddd..112bc30 100644 --- a/src/dev/kske/chess/game/ai/MoveProcessor.java +++ b/src/dev/kske/chess/game/ai/MoveProcessor.java @@ -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: Chess
@@ -28,35 +34,35 @@ public class MoveProcessor implements Callable { private Move bestMove; - private static final Map positionScores; + private static final Map, 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 { 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; } diff --git a/src/dev/kske/chess/io/TextureUtil.java b/src/dev/kske/chess/io/TextureUtil.java index 25ac288..79d251b 100644 --- a/src/dev/kske/chess/io/TextureUtil.java +++ b/src/dev/kske/chess/io/TextureUtil.java @@ -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); } From 5b9937088432a53bf08df8981a3a77bc303a9b2b Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Mon, 4 Nov 2019 18:11:23 +0100 Subject: [PATCH 4/5] Added pawn promotion support to LAN --- src/dev/kske/chess/board/Board.java | 29 ++++++++++++++++----- src/dev/kske/chess/board/Move.java | 18 ++++++++++--- src/dev/kske/chess/board/Pawn.java | 4 ++- src/dev/kske/chess/board/PawnPromotion.java | 19 +++++++++++--- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index d6c440f..997326b 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -1,5 +1,6 @@ package dev.kske.chess.board; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -133,25 +134,39 @@ public class Board { 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(Pawn.class, file) : Integer.parseInt(m.group("fromRank")); - pos = Position.fromLAN(String.format("%c%d", file, rank)); - if (m.group("promotedTo") != null) { - } - move = new Move(pos, dest); + 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 | IllegalAccessException | IllegalArgumentException + | InvocationTargetException | InstantiationException 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); - move = new Move(pos, dest); + + if (m.group("promotedTo") != null) { + try { + move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0))); + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException + | InvocationTargetException | InstantiationException e) { + e.printStackTrace(); + } + } else move = new Move(pos, dest); break; case "castling": pos = new Position(4, log.getActiveColor() == Color.WHITE ? 7 : 0); diff --git a/src/dev/kske/chess/board/Move.java b/src/dev/kske/chess/board/Move.java index f71cf37..e016246 100644 --- a/src/dev/kske/chess/board/Move.java +++ b/src/dev/kske/chess/board/Move.java @@ -1,5 +1,6 @@ package dev.kske.chess.board; +import java.lang.reflect.InvocationTargetException; import java.util.Objects; /** @@ -26,9 +27,6 @@ public class Move { public Move(int xPos, int yPos, int xDest, int yDest) { this(new Position(xPos, yPos), new Position(xDest, yDest)); } - // TODO: Pawn promotion - public static Move fromLAN(String move) { return new Move(Position.fromLAN(move.substring(0, 2)), Position.fromLAN(move.substring(2))); } - 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)); @@ -41,6 +39,20 @@ public class Move { board.set(dest, capturedPiece); } + 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 (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | InstantiationException e) { + e.printStackTrace(); + return null; + } + } else return new Move(pos, dest); + } + public String toLAN() { return getPos().toLAN() + getDest().toLAN(); } public boolean isHorizontal() { return getyDist() == 0; } diff --git a/src/dev/kske/chess/board/Pawn.java b/src/dev/kske/chess/board/Pawn.java index 177abba..eb048ad 100644 --- a/src/dev/kske/chess/board/Pawn.java +++ b/src/dev/kske/chess/board/Pawn.java @@ -1,5 +1,6 @@ package dev.kske.chess.board; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; @@ -74,7 +75,8 @@ public class Pawn extends Piece { 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 e) { + } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException + | InstantiationException e) { e.printStackTrace(); } } else moves.add(move); diff --git a/src/dev/kske/chess/board/PawnPromotion.java b/src/dev/kske/chess/board/PawnPromotion.java index f8fa22b..e2c6896 100644 --- a/src/dev/kske/chess/board/PawnPromotion.java +++ b/src/dev/kske/chess/board/PawnPromotion.java @@ -15,16 +15,24 @@ import dev.kske.chess.board.Piece.Color; */ public class PawnPromotion extends Move { - private Constructor promotionPieceConstructor; + private final Constructor promotionPieceConstructor; + private final char promotionPieceChar; - public PawnPromotion(Position pos, Position dest, Class promotionPiece) throws NoSuchMethodException, SecurityException { + public PawnPromotion(Position pos, Position dest, Class promotionPiece) throws NoSuchMethodException, SecurityException, + IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException { super(pos, dest); + + // Cache piece constructor promotionPieceConstructor = promotionPiece.getDeclaredConstructor(Color.class, Board.class); promotionPieceConstructor.setAccessible(true); + + // Cache piece first char + promotionPieceChar = (char) promotionPiece.getMethod("firstChar").invoke(promotionPieceConstructor.newInstance(null, null)); } public PawnPromotion(int xPos, int yPos, int xDest, int yDest, Class promotionPiece) - throws NoSuchMethodException, SecurityException { + throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, + InstantiationException { this(new Position(xPos, yPos), new Position(xDest, yDest), promotionPiece); } @@ -40,7 +48,10 @@ public class PawnPromotion extends Move { @Override public void revert(Board board, Piece capturedPiece) { + board.set(dest, new Pawn(board.get(dest).getColor(), board)); super.revert(board, capturedPiece); - board.set(pos, new Pawn(board.get(dest).getColor(), board)); } + + @Override + public String toLAN() { return pos.toLAN() + dest.toLAN() + promotionPieceChar; } } From 86a1c70edad009b48f67c8db650102fe396d1900 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Tue, 5 Nov 2019 05:41:26 +0100 Subject: [PATCH 5/5] Added pawn promotion selection * Letting a NaturalPlayer select the promotion piece with a combo box * Optimized reflection use in PawnPromotion * Changed toString method of Move to use LAN Closes #9 --- src/dev/kske/chess/board/Board.java | 7 +-- src/dev/kske/chess/board/Move.java | 6 +-- src/dev/kske/chess/board/Pawn.java | 4 +- src/dev/kske/chess/board/PawnPromotion.java | 40 +++++++++++--- src/dev/kske/chess/game/NaturalPlayer.java | 60 ++++++++++++++++----- 5 files changed, 84 insertions(+), 33 deletions(-) diff --git a/src/dev/kske/chess/board/Board.java b/src/dev/kske/chess/board/Board.java index 997326b..fbbf2bd 100644 --- a/src/dev/kske/chess/board/Board.java +++ b/src/dev/kske/chess/board/Board.java @@ -1,6 +1,5 @@ package dev.kske.chess.board; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; @@ -143,8 +142,7 @@ public class Board { if (m.group("promotedTo") != null) { try { move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0))); - } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException | InstantiationException e) { + } catch (NoSuchMethodException | SecurityException | IllegalArgumentException e) { e.printStackTrace(); } } else move = new Move(pos, dest); @@ -162,8 +160,7 @@ public class Board { if (m.group("promotedTo") != null) { try { move = new PawnPromotion(pos, dest, Piece.fromFirstChar(m.group("promotedTo").charAt(0))); - } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException - | InvocationTargetException | InstantiationException e) { + } catch (NoSuchMethodException | SecurityException | IllegalArgumentException e) { e.printStackTrace(); } } else move = new Move(pos, dest); diff --git a/src/dev/kske/chess/board/Move.java b/src/dev/kske/chess/board/Move.java index e016246..a03a78c 100644 --- a/src/dev/kske/chess/board/Move.java +++ b/src/dev/kske/chess/board/Move.java @@ -1,6 +1,5 @@ package dev.kske.chess.board; -import java.lang.reflect.InvocationTargetException; import java.util.Objects; /** @@ -45,8 +44,7 @@ public class Move { if (move.length() == 5) { try { return new PawnPromotion(pos, dest, Piece.fromFirstChar(move.charAt(4))); - } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException - | InstantiationException e) { + } catch (NoSuchMethodException | SecurityException | IllegalArgumentException e) { e.printStackTrace(); return null; } @@ -62,7 +60,7 @@ public class Move { public boolean isDiagonal() { return getxDist() == getyDist(); } @Override - public String toString() { return String.format("%s -> %s", getPos(), getDest()); } + public String toString() { return toLAN(); } @Override public int hashCode() { return Objects.hash(getDest(), getPos(), getxDist(), getxSign(), getyDist(), getySign()); } diff --git a/src/dev/kske/chess/board/Pawn.java b/src/dev/kske/chess/board/Pawn.java index eb048ad..18e275e 100644 --- a/src/dev/kske/chess/board/Pawn.java +++ b/src/dev/kske/chess/board/Pawn.java @@ -1,6 +1,5 @@ package dev.kske.chess.board; -import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; @@ -75,8 +74,7 @@ public class Pawn extends Piece { 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 | IllegalAccessException | IllegalArgumentException | InvocationTargetException - | InstantiationException e) { + } catch (NoSuchMethodException | SecurityException | IllegalArgumentException e) { e.printStackTrace(); } } else moves.add(move); diff --git a/src/dev/kske/chess/board/PawnPromotion.java b/src/dev/kske/chess/board/PawnPromotion.java index e2c6896..91ddaac 100644 --- a/src/dev/kske/chess/board/PawnPromotion.java +++ b/src/dev/kske/chess/board/PawnPromotion.java @@ -2,6 +2,7 @@ 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; @@ -15,19 +16,16 @@ import dev.kske.chess.board.Piece.Color; */ public class PawnPromotion extends Move { + private final Class promotionPieceClass; private final Constructor promotionPieceConstructor; - private final char promotionPieceChar; - public PawnPromotion(Position pos, Position dest, Class promotionPiece) throws NoSuchMethodException, SecurityException, - IllegalAccessException, IllegalArgumentException, InvocationTargetException, InstantiationException { + public PawnPromotion(Position pos, Position dest, Class promotionPieceClass) throws NoSuchMethodException, SecurityException { super(pos, dest); + this.promotionPieceClass = promotionPieceClass; // Cache piece constructor - promotionPieceConstructor = promotionPiece.getDeclaredConstructor(Color.class, Board.class); + promotionPieceConstructor = promotionPieceClass.getDeclaredConstructor(Color.class, Board.class); promotionPieceConstructor.setAccessible(true); - - // Cache piece first char - promotionPieceChar = (char) promotionPiece.getMethod("firstChar").invoke(promotionPieceConstructor.newInstance(null, null)); } public PawnPromotion(int xPos, int yPos, int xDest, int yDest, Class promotionPiece) @@ -53,5 +51,31 @@ public class PawnPromotion extends Move { } @Override - public String toLAN() { return pos.toLAN() + dest.toLAN() + promotionPieceChar; } + 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); + } } diff --git a/src/dev/kske/chess/game/NaturalPlayer.java b/src/dev/kske/chess/game/NaturalPlayer.java index 4e12c04..864c4e5 100644 --- a/src/dev/kske/chess/game/NaturalPlayer.java +++ b/src/dev/kske/chess/game/NaturalPlayer.java @@ -5,8 +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.Piece; import dev.kske.chess.board.Piece.Color; import dev.kske.chess.board.Position; import dev.kske.chess.ui.OverlayComponent; @@ -24,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 possibleMoves; public NaturalPlayer(Color color, OverlayComponent overlayComponent) { super(color); @@ -47,22 +51,52 @@ public class NaturalPlayer extends Player implements MouseListener { @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 positions = board.getMoves(pos).stream().map(move -> move.getDest()).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()); + // Get all moves leading to the specified destination + List 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 comboBox = new JComboBox(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; - // TODO: Special moves - game.onMove(NaturalPlayer.this, new Move(pos, dest)); - pos = null; + selectedPiece = null; + possibleMoves = null; } }