Im currently Programming a Chess game. I want to mark all possible Moves for Piece if I click it. For a Move to be Legal it has to be following the Movement of the Piece, it has to be in Bounds of the Chessboard and it has to be safe for the own King. My Problem is with the last one. Because to determine if a Piece is attacking the King after a Move you would have to simulate it and than check if something can attack the King. but if just update all possible Moves and look if someting is attacking the King after the Simulation you get infinite Rekursion. My only way was to have an additonal variable (canAttackKing // after a move) and a Method just like the one to determine the possible Moves just without the Check if its safe. But this is just annoying and hard to take care of. Constantly there are some Bugs etc. So my Question is, is there a better way to determine if a Move is Safe?
All the Pieces have different classes which inherit the Piece class like this:
import lombok.Getter;
import javafx.scene.image.Image;
import schach.SchachScreen.Chessboard;
import lombok.Setter;
import java.util.Objects;
@Getter
public abstract class Piece {
private final boolean white;
protected int x,y;
private final Image image;
protected Chessboard c;
@Setter
protected int[][] possibleMoves = new int[8][8]; // 0 = no move, 1 = move, 2 = capture
@Setter
protected boolean canAttackKing = false; // If something else Moves
public Piece(boolean white) {
this.white = white;
this.image = loadImage();
}
private Image loadImage() {
String color = white ? "white" : "black";
String className = this.getClass().getSimpleName().toLowerCase();
String imagePath = "/Images/figuren/" + color + "_" + className + ".png";
return new Image(Objects.requireNonNull(getClass().getResourceAsStream(imagePath)));
}
public void setPosition(int x, int y) {
this.x = x;
this.y = y;
}
public abstract void setPossibleMoves();
public abstract void updateCanAttackKing2();
public abstract String getFenNotation();
public boolean canAttackKing() {
return canAttackKing;
}
protected void addMoveIfSafe(int x, int y) {
if (c.simMovePiece(x, y, this)) {
possibleMoves[x][y] = c.getPiece(x, y) == null ? 1 : 2;
}
}
protected void checkDiagonals() {
c = Chessboard.getInstance();
int originalX = x;
int originalY = y;
checkLine(originalX, originalY, 1, 1); // Top-right
checkLine(originalX, originalY, -1, -1); // Bottom-left
checkLine(originalX, originalY, -1, 1); // Top-left
checkLine(originalX, originalY, 1, -1); // Bottom-right
}
protected void checkStraights() {
c = Chessboard.getInstance();
int originalX = x;
int originalY = y;
checkLine(originalX, originalY, 1, 0); // Right
checkLine(originalX, originalY, -1, 0); // Left
checkLine(originalX, originalY, 0, 1); // Up
checkLine(originalX, originalY, 0, -1); // Down
}
private void checkLine(int startX, int startY, int xIncrement, int yIncrement) {
for (int i = 1; startX + i * xIncrement < 8 && startX + i * xIncrement >= 0
&& startY + i * yIncrement < 8 && startY + i * yIncrement >= 0; i++) {
int newX = startX + i * xIncrement;
int newY = startY + i * yIncrement;
if (c.check(newX, newY, this) == 0) break;
addMoveIfSafe(newX, newY);
if (c.check(newX, newY, this) == 2) break;
}
}
// 2cnd Step (It doesn't have to check if its safe,
// if it attacks the King its over before the other player can move)
protected void checkDiagonals2() {
c = Chessboard.getInstance();
int originalX = x;
int originalY = y;
checkLine2(originalX, originalY, 1, 1); // Top-right
checkLine2(originalX, originalY, -1, -1); // Bottom-left
checkLine2(originalX, originalY, -1, 1); // Top-left
checkLine2(originalX, originalY, 1, -1); // Bottom-right
}
protected void checkStraights2() {
c = Chessboard.getInstance();
int originalX = x;
int originalY = y;
checkLine2(originalX, originalY, 1, 0); // Right
checkLine2(originalX, originalY, -1, 0); // Left
checkLine2(originalX, originalY, 0, 1); // Up
checkLine2(originalX, originalY, 0, -1); // Down
}
private void checkLine2(int startX, int startY, int xIncrement, int yIncrement) {
for (int i = 1; startX + i * xIncrement < 8 && startX + i * xIncrement >= 0
&& startY + i * yIncrement < 8 && startY + i * yIncrement >= 0; i++) {
int newX = startX + i * xIncrement;
int newY = startY + i * yIncrement;
if (c.check(newX, newY, this) == 2) {
if (c.getBoard()[newX][newY] instanceof King) {
canAttackKing = true;
}
break;
}
}
}
}
import schach.SchachScreen.Chessboard;
import schach.SchachScreen.SchachScreenPresenter;
import lombok.Getter;
import lombok.Setter;
@Setter
@Getter
public class Pawn extends Piece {
private boolean moved = false;
private int direction;
public Pawn(boolean white) {
super(white);
direction = isWhite() ? -1 : 1; // White moves up (negative), Black moves down (positive)
}
@Override
public String getFenNotation() {
return isWhite() ? "P" : "p"; // Return "P" for white, "p" for black
}
@Override
public void setPossibleMoves() {
c = Chessboard.getInstance();
possibleMoves = new int[8][8]; // Initialize the move matrix
checkMoveForward(); // Regular move forward by one
checkInitialDoubleMove(); // First move double-step
checkEnPassant(); // En Passant
checkDiagonalCaptures(); // Diagonal captures
}
private void checkMoveForward() {
if (c.check(x, y + direction, this) == 1) {
addMoveIfSafe(x, y + direction); // Regular forward move if the square is empty
}
}
private void checkInitialDoubleMove() {
if (!moved && c.check(x, y + 2 * direction, this) == 1
&& c.check(x, y + direction, this) == 1) {
addMoveIfSafe(x, y + 2 * direction); // Double move on first move if both squares are free
}
}
private void checkDiagonalCaptures() {
if (c.getPiece(x + 1, y + direction) != null
&& c.check(x + 1, y + direction, this) == 2) {
addMoveIfSafe(x + 1, y + direction); // Capture to the right
}
if (c.getPiece(x - 1, y + direction) != null
&& c.check(x - 1, y + direction, this) == 2) {
addMoveIfSafe(x - 1, y + direction); // Capture to the left
}
}
private void checkEnPassant() {
// En Passant to the right (if valid)
if (SchachScreenPresenter.convertToChessNotation(x + 1, y + direction)
.equals(c.getPossibleEnPassant()) && c.getPiece(x + 1, y) instanceof Pawn) {
addEnPassantIfSafe(true); // Right side en passant
}
// En Passant to the left (if valid)
else if (SchachScreenPresenter.convertToChessNotation(x - 1, y + direction)
.equals(c.getPossibleEnPassant()) && c.getPiece(x - 1, y) instanceof Pawn) {
addEnPassantIfSafe(false); // Left side en passant
}
}
private void addEnPassantIfSafe(boolean right) {
if (c.simEnPassant(this, right)) {
// En Passant capture move is represented as `5` in the move matrix
possibleMoves[x + (right ? 1 : -1)][y + direction] = 5;
}
}
@Override
public void updateCanAttackKing2() {
canAttackKing = false;
c = Chessboard.getInstance();
int x = getX();
int y = getY();
checkKingAttack(x + 1, y + direction); // Check for possible attack on the right diagonal
checkKingAttack(x - 1, y + direction); // Check for possible attack on the left diagonal
}
private void checkKingAttack(int x, int y) {
if (c.check(x, y, this) == 2 && c.getPiece(x, y) instanceof King) {
canAttackKing = true; // Mark as can attack king if the square is occupied by the opposing king
}
}
}
import schach.figuren.*;
import javafx.scene.shape.MoveTo;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Getter
@Setter
public class Chessboard {
private boolean gameRunning = true;
private Piece[][] board = new Piece[8][8];
private List<Piece> whitePieces = new ArrayList<>();
private List<Piece> blackPieces = new ArrayList<>();
private int moveCounter;
private boolean whiteTurn;
private King whiteKing, blackKing;
private Piece selectedPiece = null;
private static Chessboard instance;
private String possibleEnPassant;
//fifty move rule and threefold repetition rule
private int fiftyMoveCounter; // 1 Move consists of both Players making a move, therefore this variable has to reach 100 for a draw
private List<String> boardStates;
private Chessboard() {
fiftyMoveCounter = 0;
possibleEnPassant = "-";
moveCounter = 0;
whiteTurn = true;
boardStates = new ArrayList<>();
}
public static Chessboard getInstance() {
if (instance == null) {
instance = new Chessboard();
}
return instance;
}
public static void resetInstance() {
instance = new Chessboard();
}
public void initializeBoard() {
board = new Piece[8][8];
whiteTurn = true;
moveCounter = 0;
selectedPiece = null;
// Place white pieces at the bottom
addPiece(new Rook(true), 0, 7);
addPiece(new Knight(true), 1, 7);
addPiece(new Bishop(true), 2, 7);
addPiece(new Queen(true), 3, 7);
addPiece(new King(true), 4, 7);
addPiece(new Bishop(true), 5, 7);
addPiece(new Knight(true), 6, 7);
addPiece(new Rook(true), 7, 7);
for (int i = 0; i < 8; i++) {
addPiece(new Pawn(true), i, 6);
}
// Place black pieces at the top
addPiece(new Rook(false), 0, 0);
addPiece(new Knight(false), 1, 0);
addPiece(new Bishop(false), 2, 0);
addPiece(new Queen(false), 3, 0);
addPiece(new King(false), 4, 0);
addPiece(new Bishop(false), 5, 0);
addPiece(new Knight(false), 6, 0);
addPiece(new Rook(false), 7, 0);
for (int i = 0; i < 8; i++) {
addPiece(new Pawn(false), i, 1);
}
whiteKing = (King) board[4][7];
blackKing = (King) board[4][0];
updatePossibleMoves();
}
// Generates a valid FEN string for UCI compatibility
public String getFen() {
StringBuilder fen = new StringBuilder();
fen.append(getBoardState());
fen.append(' ').append(isWhiteTurn() ? 'w' : 'b');
fen.append(' ').append(getCastlingRights());
fen.append(' ').append(possibleEnPassant);
fen.append(' ').append(fiftyMoveCounter);
fen.append(' ').append((moveCounter / 2) + 1);
return fen.toString();
}
private String getCastlingRights() {
StringBuilder rights = new StringBuilder();
if (!whiteKing.isMoved()) {
if (!isRookMoved(7, 7)) rights.append('K');
if (!isRookMoved(0, 7)) rights.append('Q');
}
if (!blackKing.isMoved()) {
if (!isRookMoved(7, 0)) rights.append('k');
if (!isRookMoved(0, 0)) rights.append('q');
}
return rights.length() > 0 ? rights.toString() : "-";
}
private boolean isRookMoved(int x, int y) {
Piece rook = board[x][y];
return !(rook instanceof Rook) || ((Rook) rook).isMoved();
}
public void updateGameState(Piece piece, Piece capturedPiece, boolean doubleMove) {
whiteTurn = !whiteTurn;
moveCounter++;
selectedPiece = null;
switch (piece) {
case Pawn pawn -> {
pawn.setMoved(true);
if (doubleMove) {
possibleEnPassant = SchachScreenPresenter.convertToChessNotation(piece.getX(),
piece.getY() + (pawn.isWhite() ? 1 : -1));
}
else {
possibleEnPassant = "-";
}
}
case King king -> {
king.setMoved(true);
possibleEnPassant = "-";
}
case Rook rook -> {
rook.setMoved(true);
possibleEnPassant = "-";
}
default -> { possibleEnPassant = "-";
}
}
updatePossibleMoves();
if (piece instanceof Pawn || capturedPiece != null) {
fiftyMoveCounter = 0;
} else {
fiftyMoveCounter++;
}
boardStates.add(getBoardState());
}
public String getBoardState() {
StringBuilder boardState = new StringBuilder();
for (int y = 0; y < 8; y++) {
int emptyCount = 0;
for (int x = 0; x < 8; x++) {
Piece piece = board[x][y];
if (piece == null) {
emptyCount++;
} else {
if (emptyCount > 0) {
boardState.append(emptyCount);
emptyCount = 0;
}
boardState.append(piece.getFenNotation());
}
}
if (emptyCount > 0) {
boardState.append(emptyCount);
}
if (y < 7) {
boardState.append('/');
}
}
return boardState.toString();
}
private boolean checkThreefoldRepetition() {
int count = 0;
String currentState = getBoardState();
for (String state : boardStates) {
if (state.equals(currentState)) {
count++;
}
}
return count >= 3;
}
public void addPiece(Piece piece, int x, int y) {
board[x][y] = piece;
piece.setPosition(x, y);
if(piece.isWhite()){
whitePieces.add(piece);
}else{
blackPieces.add(piece);
}
}
public void removePiece(Piece piece) {
int x = piece.getX();
int y = piece.getY();
piece.setPosition(-1, -1);
board[x][y] = null;
if(piece.isWhite()){
whitePieces.remove(piece);
}else{
blackPieces.remove(piece);
}
}
//returns 0 if the move is invalid, 1 if the move is valid, 2 if the move is a valid capture
public int check(int x, int y, Piece piece) {
if (x < 0 || x >= 8 || y < 0 || y >= 8) {
return 0; // Out of bounds
}
Piece targetPiece = board[x][y];
if (targetPiece == null) {
return 1; // Empty square
} else if (targetPiece.isWhite() != piece.isWhite()) {
return 2; // Opponent's piece
} else {
return 0; // Own piece
}
}
public boolean isInCheck(King king, int i) {
if (i == 1) {
return canAttack(king.getX(), king.getY(), !king.isWhite());
} else {
return canAttack2(king.getX(), king.getY(), !king.isWhite());
}
}
public boolean canAttack(int targetX, int targetY, boolean isWhite) {
List<Piece> pieces = isWhite ? whitePieces : blackPieces;
for (Piece piece : pieces) {
int cell = piece.getPossibleMoves()[targetX][targetY];
if (cell == 2) {
System.out.println("Piece: " + piece.getFenNotation() + SchachScreenPresenter.convertToChessNotation(piece.getX(), piece.getY())
+ " can attack: " + SchachScreenPresenter.convertToChessNotation(targetX, targetY));
return true;
}
}
return false;
}
public boolean isSquareSafe(int targetX, int targetY, boolean isWhite){
List<Piece> pieces = isWhite ? whitePieces : blackPieces;
for (Piece piece : pieces) {
int cell = piece.getPossibleMoves()[targetX][targetY];
if (cell == 2 || (cell == 1) && !(piece instanceof Pawn)) {
System.out.println("Piece: " + piece.getFenNotation() + SchachScreenPresenter.convertToChessNotation(piece.getX(), piece.getY())
+ " can attack: " + SchachScreenPresenter.convertToChessNotation(targetX, targetY));
return false;
}
}
return true;
}
public boolean canAttack2(int x, int y, boolean isWhite) {
List<Piece> pieces = isWhite ? whitePieces : blackPieces;
for (Piece piece : pieces) {
if (piece.canAttackKing()) {
return true;
}
}
return false;
}
public void movePiece(int x, int y, Piece piece) {
int[] originalPosition = new int[]{piece.getX(), piece.getY()};
Piece capturedPiece = null;
// Clear the target position
if (board[x][y] != null) {
capturedPiece = board[x][y];
removePiece(capturedPiece);
}
// Execute the move
addPiece(piece, x, y);
board[originalPosition[0]][originalPosition[1]] = null;
updateGameState(piece, capturedPiece,
y - originalPosition[1] == 2 || y - originalPosition[1] == -2);
}
public boolean simMovePiece(int x, int y, Piece piece) {
if (moveCounter < 3) return true;
boolean isSafe = true;
// Save original position
int[] originalPosition = new int[]{piece.getX(), piece.getY()};
Piece targetedPiece = board[x][y];
// Simulate move
addPiece(piece, x, y);
board[originalPosition[0]][originalPosition[1]] = null;
if (targetedPiece != null) {
removePiece(targetedPiece);
}
// Update possible King attacks for opponent pieces after the Move of the piece
updatePossibleMoves2(piece);
// Check if the king is in check
if (isInCheck(getKing(piece.isWhite()), 2)) {
isSafe = false;
}
// Revert move
addPiece(piece, originalPosition[0], originalPosition[1]);
if (targetedPiece != null) {
addPiece(targetedPiece, x, y);
} else {
board[x][y] = null;
}
return isSafe;
}
public boolean simEnPassant(Pawn pawn, boolean right) {
int startX = pawn.getX();
int startY = pawn.getY();
int endX = pawn.getX() + (right ? 1 : -1);
int endY = pawn.getY() + (pawn.isWhite() ? -1 : 1);
Pawn capturedPawn = (Pawn) board[endX][endY - (pawn.isWhite() ? -1 : 1)];
boolean isSafe = true;
// Simulate move
addPiece(pawn, endX, endY);
board[startX][startY] = null;
removePiece(capturedPawn);
// Update possible King attacks for opponent pieces after the move of the piece
updatePossibleMoves2(pawn);
// Check if the king is in check
if (isInCheck(getKing(pawn.isWhite()), 2)) {
isSafe = false;
}
// Revert move
addPiece(pawn, startX, startY);
addPiece(capturedPawn, endX,endY - (pawn.isWhite() ? -1 : 1)); // Restore the en passant captured pawn
board[endX][endY] = null;
return isSafe;
}
public String checkForEnd(){
//Check for fifty move rule
if(fiftyMoveCounter >= 100){
return "Fifty move rule";
}
if (checkThreefoldRepetition()) {
return "Threefold repetition rule";
}
//Check for checkmate or stalemate
boolean check = isInCheck(getKing(whiteTurn),1);
boolean possibleMoveExists = false;
for(int i = 0; i < 8; i++){
for(int j = 0; j < 8; j++){
Piece piece = board[i][j];
if(piece != null && piece.isWhite() == whiteTurn){
for(int k = 0; k < 8; k++){
for(int l = 0; l < 8; l++){
if(piece.getPossibleMoves()[k][l] != 0){
possibleMoveExists = true;
}
}
}
}
}
}
if(!possibleMoveExists){
if(check){
return "Checkmate";
}else{
return "Stalemate";
}
}
return "";
}
public void castle(King king, boolean kingside) {
int y = king.getY();
int x = king.getX();
int rookX = kingside ? 7 : 0;
int newKingX = kingside ? 6 : 2;
int newRookX = kingside ? 5 : 3;
// Move the king and rook to their new positions
addPiece(king, newKingX, y);
addPiece(board[rookX][y], newRookX, y);
// Clear the old positions
board[x][y] = null;
board[rookX][y] = null;
updateGameState(king, null, false);
}
public void enPassant(Pawn pawn, int x, int y) {
int direction = pawn.isWhite() ? -1 : 1;
board[x][y - direction] = null;
movePiece(x, y, pawn);
}
public King getKing(boolean isWhite) {
return isWhite ? whiteKing : blackKing;
}
public void updatePossibleMoves() {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
Piece piece = board[i][j];
if (piece != null) {
piece.setPossibleMoves();
}
}
}
}
public void updatePossibleMoves2(Piece exception) {
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
Piece piece = board[i][j];
if (piece != null) {
if (piece.isWhite() != exception.isWhite()) {
piece.updateCanAttackKing2();
}
}
}
}
}
public Piece getPiece(int x, int y) {
if(x > 7 || x < 0 || y > 7 || y < 0) return null;
return board[x][y];
}
public void setPiece(int x, int y, Piece piece) {
board[x][y] = piece;
}
public Piece promotePawn(Pawn pawn, String pieceType, int newX, int newY) {
Piece capturedPiece = board[newX][newY];
Piece newPiece = switch (pieceType) {
case "Queen" -> new Queen(pawn.isWhite());
case "Rook" -> new Rook(pawn.isWhite());
case "Bishop" -> new Bishop(pawn.isWhite());
case "Knight" -> new Knight(pawn.isWhite());
default -> null;
};
if (newPiece != null) {
removePiece(pawn);
addPiece(newPiece, newX, newY);
}
updateGameState(pawn, capturedPiece, false);
return newPiece;
}
}
Like i have said i’ve tried to check if the Move is safe with an additional variable and Methods but the way its programmed right now is very unclear/confusing/hard to work with.
It did work at some point but the minute i started to add or change something it always completly broke.
katsuro is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.