From c8ca4e022985d0a696fa78f990d4cc237b5f91d3 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Fri, 5 Jul 2019 14:14:48 +0200 Subject: [PATCH] Added move generation + Methods for generating legal and pseudolegal moves in Piece + Implementations of move generation for every piece + Highlighting of available moves for the selected piece in BoardPanel - Split up the move method in Board to move and attemptMove --- src/dev/kske/chess/Board.java | 69 +++++++++++++++++------ src/dev/kske/chess/BoardPanel.java | 42 +++++++++++--- src/dev/kske/chess/Move.java | 5 ++ src/dev/kske/chess/Position.java | 5 ++ src/dev/kske/chess/piece/Bishop.java | 46 ++++++++++++++++ src/dev/kske/chess/piece/King.java | 19 +++++++ src/dev/kske/chess/piece/Knight.java | 25 +++++++++ src/dev/kske/chess/piece/Pawn.java | 36 ++++++++++++ src/dev/kske/chess/piece/Piece.java | 22 ++++++++ src/dev/kske/chess/piece/Queen.java | 82 ++++++++++++++++++++++++++++ src/dev/kske/chess/piece/Rook.java | 46 ++++++++++++++++ 11 files changed, 372 insertions(+), 25 deletions(-) diff --git a/src/dev/kske/chess/Board.java b/src/dev/kske/chess/Board.java index 952407c..494ac72 100644 --- a/src/dev/kske/chess/Board.java +++ b/src/dev/kske/chess/Board.java @@ -32,28 +32,27 @@ public class Board { private List gameEventListeners; public Board() { - boardArr = new Piece[8][8]; - kingPos = new HashMap<>(); + boardArr = new Piece[8][8]; + kingPos = new HashMap<>(); gameEventListeners = new ArrayList<>(); initializeDefaultPositions(); } - public boolean move(int xPos, int yPos, int xDest, int yDest) { - return move(new Move(xPos, yPos, xDest, yDest)); - } - - public boolean move(Move move) { + /** + * Moves a piece across the board if the move is legal. + * + * @param move The move to execute + * @return {@code true}, if the attempted move was legal and thus executed + */ + public boolean attemptMove(Move move) { Piece piece = getPos(move); if (piece == null || !piece.isValidMove(move)) return false; else { - // Move piece - // Save destination piece for possible canceling of the move - Piece capturePiece = getDest(move); - setDest(move, getPos(move)); - setPos(move, null); - - // Update the king's position if the moved piece is the king - if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest); + /* + * Move piece + * Save destination piece for possible canceling of the move + */ + Piece capturePiece = move(move); // Revert move if it caused a check for its team if (checkCheck(piece.getColor())) { @@ -64,13 +63,30 @@ public class Board { // TODO: detecting checkmate // Check for check on the opposite team Color oppositeColor = piece.getColor() == Color.WHITE ? Color.BLACK : Color.WHITE; - if (checkCheck(oppositeColor)) - notifyListeners(new GameEvent(this, GameEventType.CHECK, oppositeColor)); + if (checkCheck(oppositeColor)) notifyListeners(new GameEvent(this, GameEventType.CHECK, oppositeColor)); return true; } } + /** + * Moves a piece across the board without checking if the move is legal. + * + * @param move The move to execute + * @return The captures piece, or null if the move's destination was empty + */ + public Piece move(Move move) { + Piece piece = getPos(move); + Piece capturePiece = getDest(move); + setDest(move, piece); + setPos(move, null); + + // Update the king's position if the moved piece is the king + if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest); + + return capturePiece; + } + /** * Reverts a move. * @@ -86,6 +102,25 @@ public class Board { if (getPos(move).getType() == Type.KING) kingPos.put(getPos(move).getColor(), move.pos); } + /** + * Generated every legal move for one color + * + * @param color The color to generate the moves for + * @return A list of all legal moves + */ + public List getMoves(Color color) { + List moves = new ArrayList<>(); + for (int i = 0; i < 8; i++) + for (int j = 0; j < 8; j++) + if (boardArr[i][j] != null && boardArr[i][j].getColor() == color) + moves.addAll(boardArr[i][j].getMoves(new Position(i, j))); + return moves; + } + + public List getMoves(Position pos) { + return get(pos).getMoves(pos); + } + public boolean checkCheck(Color color) { for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) diff --git a/src/dev/kske/chess/BoardPanel.java b/src/dev/kske/chess/BoardPanel.java index 9237ef8..f9fd49f 100644 --- a/src/dev/kske/chess/BoardPanel.java +++ b/src/dev/kske/chess/BoardPanel.java @@ -3,11 +3,12 @@ package dev.kske.chess; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; -import java.awt.Point; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; import javax.swing.JOptionPane; import javax.swing.JPanel; @@ -32,12 +33,16 @@ public class BoardPanel extends JPanel implements GameEventListener { private int tileSize; private Board board; + private List displayMoves; + public BoardPanel(Board board) { this(); setBoard(board); } public BoardPanel() { + displayMoves = new ArrayList<>(); + /* * Add a component listener for adjusting the tile size on resizing. * The size of the board is assumed to be 8x8, as well as the both the board and @@ -57,16 +62,26 @@ public class BoardPanel extends JPanel implements GameEventListener { // Add a mouse adapter for testing piece movement addMouseListener(new MouseAdapter() { - private Point src; + private Position pos; @Override public void mousePressed(MouseEvent evt) { - if (src == null) src = evt.getPoint(); - else { - Point dest = evt.getPoint(); - board.move(src.x / tileSize, src.y / tileSize, dest.x / tileSize, dest.y / tileSize); - repaint(); - src = null; + if (pos == null) { + pos = new Position(evt.getPoint().x / tileSize, evt.getPoint().y / tileSize); + + if (board.get(pos) != null) { + displayMoves.clear(); + displayMoves.addAll(board.getMoves(pos)); + repaint(); + } + } else { + Position dest = new Position(evt.getPoint().x / tileSize, evt.getPoint().y / tileSize); + + if (board.attemptMove(new Move(pos, dest))) { + displayMoves.clear(); + repaint(); + } + pos = null; } } }); @@ -89,6 +104,17 @@ public class BoardPanel extends JPanel implements GameEventListener { for (int j = 0; j < 8; j++) if (board.getBoardArr()[i][j] != null) g.drawImage(TextureUtil .getPieceTexture(board.getBoardArr()[i][j]), i * tileSize, j * tileSize, this); + + // Draw possible moves if a piece was selected + if (!displayMoves.isEmpty()) { + g.setColor(Color.green); + int radius = tileSize / 4; + for (Move move : displayMoves) + g.fillOval(move.dest.x * tileSize + tileSize / 2 - radius / 2, + move.dest.y * tileSize + tileSize / 2 - radius / 2, + radius, + radius); + } } @Override diff --git a/src/dev/kske/chess/Move.java b/src/dev/kske/chess/Move.java index 827046f..e6df053 100644 --- a/src/dev/kske/chess/Move.java +++ b/src/dev/kske/chess/Move.java @@ -29,4 +29,9 @@ public class Move { public boolean isVertical() { return xDist == 0; } public boolean isDiagonal() { return xDist == yDist; } + + @Override + public String toString() { + return String.format("%s -> %s", pos, dest); + } } diff --git a/src/dev/kske/chess/Position.java b/src/dev/kske/chess/Position.java index 5b737a5..17c64ef 100644 --- a/src/dev/kske/chess/Position.java +++ b/src/dev/kske/chess/Position.java @@ -14,4 +14,9 @@ public class Position { this.x = x; this.y = y; } + + @Override + public String toString() { + return String.format("[%d, %d]", x, y); + } } diff --git a/src/dev/kske/chess/piece/Bishop.java b/src/dev/kske/chess/piece/Bishop.java index 067485b..7727550 100644 --- a/src/dev/kske/chess/piece/Bishop.java +++ b/src/dev/kske/chess/piece/Bishop.java @@ -1,7 +1,11 @@ package dev.kske.chess.piece; +import java.util.ArrayList; +import java.util.List; + import dev.kske.chess.Board; import dev.kske.chess.Move; +import dev.kske.chess.Position; /** * Project: Chess
@@ -28,6 +32,48 @@ public class Bishop extends Piece { return checkDestination(move); } + @Override + protected List getPseudolegalMoves(Position pos) { + List moves = new ArrayList<>(); + + // Diagonal moves to the lower right + for (int i = pos.x + 1, j = pos.y + 1; i < 8 && j < 8; i++, j++) { + Move move = new Move(pos, new Position(i, j)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + + // Diagonal moves to the lower left + for (int i = pos.x - 1, j = pos.y + 1; i >= 0 && j < 8; i--, j++) { + Move move = new Move(pos, new Position(i, j)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + + // Diagonal moves to the upper right + for (int i = pos.x + 1, j = pos.y - 1; i < 8 && j >= 0; i++, j--) { + Move move = new Move(pos, new Position(i, j)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + + // Diagonal moves to the upper left + for (int i = pos.x - 1, j = pos.y - 1; i >= 0 && j >= 0; i--, j--) { + Move move = new Move(pos, new Position(i, j)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + return moves; + } + @Override public Type getType() { return Type.BISHOP; } } diff --git a/src/dev/kske/chess/piece/King.java b/src/dev/kske/chess/piece/King.java index 2766215..18428a9 100644 --- a/src/dev/kske/chess/piece/King.java +++ b/src/dev/kske/chess/piece/King.java @@ -1,7 +1,11 @@ package dev.kske.chess.piece; +import java.util.ArrayList; +import java.util.List; + import dev.kske.chess.Board; import dev.kske.chess.Move; +import dev.kske.chess.Position; /** * Project: Chess
@@ -20,6 +24,21 @@ public class King extends Piece { return move.xDist <= 1 && move.yDist <= 1 && isFreePath(move); } + @Override + protected List getPseudolegalMoves(Position pos) { + List moves = new ArrayList<>(); + for (int i = Math.max(0, pos.x - 1); i < Math.min(8, pos.x + 2); i++) + for (int j = Math.max(0, pos.y - 1); j < Math.min(8, pos.y + 2); j++) + if (i != pos.x || j != pos.y) { + Move move = new Move(pos, new Position(i, j)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } + } + return moves; + } + @Override public Type getType() { return Type.KING; } } diff --git a/src/dev/kske/chess/piece/Knight.java b/src/dev/kske/chess/piece/Knight.java index 69107c0..ed94f36 100644 --- a/src/dev/kske/chess/piece/Knight.java +++ b/src/dev/kske/chess/piece/Knight.java @@ -1,7 +1,11 @@ package dev.kske.chess.piece; +import java.util.ArrayList; +import java.util.List; + import dev.kske.chess.Board; import dev.kske.chess.Move; +import dev.kske.chess.Position; /** * Project: Chess
@@ -20,6 +24,27 @@ public class Knight extends Piece { return Math.abs(move.xDist - move.yDist) == 1 && move.xDist != 0 && move.yDist != 0 && isFreePath(move); } + private void checkAndInsertMove(List moves, Position pos, int offsetX, int offsetY) { + if (pos.x + offsetX >= 0 && pos.x + offsetX < 8 && pos.y + offsetY >= 0 && pos.y + offsetY < 8) { + Move move = new Move(pos, new Position(pos.x + offsetX, pos.y + offsetY)); + if (checkDestination(move)) moves.add(move); + } + } + + @Override + protected List getPseudolegalMoves(Position pos) { + List moves = new ArrayList<>(); + checkAndInsertMove(moves, pos, -2, 1); + checkAndInsertMove(moves, pos, -1, 2); + checkAndInsertMove(moves, pos, 1, 2); + checkAndInsertMove(moves, pos, 2, 1); + checkAndInsertMove(moves, pos, -2, -1); + checkAndInsertMove(moves, pos, -1, -2); + checkAndInsertMove(moves, pos, 1, -2); + checkAndInsertMove(moves, pos, 2, -1); + return moves; + } + @Override public Type getType() { return Type.KNIGHT; } } diff --git a/src/dev/kske/chess/piece/Pawn.java b/src/dev/kske/chess/piece/Pawn.java index 3a6a8b3..43b6c8e 100644 --- a/src/dev/kske/chess/piece/Pawn.java +++ b/src/dev/kske/chess/piece/Pawn.java @@ -1,7 +1,11 @@ package dev.kske.chess.piece; +import java.util.ArrayList; +import java.util.List; + import dev.kske.chess.Board; import dev.kske.chess.Move; +import dev.kske.chess.Position; /** * Project: Chess
@@ -37,6 +41,38 @@ public class Pawn extends Piece { else return board.getDest(move) != null && board.getDest(move).getColor() != getColor(); } + @Override + protected List getPseudolegalMoves(Position pos) { + List moves = new ArrayList<>(); + + int sign = getColor() == Color.WHITE ? -1 : 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); + } + + // 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); + } + + // 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); + } + + // 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); + } + return moves; + } + @Override public Type getType() { return Type.PAWN; } } diff --git a/src/dev/kske/chess/piece/Piece.java b/src/dev/kske/chess/piece/Piece.java index ef22b19..6aacc4c 100644 --- a/src/dev/kske/chess/piece/Piece.java +++ b/src/dev/kske/chess/piece/Piece.java @@ -1,7 +1,11 @@ package dev.kske.chess.piece; +import java.util.Iterator; +import java.util.List; + import dev.kske.chess.Board; import dev.kske.chess.Move; +import dev.kske.chess.Position; /** * Project: Chess
@@ -19,6 +23,20 @@ public abstract class Piece { this.board = board; } + public List getMoves(Position pos) { + List moves = getPseudolegalMoves(pos); + for (Iterator iterator = moves.iterator(); iterator.hasNext();) { + Move move = iterator.next(); + Piece capturePiece = board.move(move); + if (board.checkCheck(getColor())) + iterator.remove(); + board.revert(move, capturePiece); + } + return moves; + } + + protected abstract List getPseudolegalMoves(Position pos); + public abstract boolean isValidMove(Move move); protected boolean isFreePath(Move move) { @@ -47,5 +65,9 @@ public abstract class Piece { public static enum Color { WHITE, BLACK; + + public Color opposite() { + return this == WHITE ? BLACK : WHITE; + } } } diff --git a/src/dev/kske/chess/piece/Queen.java b/src/dev/kske/chess/piece/Queen.java index 107bede..4a0589a 100644 --- a/src/dev/kske/chess/piece/Queen.java +++ b/src/dev/kske/chess/piece/Queen.java @@ -1,7 +1,11 @@ package dev.kske.chess.piece; +import java.util.ArrayList; +import java.util.List; + import dev.kske.chess.Board; import dev.kske.chess.Move; +import dev.kske.chess.Position; /** * Project: Chess
@@ -36,6 +40,84 @@ public class Queen extends Piece { return checkDestination(move); } + @Override + protected List getPseudolegalMoves(Position pos) { + List moves = new ArrayList<>(); + + // Horizontal moves to the right + for (int i = pos.x + 1; i < 8; i++) { + Move move = new Move(pos, new Position(i, pos.y)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + + // Horizontal moves to the left + for (int i = pos.x - 1; i >= 0; i--) { + Move move = new Move(pos, new Position(i, pos.y)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + + // Vertical moves to the top + for (int i = pos.y - 1; i >= 0; i--) { + Move move = new Move(pos, new Position(pos.x, i)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + + // Vertical moves to the bottom + for (int i = pos.y + 1; i < 8; i++) { + Move move = new Move(pos, new Position(pos.x, i)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + + // Diagonal moves to the lower right + for (int i = pos.x + 1, j = pos.y + 1; i < 8 && j < 8; i++, j++) { + Move move = new Move(pos, new Position(i, j)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + + // Diagonal moves to the lower left + for (int i = pos.x - 1, j = pos.y + 1; i >= 0 && j < 8; i--, j++) { + Move move = new Move(pos, new Position(i, j)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + + // Diagonal moves to the upper right + for (int i = pos.x + 1, j = pos.y - 1; i < 8 && j >= 0; i++, j--) { + Move move = new Move(pos, new Position(i, j)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + + // Diagonal moves to the upper left + for (int i = pos.x - 1, j = pos.y - 1; i >= 0 && j >= 0; i--, j--) { + Move move = new Move(pos, new Position(i, j)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + return moves; + } + @Override public Type getType() { return Type.QUEEN; } } diff --git a/src/dev/kske/chess/piece/Rook.java b/src/dev/kske/chess/piece/Rook.java index 4a12d28..e69254d 100644 --- a/src/dev/kske/chess/piece/Rook.java +++ b/src/dev/kske/chess/piece/Rook.java @@ -1,7 +1,11 @@ package dev.kske.chess.piece; +import java.util.ArrayList; +import java.util.List; + import dev.kske.chess.Board; import dev.kske.chess.Move; +import dev.kske.chess.Position; /** * Project: Chess
@@ -32,6 +36,48 @@ public class Rook extends Piece { return checkDestination(move); } + @Override + protected List getPseudolegalMoves(Position pos) { + List moves = new ArrayList<>(); + + // Horizontal moves to the right + for (int i = pos.x + 1; i < 8; i++) { + Move move = new Move(pos, new Position(i, pos.y)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + + // Horizontal moves to the left + for (int i = pos.x - 1; i >= 0; i--) { + Move move = new Move(pos, new Position(i, pos.y)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + + // Vertical moves to the top + for (int i = pos.y - 1; i >= 0; i--) { + Move move = new Move(pos, new Position(pos.x, i)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + + // Vertical moves to the bottom + for (int i = pos.y + 1; i < 8; i++) { + Move move = new Move(pos, new Position(pos.x, i)); + if (board.getDest(move) == null || board.getDest(move).getColor() != getColor()) { + moves.add(move); + if (board.getDest(move) != null) break; + } else break; + } + return moves; + } + @Override public Type getType() { return Type.ROOK; } }