diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 3fd6c25..6344cf5 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,5 +1,6 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve org.eclipse.jdt.core.compiler.compliance=1.8 @@ -7,6 +8,8 @@ org.eclipse.jdt.core.compiler.debug.lineNumber=generate org.eclipse.jdt.core.compiler.debug.localVariable=generate org.eclipse.jdt.core.compiler.debug.sourceFile=generate org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning org.eclipse.jdt.core.compiler.release=enabled org.eclipse.jdt.core.compiler.source=1.8 diff --git a/src/backend/AutoPlayer.java b/src/backend/AutoPlayer.java index e012d20..1afdcce 100644 --- a/src/backend/AutoPlayer.java +++ b/src/backend/AutoPlayer.java @@ -1,17 +1,36 @@ package backend; -import java.util.ArrayList; +import java.util.List; import java.util.Random; public class AutoPlayer { private final Random rnd = new Random(); - /** - * Picks a random legal move for the active player. - */ + private int valueOf(PieceType t) { + switch (t) { + case Pawn: return 1; + case Knight: + case Bishop: return 3; + case Rook: return 5; + case Queen: return 9; + default: return 0; + } + } + + /** Greedy‐capture AI with random tie‐break */ public Move computeBestMove(Board board) { - ArrayList moves = board.generateLegalMoves(); + List moves = ChessRules.generateLegalMoves(board, board.isTurnWhite()); if (moves.isEmpty()) return null; - return moves.get(rnd.nextInt(moves.size())); + + int bestScore = Integer.MIN_VALUE; + Move best = null; + for (Move m : moves) { + int score = (m.getCapturedPiece()==null ? 0 : valueOf(m.getCapturedPiece().getType())); + if (score > bestScore || (score==bestScore && rnd.nextBoolean())) { + bestScore = score; + best = m; + } + } + return best; } } diff --git a/src/backend/Board.java b/src/backend/Board.java index 94eabe9..ed89657 100644 --- a/src/backend/Board.java +++ b/src/backend/Board.java @@ -16,6 +16,7 @@ public class Board { private boolean canCastleWK = true, canCastleWQ = true; private boolean canCastleBK = true, canCastleBQ = true; + /** Empty board */ public Board(int colNum, int lineNum) { this.width = colNum; this.height = lineNum; @@ -24,6 +25,7 @@ public class Board { this.pieces = new ArrayList<>(); } + /** Load from file */ public Board(String[] array) { this(array[0].split(",").length, array.length - 1); this.isWhiteTurn = array[height].equals("W"); @@ -40,36 +42,38 @@ public class Board { } } + /** Deep copy */ public Board(Board other) { this(other.width, other.height); - this.turnNumber = other.turnNumber; - this.isWhiteTurn = other.isWhiteTurn; - this.canCastleWK = other.canCastleWK; - this.canCastleWQ = other.canCastleWQ; - this.canCastleBK = other.canCastleBK; - this.canCastleBQ = other.canCastleBQ; - this.pieces = new ArrayList<>(); - for (Piece p : other.pieces) { + this.turnNumber = other.turnNumber; + this.isWhiteTurn = other.isWhiteTurn; + this.canCastleWK = other.canCastleWK; + this.canCastleWQ = other.canCastleWQ; + this.canCastleBK = other.canCastleBK; + this.canCastleBQ = other.canCastleBQ; + this.pieces = new ArrayList<>(); + for (Piece p : other.pieces) this.pieces.add(new Piece(p.getX(), p.getY(), p.isWhite(), p.getType())); - } } - public int getWidth() { return width; } - public int getHeight() { return height; } - public int getTurnNumber() { return turnNumber; } - public boolean isTurnWhite() { return isWhiteTurn; } + // Accessors + public int getWidth() { return width; } + public int getHeight() { return height; } + public int getTurnNumber() { return turnNumber; } + public boolean isTurnWhite(){ return isWhiteTurn; } + /** Standard setup */ public void populateBoard() { PieceType[] back = { PieceType.Rook, PieceType.Knight, PieceType.Bishop, PieceType.Queen, PieceType.King, PieceType.Bishop, PieceType.Knight, PieceType.Rook }; - // black pawns + back + // black for (int x = 0; x < width; x++) { pieces.add(new Piece(x,1,false,PieceType.Pawn)); pieces.add(new Piece(x,0,false,back[x])); } - // white pawns + back + // white for (int x = 0; x < width; x++) { pieces.add(new Piece(x,6,true,PieceType.Pawn)); pieces.add(new Piece(x,7,true,back[x])); @@ -77,6 +81,7 @@ public class Board { canCastleWK = canCastleWQ = canCastleBK = canCastleBQ = true; } + /** Clear everything */ public void cleanBoard() { pieces.clear(); moveHistory.clear(); @@ -86,85 +91,74 @@ public class Board { canCastleWK = canCastleWQ = canCastleBK = canCastleBQ = true; } - @Override - public String toString() { + @Override public String toString() { 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(p==null?"..":(p.isWhite()?"W":"B")+p.getType().getSummary()); + if (x getPieces() { - return new ArrayList<>(pieces); - } - + public ArrayList getPieces() { return new ArrayList<>(pieces); } public void setPiece(boolean w, PieceType t, int x, int y) { - removePieceAt(x,y); - pieces.add(new Piece(x,y,w,t)); + removePieceAt(x,y); pieces.add(new Piece(x,y,w,t)); } + /** User click → select/move */ public void userTouch(int x, int y) { Piece clicked = getPieceAt(x,y); - if (selectedX == null) { - if (clicked != null && clicked.isWhite() == isWhiteTurn) { - selectedX = x; - selectedY = y; + if (selectedX==null) { + if (clicked!=null && clicked.isWhite()==isWhiteTurn) { + selectedX=x; selectedY=y; highlightedSquares = computeLegalMoves(clicked); } return; } - if (selectedX == x && selectedY == y) { - selectedX = selectedY = null; + if (selectedX==x && selectedY==y) { + selectedX=selectedY=null; highlightedSquares.clear(); return; } for (int[] mv : highlightedSquares) { - if (mv[0] == x && mv[1] == y) { + if (mv[0]==x && mv[1]==y) { Piece sel = getPieceAt(selectedX, selectedY); - Piece cap = getPieceAt(x, y); - - // en passant detection - if (cap == null && sel.getType() == PieceType.Pawn && x != selectedX) { - int dir = sel.isWhite() ? -1 : 1; - Move last = moveHistory.isEmpty() ? null : moveHistory.peek(); - Piece behind = getPieceAt(x, y - dir); - if (behind != null - && behind.getType() == PieceType.Pawn - && last != null - && last.getType() == PieceType.Pawn - && Math.abs(last.getFromY() - last.getToY()) == 2 - && last.getToX() == behind.getX() - && last.getToY() == behind.getY()) { + Piece cap = getPieceAt(x,y); + // en passant + if (cap==null && sel.getType()==PieceType.Pawn && x!=selectedX) { + int dir = sel.isWhite()? -1:1; + Move last = moveHistory.isEmpty()?null:moveHistory.peek(); + Piece behind = getPieceAt(x, y-dir); + if (behind!=null + && behind.getType()==PieceType.Pawn + && last!=null + && last.getType()==PieceType.Pawn + && Math.abs(last.getFromY()-last.getToY())==2 + && last.getToX()==behind.getX() + && last.getToY()==behind.getY()) { cap = behind; } } - Move m = new Move( selectedX, selectedY, x, y, @@ -175,74 +169,65 @@ public class Board { break; } } - selectedX = selectedY = null; + selectedX=selectedY=null; highlightedSquares.clear(); } - public boolean isSelected(int x, int y) { - return selectedX != null && selectedX == x && selectedY == y; + 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) { - 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; } + /** Execute a move (incl. castle & en passant) */ public void playMove(Move m) { - if (m == null) return; - - // castling rook reposition - if (m.getType() == PieceType.King && Math.abs(m.getToX() - m.getFromX()) == 2) { - int y = m.isWhite() ? 7 : 0; - if (m.getToX() - m.getFromX() == 2) { - removePieceAt(7, y); - pieces.add(new Piece(5, y, m.isWhite(), PieceType.Rook)); + if (m==null) return; + // castling rook + if (m.getType()==PieceType.King && Math.abs(m.getToX()-m.getFromX())==2) { + int y = m.isWhite()?7:0; + if (m.getToX()-m.getFromX()==2) { + removePieceAt(7,y); pieces.add(new Piece(5,y,m.isWhite(),PieceType.Rook)); } else { - removePieceAt(0, y); - pieces.add(new Piece(3, y, m.isWhite(), PieceType.Rook)); + removePieceAt(0,y); pieces.add(new Piece(3,y,m.isWhite(),PieceType.Rook)); } } - - // normal or en passant capture - if (m.getCapturedPiece() != null) { + // normal or en passant cap + if (m.getCapturedPiece()!=null) { removePieceAt( m.getCapturedPiece().getX(), m.getCapturedPiece().getY() ); } - // update opponent's castling rights if rook was captured - if (m.getCapturedPiece() != null && m.getCapturedPiece().getType() == PieceType.Rook) { - Piece cap = m.getCapturedPiece(); + // 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; + if (cap.getX()==0 && cap.getY()==7) canCastleWQ=false; + if (cap.getX()==7 && cap.getY()==7) canCastleWK=false; } else { - if (cap.getX() == 0 && cap.getY() == 0) canCastleBQ = false; - if (cap.getX() == 7 && cap.getY() == 0) canCastleBK = false; + if (cap.getX()==0 && cap.getY()==0) canCastleBQ=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() - )); + removePieceAt(m.getFromX(),m.getFromY()); + pieces.add(new Piece(m.getToX(),m.getToY(),m.isWhite(),m.getType())); // moving piece castling rights - if (m.getType() == PieceType.King) { - if (m.isWhite()) canCastleWK = canCastleWQ = false; - else canCastleBK = canCastleBQ = false; + 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.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; } } @@ -251,53 +236,43 @@ public class Board { isWhiteTurn = !isWhiteTurn; } + /** Undo last */ public void undoLastMove() { if (moveHistory.isEmpty()) return; Move m = moveHistory.pop(); - - // undo castling rook move - if (m.getType() == PieceType.King && Math.abs(m.getToX() - m.getFromX()) == 2) { - int y = m.isWhite() ? 7 : 0; - if (m.getToX() - m.getFromX() == 2) { - removePieceAt(5, y); - pieces.add(new Piece(7, y, m.isWhite(), PieceType.Rook)); + // undo castling + if (m.getType()==PieceType.King && Math.abs(m.getToX()-m.getFromX())==2) { + int y = m.isWhite()?7:0; + if (m.getToX()-m.getFromX()==2) { + removePieceAt(5,y); pieces.add(new Piece(7,y,m.isWhite(),PieceType.Rook)); } else { - removePieceAt(3, y); - pieces.add(new Piece(0, y, m.isWhite(), PieceType.Rook)); + removePieceAt(3,y); pieces.add(new Piece(0,y,m.isWhite(),PieceType.Rook)); } } - - // move piece back - removePieceAt(m.getToX(), m.getToY()); - pieces.add(new Piece( - m.getFromX(), m.getFromY(), - m.isWhite(), m.getType() - )); - + // 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() - )); + 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 are not fully restored in this simple undo + // note: castling rights not fully restored here } + /** Pseudo-legal moves for side to move */ public ArrayList generateLegalMoves() { ArrayList all = new ArrayList<>(); for (Piece p : pieces) { - if (p.isWhite() == isWhiteTurn) { + if (p.isWhite()==isWhiteTurn) { for (int[] d : computeLegalMoves(p)) { - Piece cap = getPieceAt(d[0], d[1]); + Piece cap = getPieceAt(d[0],d[1]); all.add(new Move( - p.getX(), p.getY(), - d[0], d[1], - p.isWhite(), p.getType(), + p.getX(),p.getY(), + d[0],d[1], + p.isWhite(),p.getType(), cap )); } @@ -306,77 +281,102 @@ public class Board { return all; } - // --- New attack-detection helpers --- - private boolean attacksSquare(Piece p, int targetX, int targetY) { - int x = p.getX(), y = p.getY(); - boolean w = p.isWhite(); - int dx = targetX - x, dy = targetY - y; - switch (p.getType()) { + /** Is that color’s king under attack? */ + 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 generateLegalMoves(boolean color) { + Board copy = new Board(this); + copy.isWhiteTurn = color; + ArrayList pseudo = copy.generateLegalMoves(); + ArrayList legal = new ArrayList<>(); + for (Move m : pseudo) { + copy.playMove(m); + if (!copy.isInCheck(color)) legal.add(m); + copy.undoLastMove(); + } + return legal; + } + + /** Square attacked by given side? */ + 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 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; + 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); + 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, targetX, targetY); + 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, targetX, targetY); + 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, targetX, targetY); + 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; + 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 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; + 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 boolean isSquareAttacked(int x, int y, boolean byWhite) { - for (Piece p : pieces) { - if (p.isWhite() == byWhite && attacksSquare(p, x, y)) - return true; - } - return false; - } - - // --- Original 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 boolean in(int x, int y) { - return x >= 0 && x < width && y >= 0 && y < height; + + private void removePieceAt(int x,int y) { + pieces.removeIf(p->p.getX()==x&&p.getY()==y); } - private void slide(ArrayList 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}); + private boolean in(int x,int y) { + return x>=0&&x=0&&y 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; } } @@ -385,91 +385,84 @@ public class Board { private ArrayList computeLegalMoves(Piece p) { ArrayList out = new ArrayList<>(); - int x = p.getX(), y = p.getY(); - boolean w = p.isWhite(); - switch (p.getType()) { + int x=p.getX(), y=p.getY(); + boolean w=p.isWhite(); + switch(p.getType()) { case Pawn: - int dir = w ? -1 : 1, sr = w ? 6 : 1; - if (in(x, y + dir) && getPieceAt(x, y + dir) == null) { - out.add(new int[]{x, y + dir}); - if (y == sr && getPieceAt(x, y + 2*dir) == null) { - out.add(new int[]{x, y + 2*dir}); - } + int dir = w?-1:1, sr = w?6:1; + if (in(x,y+dir)&&getPieceAt(x,y+dir)==null) { + out.add(new int[]{x,y+dir}); + if (y==sr&&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}); - } + 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}); } } 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}); + 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; case Rook: - slide(out, x, y, w, new int[][]{{1,0},{-1,0},{0,1},{0,-1}}); + 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}}); + 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[][]{ + slide(out,x,y,w,new int[][]{ {1,0},{-1,0},{0,1},{0,-1}, - {1,1},{1,-1},{-1,1},{-1,-1} - }); + {1,1},{1,-1},{-1,1},{-1,-1}}); break; case Knight: - int[][] knightMoves = {{1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}}; - for (int[] m : knightMoves) { - int nx = x + m[0], ny = y + m[1]; - if (in(nx, ny)) { - Piece t = getPieceAt(nx, ny); - if (t == null || t.isWhite() != w) - out.add(new int[]{nx, ny}); + int[][] km={{1,2},{1,-2},{-1,2},{-1,-2},{2,1},{2,-1},{-2,1},{-2,-1}}; + for (int[] m:km) { + int nx=x+m[0], ny=y+m[1]; + if (in(nx,ny)) { + Piece t=getPieceAt(nx,ny); + if (t==null||t.isWhite()!=w) out.add(new int[]{nx,ny}); } } 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}); + 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}); } } - boolean opp = !w; - if (!isSquareAttacked(x, y, opp)) { - if (w && x == 4 && y == 7) { - if (canCastleWK && getPieceAt(5,7) == null && getPieceAt(6,7) == null - && !isSquareAttacked(5,7,opp) && !isSquareAttacked(6,7,opp)) + boolean opp=!w; + if (!isSquareAttacked(x,y,opp)){ + 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)) + 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}); } - if (!w && x == 4 && y == 0) { - if (canCastleBK && getPieceAt(5,0) == null && getPieceAt(6,0) == null - && !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}); - if (canCastleBQ && getPieceAt(3,0) == null && getPieceAt(2,0) == null - && getPieceAt(1,0) == null && !isSquareAttacked(3,0,opp) - && !isSquareAttacked(2,0,opp)) + 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}); } } @@ -477,4 +470,12 @@ public class Board { } return out; } -} \ No newline at end of file + + public Move getLastMove() { + return moveHistory.isEmpty() ? null : moveHistory.peek(); + } +} + + + + \ No newline at end of file diff --git a/src/backend/ChessRules.java b/src/backend/ChessRules.java new file mode 100644 index 0000000..833ebb1 --- /dev/null +++ b/src/backend/ChessRules.java @@ -0,0 +1,25 @@ +package backend; + +import java.util.List; + +public class ChessRules { + + public static boolean isInCheck(Board board, boolean color) { + return board.isInCheck(color); + } + + public static List generateLegalMoves(Board board, boolean color) { + return board.generateLegalMoves(color); + } + + public static boolean isCheckmate(Board board, boolean color) { + List legal = board.generateLegalMoves(color); + return legal.isEmpty() && board.isInCheck(color); + } + + public static boolean isStalemate(Board board, boolean color) { + List legal = board.generateLegalMoves(color); + return legal.isEmpty() && !board.isInCheck(color); + } +} + diff --git a/src/backend/Game.java b/src/backend/Game.java index 046d829..6e5a0e6 100644 --- a/src/backend/Game.java +++ b/src/backend/Game.java @@ -1,115 +1,111 @@ package backend; import windowInterface.MyInterface; +import java.util.List; public class Game extends Thread { - private AutoPlayer aiPlayer; - private Board board; + private final AutoPlayer aiPlayer; + private Board board; + private final MyInterface mjf; + private final boolean[] activationAIFlags = new boolean[2]; + private boolean gameOver = false; + private int whiteCaptures = 0, blackCaptures = 0; + private int loopDelay = 250; - private MyInterface mjf; - private int COL_NUM = 8; - private int LINE_NUM = 8; - private int loopDelay = 250; - boolean[] activationAIFlags; + public Game(MyInterface mjfParam) { + this.mjf = mjfParam; + this.board = new Board(8, 8); + this.aiPlayer = new AutoPlayer(); + } - public Game(MyInterface mjfParam) { - mjf = mjfParam; - board = new Board(COL_NUM, LINE_NUM); - loopDelay = 250; - LINE_NUM = 8; - COL_NUM = 8; - activationAIFlags = new boolean[2]; - 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 int getWidth() { - return board.getWidth(); - } + @Override + public void run() { + while (!gameOver) { + if (isAITurn()) { + Move m = aiPlayer.computeBestMove(new Board(board)); + board.playMove(m); + recordCapture(m); + checkGameEnd(); + } + mjf.update(board.getTurnNumber(), board.isTurnWhite()); + try { Thread.sleep(loopDelay); } + catch (InterruptedException e) { e.printStackTrace(); } + } + } - public int getHeight() { - return board.getHeight(); - } + private boolean isAITurn() { + // [1] = white, [0] = black + return activationAIFlags[ board.isTurnWhite()?1:0 ]; + } - public void run() { - while(true) { - aiPlayerTurn(); - mjf.update(board.getTurnNumber(), board.isTurnWhite()); - try { - Thread.sleep(loopDelay); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - } - - private boolean isAITurn() { - return activationAIFlags[board.isTurnWhite()?1:0]; - } + public void clickCoords(int x, int y) { + if (gameOver) return; + if (x<0||y<0||x>=getWidth()||y>=getHeight()) return; + if (!isAITurn()) { + board.userTouch(x,y); + // lastMove not returned, but board.moveHistory.peek() is it: + Move last = board.getLastMove(); + recordCapture(last); + checkGameEnd(); + } + } - private void aiPlayerTurn() { - if(isAITurn()) { - board.playMove(aiPlayer.computeBestMove(new Board(board))); - } - } + private void recordCapture(Move m) { + if (m!=null && m.getCapturedPiece()!=null) { + if (m.isWhite()) whiteCaptures++; + else blackCaptures++; + } + } - public void clickCoords(int x, int y) { - int width = this.getWidth(); - int height = this.getHeight(); - if(0>x || 0>y || x>width || y>height) { - System.out.println("Click out of bounds"); - return; - } - if(!isAITurn()) { - board.userTouch(x, y); - } - - } + /** check for end‐of‐game and notify GUI */ + private void checkGameEnd() { + boolean sideToMove = board.isTurnWhite(); + List replies = ChessRules.generateLegalMoves(board, sideToMove); + if (replies.isEmpty()) { + String msg; + if (ChessRules.isCheckmate(board, sideToMove)) { + msg = (sideToMove?"White":"Black") + + " is checkmated. " + + (!sideToMove?"White":"Black") + + " wins!"; + } else { + msg = "Stalemate—draw."; + } + System.out.println(msg); + mjf.gameOver(msg); + gameOver = true; + } + } - public void setPiece(boolean isWhite, PieceType type, int x, int y) { - board.setPiece(isWhite, type, x, y); - } - - public String[] getFileRepresentation() { - return board.toFileRep(); - } - - public void setLoopDelay(int delay) { - this.loopDelay = delay; - } - - public void setDefaultSetup() { - board.cleanBoard(); - board.populateBoard(); - } - - public void setBoard(String[] array) { - board = new Board(array); - } - - public Iterable 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) { - this.activationAIFlags[isWhite?1:0] = !this.activationAIFlags[isWhite?1:0]; - } + // --- existing API below --- + 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(); } + public void setBoard(String[] a){ board = new Board(a); } + public Iterable 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 w) { activationAIFlags[w?1:0] = !activationAIFlags[w?1:0]; } + public Board getBoard() { return board; } } + + + diff --git a/src/windowInterface/JPanelChessBoard.java b/src/windowInterface/JPanelChessBoard.java index 78a68de..6ddce0f 100644 --- a/src/windowInterface/JPanelChessBoard.java +++ b/src/windowInterface/JPanelChessBoard.java @@ -1,196 +1,112 @@ package windowInterface; -import java.awt.Color; -import java.awt.Graphics; -import java.awt.Image; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; +import java.awt.*; +import java.awt.event.*; import java.awt.image.BufferedImage; import java.io.File; -import java.io.IOException; - +import java.util.HashSet; +import java.util.List; +import java.util.Set; import javax.imageio.ImageIO; -import javax.swing.JPanel; +import javax.swing.*; -import backend.Game; -import backend.Piece; -import backend.PieceType; +import backend.*; public class JPanelChessBoard extends JPanel { + private static final long serialVersionUID = 1L; + private Game myGame; + private final MyInterface interfaceGlobal; + private BufferedImage spriteSheet; + private final int PIECE_W=16, PIECE_H=16, M=6; - private static final long serialVersionUID = 1L; - private Game myGame; - private MyInterface interfaceGlobal; - private BufferedImage spriteSheet; - private int PIECE_WIDTH = 16; //in spritesheet - private int PIECE_HEIGHT = 16; //in spritesheet - private int MARGIN = 6; - - private boolean pieceSelectorMode; - private boolean selectedPieceIsWhite; - private PieceType selectedPieceType; - private boolean pieceAdderMode; + private boolean pieceSelectorMode=false, pieceAdderMode=false; + private boolean selIsWhite=true; private PieceType selType=PieceType.Pawn; - public JPanelChessBoard(MyInterface itf) { - super(); - myGame = null; - interfaceGlobal = itf; - selectedPieceIsWhite = true; - selectedPieceType = PieceType.Pawn; - pieceSelectorMode = false; - try { - spriteSheet = ImageIO.read(new File("pieces.png")); - } catch (IOException e) { - e.printStackTrace(); - } - pieceSelectorMode = false; - pieceAdderMode = false; - addMouseListener(new MouseAdapter() { - public void mousePressed(MouseEvent me) { - // System.out.println(me); - if(pieceSelectorMode) { - int x = Math.round(me.getX()/cellWidth()); - selectedPieceType = PieceType.values()[5-x]; - selectedPieceIsWhite = (me.getY() > cellHeight()); - pieceSelectorMode = false; - } else { - if(myGame == null) { - interfaceGlobal.instantiateSimu(); - } - int x = (me.getX()*myGame.getWidth())/getWidth(); - int y = (me.getY()*myGame.getHeight())/getHeight(); - if(pieceAdderMode) { - //TODO - myGame.setPiece(selectedPieceIsWhite,selectedPieceType, x, y); - pieceAdderMode = false; - } else { - myGame.clickCoords(x,y); - } - } - repaint(); - } - }); - } - + public JPanelChessBoard(MyInterface itf) { + super(); interfaceGlobal=itf; + try { spriteSheet=ImageIO.read(new File("pieces.png")); } + catch(Exception e){ e.printStackTrace(); } + addMouseListener(new MouseAdapter(){ + public void mousePressed(MouseEvent me){ + if(pieceSelectorMode){ + int x=(int)(me.getX()/cellW()), y=(int)(me.getY()/cellH()); + selType = PieceType.values()[5-x]; + selIsWhite = y>1; + pieceSelectorMode=false; + } else { + if(myGame==null) interfaceGlobal.instantiateSimu(); + int x=(me.getX()*myGame.getWidth())/getWidth(), + y=(me.getY()*myGame.getHeight())/getHeight(); + if(pieceAdderMode){ + myGame.setPiece(selIsWhite,selType,x,y); + pieceAdderMode=false; + } else { + myGame.clickCoords(x,y); + } + } + repaint(); + } + }); + } - public void setGame(Game simu) { - myGame = simu; - } + public void setGame(Game g){ myGame=g; } - @Override - protected void paintComponent(Graphics g) { - super.paintComponent(g); - this.setBackground(Color.black); - if(pieceSelectorMode) { - g.drawImage( - spriteSheet, - 0, - 0, - Math.round(5*cellWidth()), - Math.round(2*cellHeight()), - null - ); - return; - } - if (myGame != null) { - // Draw Interface from state of simulator - float cellWidth = cellWidth(); - float cellHeight = cellHeight(); + @Override protected void paintComponent(Graphics g){ + super.paintComponent(g); + if (myGame==null) return; - g.setColor(Color.white); - for(int x=0; x legalH = new HashSet<>(); + if(selX>=0){ + List lm = ChessRules.generateLegalMoves( + myGame.getBoard(), myGame.getBoard().isTurnWhite() + ); + for(Move m:lm) if(m.getFromX()==selX&&m.getFromY()==selY) + legalH.add(new Point(m.getToX(),m.getToY())); + } - private void drawPiece(Graphics g, Piece piece) { - g.drawImage( - getChessPieceImageFromType(piece.getType(), piece.isWhite()), - MARGIN+(xCoordFromGame(piece.getX())), - MARGIN+(yCoordFromGame(piece.getY())), - null - ); - } + float w=cellW(), h=cellH(); + for(int x=0;xclicButtonStart()); + top.add(start); - actionLabel = new JLabel("Waiting For Start"); - panelTop.add(actionLabel); + turnLabel = new JLabel("Turn: 0"); + top.add(turnLabel); - JButton btnGo = new JButton("Start/Restart"); - btnGo.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clicButtonStart(); - } - }); - panelTop.add(btnGo); + whiteCapLabel = new JLabel("W captures: 0"); + top.add(whiteCapLabel); + blackCapLabel = new JLabel("B captures: 0"); + top.add(blackCapLabel); - turnLabel = new JLabel("Turn : X"); - panelTop.add(turnLabel); - - JButton btnLoad = new JButton("Load File"); - btnLoad.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clicLoadFileButton(); - } - }); - panelRight.add(btnLoad); + JCheckBox wAI = new JCheckBox("White AI"); + wAI.addActionListener(e->clicAIToggle(true)); + top.add(wAI); + JCheckBox bAI = new JCheckBox("Black AI"); + bAI.addActionListener(e->clicAIToggle(false)); + top.add(bAI); - JButton btnSave = new JButton("Save To File"); - btnSave.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clicSaveToFileButton(); - } - }); - panelRight.add(btnSave); + JButton undo = new JButton("Undo"); + undo.addActionListener(e->game.undoLastMove()); + top.add(undo); - JButton btnAdder = new JButton("Add Piece"); - btnAdder.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clickButtonAdder(); - } - }); - panelRight.add(btnAdder); - - JButton btnPieceSelector = new JButton("Piece Select"); - btnPieceSelector.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clickButtonSelector(); - } - }); - panelRight.add(btnPieceSelector); + panelDraw = new JPanelChessBoard(this); + getContentPane().add(panelDraw,BorderLayout.CENTER); - JButton btnUndo = new JButton("Undo"); - btnUndo.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clicUndoButton(); - } + JPanel right = new JPanel(new GridLayout(4,1)); + getContentPane().add(right,BorderLayout.EAST); - }); - panelTop.add(btnUndo); - - chckbxWhiteAI = new JCheckBox("WhiteAI"); - chckbxWhiteAI.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clicAIToggle(true); - } + JButton load = new JButton("Load File"); + load.addActionListener(e->clicLoadFileButton()); + right.add(load); - }); - panelTop.add(chckbxWhiteAI); - - chckbxBlackAI = new JCheckBox("BlackAI"); - chckbxBlackAI.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent arg0) { - clicAIToggle(false); - } + JButton save = new JButton("Save To File"); + save.addActionListener(e->clicSaveToFileButton()); + right.add(save); - }); - panelTop.add(chckbxBlackAI); + JButton adder = new JButton("Add Piece"); + adder.addActionListener(e->panelDraw.toggleAdderMode()); + right.add(adder); - panelDraw = new JPanelChessBoard(this); - contentPane.add(panelDraw, BorderLayout.CENTER); - } + JButton selP = new JButton("Piece Select"); + selP.addActionListener(e->panelDraw.togglePieceSelector()); + right.add(selP); + } - public void setStepBanner(String s) { - turnLabel.setText(s); - } + public void instantiateSimu(){ + if (game==null){ + game = new Game(this); + panelDraw.setGame(game); + game.start(); + } + } - public void setBorderBanner(String s) { - borderLabel.setText(s); - } + public void clicButtonStart(){ + instantiateSimu(); + game.setDefaultSetup(); + } - public JPanelChessBoard getPanelDessin() { - return panelDraw; - } - - public void instantiateSimu() { - if(game==null) { - game = new Game(this); - panelDraw.setGame(game); - game.start(); - } - } + public void clicAIToggle(boolean isWhite){ + if (game!=null) game.toggleAI(isWhite); + } - public void clicButtonStart() { - this.instantiateSimu(); - game.setDefaultSetup(); - } - - public void clickButtonAdder() { - panelDraw.toggleAdderMode(); - } - public void clickButtonSelector() { - panelDraw.togglePieceSelector(); - } + public void clicLoadFileButton(){ + instantiateSimu(); + String f = selectFile(); + if (f.isEmpty()) return; + LinkedList lines = new LinkedList<>(); + try(BufferedReader r = new BufferedReader(new FileReader(f))){ + String L; + while((L=r.readLine())!=null) lines.add(L); + }catch(Exception e){ e.printStackTrace(); } + game.setBoard(lines.toArray(new String[0])); + panelDraw.repaint(); + } - private void clicUndoButton() { - if(game == null) { - System.out.println("error : can't undo while no game present"); - } else { - game.undoLastMove(); - } + public void clicSaveToFileButton(){ + String f = selectFile(); + if (f.isEmpty()||game==null) return; + String[] out = game.getFileRepresentation(); + try(PrintWriter w=new PrintWriter(new FileWriter(f))){ + for(String row:out) w.println(row); + }catch(Exception e){e.printStackTrace();} + } - } - public void clicAIToggle(boolean isWhite) { - if(game == null) { - System.out.println("error : can't activate AI while no game present"); - if(isWhite) { - chckbxWhiteAI.setSelected(false); - }else { - chckbxBlackAI.setSelected(false); - } - } else { - game.toggleAI(isWhite); - } - } - - public void clicLoadFileButton() { - Game loadedSim = new Game(this); - String fileName=SelectFile(); - LinkedList lines = new LinkedList(); - if (fileName.length()>0) { - try { - BufferedReader fileContent = new BufferedReader(new FileReader(fileName)); - String line = fileContent.readLine(); - int colorID = 0; - while (line != null) { - lines.add(line); - line = fileContent.readLine(); - } - loadedSim.setBoard(Arrays.stream(lines.toArray()).map(Object::toString).toArray(String[]::new)); - fileContent.close(); - } catch (Exception e) { - e.printStackTrace(); - } - game = loadedSim; - panelDraw.setGame(game); - this.repaint(); - } - } + private String selectFile(){ + JFileChooser C=new JFileChooser(); + if(C.showOpenDialog(this)==JFileChooser.APPROVE_OPTION) + return C.getSelectedFile().toString(); + return ""; + } - public void clicSaveToFileButton() { - String fileName=SelectFile(); - if (fileName.length()>0) { - String[] content = game.getFileRepresentation(); - writeFile(fileName, content); - } - } - - public String SelectFile() { - String s; - JFileChooser chooser = new JFileChooser(); - chooser.setCurrentDirectory(new java.io.File(".")); - chooser.setDialogTitle("Choose a file"); - chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); - chooser.setAcceptAllFileFilterUsed(true); - if (chooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { - s=chooser.getSelectedFile().toString(); - } else { - System.out.println("No Selection "); - s=""; - } - return s; - } - - public void writeFile(String fileName, String[] content) { - FileWriter csvWriter; - try { - csvWriter = new FileWriter(fileName); - for (String row : content) { - csvWriter.append(row); - csvWriter.append("\n"); - } - csvWriter.flush(); - csvWriter.close(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - public void update(int turnCount, boolean turnIsWhite) { - turnLabel.setText("Turn : "+turnCount+", "+ (turnIsWhite?"White":"Black")); - actionLabel.setText(panelDraw.isPieceAdderMode()?"Adding Piece": - (panelDraw.isPieceSelectorMode()?"Selecting Piece to Add": - "Playing")); - this.repaint(); - } - - public void eraseLabels() { - this.setStepBanner("Turn : X"); - } + public void update(int turnCount, boolean turnIsWhite){ + turnLabel.setText("Turn : "+turnCount+", "+(turnIsWhite?"White":"Black")); + actionLabel.setText(panelDraw.isPieceAdderMode()?"Adding Piece": + panelDraw.isPieceSelectorMode()?"Selecting Piece":"Playing"); + whiteCapLabel.setText("W captures: "+game.getWhiteCaptures()); + blackCapLabel.setText("B captures: "+game.getBlackCaptures()); + panelDraw.repaint(); + } + public void gameOver(String message){ + JOptionPane.showMessageDialog( + this, message, "Game Over", JOptionPane.INFORMATION_MESSAGE + ); + } }