new class

This commit is contained in:
rheaa 2025-05-20 18:45:24 +02:00
parent 5e4ffefdbf
commit d75c05d4b5
5 changed files with 627 additions and 428 deletions

View File

@ -3,46 +3,51 @@ package backend;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Stack; import java.util.Stack;
/**
* Represents a chessboard (arbitrary dimensions),
* handles piece placement, move execution (including
* castling & en passant), legal-move generation, and check detection.
*/
public class Board { public class Board {
private int width, height; private final int width, height;
private ArrayList<Piece> pieces; private final ArrayList<Piece> pieces = new ArrayList<>();
private int turnNumber; private final Stack<Move> moveHistory = new Stack<>();
private boolean isWhiteTurn;
private Integer selectedX, selectedY; private int turnNumber = 0;
private boolean isWhiteTurn = true;
// For GUI selection/highlighting
private Integer selectedX = null, selectedY = null;
private ArrayList<int[]> highlightedSquares = new ArrayList<>(); private ArrayList<int[]> highlightedSquares = new ArrayList<>();
private Stack<Move> moveHistory = new Stack<>();
// Castling rights // Castling rights
private boolean canCastleWK = true, canCastleWQ = true; private boolean canCastleWK = true, canCastleWQ = true;
private boolean canCastleBK = true, canCastleBQ = true; private boolean canCastleBK = true, canCastleBQ = true;
/** Empty board */ /** Empty board of given dimensions. */
public Board(int colNum, int lineNum) { public Board(int width, int height) {
this.width = colNum; this.width = width;
this.height = lineNum; this.height = height;
this.turnNumber = 0;
this.isWhiteTurn = true;
this.pieces = new ArrayList<>();
} }
/** Load from file */ /** Load from file lines. Last line is "W" or "B" for side to move. */
public Board(String[] array) { public Board(String[] rows) {
this(array[0].split(",").length, array.length - 1); this(rows[0].split(",").length, rows.length - 1);
this.isWhiteTurn = array[height].equals("W"); this.isWhiteTurn = rows[height].equals("W");
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
String[] row = array[y].split(","); String[] cols = rows[y].split(",");
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
String t = row[x]; String cell = cols[x];
if (!t.equals("..")) { if (!cell.equals("..")) {
boolean w = t.charAt(0) == 'W'; boolean w = cell.charAt(0) == 'W';
PieceType tp = PieceType.fromSummary(t.charAt(1)); PieceType type = PieceType.fromSummary(cell.charAt(1));
pieces.add(new Piece(x, y, w, tp)); pieces.add(new Piece(x, y, w, type));
} }
} }
} }
} }
/** Deep copy */ /** Deep copy (excludes moveHistory). */
public Board(Board other) { public Board(Board other) {
this(other.width, other.height); this(other.width, other.height);
this.turnNumber = other.turnNumber; this.turnNumber = other.turnNumber;
@ -51,37 +56,55 @@ public class Board {
this.canCastleWQ = other.canCastleWQ; this.canCastleWQ = other.canCastleWQ;
this.canCastleBK = other.canCastleBK; this.canCastleBK = other.canCastleBK;
this.canCastleBQ = other.canCastleBQ; this.canCastleBQ = other.canCastleBQ;
this.pieces = new ArrayList<>(); for (Piece p : other.pieces) {
for (Piece p : other.pieces) pieces.add(new Piece(p.getX(), p.getY(), p.isWhite(), p.getType()));
this.pieces.add(new Piece(p.getX(), p.getY(), p.isWhite(), p.getType())); }
} }
// Accessors // ACCESSORS
public int getWidth() { return width; } public int getWidth() { return width; }
public int getHeight() { return height; } public int getHeight() { return height; }
public int getTurnNumber() { return turnNumber; } public int getTurnNumber() { return turnNumber; }
public boolean isTurnWhite(){ return isWhiteTurn; } public boolean isTurnWhite() { return isWhiteTurn; }
public Iterable<Piece> getPieces() { return new ArrayList<>(pieces); }
/** Place a piece of the given color & type at (x,y) (used by the “Add Piece” button). */
public void setPiece(boolean white, PieceType type, int x, int y) {
// remove whatever was on that square (if any) and drop in your new piece
removePieceAt(x, y);
pieces.add(new Piece(x, y, white, type));
}
/** Standard setup */ /** Last move pushed onto history, or null if none. */
public Move getLastMove() {
return moveHistory.isEmpty() ? null : moveHistory.peek();
}
// BOARD SETUP & I/O
/** Place the standard 8×8 chess starting array. */
public void populateBoard() { public void populateBoard() {
PieceType[] back = { PieceType[] back = {
PieceType.Rook, PieceType.Knight, PieceType.Bishop, PieceType.Queen, PieceType.Rook, PieceType.Knight, PieceType.Bishop, PieceType.Queen,
PieceType.King, PieceType.Bishop, PieceType.Knight, PieceType.Rook PieceType.King, PieceType.Bishop, PieceType.Knight, PieceType.Rook
}; };
// black // Black
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
pieces.add(new Piece(x,1,false,PieceType.Pawn)); pieces.add(new Piece(x, 1, false, PieceType.Pawn));
pieces.add(new Piece(x,0,false,back[x])); pieces.add(new Piece(x, 0, false, back[x]));
} }
// white // White
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
pieces.add(new Piece(x,6,true,PieceType.Pawn)); pieces.add(new Piece(x, 6, true, PieceType.Pawn));
pieces.add(new Piece(x,7,true,back[x])); pieces.add(new Piece(x, 7, true, back[x]));
} }
canCastleWK = canCastleWQ = canCastleBK = canCastleBQ = true; canCastleWK = canCastleWQ = canCastleBK = canCastleBQ = true;
moveHistory.clear();
turnNumber = 0;
isWhiteTurn = true;
} }
/** Clear everything */ /** Remove all pieces, reset rights & turn. */
public void cleanBoard() { public void cleanBoard() {
pieces.clear(); pieces.clear();
moveHistory.clear(); moveHistory.clear();
@ -91,74 +114,74 @@ public class Board {
canCastleWK = canCastleWQ = canCastleBK = canCastleBQ = true; canCastleWK = canCastleWQ = canCastleBK = canCastleBQ = true;
} }
@Override public String toString() { /** Saveable representation: one CSV row per rank, then "W" or "B". */
StringBuilder sb = new StringBuilder();
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
Piece p = getPieceAt(x,y);
sb.append(p==null?"..":(p.isWhite()?"W":"B")+p.getType().getSummary());
if (x<width-1) sb.append(",");
}
sb.append("\n");
}
sb.append(isWhiteTurn?"W":"B").append("\n");
return sb.toString();
}
public String[] toFileRep() { public String[] toFileRep() {
String[] out = new String[height+1]; String[] out = new String[height + 1];
for (int y = 0; y < height; y++) { for (int y = 0; y < height; y++) {
StringBuilder row = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int x = 0; x < width; x++) { for (int x = 0; x < width; x++) {
Piece p = getPieceAt(x,y); Piece p = getPieceAt(x, y);
row.append(p==null?"..":(p.isWhite()?"W":"B")+p.getType().getSummary()); sb.append(p == null
if (x<width-1) row.append(","); ? ".."
: (p.isWhite() ? "W" : "B") + p.getType().getSummary());
if (x < width - 1) sb.append(",");
} }
out[y] = row.toString(); out[y] = sb.toString();
} }
out[height] = isWhiteTurn?"W":"B"; out[height] = isWhiteTurn ? "W" : "B";
return out; return out;
} }
public ArrayList<Piece> getPieces() { return new ArrayList<>(pieces); } // GUI SELECTION HELPERS
public void setPiece(boolean w, PieceType t, int x, int y) {
removePieceAt(x,y); pieces.add(new Piece(x,y,w,t));
}
/** User click → select/move */ /**
* Called by GUI on mouse click at (x,y).
* First click selects a piece, second click attempts move.
*/
public void userTouch(int x, int y) { public void userTouch(int x, int y) {
Piece clicked = getPieceAt(x,y); Piece clicked = getPieceAt(x, y);
if (selectedX==null) { // select
if (clicked!=null && clicked.isWhite()==isWhiteTurn) { if (selectedX == null) {
selectedX=x; selectedY=y; if (clicked != null && clicked.isWhite() == isWhiteTurn) {
selectedX = x;
selectedY = y;
highlightedSquares = computeLegalMoves(clicked); highlightedSquares = computeLegalMoves(clicked);
} }
return; return;
} }
if (selectedX==x && selectedY==y) { // deselect
selectedX=selectedY=null; if (selectedX == x && selectedY == y) {
selectedX = selectedY = null;
highlightedSquares.clear(); highlightedSquares.clear();
return; return;
} }
// attempt move
for (int[] mv : highlightedSquares) { for (int[] mv : highlightedSquares) {
if (mv[0]==x && mv[1]==y) { if (mv[0] == x && mv[1] == y) {
Piece sel = getPieceAt(selectedX, selectedY); Piece sel = getPieceAt(selectedX, selectedY);
Piece cap = getPieceAt(x,y); Piece cap = getPieceAt(x, y);
// en passant
if (cap==null && sel.getType()==PieceType.Pawn && x!=selectedX) { // en passant capture
int dir = sel.isWhite()? -1:1; if (cap == null
Move last = moveHistory.isEmpty()?null:moveHistory.peek(); && sel.getType() == PieceType.Pawn
Piece behind = getPieceAt(x, y-dir); && x != selectedX)
if (behind!=null {
&& behind.getType()==PieceType.Pawn int dir = sel.isWhite() ? -1 : 1;
&& last!=null Move last = getLastMove();
&& last.getType()==PieceType.Pawn Piece behind = getPieceAt(x, y - dir);
&& Math.abs(last.getFromY()-last.getToY())==2 if (behind != null
&& last.getToX()==behind.getX() && behind.getType() == PieceType.Pawn
&& last.getToY()==behind.getY()) { && last != null
&& last.getType() == PieceType.Pawn
&& Math.abs(last.getFromY() - last.getToY()) == 2
&& last.getToX() == behind.getX()
&& last.getToY() == behind.getY())
{
cap = behind; cap = behind;
} }
} }
Move m = new Move( Move m = new Move(
selectedX, selectedY, selectedX, selectedY,
x, y, x, y,
@ -169,131 +192,161 @@ public class Board {
break; break;
} }
} }
selectedX=selectedY=null; selectedX = selectedY = null;
highlightedSquares.clear(); highlightedSquares.clear();
} }
public boolean isSelected(int x,int y) { public boolean isSelected(int x, int y) {
return selectedX!=null && selectedX==x && selectedY==y; return selectedX != null && selectedX == x && selectedY == y;
}
public boolean isHighlighted(int x, int y) {
for (int[] mv : highlightedSquares) {
if (mv[0] == x && mv[1] == y) return true;
} }
public boolean isHighlighted(int x,int y) {
for (int[] mv: highlightedSquares)
if (mv[0]==x && mv[1]==y) return true;
return false; return false;
} }
/** Execute a move (incl. castle & en passant) */ // MOVE EXECUTION
/**
* Execute a move:
* - normal move
* - capture / en passant
* - castle rook reposition
* - update castling rights
* - push history, toggle turn
*/
public void playMove(Move m) { public void playMove(Move m) {
if (m==null) return; if (m == null) return;
// castling rook
if (m.getType()==PieceType.King && Math.abs(m.getToX()-m.getFromX())==2) { // 1) handle castling rook move
int y = m.isWhite()?7:0; if (m.getType() == PieceType.King
if (m.getToX()-m.getFromX()==2) { && Math.abs(m.getToX() - m.getFromX()) == 2)
removePieceAt(7,y); pieces.add(new Piece(5,y,m.isWhite(),PieceType.Rook)); {
int yRank = m.isWhite() ? 7 : 0;
if (m.getToX() - m.getFromX() == 2) {
// king-side
removePieceAt(7, yRank);
pieces.add(new Piece(5, yRank, m.isWhite(), PieceType.Rook));
} else { } else {
removePieceAt(0,y); pieces.add(new Piece(3,y,m.isWhite(),PieceType.Rook)); // queen-side
removePieceAt(0, yRank);
pieces.add(new Piece(3, yRank, m.isWhite(), PieceType.Rook));
} }
} }
// normal or en passant cap
if (m.getCapturedPiece()!=null) { // 2) capture / en passant
if (m.getCapturedPiece() != null) {
removePieceAt( removePieceAt(
m.getCapturedPiece().getX(), m.getCapturedPiece().getX(),
m.getCapturedPiece().getY() m.getCapturedPiece().getY()
); );
} }
// update opponent castling rights if rook was captured
if (m.getCapturedPiece()!=null && m.getCapturedPiece().getType()==PieceType.Rook) { // 3) update opponent castling rights if rook was captured
Piece cap=m.getCapturedPiece(); if (m.getCapturedPiece() != null
&& m.getCapturedPiece().getType() == PieceType.Rook)
{
Piece cap = m.getCapturedPiece();
if (cap.isWhite()) { if (cap.isWhite()) {
if (cap.getX()==0 && cap.getY()==7) canCastleWQ=false; if (cap.getX() == 0 && cap.getY() == 7) canCastleWQ = false;
if (cap.getX()==7 && cap.getY()==7) canCastleWK=false; if (cap.getX() == 7 && cap.getY() == 7) canCastleWK = false;
} else { } else {
if (cap.getX()==0 && cap.getY()==0) canCastleBQ=false; if (cap.getX() == 0 && cap.getY() == 0) canCastleBQ = false;
if (cap.getX()==7 && cap.getY()==0) canCastleBK=false; if (cap.getX() == 7 && cap.getY() == 0) canCastleBK = false;
} }
} }
// move the piece
removePieceAt(m.getFromX(),m.getFromY());
pieces.add(new Piece(m.getToX(),m.getToY(),m.isWhite(),m.getType()));
// moving piece castling rights // 4) move the piece
if (m.getType()==PieceType.King) { removePieceAt(m.getFromX(), m.getFromY());
if (m.isWhite()) canCastleWK=canCastleWQ=false; pieces.add(new Piece(
else canCastleBK=canCastleBQ=false; m.getToX(), m.getToY(),
m.isWhite(), m.getType()
));
// 5) moving-piece loses castling rights if king or rook moved
if (m.getType() == PieceType.King) {
if (m.isWhite()) canCastleWK = canCastleWQ = false;
else canCastleBK = canCastleBQ = false;
} }
if (m.getType()==PieceType.Rook) { if (m.getType() == PieceType.Rook) {
if (m.isWhite()) { if (m.isWhite()) {
if (m.getFromX()==0 && m.getFromY()==7) canCastleWQ=false; if (m.getFromX() == 0 && m.getFromY() == 7) canCastleWQ = false;
if (m.getFromX()==7 && m.getFromY()==7) canCastleWK=false; if (m.getFromX() == 7 && m.getFromY() == 7) canCastleWK = false;
} else { } else {
if (m.getFromX()==0 && m.getFromY()==0) canCastleBQ=false; if (m.getFromX() == 0 && m.getFromY() == 0) canCastleBQ = false;
if (m.getFromX()==7 && m.getFromY()==0) canCastleBK=false; if (m.getFromX() == 7 && m.getFromY() == 0) canCastleBK = false;
} }
} }
// 6) push history & toggle
moveHistory.push(m); moveHistory.push(m);
turnNumber++; turnNumber++;
isWhiteTurn = !isWhiteTurn; isWhiteTurn = !isWhiteTurn;
} }
/** Undo last */ /** Undo last—restores piece positions, not full castling rights. */
public void undoLastMove() { public void undoLastMove() {
if (moveHistory.isEmpty()) return; if (moveHistory.isEmpty()) return;
Move m = moveHistory.pop(); Move m = moveHistory.pop();
// undo castling
if (m.getType()==PieceType.King && Math.abs(m.getToX()-m.getFromX())==2) { // undo castling rook reposition
int y = m.isWhite()?7:0; if (m.getType() == PieceType.King
if (m.getToX()-m.getFromX()==2) { && Math.abs(m.getToX() - m.getFromX()) == 2)
removePieceAt(5,y); pieces.add(new Piece(7,y,m.isWhite(),PieceType.Rook)); {
int yRank = m.isWhite() ? 7 : 0;
if (m.getToX() - m.getFromX() == 2) {
removePieceAt(5, yRank);
pieces.add(new Piece(7, yRank, m.isWhite(), PieceType.Rook));
} else { } else {
removePieceAt(3,y); pieces.add(new Piece(0,y,m.isWhite(),PieceType.Rook)); removePieceAt(3, yRank);
pieces.add(new Piece(0, yRank, m.isWhite(), PieceType.Rook));
} }
} }
// move the king/piece back
removePieceAt(m.getToX(),m.getToY());
pieces.add(new Piece(m.getFromX(),m.getFromY(),m.isWhite(),m.getType()));
// restore capture
if (m.getCapturedPiece()!=null) {
Piece c=m.getCapturedPiece();
pieces.add(new Piece(c.getX(),c.getY(),c.isWhite(),c.getType()));
}
turnNumber--;
isWhiteTurn = !isWhiteTurn;
// note: castling rights not fully restored here
}
/** Pseudo-legal moves for side to move */ // move back
removePieceAt(m.getToX(), m.getToY());
pieces.add(new Piece(
m.getFromX(), m.getFromY(),
m.isWhite(), m.getType()
));
// restore capture
if (m.getCapturedPiece() != null) {
Piece c = m.getCapturedPiece();
pieces.add(new Piece(
c.getX(), c.getY(),
c.isWhite(), c.getType()
));
}
turnNumber--;
isWhiteTurn = !isWhiteTurn;
}
// MOVE-GENERATION & CHECK DETECTION
/** Returns pseudo-legal moves (ignores self-check). */
public ArrayList<Move> generateLegalMoves() { public ArrayList<Move> generateLegalMoves() {
ArrayList<Move> all = new ArrayList<>(); ArrayList<Move> out = new ArrayList<>();
for (Piece p : pieces) { for (Piece p : pieces) {
if (p.isWhite()==isWhiteTurn) { if (p.isWhite() == isWhiteTurn) {
for (int[] d : computeLegalMoves(p)) { for (int[] d : computeLegalMoves(p)) {
Piece cap = getPieceAt(d[0],d[1]); Piece cap = getPieceAt(d[0], d[1]);
all.add(new Move( out.add(new Move(
p.getX(),p.getY(), p.getX(), p.getY(),
d[0],d[1], d[0], d[1],
p.isWhite(),p.getType(), p.isWhite(), p.getType(),
cap cap
)); ));
} }
} }
} }
return all; return out;
} }
/** Is that colors king under attack? */ /** Filtered to remove moves that leave king in check. */
public boolean isInCheck(boolean color) {
int kx=-1, ky=-1;
for (Piece p:pieces) {
if (p.getType()==PieceType.King && p.isWhite()==color) {
kx=p.getX(); ky=p.getY(); break;
}
}
if (kx<0) throw new IllegalStateException("No king for " + color);
return isSquareAttacked(kx,ky,!color);
}
/** True legal moves for `color` (filter out self-check) */
public ArrayList<Move> generateLegalMoves(boolean color) { public ArrayList<Move> generateLegalMoves(boolean color) {
Board copy = new Board(this); Board copy = new Board(this);
copy.isWhiteTurn = color; copy.isWhiteTurn = color;
@ -301,181 +354,253 @@ public class Board {
ArrayList<Move> legal = new ArrayList<>(); ArrayList<Move> legal = new ArrayList<>();
for (Move m : pseudo) { for (Move m : pseudo) {
copy.playMove(m); copy.playMove(m);
if (!copy.isInCheck(color)) legal.add(m); if (!copy.isInCheck(color)) {
legal.add(m);
}
copy.undoLastMove(); copy.undoLastMove();
} }
return legal; return legal;
} }
/** Square attacked by given side? */ /** True if that colors king is attacked. */
public boolean isSquareAttacked(int x,int y,boolean byWhite) { public boolean isInCheck(boolean color) {
for (Piece p:pieces) int kx = -1, ky = -1;
if (p.isWhite()==byWhite && attacksSquare(p,x,y)) for (Piece p : pieces) {
return true; if (p.getType() == PieceType.King && p.isWhite() == color) {
return false; kx = p.getX(); ky = p.getY();
}
// Private helpers
private boolean attacksSquare(Piece p,int tx,int ty) {
int x=p.getX(), y=p.getY();
boolean w=p.isWhite();
int dx=tx-x, dy=ty-y;
switch(p.getType()) {
case Pawn:
int dir = w?-1:1;
return dy==dir && Math.abs(dx)==1;
case Knight:
return (Math.abs(dx)==1&&Math.abs(dy)==2)
|| (Math.abs(dx)==2&&Math.abs(dy)==1);
case Bishop:
if (Math.abs(dx)!=Math.abs(dy)) return false;
return clearPath(x,y,tx,ty);
case Rook:
if (dx!=0&&dy!=0) return false;
return clearPath(x,y,tx,ty);
case Queen:
if (dx==0||dy==0||Math.abs(dx)==Math.abs(dy))
return clearPath(x,y,tx,ty);
return false;
case King:
return Math.max(Math.abs(dx),Math.abs(dy))==1;
default:
return false;
}
}
private boolean clearPath(int x1,int y1,int x2,int y2) {
int sx=Integer.signum(x2-x1), sy=Integer.signum(y2-y1);
int cx=x1+sx, cy=y1+sy;
while (cx!=x2||cy!=y2) {
if (getPieceAt(cx,cy)!=null) return false;
cx+=sx; cy+=sy;
}
return true;
}
private Piece getPieceAt(int x,int y) {
for (Piece p:pieces) if (p.getX()==x&&p.getY()==y) return p;
return null;
}
private void removePieceAt(int x,int y) {
pieces.removeIf(p->p.getX()==x&&p.getY()==y);
}
private boolean in(int x,int y) {
return x>=0&&x<width&&y>=0&&y<height;
}
private void slide(ArrayList<int[]> out,int x,int y,boolean w,int[][] dirs) {
for (int[] d:dirs) {
for (int i=1;i<8;i++){
int nx=x+d[0]*i, ny=y+d[1]*i;
if (!in(nx,ny)) break;
Piece t = getPieceAt(nx,ny);
if (t==null) out.add(new int[]{nx,ny});
else {
if (t.isWhite()!=w) out.add(new int[]{nx,ny});
break; break;
} }
} }
} if (kx < 0) throw new IllegalStateException("No king for " + (color ? "white" : "black"));
return isSquareAttacked(kx, ky, !color);
} }
/** Returns true if (x,y) is under attack by side “byWhite”. */
public boolean isSquareAttacked(int x, int y, boolean byWhite) {
for (Piece p : pieces) {
if (p.isWhite() == byWhite && attacksSquare(p, x, y)) {
return true;
}
}
return false;
}
// PRIVATE HELPER METHODS
/** Pseudo-moves for a single piece (ignores check). */
private ArrayList<int[]> computeLegalMoves(Piece p) { private ArrayList<int[]> computeLegalMoves(Piece p) {
ArrayList<int[]> out = new ArrayList<>(); ArrayList<int[]> out = new ArrayList<>();
int x=p.getX(), y=p.getY(); int x = p.getX(), y = p.getY();
boolean w=p.isWhite(); boolean w = p.isWhite();
switch(p.getType()) { switch (p.getType()) {
case Pawn: case Pawn:
int dir = w?-1:1, sr = w?6:1; int dir = w ? -1 : 1, startRow = w ? 6 : 1;
if (in(x,y+dir)&&getPieceAt(x,y+dir)==null) { // forward
out.add(new int[]{x,y+dir}); if (in(x, y + dir) && getPieceAt(x, y + dir) == null) {
if (y==sr&&getPieceAt(x,y+2*dir)==null) out.add(new int[]{x, y + dir});
out.add(new int[]{x,y+2*dir}); if (y == startRow && getPieceAt(x, y + 2 * dir) == null) {
} out.add(new int[]{x, y + 2 * dir});
for (int dx : new int[]{-1,1}) {
int nx=x+dx, ny=y+dir;
if (in(nx,ny)) {
Piece t=getPieceAt(nx,ny);
if (t!=null&&t.isWhite()!=w) out.add(new int[]{nx,ny});
} }
} }
// captures
for (int dx : new int[]{-1, 1}) {
int nx = x + dx, ny = y + dir;
if (in(nx, ny)) {
Piece t = getPieceAt(nx, ny);
if (t != null && t.isWhite() != w) {
out.add(new int[]{nx, ny});
}
}
}
// en passant capture possibility
if (!moveHistory.isEmpty()) { if (!moveHistory.isEmpty()) {
Move last=moveHistory.peek(); Move last = moveHistory.peek();
if (last.getType()==PieceType.Pawn if (last.getType() == PieceType.Pawn
&&Math.abs(last.getFromY()-last.getToY())==2 && Math.abs(last.getFromY() - last.getToY()) == 2
&&last.getToY()==y && last.getToY() == y
&&Math.abs(last.getToX()-x)==1) { && Math.abs(last.getToX() - x) == 1)
int ex=last.getToX(), ey=y+dir; {
if (in(ex,ey)&&getPieceAt(ex,ey)==null) int ex = last.getToX(), ey = y + dir;
out.add(new int[]{ex,ey}); if (in(ex, ey) && getPieceAt(ex, ey) == null) {
out.add(new int[]{ex, ey});
}
} }
} }
break; break;
case Rook:
slide(out,x,y,w,new int[][]{{1,0},{-1,0},{0,1},{0,-1}});
break;
case Bishop:
slide(out,x,y,w,new int[][]{{1,1},{1,-1},{-1,1},{-1,-1}});
break;
case Queen:
slide(out,x,y,w,new int[][]{
{1,0},{-1,0},{0,1},{0,-1},
{1,1},{1,-1},{-1,1},{-1,-1}});
break;
case Knight: case Knight:
int[][] km={{1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}}; int[][] km = {{1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}};
for (int[] m:km) { for (int[] d : km) {
int nx=x+m[0], ny=y+m[1]; int nx = x + d[0], ny = y + d[1];
if (in(nx,ny)) { if (in(nx, ny)) {
Piece t=getPieceAt(nx,ny); Piece t = getPieceAt(nx, ny);
if (t==null||t.isWhite()!=w) out.add(new int[]{nx,ny}); if (t == null || t.isWhite() != w) {
out.add(new int[]{nx, ny});
}
} }
} }
break; break;
case Bishop:
slide(out, x, y, w, new int[][]{{1,1},{1,-1},{-1,1},{-1,-1}});
break;
case Rook:
slide(out, x, y, w, new int[][]{{1,0},{-1,0},{0,1},{0,-1}});
break;
case Queen:
slide(out, x, y, w, new int[][]{
{1,0},{-1,0},{0,1},{0,-1},
{1,1},{1,-1},{-1,1},{-1,-1}
});
break;
case King: case King:
for (int dx=-1;dx<=1;dx++) for (int dx = -1; dx <= 1; dx++) {
for (int dy=-1;dy<=1;dy++){ for (int dy = -1; dy <= 1; dy++) {
if (dx==0&&dy==0) continue; if (dx == 0 && dy == 0) continue;
int nx=x+dx, ny=y+dy; int nx = x + dx, ny = y + dy;
if (in(nx,ny)){ if (in(nx, ny)) {
Piece t=getPieceAt(nx,ny); Piece t = getPieceAt(nx, ny);
if (t==null||t.isWhite()!=w) out.add(new int[]{nx,ny}); if (t == null || t.isWhite() != w) {
out.add(new int[]{nx, ny});
} }
} }
boolean opp=!w; }
if (!isSquareAttacked(x,y,opp)){ }
if (w&&x==4&&y==7){ // castling
if (canCastleWK&&getPieceAt(5,7)==null&&getPieceAt(6,7)==null boolean opp = !w;
&&!isSquareAttacked(5,7,opp)&&!isSquareAttacked(6,7,opp)) if (!isSquareAttacked(x, y, opp)) {
// white back rank
if (w && x == 4 && y == 7) {
if (canCastleWK
&& getPieceAt(5,7) == null
&& getPieceAt(6,7) == null
&& !isSquareAttacked(5,7,opp)
&& !isSquareAttacked(6,7,opp))
{
out.add(new int[]{6,7}); out.add(new int[]{6,7});
if (canCastleWQ&&getPieceAt(3,7)==null&&getPieceAt(2,7)==null }
&&getPieceAt(1,7)==null&& !isSquareAttacked(3,7,opp) if (canCastleWQ
&&!isSquareAttacked(2,7,opp)) && getPieceAt(3,7) == null
&& getPieceAt(2,7) == null
&& getPieceAt(1,7) == null
&& !isSquareAttacked(3,7,opp)
&& !isSquareAttacked(2,7,opp))
{
out.add(new int[]{2,7}); out.add(new int[]{2,7});
} }
if (!w&&x==4&&y==0){ }
if (canCastleBK&&getPieceAt(5,0)==null&&getPieceAt(6,0)==null // black back rank
&&!isSquareAttacked(5,0,opp)&&!isSquareAttacked(6,0,opp)) if (!w && x == 4 && y == 0) {
if (canCastleBK
&& getPieceAt(5,0) == null
&& getPieceAt(6,0) == null
&& !isSquareAttacked(5,0,opp)
&& !isSquareAttacked(6,0,opp))
{
out.add(new int[]{6,0}); out.add(new int[]{6,0});
if (canCastleBQ&&getPieceAt(3,0)==null&&getPieceAt(2,0)==null }
&&getPieceAt(1,0)==null&& !isSquareAttacked(3,0,opp) if (canCastleBQ
&&!isSquareAttacked(2,0,opp)) && getPieceAt(3,0) == null
&& getPieceAt(2,0) == null
&& getPieceAt(1,0) == null
&& !isSquareAttacked(3,0,opp)
&& !isSquareAttacked(2,0,opp))
{
out.add(new int[]{2,0}); out.add(new int[]{2,0});
} }
} }
}
break; break;
} }
return out; return out;
} }
public Move getLastMove() { /** Helper for sliding pieces (bishop, rook, queen). */
return moveHistory.isEmpty() ? null : moveHistory.peek(); private void slide(ArrayList<int[]> out,
int x, int y, boolean w,
int[][] directions)
{
for (int[] d : directions) {
for (int i = 1; i < 8; i++) {
int nx = x + d[0]*i, ny = y + d[1]*i;
if (!in(nx, ny)) break;
Piece t = getPieceAt(nx, ny);
if (t == null) {
out.add(new int[]{nx, ny});
} else {
if (t.isWhite() != w) {
out.add(new int[]{nx, ny});
}
break;
}
}
}
}
/** Attack pattern ignoring blocks (for knight & king & pawn). */
private boolean attacksSquare(Piece p, int tx, int ty) {
int x = p.getX(), y = p.getY();
boolean w = p.isWhite();
int dx = tx - x, dy = ty - y;
switch (p.getType()) {
case Pawn:
int dir = w ? -1 : 1;
return dy == dir && Math.abs(dx) == 1;
case Knight:
return (Math.abs(dx)==1 && Math.abs(dy)==2)
|| (Math.abs(dx)==2 && Math.abs(dy)==1);
case Bishop:
if (Math.abs(dx) != Math.abs(dy)) return false;
return clearPath(x, y, tx, ty);
case Rook:
if (dx != 0 && dy != 0) return false;
return clearPath(x, y, tx, ty);
case Queen:
if (dx==0 || dy==0 || Math.abs(dx)==Math.abs(dy))
return clearPath(x, y, tx, ty);
return false;
case King:
return Math.max(Math.abs(dx), Math.abs(dy)) == 1;
default:
return false;
}
}
/** Checks that no piece blocks the straight-line path. */
private boolean clearPath(int x1, int y1, int x2, int y2) {
int stepX = Integer.signum(x2 - x1);
int stepY = Integer.signum(y2 - y1);
int cx = x1 + stepX, cy = y1 + stepY;
while (cx != x2 || cy != y2) {
if (getPieceAt(cx, cy) != null) return false;
cx += stepX; cy += stepY;
}
return true;
}
// PRIVATE HELPERS
private Piece getPieceAt(int x, int y) {
for (Piece p : pieces) {
if (p.getX() == x && p.getY() == y) return p;
}
return null;
}
private void removePieceAt(int x, int y) {
pieces.removeIf(p -> p.getX() == x && p.getY() == y);
}
private boolean in(int x, int y) {
return x >= 0 && x < width && y >= 0 && y < height;
} }
} }

View File

@ -1,20 +1,23 @@
package backend; package backend;
import windowInterface.MyInterface;
import java.util.List; import java.util.List;
import windowInterface.MyInterface;
/**
* Main game loop thread. Applies user & AI moves,
* counts captures, checks for end-of-game, and updates the GUI.
*/
public class Game extends Thread { public class Game extends Thread {
private final AutoPlayer aiPlayer; private final AutoPlayer aiPlayer;
private Board board; private Board board;
private final MyInterface mjf; private final MyInterface mjf;
private final boolean[] activationAIFlags = new boolean[2]; private final boolean[] activationAI = new boolean[2]; // [0]=blackAI, [1]=whiteAI
private boolean gameOver = false; private boolean gameOver = false;
private int whiteCaptures = 0, blackCaptures = 0; private int whiteCaptures = 0, blackCaptures = 0;
private int loopDelay = 250; private int loopDelay = 250;
public Game(MyInterface mjfParam) { public Game(MyInterface mjf) {
this.mjf = mjfParam; this.mjf = mjf;
this.board = new Board(8, 8); this.board = new Board(8, 8);
this.aiPlayer = new AutoPlayer(); this.aiPlayer = new AutoPlayer();
} }
@ -23,83 +26,108 @@ public class Game extends Thread {
public int getHeight() { return board.getHeight(); } public int getHeight() { return board.getHeight(); }
public int getWhiteCaptures() { return whiteCaptures; } public int getWhiteCaptures() { return whiteCaptures; }
public int getBlackCaptures() { return blackCaptures; } public int getBlackCaptures() { return blackCaptures; }
public Board getBoard() { return board; }
@Override @Override
public void run() { public void run() {
while (!gameOver) { while (!gameOver) {
if (isAITurn()) { // AIs turn?
Move m = aiPlayer.computeBestMove(new Board(board)); if (activationAI[board.isTurnWhite() ? 1 : 0]) {
board.playMove(m); Move aiMove = aiPlayer.computeBestMove(new Board(board));
recordCapture(m); board.playMove(aiMove);
recordCapture(aiMove);
checkGameEnd(); checkGameEnd();
} }
// always update GUI
mjf.update(board.getTurnNumber(), board.isTurnWhite()); mjf.update(board.getTurnNumber(), board.isTurnWhite());
try { Thread.sleep(loopDelay); }
catch (InterruptedException e) { e.printStackTrace(); } try {
Thread.sleep(loopDelay);
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
} }
private boolean isAITurn() { /**
// [1] = white, [0] = black * Called from the panel when the user clicks square (x,y).
return activationAIFlags[ board.isTurnWhite()?1:0 ]; */
}
public void clickCoords(int x, int y) { public void clickCoords(int x, int y) {
if (gameOver) return; if (gameOver) return;
if (x<0||y<0||x>=getWidth()||y>=getHeight()) return; board.userTouch(x, y);
if (!isAITurn()) {
board.userTouch(x,y);
// lastMove not returned, but board.moveHistory.peek() is it:
Move last = board.getLastMove(); Move last = board.getLastMove();
recordCapture(last); recordCapture(last);
checkGameEnd(); checkGameEnd();
} }
}
private void recordCapture(Move m) { private void recordCapture(Move m) {
if (m!=null && m.getCapturedPiece()!=null) { if (m != null && m.getCapturedPiece() != null) {
if (m.isWhite()) whiteCaptures++; if (m.isWhite()) whiteCaptures++;
else blackCaptures++; else blackCaptures++;
} }
} }
/** check for endofgame and notify GUI */
private void checkGameEnd() { private void checkGameEnd() {
boolean sideToMove = board.isTurnWhite(); boolean sideToMove = board.isTurnWhite();
List<Move> replies = ChessRules.generateLegalMoves(board, sideToMove); List<Move> legal = ChessRules.generateLegalMoves(board, sideToMove);
if (replies.isEmpty()) { if (legal.isEmpty()) {
String msg; String message;
if (ChessRules.isCheckmate(board, sideToMove)) { if (ChessRules.isCheckmate(board, sideToMove)) {
msg = (sideToMove?"White":"Black") message = (sideToMove ? "White" : "Black")
+ " is checkmated. " + " is checkmated. "
+ (!sideToMove?"White":"Black") + (!sideToMove ? "White" : "Black")
+ " wins!"; + " wins!";
} else { } else {
msg = "Stalemate—draw."; message = "Stalemate—draw.";
} }
System.out.println(msg); mjf.gameOver(message);
mjf.gameOver(msg);
gameOver = true; gameOver = true;
} }
} }
// --- existing API below --- // API methods forwarded to Board
public void setPiece(boolean w, PieceType t, int x, int y) { public void setPiece(boolean w, PieceType t, int x, int y) {
board.setPiece(w,t,x,y); board.setPiece(w, t, x, y);
} }
public String[] getFileRepresentation() { public String[] getFileRepresentation() {
return board.toFileRep(); return board.toFileRep();
} }
public void setLoopDelay(int d) { loopDelay = d; }
public void setDefaultSetup() { board.cleanBoard(); board.populateBoard(); } public void setLoopDelay(int d) {
public void setBoard(String[] a){ board = new Board(a); } loopDelay = d;
public Iterable<Piece> getPieces() { return board.getPieces(); } }
public boolean isSelected(int x,int y) { return board.isSelected(x,y); }
public boolean isHighlighted(int x,int y){ return board.isHighlighted(x,y); } public void setDefaultSetup() {
public void undoLastMove() { board.undoLastMove(); } board.cleanBoard();
public void toggleAI(boolean w) { activationAIFlags[w?1:0] = !activationAIFlags[w?1:0]; } board.populateBoard();
public Board getBoard() { return board; } }
public void setBoard(String[] rows) {
board = new Board(rows);
}
public Iterable<Piece> getPieces() {
return board.getPieces();
}
public boolean isSelected(int x, int y) {
return board.isSelected(x, y);
}
public boolean isHighlighted(int x, int y) {
return board.isHighlighted(x, y);
}
public void undoLastMove() {
board.undoLastMove();
}
public void toggleAI(boolean isWhite) {
activationAI[isWhite ? 1 : 0] = !activationAI[isWhite ? 1 : 0];
}
} }
@ -109,3 +137,5 @@ public class Game extends Thread {

View File

@ -0,0 +1,11 @@
package backend;
/** Thrown by Board.playMove(...) if you ever capture a king. */
public class KingCapturedException extends RuntimeException {
/** true = white did the capture, false = black did it */
public final boolean byWhite;
public KingCapturedException(boolean byWhite) {
this.byWhite = byWhite;
}
}

View File

@ -1,47 +1,54 @@
package windowInterface; package windowInterface;
import javax.swing.*;
import java.awt.*; import java.awt.*;
import java.awt.event.*; import java.awt.event.*;
import java.awt.image.BufferedImage; import java.awt.image.*;
import java.io.File; import java.io.*;
import java.util.HashSet; import java.util.*;
import java.util.List;
import java.util.Set;
import javax.imageio.ImageIO;
import javax.swing.*;
import backend.*; import backend.*;
import java.util.List;
import backend.Move;
public class JPanelChessBoard extends JPanel { public class JPanelChessBoard extends JPanel {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private Game myGame; private Game myGame;
private final MyInterface interfaceGlobal; private final MyInterface interfaceGlobal;
private BufferedImage spriteSheet; private BufferedImage spriteSheet;
private final int PIECE_W=16, PIECE_H=16, M=6; private final int PIECE_W = 16, PIECE_H = 16, MARGIN = 6;
private boolean pieceSelectorMode=false, pieceAdderMode=false; private boolean pieceSelectorMode = false;
private boolean selIsWhite=true; private PieceType selType=PieceType.Pawn; private boolean pieceAdderMode = false;
private boolean selIsWhite = true;
private PieceType selType = PieceType.Pawn;
public JPanelChessBoard(MyInterface itf) { public JPanelChessBoard(MyInterface itf) {
super(); interfaceGlobal=itf; super();
try { spriteSheet=ImageIO.read(new File("pieces.png")); } interfaceGlobal = itf;
catch(Exception e){ e.printStackTrace(); } try {
addMouseListener(new MouseAdapter(){ spriteSheet = javax.imageio.ImageIO.read(new File("pieces.png"));
public void mousePressed(MouseEvent me){ } catch (IOException e) {
if(pieceSelectorMode){ e.printStackTrace();
int x=(int)(me.getX()/cellW()), y=(int)(me.getY()/cellH()); }
selType = PieceType.values()[5-x]; addMouseListener(new MouseAdapter() {
selIsWhite = y>1; @Override
pieceSelectorMode=false; public void mousePressed(MouseEvent me) {
if (pieceSelectorMode) {
int x = (int) (me.getX() / cellW());
int y = (int) (me.getY() / cellH());
selType = PieceType.values()[5 - x];
selIsWhite = (y > 1);
pieceSelectorMode = false;
} else { } else {
if(myGame==null) interfaceGlobal.instantiateSimu(); if (myGame == null) interfaceGlobal.instantiateSimu();
int x=(me.getX()*myGame.getWidth())/getWidth(), int x = me.getX() * myGame.getWidth() / getWidth();
y=(me.getY()*myGame.getHeight())/getHeight(); int y = me.getY() * myGame.getHeight() / getHeight();
if(pieceAdderMode){ if (pieceAdderMode) {
myGame.setPiece(selIsWhite,selType,x,y); myGame.setPiece(selIsWhite, selType, x, y);
pieceAdderMode=false; pieceAdderMode = false;
} else { } else {
myGame.clickCoords(x,y); myGame.clickCoords(x, y);
} }
} }
repaint(); repaint();
@ -49,64 +56,90 @@ public class JPanelChessBoard extends JPanel {
}); });
} }
public void setGame(Game g){ myGame=g; } public void setGame(Game g) {
myGame = g;
}
@Override protected void paintComponent(Graphics g){ @Override
super.paintComponent(g); protected void paintComponent(Graphics gg) {
if (myGame==null) return; super.paintComponent(gg);
if (myGame == null) return;
// compute legal highlights Graphics2D g = (Graphics2D) gg;
int selX=-1, selY=-1; float w = cellW(), h = cellH();
for(int xx=0;xx<myGame.getWidth();xx++) for(int yy=0;yy<myGame.getHeight();yy++)
if(myGame.isSelected(xx,yy)){ selX=xx; selY=yy; break;} // find selected square
Set<Point> legalH = new HashSet<>(); int selX = -1, selY = -1;
if(selX>=0){ for (int xx = 0; xx < myGame.getWidth(); xx++)
for (int yy = 0; yy < myGame.getHeight(); yy++)
if (myGame.isSelected(xx, yy)) {
selX = xx;
selY = yy;
}
// legal highlights
Set<Point> legal = new HashSet<>();
if (selX >= 0) {
List<Move> lm = ChessRules.generateLegalMoves( List<Move> lm = ChessRules.generateLegalMoves(
myGame.getBoard(), myGame.getBoard().isTurnWhite() myGame.getBoard(), myGame.getBoard().isTurnWhite()
); );
for(Move m:lm) if(m.getFromX()==selX&&m.getFromY()==selY) for (Move m : lm) {
legalH.add(new Point(m.getToX(),m.getToY())); if (m.getFromX() == selX && m.getFromY() == selY) {
legal.add(new Point(m.getToX(), m.getToY()));
}
}
} }
float w=cellW(), h=cellH(); // draw board
for(int x=0;x<myGame.getWidth();x++){ for (int x = 0; x < myGame.getWidth(); x++) {
for(int y=0;y<myGame.getHeight();y++){ for (int y = 0; y < myGame.getHeight(); y++) {
boolean isSel= myGame.isSelected(x,y), boolean isSel = (x == selX && y == selY);
isHl = legalH.contains(new Point(x,y)); boolean isHl = legal.contains(new Point(x, y));
if (isSel) g.setColor(Color.PINK); if (isSel) g.setColor(Color.PINK);
else if(isHl) g.setColor(Color.GREEN); else if (isHl) g.setColor(Color.GREEN);
else g.setColor((x+y)%2==0?Color.WHITE:Color.RED); else g.setColor((x + y) % 2 == 0 ? Color.WHITE : Color.RED);
g.fillRect((int)(x*w),(int)(y*h),(int)w,(int)h); g.fillRect((int)(x*w),(int)(y*h),(int)w,(int)h);
} }
} }
g.setColor(Color.GRAY); g.setColor(Color.GRAY);
for(int x=0;x<=myGame.getWidth();x++) for (int x = 0; x <= myGame.getWidth(); x++)
g.drawLine((int)(x*w),0,(int)(x*w),getHeight()); g.drawLine((int)(x*w), 0, (int)(x*w), getHeight());
for(int y=0;y<=myGame.getHeight();y++) for (int y = 0; y <= myGame.getHeight(); y++)
g.drawLine(0,(int)(y*h),getWidth(),(int)(y*h)); g.drawLine(0, (int)(y*h), getWidth(), (int)(y*h));
// draw pieces // draw pieces
for(Piece p: myGame.getPieces()) drawPiece(g,p); for (Piece p : myGame.getPieces()) {
drawPiece(g, p, w, h);
}
} }
private void drawPiece(Graphics g, Piece p){ private void drawPiece(Graphics2D g, Piece p, float w, float h) {
Image sub = spriteSheet.getSubimage( int idx = 5 - p.getType().ordinal();
spriteIndex(p.getType())*PIECE_W, BufferedImage sub = spriteSheet.getSubimage(
p.isWhite()?PIECE_H:0, idx * PIECE_W,
PIECE_W,PIECE_H p.isWhite() ? PIECE_H : 0,
PIECE_W, PIECE_H
);
Image scaled = sub.getScaledInstance(
(int)(w - 2 * MARGIN),
(int)(h - 2 * MARGIN),
Image.SCALE_SMOOTH
); );
g.drawImage( g.drawImage(
sub.getScaledInstance((int)(cellW()-2*M),(int)(cellH()-2*M),0), scaled,
(int)(p.getX()*cellW())+M, (int)(p.getY()*cellH())+M, null (int)(p.getX() * w) + MARGIN,
(int)(p.getY() * h) + MARGIN,
null
); );
} }
private int spriteIndex(PieceType t){ return 5 - t.ordinal(); } private float cellW() { return getWidth() / (float) myGame.getWidth(); }
private float cellW(){ return getWidth()/(float)myGame.getWidth(); } private float cellH() { return getHeight() / (float) myGame.getHeight(); }
private float cellH(){ return getHeight()/(float)myGame.getHeight(); }
public void togglePieceSelector(){ pieceSelectorMode = !pieceSelectorMode; } // modes
public void togglePieceSelector() { pieceSelectorMode = !pieceSelectorMode; }
public void toggleAdderMode() { pieceAdderMode = !pieceAdderMode; } public void toggleAdderMode() { pieceAdderMode = !pieceAdderMode; }
public boolean isPieceSelectorMode(){ return pieceSelectorMode; } public boolean isPieceSelectorMode() { return pieceSelectorMode; }
public boolean isPieceAdderMode() { return pieceAdderMode; } public boolean isPieceAdderMode() { return pieceAdderMode; }
} }

View File

@ -28,12 +28,12 @@ public class MyInterface extends JFrame {
start.addActionListener(e->clicButtonStart()); start.addActionListener(e->clicButtonStart());
top.add(start); top.add(start);
turnLabel = new JLabel("Turn: 0"); turnLabel = new JLabel("Turn: 0 /");
top.add(turnLabel); top.add(turnLabel);
whiteCapLabel = new JLabel("W captures: 0"); whiteCapLabel = new JLabel(" W captures: 0 /");
top.add(whiteCapLabel); top.add(whiteCapLabel);
blackCapLabel = new JLabel("B captures: 0"); blackCapLabel = new JLabel(" B captures: 0");
top.add(blackCapLabel); top.add(blackCapLabel);
JCheckBox wAI = new JCheckBox("White AI"); JCheckBox wAI = new JCheckBox("White AI");