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
This commit is contained in:
Kai S. K. Engelbart 2019-07-05 14:14:48 +02:00
parent 55389be39a
commit c8ca4e0229
11 changed files with 372 additions and 25 deletions

View File

@ -38,22 +38,21 @@ public class Board {
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<Move> getMoves(Color color) {
List<Move> 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<Move> 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++)

View File

@ -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<Move> 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);
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();
src = null;
}
} 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

View File

@ -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);
}
}

View File

@ -14,4 +14,9 @@ public class Position {
this.x = x;
this.y = y;
}
@Override
public String toString() {
return String.format("[%d, %d]", x, y);
}
}

View File

@ -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: <strong>Chess</strong><br>
@ -28,6 +32,48 @@ public class Bishop extends Piece {
return checkDestination(move);
}
@Override
protected List<Move> getPseudolegalMoves(Position pos) {
List<Move> 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; }
}

View File

@ -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: <strong>Chess</strong><br>
@ -20,6 +24,21 @@ public class King extends Piece {
return move.xDist <= 1 && move.yDist <= 1 && isFreePath(move);
}
@Override
protected List<Move> getPseudolegalMoves(Position pos) {
List<Move> 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; }
}

View File

@ -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: <strong>Chess</strong><br>
@ -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<Move> 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<Move> getPseudolegalMoves(Position pos) {
List<Move> 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; }
}

View File

@ -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: <strong>Chess</strong><br>
@ -37,6 +41,38 @@ public class Pawn extends Piece {
else return board.getDest(move) != null && board.getDest(move).getColor() != getColor();
}
@Override
protected List<Move> getPseudolegalMoves(Position pos) {
List<Move> moves = new ArrayList<>();
int sign = getColor() == Color.WHITE ? -1 : 1;
// 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; }
}

View File

@ -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: <strong>Chess</strong><br>
@ -19,6 +23,20 @@ public abstract class Piece {
this.board = board;
}
public List<Move> getMoves(Position pos) {
List<Move> moves = getPseudolegalMoves(pos);
for (Iterator<Move> 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<Move> 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;
}
}
}

View File

@ -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: <strong>Chess</strong><br>
@ -36,6 +40,84 @@ public class Queen extends Piece {
return checkDestination(move);
}
@Override
protected List<Move> getPseudolegalMoves(Position pos) {
List<Move> 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; }
}

View File

@ -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: <strong>Chess</strong><br>
@ -32,6 +36,48 @@ public class Rook extends Piece {
return checkDestination(move);
}
@Override
protected List<Move> getPseudolegalMoves(Position pos) {
List<Move> 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; }
}