552 lines
17 KiB
Java
552 lines
17 KiB
Java
import javax.swing.*;
|
|
import java.awt.*;
|
|
import java.awt.geom.AffineTransform;
|
|
import java.util.*;
|
|
import java.util.Timer;
|
|
|
|
/**
|
|
* Chessboard class
|
|
*/
|
|
public class Chessboard extends JPanel {
|
|
|
|
/**
|
|
* Size of single square
|
|
*/
|
|
public final int SQUARE_SIZE = 140;
|
|
|
|
/**
|
|
* Count of squares on the board
|
|
*/
|
|
public final int SQUARE_COUNT = 8;
|
|
|
|
/**
|
|
* Width of the chessboard
|
|
*/
|
|
private final double BOARD_WIDTH = SQUARE_COUNT*SQUARE_SIZE;
|
|
|
|
/**
|
|
* Scale of the chessboard
|
|
*/
|
|
public double boardScale;
|
|
|
|
/**
|
|
* Top location of the board
|
|
*/
|
|
public double startX;
|
|
|
|
/**
|
|
* Left location of the board
|
|
*/
|
|
public double startY;
|
|
|
|
/**
|
|
* Instance of the selected piece
|
|
*/
|
|
private APiece selected;
|
|
|
|
/**
|
|
* Two-dimensional array of pieces
|
|
*/
|
|
public APiece[][] pieces = new APiece[SQUARE_COUNT][SQUARE_COUNT];
|
|
|
|
public Set<APiece> removedPieces = new HashSet<>();
|
|
|
|
public boolean[][] possibleMoves = new boolean[SQUARE_COUNT][SQUARE_COUNT];
|
|
|
|
public boolean[][] lastMove = new boolean[SQUARE_COUNT][SQUARE_COUNT];
|
|
|
|
private Player activePlayer;
|
|
|
|
private Player player1;
|
|
|
|
private Player player2;
|
|
|
|
public boolean blindMode = false;
|
|
|
|
static private Theme theme = Theme.activeTheme;
|
|
|
|
private long lastMoveTime = System.currentTimeMillis();
|
|
|
|
/**
|
|
* Constructor of the chessboard
|
|
*/
|
|
public Chessboard() {
|
|
|
|
ChessboardMouseAdapter ma = new ChessboardMouseAdapter();
|
|
addMouseListener(ma);
|
|
addMouseMotionListener(ma);
|
|
|
|
}
|
|
|
|
public static Chessboard fromFEN(String input) {
|
|
|
|
String[] parts = input.split(" ");
|
|
if(parts.length != 6) return null;
|
|
String[] pieces = parts[0].split("/");
|
|
if(pieces.length != 8) return null;
|
|
|
|
Chessboard c = new Chessboard();
|
|
Player white = new Player(c, StartPosition.BOTTOM, PieceColor.WHITE);
|
|
Player black = new Player(c, StartPosition.TOP, PieceColor.BLACK);
|
|
|
|
int x = 0;
|
|
int y = 0;
|
|
for (String pieceRow : pieces) {
|
|
for (char item : pieceRow.toCharArray()) {
|
|
if (Character.isDigit(item)) {
|
|
x += Integer.parseInt(String.valueOf(item));
|
|
} else {
|
|
char pieceType = Character.toLowerCase(item);
|
|
Player pieceOwner = pieceType != item ? white : black;
|
|
if(pieceType == 'p') {
|
|
Pawn pawn = new Pawn(pieceOwner, x, y);
|
|
if(y != (pieceOwner.getStartPosition() == StartPosition.TOP ? 1 : 6)) pawn.setMoveCount(1);
|
|
}
|
|
if(pieceType == 'r') {
|
|
Rook rook = new Rook(pieceOwner, x, y);
|
|
rook.setMoveCount(1);
|
|
}
|
|
if(pieceType == 'n') new Knight(pieceOwner, x, y);
|
|
if(pieceType == 'b') new Bishop(pieceOwner, x, y);
|
|
if(pieceType == 'q') new Queen(pieceOwner, x, y);
|
|
if(pieceType == 'k') new King(pieceOwner, x, y);
|
|
x++;
|
|
}
|
|
}
|
|
x = 0;
|
|
y++;
|
|
}
|
|
|
|
c.setPlayer1(white);
|
|
c.setPlayer2(black);
|
|
|
|
if(parts[1].equals("b")) {
|
|
c.setActivePlayer(black);
|
|
}
|
|
|
|
for (char item : parts[2].toCharArray()) {
|
|
char pieceType = Character.toLowerCase(item);
|
|
if(pieceType == 'q' || pieceType == 'k') {
|
|
Player pieceOwner = pieceType != item ? white : black;
|
|
int rookX = pieceType == 'q' ? 0 : 7;
|
|
int rookY = pieceType != item ? 7 : 0;
|
|
APiece rook = c.getPiece(rookX, rookY);
|
|
if(rook != null && rook.getPlayer() == pieceOwner) rook.setMoveCount(0);
|
|
}
|
|
}
|
|
|
|
if(!parts[3].equals("-") && parts[3].length() == 2) {
|
|
PiecePosition pos = PiecePosition.fromString(parts[3]);
|
|
if(pos == null) return null;
|
|
boolean[][] lastMove = new boolean[c.SQUARE_COUNT][c.SQUARE_COUNT];
|
|
lastMove[pos.y-1][pos.x] = true;
|
|
lastMove[pos.y+1][pos.x] = true;
|
|
c.showLastMove(lastMove);
|
|
}
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
public String toFEN() {
|
|
|
|
StringBuilder chessboardSB = new StringBuilder();
|
|
for (int y = 0; y < pieces.length; y++) {
|
|
int empty = 0;
|
|
for (APiece piece : pieces[y]) {
|
|
if (piece == null) {
|
|
empty++;
|
|
continue;
|
|
}
|
|
Character pieceLetter = null;
|
|
if (piece instanceof Pawn) pieceLetter = 'p';
|
|
if (piece instanceof Knight) pieceLetter = 'n';
|
|
if (piece instanceof King) pieceLetter = 'k';
|
|
if (piece instanceof Queen) pieceLetter = 'q';
|
|
if (piece instanceof Bishop) pieceLetter = 'b';
|
|
if (piece instanceof Rook) pieceLetter = 'r';
|
|
if (empty != 0) chessboardSB.append(empty);
|
|
chessboardSB.append(piece.getPlayer() == player1 ? Character.toUpperCase(pieceLetter) : pieceLetter);
|
|
empty = 0;
|
|
}
|
|
if (empty != 0) chessboardSB.append(empty);
|
|
if(y != 7) chessboardSB.append('/');
|
|
}
|
|
|
|
StringBuilder castlingSB = new StringBuilder();
|
|
for (Player player : new Player[]{player1, player2}) {
|
|
if(player.getKing().getMoveCount() != 0) continue;
|
|
StartPosition sp = player.getStartPosition();
|
|
for (int x : new int[]{7, 0}) {
|
|
APiece rook = getPiece(x, sp == StartPosition.TOP ? 0 : 7);
|
|
if(rook instanceof Rook && rook.getMoveCount() == 0 && rook.getPlayer() == player) {
|
|
char castlingType = x == 0 ? 'q' : 'k';
|
|
castlingSB.append(sp == StartPosition.TOP ? castlingType : Character.toUpperCase(castlingType));
|
|
}
|
|
}
|
|
}
|
|
if(castlingSB.length() == 0) castlingSB.append('-');
|
|
|
|
char player = player1 == activePlayer ? 'w' : 'b';
|
|
|
|
String enPassantTargetSquare = "-";
|
|
for (int y = 0; y < lastMove.length; y++) {
|
|
for (int x = 0; x < lastMove[y].length; x++) {
|
|
if(lastMove[y][x] && (y == 1 || y == 4) && lastMove[y+2][x]) {
|
|
APiece pawn = pieces[y == 1 ? y+2 : y][x];
|
|
if(pawn instanceof Pawn && pawn.getMoveCount() == 1) {
|
|
enPassantTargetSquare = (new PiecePosition(x, y+2)).toString();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return chessboardSB + " " + player + " " + castlingSB + " " + enPassantTargetSquare + " 0 0";
|
|
|
|
}
|
|
|
|
/**
|
|
* Add passed piece to the specified coordinates
|
|
* @param piece piece
|
|
* @param x piece's X location
|
|
* @param y piese's Y location
|
|
*/
|
|
public void addPiece(APiece piece, int x, int y) {
|
|
if(!isOnBoard(x, y)) return;
|
|
APiece removedPiece = pieces[y][x];
|
|
if(removedPiece != null) removedPieces.add(removedPiece);
|
|
removedPieces.remove(piece);
|
|
pieces[y][x] = piece;
|
|
}
|
|
|
|
/**
|
|
* Remove piece at specified coordinates
|
|
* @param x piece's X location
|
|
* @param y piece's Y location
|
|
* @return removed piece
|
|
*/
|
|
public APiece removePiece(int x, int y) {
|
|
if(!isOnBoard(x, y)) return null;
|
|
APiece piece = pieces[y][x];
|
|
if(piece != null) removedPieces.add(piece);
|
|
pieces[y][x] = null;
|
|
return piece;
|
|
}
|
|
|
|
/**
|
|
* Paint the chessboard
|
|
* @param g Graphics context
|
|
*/
|
|
public void paint(Graphics g) {
|
|
paint(g, this.getWidth(), this.getHeight());
|
|
}
|
|
|
|
/**
|
|
* Paint the chessboard
|
|
* @param g Graphics context
|
|
* @param panelWidth chessboard width in px
|
|
* @param panelHeight chessboard height in px
|
|
*/
|
|
public void paint(Graphics g, double panelWidth, double panelHeight) {
|
|
|
|
Graphics2D g2 = getGraphics2D(g);
|
|
|
|
double cx = panelWidth / 2.0;
|
|
double cy = panelHeight / 2.0;
|
|
|
|
boardScale = Math.min(panelWidth, panelHeight)/BOARD_WIDTH;
|
|
|
|
startX = cx-(BOARD_WIDTH/2.0*boardScale);
|
|
startY = cy-(BOARD_WIDTH/2.0*boardScale);
|
|
|
|
g2.translate(startX, startY);
|
|
g2.scale(boardScale, boardScale);
|
|
|
|
AffineTransform beforeSquares = g2.getTransform();
|
|
|
|
boolean isBlack = true;
|
|
|
|
g2.setStroke(new BasicStroke(16));
|
|
|
|
for(int i=0; i<SQUARE_COUNT; i++) {
|
|
if(SQUARE_COUNT % 2 == 0) isBlack = !isBlack;
|
|
for(int j=0; j<SQUARE_COUNT; j++) {
|
|
g2.setTransform(beforeSquares);
|
|
g2.translate(i*SQUARE_SIZE, j*SQUARE_SIZE);
|
|
paintSquare(g2, isBlack);
|
|
paintHighlights(g2, i, j);
|
|
isBlack = !isBlack;
|
|
}
|
|
}
|
|
|
|
g2.setTransform(beforeSquares);
|
|
|
|
for (APiece piece : removedPieces) {
|
|
boolean isFloating = piece.isFloating();
|
|
double pScale = piece.getScale();
|
|
if(!isFloating && pScale == 1) continue;
|
|
g2.setTransform(beforeSquares);
|
|
paintPiece(g2, piece);
|
|
}
|
|
|
|
for (int y = 0; y < pieces.length; y++) {
|
|
for (int x = 0; x < pieces[y].length; x++) {
|
|
APiece piece = pieces[y][x];
|
|
if(piece != null && !piece.isFloating()) {
|
|
if(blindMode && piece.getPlayer() != activePlayer) continue;
|
|
g2.setTransform(beforeSquares);
|
|
paintPiece(g2, piece);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int y = 0; y < pieces.length; y++) {
|
|
for (int x = 0; x < pieces[y].length; x++) {
|
|
APiece piece = pieces[y][x];
|
|
if(piece != null && piece.isFloating()) {
|
|
g2.setTransform(beforeSquares);
|
|
paintPiece(g2, piece);
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
private void paintSquare(Graphics g, boolean isBlack) {
|
|
if(isBlack) g.setColor(theme.darkSquare);
|
|
else g.setColor(theme.lightSquare);
|
|
g.fillRect(0, 0, SQUARE_SIZE+1, SQUARE_SIZE+1);
|
|
}
|
|
|
|
private void paintHighlights(Graphics g, int x, int y) {
|
|
if(lastMove != null && lastMove[y][x]) {
|
|
g.setColor(new Color(50, 50, 250, 30));
|
|
g.fillRect(0, 0, SQUARE_SIZE, SQUARE_SIZE);
|
|
}
|
|
if(possibleMoves != null && possibleMoves[y][x]) {
|
|
g.setColor(new Color(0, 0, 0, 40));
|
|
//if(otherPlayer.getAttackArea()[y][x]) g.setColor(new Color(255, 0, 0, 40));
|
|
if(pieces[y][x] == null) g.fillOval(50, 50, 40, 40);
|
|
else g.drawRect(8, 8, SQUARE_SIZE-16, SQUARE_SIZE-16);
|
|
}
|
|
}
|
|
|
|
private void paintPiece(Graphics2D g2, APiece piece) {
|
|
g2.translate(20+piece.getRealX(), 20+piece.getRealY());
|
|
double pieceScale = piece.getScale();
|
|
if(pieceScale != 1) {
|
|
double offset = (1-pieceScale)*50;
|
|
g2.translate(offset, offset);
|
|
g2.scale(pieceScale, pieceScale);
|
|
}
|
|
piece.paint(g2);
|
|
}
|
|
|
|
public void repaintRootPane() {
|
|
repaintRootPane(null);
|
|
}
|
|
|
|
public void repaintRootPane(Rectangle r) {
|
|
JComponent pane = getRootPane();
|
|
if(pane != null) {
|
|
if (r != null) pane.repaint(r);
|
|
else pane.repaint();
|
|
}
|
|
}
|
|
|
|
public static void setTheme(Theme newTheme) {
|
|
theme = newTheme;
|
|
}
|
|
|
|
/**
|
|
* Setup Graphics2D with anti-aliasing
|
|
* @param g Graphics instance
|
|
* @return Graphics2D instance
|
|
*/
|
|
public Graphics2D getGraphics2D(Graphics g) {
|
|
|
|
Graphics2D g2 = (Graphics2D) g;
|
|
RenderingHints rh = new RenderingHints(RenderingHints.KEY_RENDERING,
|
|
RenderingHints.VALUE_RENDER_QUALITY);
|
|
rh.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
|
g2.setRenderingHints(rh);
|
|
return g2;
|
|
|
|
}
|
|
|
|
/**
|
|
* Get coordinates of piece from mouse X and Y location
|
|
* @param x mouse X location
|
|
* @param y mouse Y location
|
|
* @return piece position
|
|
*/
|
|
public PiecePosition getPieceCoordinates(int x, int y) {
|
|
int pieceX = (int) ((x-startX)/(SQUARE_SIZE*boardScale));
|
|
int pieceY = (int) ((y-startY)/(SQUARE_SIZE*boardScale));
|
|
return new PiecePosition(
|
|
Math.min(Math.max(pieceX, 0), SQUARE_COUNT-1),
|
|
Math.min(Math.max(pieceY, 0), SQUARE_COUNT-1)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get piece on specified piece position
|
|
* @param x piece's X location
|
|
* @param y piece's Y location
|
|
* @return piece
|
|
*/
|
|
public APiece getPiece(int x, int y) {
|
|
if(!isOnBoard(x, y)) return null;
|
|
return pieces[y][x];
|
|
}
|
|
|
|
/**
|
|
* Set piece as selected
|
|
* @return piece
|
|
*/
|
|
public void setSelectedPiece(APiece piece) {
|
|
if(piece == null) {
|
|
selected.setOverride(0, 0);
|
|
}
|
|
selected = piece;
|
|
}
|
|
|
|
/**
|
|
* Get current selected piece
|
|
* @return selected piece
|
|
*/
|
|
public APiece getSelectedPiece() {
|
|
return selected;
|
|
}
|
|
|
|
public boolean isEndangered(int x, int y) {
|
|
|
|
APiece piece = getPiece(x, y);
|
|
if(piece == null) return false;
|
|
int[] directions = new int[]{-1, 0, 1};
|
|
for (int i : directions) {
|
|
for (int j : directions) {
|
|
if(i == 0 && j == 0) continue;
|
|
int relX = i, relY = j;
|
|
while(isOnBoard(x+relX, y+relY)) {
|
|
APiece currentPiece = pieces[y+relY][x+relX];
|
|
if(currentPiece != null) {
|
|
if(currentPiece.getPlayer() != piece.getPlayer()) {
|
|
if(currentPiece instanceof Queen) return true;
|
|
if(Math.abs(j) == 1 && Math.abs(i) == 1) {
|
|
if(currentPiece instanceof Bishop) return true;
|
|
} else {
|
|
if(currentPiece instanceof Rook) return true;
|
|
}
|
|
if(relX != 0 && currentPiece instanceof Pawn) {
|
|
StartPosition sp = currentPiece.getPlayer().getStartPosition();
|
|
if(relY == -1 && sp == StartPosition.TOP || relY == 1 && sp == StartPosition.BOTTOM) {
|
|
return true;
|
|
}
|
|
}
|
|
if(relX == i && relY == j && currentPiece instanceof King) {
|
|
return true;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
relX += i;
|
|
relY += j;
|
|
}
|
|
}
|
|
}
|
|
for (int i : new int[]{-1, 1}) {
|
|
for (int j : new int[]{-2, 2}) {
|
|
APiece piece1 = getPiece(x+j, y+i);
|
|
APiece piece2 = getPiece(x+i, y+j);
|
|
if(piece1 instanceof Knight && piece1.getPlayer() != piece.getPlayer() || piece2 instanceof Knight && piece2.getPlayer() != piece.getPlayer()) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
}
|
|
|
|
public void showPossibleMoves(boolean[][] moves) {
|
|
possibleMoves = moves;
|
|
}
|
|
|
|
public void showLastMove(boolean[][] move) {
|
|
lastMove = move;
|
|
}
|
|
|
|
public boolean isOnBoard(int x, int y) {
|
|
return !(y < 0 || y >= SQUARE_COUNT || x < 0 || x >= SQUARE_COUNT);
|
|
}
|
|
|
|
public void setPlayer1(Player player) {
|
|
activePlayer = player;
|
|
player1 = player;
|
|
}
|
|
|
|
public void setPlayer2(Player player) {
|
|
player2 = player;
|
|
}
|
|
|
|
public Player getActivePlayer() {
|
|
return activePlayer;
|
|
}
|
|
|
|
public void setActivePlayer(Player player) {
|
|
activePlayer = player;
|
|
}
|
|
|
|
public Player getOtherPlayer() {
|
|
return activePlayer == player1 ? player2 : player1;
|
|
}
|
|
|
|
public Player getPlayer1() {
|
|
return player1;
|
|
}
|
|
|
|
public Player getPlayer2() {
|
|
return player2;
|
|
}
|
|
|
|
public void checkGame() {
|
|
Player[] players = new Player[]{player1, player2};
|
|
String[] playerNames = new String[]{"White", "Black"};
|
|
for (int i = 0; i < players.length; i++) {
|
|
if(players[i].inCheck()) {
|
|
System.out.println(playerNames[i] + " is in check!");
|
|
if(players[i].hasNoPossibleMove()) {
|
|
String winner = playerNames[(i+1)%2];
|
|
delayedMessage("Checkmate! Congratulations " + winner + ", you have won the game.");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if(!activePlayer.inCheck() && activePlayer.hasNoPossibleMove()) {
|
|
delayedMessage("It's a stalemate! Neither player can make a move without putting their king in checkmate.");
|
|
}
|
|
}
|
|
|
|
public void delayedMessage(String msg) {
|
|
Timer t = new Timer();
|
|
t.schedule(new TimerTask() {
|
|
@Override
|
|
public void run() {
|
|
JOptionPane.showMessageDialog(Chess.chessboard, msg);
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
public void changeActivePlayer() {
|
|
long now = System.currentTimeMillis();
|
|
activePlayer.addDelay((int) (now-lastMoveTime));
|
|
lastMoveTime = now;
|
|
activePlayer = getOtherPlayer();
|
|
repaintRootPane();
|
|
checkGame();
|
|
activePlayer.play();
|
|
}
|
|
|
|
}
|