Chess/src/Chessboard.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();
}
}