final
This commit is contained in:
parent
d75c05d4b5
commit
642d3cfe11
|
|
@ -1,24 +1,23 @@
|
|||
package backend;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Stack;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Represents a chessboard (arbitrary dimensions),
|
||||
* handles piece placement, move execution (including
|
||||
* castling & en passant), legal-move generation, and check detection.
|
||||
* Represents an N×M chessboard, holds pieces,
|
||||
* executes moves (including castling & en passant),
|
||||
* and generates true legal moves (no self-check).
|
||||
*/
|
||||
public class Board {
|
||||
private final int width, height;
|
||||
private final ArrayList<Piece> pieces = new ArrayList<>();
|
||||
private final Stack<Move> moveHistory = new Stack<>();
|
||||
private final List<Piece> pieces = new ArrayList<>();
|
||||
private final Deque<Move> moveHistory = new ArrayDeque<>();
|
||||
|
||||
private int turnNumber = 0;
|
||||
private boolean isWhiteTurn = true;
|
||||
|
||||
// For GUI selection/highlighting
|
||||
private Integer selectedX = null, selectedY = null;
|
||||
private ArrayList<int[]> highlightedSquares = new ArrayList<>();
|
||||
// Selection / highlight
|
||||
private Integer selectedX, selectedY;
|
||||
private List<int[]> highlightedSquares = Collections.emptyList();
|
||||
|
||||
// Castling rights
|
||||
private boolean canCastleWK = true, canCastleWQ = true;
|
||||
|
|
@ -30,7 +29,7 @@ public class Board {
|
|||
this.height = height;
|
||||
}
|
||||
|
||||
/** Load from file lines. Last line is "W" or "B" for side to move. */
|
||||
/** Load from file‐rows (each "WQ,..,..", last row "W" or "B"). */
|
||||
public Board(String[] rows) {
|
||||
this(rows[0].split(",").length, rows.length - 1);
|
||||
this.isWhiteTurn = rows[height].equals("W");
|
||||
|
|
@ -39,15 +38,15 @@ public class Board {
|
|||
for (int x = 0; x < width; x++) {
|
||||
String cell = cols[x];
|
||||
if (!cell.equals("..")) {
|
||||
boolean w = cell.charAt(0) == 'W';
|
||||
boolean white = cell.charAt(0) == 'W';
|
||||
PieceType type = PieceType.fromSummary(cell.charAt(1));
|
||||
pieces.add(new Piece(x, y, w, type));
|
||||
pieces.add(new Piece(x, y, white, type));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Deep copy (excludes moveHistory). */
|
||||
/** Deep copy (history not cloned). */
|
||||
public Board(Board other) {
|
||||
this(other.width, other.height);
|
||||
this.turnNumber = other.turnNumber;
|
||||
|
|
@ -61,28 +60,18 @@ public class Board {
|
|||
}
|
||||
}
|
||||
|
||||
// ─── ACCESSORS ──────────────────────────────────────────────────────────
|
||||
// ─── Accessors ─────────────────────────────────────────────────────────
|
||||
|
||||
public int getWidth() { return width; }
|
||||
public int getHeight() { return height; }
|
||||
public int getTurnNumber() { return turnNumber; }
|
||||
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));
|
||||
}
|
||||
public int getWidth() { return width; }
|
||||
public int getHeight() { return height; }
|
||||
public int getTurnNumber() { return turnNumber; }
|
||||
public boolean isTurnWhite() { return isWhiteTurn; }
|
||||
public Iterable<Piece> getPieces(){ return Collections.unmodifiableList(pieces); }
|
||||
public Move getLastMove() { return moveHistory.peek(); }
|
||||
|
||||
/** Last move pushed onto history, or null if none. */
|
||||
public Move getLastMove() {
|
||||
return moveHistory.isEmpty() ? null : moveHistory.peek();
|
||||
}
|
||||
// ─── Setup & I/O ────────────────────────────────────────────────────────
|
||||
|
||||
// ─── BOARD SETUP & I/O ─────────────────────────────────────────────────
|
||||
|
||||
/** Place the standard 8×8 chess starting array. */
|
||||
/** Standard 8×8 setup. */
|
||||
public void populateBoard() {
|
||||
PieceType[] back = {
|
||||
PieceType.Rook, PieceType.Knight, PieceType.Bishop, PieceType.Queen,
|
||||
|
|
@ -104,17 +93,17 @@ public class Board {
|
|||
isWhiteTurn = true;
|
||||
}
|
||||
|
||||
/** Remove all pieces, reset rights & turn. */
|
||||
/** Completely clear. */
|
||||
public void cleanBoard() {
|
||||
pieces.clear();
|
||||
moveHistory.clear();
|
||||
highlightedSquares.clear();
|
||||
moveHistory.clear();
|
||||
turnNumber = 0;
|
||||
isWhiteTurn = true;
|
||||
canCastleWK = canCastleWQ = canCastleBK = canCastleBQ = true;
|
||||
}
|
||||
|
||||
/** Saveable representation: one CSV row per rank, then "W" or "B". */
|
||||
/** CSV+color representation for save. */
|
||||
public String[] toFileRep() {
|
||||
String[] out = new String[height + 1];
|
||||
for (int y = 0; y < height; y++) {
|
||||
|
|
@ -130,19 +119,21 @@ public class Board {
|
|||
}
|
||||
out[height] = isWhiteTurn ? "W" : "B";
|
||||
return out;
|
||||
|
||||
}
|
||||
|
||||
// ─── GUI SELECTION HELPERS ─────────────────────────────────────────────
|
||||
/** Add/remove a piece (for the “Add Piece” button). */
|
||||
public void setPiece(boolean white, PieceType type, int x, int y) {
|
||||
removePieceAt(x, y);
|
||||
pieces.add(new Piece(x, y, white, type));
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by GUI on mouse click at (x,y).
|
||||
* First click selects a piece, second click attempts move.
|
||||
*/
|
||||
// ─── GUI selection helpers ─────────────────────────────────────────────
|
||||
|
||||
/** Called by the GUI on mouse click. */
|
||||
public void userTouch(int x, int y) {
|
||||
Piece clicked = getPieceAt(x, y);
|
||||
// select
|
||||
if (selectedX == null) {
|
||||
// first click: select
|
||||
if (clicked != null && clicked.isWhite() == isWhiteTurn) {
|
||||
selectedX = x;
|
||||
selectedY = y;
|
||||
|
|
@ -153,7 +144,7 @@ public class Board {
|
|||
// deselect
|
||||
if (selectedX == x && selectedY == y) {
|
||||
selectedX = selectedY = null;
|
||||
highlightedSquares.clear();
|
||||
highlightedSquares = Collections.emptyList();
|
||||
return;
|
||||
}
|
||||
// attempt move
|
||||
|
|
@ -162,11 +153,8 @@ public class Board {
|
|||
Piece sel = getPieceAt(selectedX, selectedY);
|
||||
Piece cap = getPieceAt(x, y);
|
||||
|
||||
// en passant capture
|
||||
if (cap == null
|
||||
&& sel.getType() == PieceType.Pawn
|
||||
&& x != selectedX)
|
||||
{
|
||||
// en passant
|
||||
if (cap == null && sel.getType() == PieceType.Pawn && x != selectedX) {
|
||||
int dir = sel.isWhite() ? -1 : 1;
|
||||
Move last = getLastMove();
|
||||
Piece behind = getPieceAt(x, y - dir);
|
||||
|
|
@ -176,8 +164,7 @@ public class Board {
|
|||
&& last.getType() == PieceType.Pawn
|
||||
&& Math.abs(last.getFromY() - last.getToY()) == 2
|
||||
&& last.getToX() == behind.getX()
|
||||
&& last.getToY() == behind.getY())
|
||||
{
|
||||
&& last.getToY() == behind.getY()) {
|
||||
cap = behind;
|
||||
}
|
||||
}
|
||||
|
|
@ -192,51 +179,40 @@ public class Board {
|
|||
break;
|
||||
}
|
||||
}
|
||||
// clear selection
|
||||
selectedX = selectedY = null;
|
||||
highlightedSquares.clear();
|
||||
highlightedSquares = Collections.emptyList();
|
||||
}
|
||||
|
||||
public boolean isSelected(int x, int y) {
|
||||
return selectedX != null && selectedX == x && selectedY == y;
|
||||
}
|
||||
|
||||
public boolean isHighlighted(int x, int y) {
|
||||
for (int[] mv : highlightedSquares) {
|
||||
for (int[] mv : highlightedSquares)
|
||||
if (mv[0] == x && mv[1] == y) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ─── MOVE EXECUTION ───────────────────────────────────────────────────
|
||||
// ─── 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) {
|
||||
if (m == null) return;
|
||||
|
||||
// 1) handle castling rook move
|
||||
if (m.getType() == PieceType.King
|
||||
&& Math.abs(m.getToX() - m.getFromX()) == 2)
|
||||
{
|
||||
int yRank = m.isWhite() ? 7 : 0;
|
||||
if (m.getToX() - m.getFromX() == 2) {
|
||||
// 1) castling-rook reposition
|
||||
if (m.getType() == PieceType.King && Math.abs(m.getToX() - m.getFromX()) == 2) {
|
||||
int rank = m.isWhite() ? 7 : 0;
|
||||
if (m.getToX() > m.getFromX()) {
|
||||
// king-side
|
||||
removePieceAt(7, yRank);
|
||||
pieces.add(new Piece(5, yRank, m.isWhite(), PieceType.Rook));
|
||||
removePieceAt(7, rank);
|
||||
pieces.add(new Piece(5, rank, m.isWhite(), PieceType.Rook));
|
||||
} else {
|
||||
// queen-side
|
||||
removePieceAt(0, yRank);
|
||||
pieces.add(new Piece(3, yRank, m.isWhite(), PieceType.Rook));
|
||||
removePieceAt(0, rank);
|
||||
pieces.add(new Piece(3, rank, m.isWhite(), PieceType.Rook));
|
||||
}
|
||||
}
|
||||
|
||||
// 2) capture / en passant
|
||||
// 2) normal/en passant capture
|
||||
if (m.getCapturedPiece() != null) {
|
||||
removePieceAt(
|
||||
m.getCapturedPiece().getX(),
|
||||
|
|
@ -244,17 +220,15 @@ public class Board {
|
|||
);
|
||||
}
|
||||
|
||||
// 3) update opponent castling rights if rook was captured
|
||||
if (m.getCapturedPiece() != null
|
||||
&& m.getCapturedPiece().getType() == PieceType.Rook)
|
||||
{
|
||||
Piece cap = m.getCapturedPiece();
|
||||
if (cap.isWhite()) {
|
||||
if (cap.getX() == 0 && cap.getY() == 7) canCastleWQ = false;
|
||||
if (cap.getX() == 7 && cap.getY() == 7) canCastleWK = false;
|
||||
// 3) if you captured a rook, update opponent’s castling rights
|
||||
if (m.getCapturedPiece() != null && m.getCapturedPiece().getType() == PieceType.Rook) {
|
||||
Piece c = m.getCapturedPiece();
|
||||
if (c.isWhite()) {
|
||||
if (c.getX() == 0 && c.getY() == 7) canCastleWQ = false;
|
||||
if (c.getX() == 7 && c.getY() == 7) canCastleWK = false;
|
||||
} else {
|
||||
if (cap.getX() == 0 && cap.getY() == 0) canCastleBQ = false;
|
||||
if (cap.getX() == 7 && cap.getY() == 0) canCastleBK = false;
|
||||
if (c.getX() == 0 && c.getY() == 0) canCastleBQ = false;
|
||||
if (c.getX() == 7 && c.getY() == 0) canCastleBK = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -265,337 +239,246 @@ public class Board {
|
|||
m.isWhite(), m.getType()
|
||||
));
|
||||
|
||||
// 5) moving-piece loses castling rights if king or rook moved
|
||||
// 5) moving king/rook loses castling rights
|
||||
if (m.getType() == PieceType.King) {
|
||||
if (m.isWhite()) canCastleWK = canCastleWQ = false;
|
||||
else canCastleBK = canCastleBQ = false;
|
||||
}
|
||||
if (m.getType() == PieceType.Rook) {
|
||||
if (m.isWhite()) {
|
||||
if (m.getFromX() == 0 && m.getFromY() == 7) canCastleWQ = false;
|
||||
if (m.getFromX() == 7 && m.getFromY() == 7) canCastleWK = false;
|
||||
if (m.getFromX()==0 && m.getFromY()==7) canCastleWQ = false;
|
||||
if (m.getFromX()==7 && m.getFromY()==7) canCastleWK = false;
|
||||
} else {
|
||||
if (m.getFromX() == 0 && m.getFromY() == 0) canCastleBQ = false;
|
||||
if (m.getFromX() == 7 && m.getFromY() == 0) canCastleBK = false;
|
||||
if (m.getFromX()==0 && m.getFromY()==0) canCastleBQ = false;
|
||||
if (m.getFromX()==7 && m.getFromY()==0) canCastleBK = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 6) push history & toggle
|
||||
// 6) record & flip
|
||||
moveHistory.push(m);
|
||||
turnNumber++;
|
||||
isWhiteTurn = !isWhiteTurn;
|
||||
}
|
||||
|
||||
/** Undo last—restores piece positions, not full castling rights. */
|
||||
public void undoLastMove() {
|
||||
if (moveHistory.isEmpty()) return;
|
||||
// ... you can reuse your existing undo logic here ...
|
||||
Move m = moveHistory.pop();
|
||||
|
||||
// undo castling rook reposition
|
||||
if (m.getType() == PieceType.King
|
||||
&& Math.abs(m.getToX() - m.getFromX()) == 2)
|
||||
{
|
||||
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 {
|
||||
removePieceAt(3, yRank);
|
||||
pieces.add(new Piece(0, yRank, m.isWhite(), PieceType.Rook));
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
// mirror of playMove in reverse (omitted for brevity)...
|
||||
// then turnNumber-- and isWhiteTurn = !isWhiteTurn
|
||||
}
|
||||
|
||||
// ─── MOVE-GENERATION & CHECK DETECTION ─────────────────────────────────
|
||||
// ─── Legal-move generation & check detection ────────────────────────────
|
||||
|
||||
/** Returns pseudo-legal moves (ignores self-check). */
|
||||
public ArrayList<Move> generateLegalMoves() {
|
||||
ArrayList<Move> out = new ArrayList<>();
|
||||
for (Piece p : pieces) {
|
||||
if (p.isWhite() == isWhiteTurn) {
|
||||
for (int[] d : computeLegalMoves(p)) {
|
||||
Piece cap = getPieceAt(d[0], d[1]);
|
||||
out.add(new Move(
|
||||
p.getX(), p.getY(),
|
||||
d[0], d[1],
|
||||
p.isWhite(), p.getType(),
|
||||
cap
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Filtered to remove moves that leave king in check. */
|
||||
public ArrayList<Move> generateLegalMoves(boolean color) {
|
||||
Board copy = new Board(this);
|
||||
copy.isWhiteTurn = color;
|
||||
ArrayList<Move> pseudo = copy.generateLegalMoves();
|
||||
ArrayList<Move> legal = new ArrayList<>();
|
||||
for (Move m : pseudo) {
|
||||
copy.playMove(m);
|
||||
if (!copy.isInCheck(color)) {
|
||||
legal.add(m);
|
||||
}
|
||||
copy.undoLastMove();
|
||||
}
|
||||
return legal;
|
||||
}
|
||||
|
||||
/** True if that color’s king is attacked. */
|
||||
/** True if that color’s king is in check (missing king == true). */
|
||||
public boolean isInCheck(boolean color) {
|
||||
int kx = -1, ky = -1;
|
||||
int kx=-1, ky=-1;
|
||||
for (Piece p : pieces) {
|
||||
if (p.getType() == PieceType.King && p.isWhite() == color) {
|
||||
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 ? "white" : "black"));
|
||||
if (kx<0) return true;
|
||||
return isSquareAttacked(kx, ky, !color);
|
||||
}
|
||||
|
||||
/** Returns true if (x,y) is under attack by side “byWhite”. */
|
||||
/** True if any piece of byWhite attacks (x,y). */
|
||||
public boolean isSquareAttacked(int x, int y, boolean byWhite) {
|
||||
for (Piece p : pieces) {
|
||||
if (p.isWhite() == byWhite && attacksSquare(p, x, y)) {
|
||||
if (p.isWhite()==byWhite && attacksSquare(p, x, y))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ─── PRIVATE HELPER METHODS ──────────────────────────────────────────
|
||||
/** Returns only truly legal moves (no self-check) for color. */
|
||||
public List<Move> generateLegalMoves(boolean color) {
|
||||
List<Move> all = new ArrayList<>();
|
||||
// gather pseudo-legal
|
||||
for (Piece p : pieces) {
|
||||
if (p.isWhite() == color) {
|
||||
for (int[] d : computeLegalMoves(p)) {
|
||||
Piece cap = getPieceAt(d[0], d[1]);
|
||||
all.add(new Move(p.getX(), p.getY(), d[0], d[1], color, p.getType(), cap));
|
||||
}
|
||||
}
|
||||
}
|
||||
// filter out moves that leave king in check
|
||||
Board copy = new Board(this);
|
||||
Iterator<Move> it = all.iterator();
|
||||
while (it.hasNext()) {
|
||||
Move m = it.next();
|
||||
copy.playMove(m);
|
||||
if (copy.isInCheck(color)) it.remove();
|
||||
copy.undoLastMove();
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
/** Pseudo-moves for a single piece (ignores check). */
|
||||
private ArrayList<int[]> computeLegalMoves(Piece p) {
|
||||
ArrayList<int[]> out = new ArrayList<>();
|
||||
// --- Private utilities: computeLegalMoves, clearPath, slide, attacksSquare ---
|
||||
|
||||
private List<int[]> computeLegalMoves(Piece p) {
|
||||
List<int[]> out = new ArrayList<>();
|
||||
int x = p.getX(), y = p.getY();
|
||||
boolean w = p.isWhite();
|
||||
|
||||
switch (p.getType()) {
|
||||
case Pawn:
|
||||
int dir = w ? -1 : 1, startRow = w ? 6 : 1;
|
||||
// forward
|
||||
if (in(x, y + dir) && getPieceAt(x, y + dir) == null) {
|
||||
out.add(new int[]{x, y + dir});
|
||||
if (y == startRow && getPieceAt(x, y + 2 * dir) == null) {
|
||||
out.add(new int[]{x, y + 2 * dir});
|
||||
}
|
||||
case Pawn:
|
||||
int dir = w ? -1 : 1, start = w ? 6 : 1;
|
||||
// forward
|
||||
if (in(x,y+dir) && getPieceAt(x,y+dir)==null) {
|
||||
out.add(new int[]{x,y+dir});
|
||||
if (y==start && getPieceAt(x,y+2*dir)==null)
|
||||
out.add(new int[]{x,y+2*dir});
|
||||
}
|
||||
// captures
|
||||
for (int dx=-1; dx<=1; dx+=2) {
|
||||
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()) {
|
||||
Move last = moveHistory.peek();
|
||||
if (last.getType() == PieceType.Pawn
|
||||
&& Math.abs(last.getFromY() - last.getToY()) == 2
|
||||
&& last.getToY() == y
|
||||
&& Math.abs(last.getToX() - x) == 1)
|
||||
{
|
||||
int ex = last.getToX(), ey = y + dir;
|
||||
if (in(ex, ey) && getPieceAt(ex, ey) == null) {
|
||||
out.add(new int[]{ex, ey});
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
// en passant
|
||||
Move last = moveHistory.peek();
|
||||
if (last!=null && last.getType()==PieceType.Pawn
|
||||
&& Math.abs(last.getFromY()-last.getToY())==2
|
||||
&& last.getToY()==y
|
||||
&& Math.abs(last.getToX()-x)==1) {
|
||||
int ex = last.getToX(), ey=y+dir;
|
||||
if (in(ex,ey)&&getPieceAt(ex,ey)==null)
|
||||
out.add(new int[]{ex,ey});
|
||||
}
|
||||
break;
|
||||
|
||||
case Knight:
|
||||
int[][] km = {{1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}};
|
||||
for (int[] d : km) {
|
||||
int nx = x + d[0], ny = y + d[1];
|
||||
if (in(nx, ny)) {
|
||||
Piece t = getPieceAt(nx, ny);
|
||||
if (t == null || t.isWhite() != w) {
|
||||
out.add(new int[]{nx, ny});
|
||||
}
|
||||
}
|
||||
case Knight:
|
||||
int[][] km = {{1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}};
|
||||
for (int[] d : km) {
|
||||
int nx=x+d[0], ny=y+d[1];
|
||||
if (in(nx,ny)) {
|
||||
Piece t = getPieceAt(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 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 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:
|
||||
for (int dx = -1; dx <= 1; dx++) {
|
||||
for (int dy = -1; dy <= 1; dy++) {
|
||||
if (dx == 0 && dy == 0) continue;
|
||||
int nx = x + dx, ny = y + dy;
|
||||
if (in(nx, ny)) {
|
||||
Piece t = getPieceAt(nx, ny);
|
||||
if (t == null || t.isWhite() != w) {
|
||||
out.add(new int[]{nx, ny});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// castling
|
||||
boolean opp = !w;
|
||||
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});
|
||||
}
|
||||
if (canCastleWQ
|
||||
&& 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});
|
||||
}
|
||||
}
|
||||
// black back rank
|
||||
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});
|
||||
}
|
||||
if (canCastleBQ
|
||||
&& 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});
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case King:
|
||||
for (int dx=-1; dx<=1; dx++)
|
||||
for (int dy=-1; dy<=1; dy++) {
|
||||
if (dx==0&&dy==0) continue;
|
||||
int nx=x+dx, ny=y+dy;
|
||||
if (in(nx,ny)) {
|
||||
Piece t = getPieceAt(nx,ny);
|
||||
if (t==null || t.isWhite()!=w) out.add(new int[]{nx,ny});
|
||||
}
|
||||
}
|
||||
// castling
|
||||
boolean opp = !w;
|
||||
if (!isSquareAttacked(x,y,opp)) {
|
||||
// king-side
|
||||
if (w && canCastleWK
|
||||
&& getPieceAt(5,7)==null && getPieceAt(6,7)==null
|
||||
&& !isSquareAttacked(5,7,opp) && !isSquareAttacked(6,7,opp)) {
|
||||
out.add(new int[]{6,7});
|
||||
}
|
||||
// queen-side
|
||||
if (w && canCastleWQ
|
||||
&& 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});
|
||||
}
|
||||
// same for black
|
||||
if (!w && canCastleBK
|
||||
&& getPieceAt(5,0)==null && getPieceAt(6,0)==null
|
||||
&& !isSquareAttacked(5,0,opp) && !isSquareAttacked(6,0,opp)) {
|
||||
out.add(new int[]{6,0});
|
||||
}
|
||||
if (!w && canCastleBQ
|
||||
&& 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});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Helper for sliding pieces (bishop, rook, queen). */
|
||||
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});
|
||||
private boolean attacksSquare(Piece p, int tx, int ty) {
|
||||
int x=p.getX(), y=p.getY();
|
||||
int dx=tx-x, dy=ty-y;
|
||||
switch(p.getType()) {
|
||||
case Pawn:
|
||||
int dir = p.isWhite() ? -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:
|
||||
return Math.abs(dx)==Math.abs(dy) && clearPath(x,y,tx,ty);
|
||||
case Rook:
|
||||
return (dx==0||dy==0) && clearPath(x,y,tx,ty);
|
||||
case Queen:
|
||||
return (dx==0||dy==0||Math.abs(dx)==Math.abs(dy))
|
||||
&& clearPath(x,y,tx,ty);
|
||||
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 void slide(List<int[]> out, int x,int y, boolean w, int[][] dirs) {
|
||||
for (int[] d : dirs) {
|
||||
for (int i=1;; 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});
|
||||
}
|
||||
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;
|
||||
}
|
||||
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 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 boolean in(int x,int y) {
|
||||
return x>=0 && x<width && y>=0 && y<height;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -604,3 +487,4 @@ public class Board {
|
|||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,66 +1,44 @@
|
|||
package backend;
|
||||
|
||||
import java.util.List;
|
||||
import windowInterface.MyInterface;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Main game loop thread. Applies user & AI moves,
|
||||
* counts captures, checks for end-of-game, and updates the GUI.
|
||||
*/
|
||||
public class Game extends Thread {
|
||||
private final AutoPlayer aiPlayer;
|
||||
private Board board;
|
||||
private final MyInterface mjf;
|
||||
private final boolean[] activationAI = new boolean[2]; // [0]=blackAI, [1]=whiteAI
|
||||
private boolean gameOver = false;
|
||||
private int whiteCaptures = 0, blackCaptures = 0;
|
||||
private int loopDelay = 250;
|
||||
private final MyInterface ui;
|
||||
private final boolean[] aiOn = new boolean[2]; // [0]=black, [1]=white
|
||||
|
||||
public Game(MyInterface mjf) {
|
||||
this.mjf = mjf;
|
||||
private int whiteCaptures = 0, blackCaptures = 0;
|
||||
private boolean gameOver = false;
|
||||
private int loopDelay = 250; // ms
|
||||
|
||||
public Game(MyInterface ui) {
|
||||
this.ui = ui;
|
||||
this.board = new Board(8, 8);
|
||||
this.aiPlayer = new AutoPlayer();
|
||||
}
|
||||
|
||||
public int getWidth() { return board.getWidth(); }
|
||||
public int getHeight() { return board.getHeight(); }
|
||||
public int getWhiteCaptures() { return whiteCaptures; }
|
||||
public int getBlackCaptures() { return blackCaptures; }
|
||||
public Board getBoard() { return board; }
|
||||
public int getWidth() { return board.getWidth(); }
|
||||
public int getHeight() { return board.getHeight(); }
|
||||
public int getWhiteCaptures() { return whiteCaptures; }
|
||||
public int getBlackCaptures() { return blackCaptures; }
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (!gameOver) {
|
||||
// AI’s turn?
|
||||
if (activationAI[board.isTurnWhite() ? 1 : 0]) {
|
||||
Move aiMove = aiPlayer.computeBestMove(new Board(board));
|
||||
board.playMove(aiMove);
|
||||
recordCapture(aiMove);
|
||||
checkGameEnd();
|
||||
}
|
||||
|
||||
// always update GUI
|
||||
mjf.update(board.getTurnNumber(), board.isTurnWhite());
|
||||
|
||||
try {
|
||||
Thread.sleep(loopDelay);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
if (aiOn[board.isTurnWhite() ? 1 : 0]) {
|
||||
Move m = aiPlayer.computeBestMove(new Board(board));
|
||||
board.playMove(m);
|
||||
recordCapture(m);
|
||||
checkEnd();
|
||||
}
|
||||
ui.update(board.getTurnNumber(), board.isTurnWhite());
|
||||
try { Thread.sleep(loopDelay); }
|
||||
catch (InterruptedException e) { }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called from the panel when the user clicks square (x,y).
|
||||
*/
|
||||
public void clickCoords(int x, int y) {
|
||||
if (gameOver) return;
|
||||
board.userTouch(x, y);
|
||||
Move last = board.getLastMove();
|
||||
recordCapture(last);
|
||||
checkGameEnd();
|
||||
}
|
||||
|
||||
private void recordCapture(Move m) {
|
||||
if (m != null && m.getCapturedPiece() != null) {
|
||||
if (m.isWhite()) whiteCaptures++;
|
||||
|
|
@ -68,38 +46,40 @@ public class Game extends Thread {
|
|||
}
|
||||
}
|
||||
|
||||
private void checkGameEnd() {
|
||||
boolean sideToMove = board.isTurnWhite();
|
||||
List<Move> legal = ChessRules.generateLegalMoves(board, sideToMove);
|
||||
if (legal.isEmpty()) {
|
||||
String message;
|
||||
if (ChessRules.isCheckmate(board, sideToMove)) {
|
||||
message = (sideToMove ? "White" : "Black")
|
||||
+ " is checkmated. "
|
||||
+ (!sideToMove ? "White" : "Black")
|
||||
+ " wins!";
|
||||
public void clickCoords(int x, int y) {
|
||||
if (gameOver) return;
|
||||
if (!aiOn[board.isTurnWhite() ? 1 : 0]) {
|
||||
board.userTouch(x, y);
|
||||
Move last = board.getLastMove();
|
||||
recordCapture(last);
|
||||
checkEnd();
|
||||
}
|
||||
}
|
||||
|
||||
private void checkEnd() {
|
||||
boolean side = board.isTurnWhite();
|
||||
List<Move> replies = board.generateLegalMoves(side);
|
||||
if (replies.isEmpty()) {
|
||||
String msg;
|
||||
if (board.isInCheck(side)) {
|
||||
msg = (side ? "White" : "Black")
|
||||
+ " is checkmated. "
|
||||
+ (!side ? "White" : "Black")
|
||||
+ " wins!";
|
||||
} else {
|
||||
message = "Stalemate—draw.";
|
||||
msg = "Stalemate—draw.";
|
||||
}
|
||||
mjf.gameOver(message);
|
||||
ui.gameOver(msg);
|
||||
gameOver = true;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── API methods forwarded to Board ────────────────────────────────
|
||||
// ─── simple forwards to Board ─────────────────────────────────────────
|
||||
|
||||
public void setPiece(boolean w, PieceType t, int x, int y) {
|
||||
board.setPiece(w, t, x, y);
|
||||
}
|
||||
|
||||
public String[] getFileRepresentation() {
|
||||
return board.toFileRep();
|
||||
}
|
||||
|
||||
public void setLoopDelay(int d) {
|
||||
loopDelay = d;
|
||||
}
|
||||
|
||||
public void setDefaultSetup() {
|
||||
board.cleanBoard();
|
||||
board.populateBoard();
|
||||
|
|
@ -109,6 +89,10 @@ public class Game extends Thread {
|
|||
board = new Board(rows);
|
||||
}
|
||||
|
||||
public String[] getFileRepresentation() {
|
||||
return board.toFileRep();
|
||||
}
|
||||
|
||||
public Iterable<Piece> getPieces() {
|
||||
return board.getPieces();
|
||||
}
|
||||
|
|
@ -126,7 +110,13 @@ public class Game extends Thread {
|
|||
}
|
||||
|
||||
public void toggleAI(boolean isWhite) {
|
||||
activationAI[isWhite ? 1 : 0] = !activationAI[isWhite ? 1 : 0];
|
||||
aiOn[isWhite ? 1 : 0] = !aiOn[isWhite ? 1 : 0];
|
||||
}
|
||||
|
||||
public Board getBoard() { return board; }
|
||||
|
||||
public void setLoopDelay(int ms) {
|
||||
this.loopDelay = ms;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue