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 f710b2d6f7
commit 8158490431
11 changed files with 372 additions and 25 deletions

View File

@ -32,28 +32,27 @@ public class Board {
private List<GameEventListener> gameEventListeners; private List<GameEventListener> gameEventListeners;
public Board() { public Board() {
boardArr = new Piece[8][8]; boardArr = new Piece[8][8];
kingPos = new HashMap<>(); kingPos = new HashMap<>();
gameEventListeners = new ArrayList<>(); gameEventListeners = new ArrayList<>();
initializeDefaultPositions(); initializeDefaultPositions();
} }
public boolean move(int xPos, int yPos, int xDest, int yDest) { /**
return move(new Move(xPos, yPos, xDest, yDest)); * Moves a piece across the board if the move is legal.
} *
* @param move The move to execute
public boolean move(Move move) { * @return {@code true}, if the attempted move was legal and thus executed
*/
public boolean attemptMove(Move move) {
Piece piece = getPos(move); Piece piece = getPos(move);
if (piece == null || !piece.isValidMove(move)) return false; if (piece == null || !piece.isValidMove(move)) return false;
else { else {
// Move piece /*
// Save destination piece for possible canceling of the move * Move piece
Piece capturePiece = getDest(move); * Save destination piece for possible canceling of the move
setDest(move, getPos(move)); */
setPos(move, null); Piece capturePiece = move(move);
// Update the king's position if the moved piece is the king
if (piece.getType() == Type.KING) kingPos.put(piece.getColor(), move.dest);
// Revert move if it caused a check for its team // Revert move if it caused a check for its team
if (checkCheck(piece.getColor())) { if (checkCheck(piece.getColor())) {
@ -64,13 +63,30 @@ public class Board {
// TODO: detecting checkmate // TODO: detecting checkmate
// Check for check on the opposite team // Check for check on the opposite team
Color oppositeColor = piece.getColor() == Color.WHITE ? Color.BLACK : Color.WHITE; Color oppositeColor = piece.getColor() == Color.WHITE ? Color.BLACK : Color.WHITE;
if (checkCheck(oppositeColor)) if (checkCheck(oppositeColor)) notifyListeners(new GameEvent(this, GameEventType.CHECK, oppositeColor));
notifyListeners(new GameEvent(this, GameEventType.CHECK, oppositeColor));
return true; 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. * Reverts a move.
* *
@ -86,6 +102,25 @@ public class Board {
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.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) { public boolean checkCheck(Color color) {
for (int i = 0; i < 8; i++) for (int i = 0; i < 8; i++)
for (int j = 0; j < 8; j++) for (int j = 0; j < 8; j++)

View File

@ -3,11 +3,12 @@ package dev.kske.chess;
import java.awt.Color; import java.awt.Color;
import java.awt.Dimension; import java.awt.Dimension;
import java.awt.Graphics; import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.ComponentAdapter; import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent; import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter; import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent; import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JOptionPane; import javax.swing.JOptionPane;
import javax.swing.JPanel; import javax.swing.JPanel;
@ -32,12 +33,16 @@ public class BoardPanel extends JPanel implements GameEventListener {
private int tileSize; private int tileSize;
private Board board; private Board board;
private List<Move> displayMoves;
public BoardPanel(Board board) { public BoardPanel(Board board) {
this(); this();
setBoard(board); setBoard(board);
} }
public BoardPanel() { public BoardPanel() {
displayMoves = new ArrayList<>();
/* /*
* Add a component listener for adjusting the tile size on resizing. * 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 * 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 // Add a mouse adapter for testing piece movement
addMouseListener(new MouseAdapter() { addMouseListener(new MouseAdapter() {
private Point src; private Position pos;
@Override @Override
public void mousePressed(MouseEvent evt) { public void mousePressed(MouseEvent evt) {
if (src == null) src = evt.getPoint(); if (pos == null) {
else { pos = new Position(evt.getPoint().x / tileSize, evt.getPoint().y / tileSize);
Point dest = evt.getPoint();
board.move(src.x / tileSize, src.y / tileSize, dest.x / tileSize, dest.y / tileSize); if (board.get(pos) != null) {
repaint(); displayMoves.clear();
src = null; 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++) for (int j = 0; j < 8; j++)
if (board.getBoardArr()[i][j] != null) g.drawImage(TextureUtil if (board.getBoardArr()[i][j] != null) g.drawImage(TextureUtil
.getPieceTexture(board.getBoardArr()[i][j]), i * tileSize, j * tileSize, this); .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 @Override

View File

@ -29,4 +29,9 @@ public class Move {
public boolean isVertical() { return xDist == 0; } public boolean isVertical() { return xDist == 0; }
public boolean isDiagonal() { return xDist == yDist; } 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.x = x;
this.y = y; 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; package dev.kske.chess.piece;
import java.util.ArrayList;
import java.util.List;
import dev.kske.chess.Board; import dev.kske.chess.Board;
import dev.kske.chess.Move; import dev.kske.chess.Move;
import dev.kske.chess.Position;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
@ -28,6 +32,48 @@ public class Bishop extends Piece {
return checkDestination(move); 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 @Override
public Type getType() { return Type.BISHOP; } public Type getType() { return Type.BISHOP; }
} }

View File

@ -1,7 +1,11 @@
package dev.kske.chess.piece; package dev.kske.chess.piece;
import java.util.ArrayList;
import java.util.List;
import dev.kske.chess.Board; import dev.kske.chess.Board;
import dev.kske.chess.Move; import dev.kske.chess.Move;
import dev.kske.chess.Position;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
@ -20,6 +24,21 @@ public class King extends Piece {
return move.xDist <= 1 && move.yDist <= 1 && isFreePath(move); 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 @Override
public Type getType() { return Type.KING; } public Type getType() { return Type.KING; }
} }

View File

@ -1,7 +1,11 @@
package dev.kske.chess.piece; package dev.kske.chess.piece;
import java.util.ArrayList;
import java.util.List;
import dev.kske.chess.Board; import dev.kske.chess.Board;
import dev.kske.chess.Move; import dev.kske.chess.Move;
import dev.kske.chess.Position;
/** /**
* Project: <strong>Chess</strong><br> * 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); 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 @Override
public Type getType() { return Type.KNIGHT; } public Type getType() { return Type.KNIGHT; }
} }

View File

@ -1,7 +1,11 @@
package dev.kske.chess.piece; package dev.kske.chess.piece;
import java.util.ArrayList;
import java.util.List;
import dev.kske.chess.Board; import dev.kske.chess.Board;
import dev.kske.chess.Move; import dev.kske.chess.Move;
import dev.kske.chess.Position;
/** /**
* Project: <strong>Chess</strong><br> * 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(); 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 @Override
public Type getType() { return Type.PAWN; } public Type getType() { return Type.PAWN; }
} }

View File

@ -1,7 +1,11 @@
package dev.kske.chess.piece; package dev.kske.chess.piece;
import java.util.Iterator;
import java.util.List;
import dev.kske.chess.Board; import dev.kske.chess.Board;
import dev.kske.chess.Move; import dev.kske.chess.Move;
import dev.kske.chess.Position;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
@ -19,6 +23,20 @@ public abstract class Piece {
this.board = board; 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); public abstract boolean isValidMove(Move move);
protected boolean isFreePath(Move move) { protected boolean isFreePath(Move move) {
@ -47,5 +65,9 @@ public abstract class Piece {
public static enum Color { public static enum Color {
WHITE, BLACK; WHITE, BLACK;
public Color opposite() {
return this == WHITE ? BLACK : WHITE;
}
} }
} }

View File

@ -1,7 +1,11 @@
package dev.kske.chess.piece; package dev.kske.chess.piece;
import java.util.ArrayList;
import java.util.List;
import dev.kske.chess.Board; import dev.kske.chess.Board;
import dev.kske.chess.Move; import dev.kske.chess.Move;
import dev.kske.chess.Position;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
@ -36,6 +40,84 @@ public class Queen extends Piece {
return checkDestination(move); 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 @Override
public Type getType() { return Type.QUEEN; } public Type getType() { return Type.QUEEN; }
} }

View File

@ -1,7 +1,11 @@
package dev.kske.chess.piece; package dev.kske.chess.piece;
import java.util.ArrayList;
import java.util.List;
import dev.kske.chess.Board; import dev.kske.chess.Board;
import dev.kske.chess.Move; import dev.kske.chess.Move;
import dev.kske.chess.Position;
/** /**
* Project: <strong>Chess</strong><br> * Project: <strong>Chess</strong><br>
@ -32,6 +36,48 @@ public class Rook extends Piece {
return checkDestination(move); 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 @Override
public Type getType() { return Type.ROOK; } public Type getType() { return Type.ROOK; }
} }