This repository has been archived on 2022-08-01. You can view files and clone it, but cannot push or open issues or pull requests.
ChessSim/src/ChessBoard.java

421 lines
18 KiB
Java
Executable File

/******************************************************************************
* ChessSim 0.9 *
* Copyright © 2015 Ben Goldsworthy (rumperuu) *
* *
* A program to simulate a game of chess between two human players. *
* *
* This file is part of ChessSim. *
* *
* ChessSim is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* ChessSim is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with ChessSim. If not, see <http://www.gnu.org/licenses/>. *
******************************************************************************/
/**
** This class represents a chess board, utilised in the ChessSim program.
**/
import javax.swing.*;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Color;
import java.awt.event.*;
import java.util.List;
import java.util.ArrayList;
/**
** @author Ben Goldsworthy (rumperuu) <me+chesssim@bengoldsworthy.net>
** @version 0.92
**/
public class ChessBoard implements ActionListener, MouseListener {
private final int DEFAULT = 0, PIECESELECTED = 1;
private final int PAWN = 0, ROOK = 1, KNIGHT = 2, BISHOP = 3, QUEEN = 4,
KING = 5;
private final int WHITE = 0, BLACK = 1;
private final int NONE = 0, MOVABLE = 1, ATTACKABLE = 2;
private final int NULL = 9999;
private boolean colour = true;
// Sets the initial piece (as a BLACK PAWN because the first piece placed
// is `piece++`, or the ROOK).
private int piece = PAWN, team = BLACK;
// Creates the 2D array to hold the `ChessBoard` of `ChessSquare`s.
private ChessSquare[][] chessSquare = new ChessSquare[8][8];
// Creates a 3D array to hold the `ChessBoard` of chess `Piece`s.
private Piece[][][] pieces = new Piece[6][2][8];
private Piece selectedPiece;
// Admittedly, the `ChessLogic` object doesn't do an awful lot right now
// but it'll be useful if I come back to this to add actual game rules
// (https://github.com/Rumperuu/ChessSim/issues/1).
private ChessLogic logic = new ChessLogic();
public ChessBoard() {
// Initialises the piece indices.
int pawnNum = 0, rookNum = 0, knightNum = 0, bishopNum = 0;
// Sorts out the window, fullscreening and suchlike.
JFrame window = new JFrame();
window.setExtendedState(Frame.MAXIMIZED_BOTH);
window.setUndecorated(true);
window.setTitle("Chess, but not as you know it");
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Creates a grid panel for the `ChessBoard` of `ChessSquare`s.
JPanel board = new JPanel();
GridLayout grid = new GridLayout(8,8);
board.setLayout(grid);
// For each rank 'y'...
for (int y = 0; y <= 7; y++) {
// ...switch the populating team to white if the black team's done.
if (y == 2) {
team = WHITE;
pawnNum = 0;
rookNum = 0;
knightNum = 0;
bishopNum = 0;
}
// Reset the square colour (e.g. the last square of row 0 is black,
// meaning the first square of row 1 would be set to white later on
// if not for this line).
colour = !colour;
// Resets the piece to `PAWN`.
piece = 0;
// ...and for each square in file 'x' of rank 'y'...
for (int x = 0; x <= 7; x++) {
// ...populate the board array with a new `ChessSquare`.
chessSquare[x][y] = new ChessSquare(x,y,colour);
// Change the colour for the next square.
colour = !colour;
// Add the `ChessSquare` to the board.
board.add(chessSquare[x][y]);
// Checks if the current rank is the top or bottom two
// (one of the only current uses of the `ChessLogic` class).
if (logic.startingRow(y)) {
// Checks if the current rank is the back rank
// (one of the other uses).
if (logic.backRank(y)) {
// If the square in file 'x' is the Queen or lower, increment
// `piece`...
if (x <= 4) piece++;
// ...else, if the square is the King, subtract 2 (to avoid
// two Queens).
else if (x == 5) piece = piece - 2;
// Otherwise, decrement `piece`.
else piece--;
// Depending on the piece currently being added, populate
// the appropriate array dimension.
switch(piece) {
case ROOK:
pieces[ROOK][team][rookNum] = new Piece(x,y,team,
ROOK,rookNum);
chessSquare[x][y].setPiece(pieces[ROOK][team][rookNum++]);
break;
case KNIGHT:
pieces[KNIGHT][team][knightNum]= new Piece(x,y,team,KNIGHT,
knightNum);
chessSquare[x][y].setPiece(pieces[KNIGHT][team][knightNum++]);
break;
case BISHOP:
pieces[BISHOP][team][bishopNum]= new Piece(x,y,team,BISHOP,
bishopNum);
chessSquare[x][y].setPiece(pieces[BISHOP][team][bishopNum++]);
break;
case QUEEN:
pieces[QUEEN][team][0] = new Piece(x,y,team,QUEEN,0);
chessSquare[x][y].setPiece(pieces[QUEEN][team][0]);
break;
case KING:
pieces[KING][team][0] = new Piece(x,y,team,KING,0);
chessSquare[x][y].setPiece(pieces[KING][team][0]);
break;
}
} else {
pieces[PAWN][team][pawnNum]= new Piece(x,y,team,PAWN,pawnNum);
chessSquare[x][y].setPiece(pieces[PAWN][team][pawnNum++]);
}
}
// Adds the relevant listeners to the `ChessSquare`s.
chessSquare[x][y].addActionListener(this);
chessSquare[x][y].addMouseListener(this);
}
}
// Finishes off the display.
window.setContentPane(board);
window.setVisible(true);
}
/*
* Handles the event of a `JButton` (or rather a `JButton`-extending
* `ChessSquare`) being moused over. As long as no piece is currently
* selected, the function displays faintly-highlighted available moves for
* the piece moused over.
*/
public void mouseEntered(MouseEvent e) {
if (logic.getState() == DEFAULT) {
ChessSquare enteredSquare = (ChessSquare)e.getSource();
Piece presentPiece = null;
if (enteredSquare.hasPiece()) {
presentPiece = enteredSquare.getPiece();
displayMoves(presentPiece, false);
} else defaultSquares();
}
}
/*
* Handle the other `MouseEvent`s, by not doing anything. Mouse dragging
* could potentially be useful in a later version for some sort of click-and-
* drag movement of pieces, but for now they're just here to get the compiler
* to stop throwing up errors at me.
*/
public void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
}
/*
* Handles the event of a `JButton` (or rather a `JButton`-extending
* `ChessSquare`) being clicked. The function, depending on the game state
* and state of the `ChessSquare` clicked on, wipes the displayed moves from
* the `ChessBoard` or moves the selected piece, removing an attacked piece
* is necessary.
*/
public void actionPerformed(ActionEvent e) {
switch(logic.getState()) {
case DEFAULT:
ChessSquare selectedSquare = (ChessSquare) e.getSource();
if (selectedSquare.hasPiece()) {
selectedPiece = selectedSquare.getPiece();
// If no piece is selected, select the clicked piece...
displayMoves(selectedPiece, true);
logic.setState(PIECESELECTED);
}
break;
case PIECESELECTED:
// ...else, if a piece is selected...
ChessSquare clickedSquare = (ChessSquare)e.getSource();
switch(clickedSquare.getState()) {
case NONE:
// ...if the square just clicked is an illegal move, wipe all moves
// from the `ChessBoard`...
defaultSquares();
break;
case MOVABLE:case ATTACKABLE:
// get the coords of the new, clicked square
int newX = clickedSquare.getXPosition();
int newY = clickedSquare.getYPosition();
// and the details of the old, selected piece
int oldX = selectedPiece.getXPosition();
int oldY = selectedPiece.getYPosition();
int oldType = selectedPiece.getType();
int oldTeam = selectedPiece.getTeam();
int oldIndex = selectedPiece.getIndex();
// move (and take the currently-residing piece, if
// applicable) to the square clicked
chessSquare[oldX][oldY].removePiece();
clickedSquare.setPiece(selectedPiece);
// update the location of the moved piece
pieces[oldType][oldTeam][oldIndex].setXPosition(newX);
pieces[oldType][oldTeam][oldIndex].setYPosition(newY);
// if the moved piece was a pawn on its first move, disable its
// two-square move ability
if (oldType == PAWN)
pieces[PAWN][oldTeam][oldIndex].usedUpFirstMove();
// wipes the `ChessBoard` clean
defaultSquares();
}
// resets the state of the game
logic.setState(DEFAULT);
}
}
/*
* Displays the legal moves a selected or hovered-over piece can make,
* coloured according to whether the piece is selected or just hovered on.
*/
private void displayMoves(Piece piece, boolean hard) {
// get the relevant details of the piece now
List moves = new ArrayList();
int pieceIndex = piece.getIndex();
int pieceTeam = piece.getTeam();
int pieceType = piece.getType();
// get the relevant piece's complete moveset
moves = pieces[pieceType][pieceTeam][pieceIndex].showMoves();
// wipe the ChessBoard
defaultSquares();
// initialise the variable used to truncate movement paths due to
// obstacles
boolean skip = false;
boolean pawn = (pieceType == PAWN) ? true : false;
// for the list of moves...
for (int i = 0; i < moves.size()-1; i++) {
// if the current move isn't the NULL character used as a terminator
// between chains of moves (e.g. all in a given direction)...
if ((int)moves.get(i) != NULL) {
// gets the x- and y-coords for later
int moveX = (int)moves.get(i);
int moveY = (int)moves.get(++i);
// sets skip to false, in case i got here from a truncate
skip = false;
// determines if the given ChessSquare is legal or illegal
skip = highlightSquare(moveX, moveY, pieceTeam, pawn, hard);
// if the square is illegal...
if (skip) {
// sets j to i to start from the current point in the movelist
int j = i;
// increment down the list from i until the next NULL
// separator/end of the line
while (((int)moves.get(j) != NULL) && (j < moves.size()-1)) {
j++;
}
// set i to the value after that NULL
i = j++;
}
}
}
// if the given piece is a pawn...
if (pieceType == PAWN) {
// get the coordinate for the square up and to the right of the pawn
// (pX and pY have been used instead of something perhaps more
// informative in order to keep the source code within 80
// characters' width in the for loop below)
int pX = pieces[PAWN][pieceTeam][pieceIndex].getXPosition() + 1;
int pY = pieces[PAWN][pieceTeam][pieceIndex].getYPosition();
// gets the appropriate rank for the direction the pawn is moving
if (pieceTeam == WHITE) pY -= 1;
else pY += 1;
// performs the same task as highlightSquare(), but for the squares
// a pawn can make an attack to
for (int i=0;(pX < 8) && (pX > -1) && (i < 2);pX-=2,i++) {
boolean squareEmpty = false;
Piece presentPiece = null;
if (chessSquare[pX][pY].hasPiece())
presentPiece = chessSquare[pX][pY].getPiece();
else squareEmpty = true;
if (!squareEmpty) {
if ((presentPiece.getTeam() != pieceTeam)
&& (presentPiece.getType() != KING)) {
if (hard) {
chessSquare[pX][pY].setBackground(new Color(255,0,0));
chessSquare[pX][pY].setState(ATTACKABLE);
} else {
chessSquare[pX][pY].setBackground(new Color(127,127,127));
}
}
}
}
}
// if this displaySquares() call is as a result of a piece being
// selected...
if (hard) {
// changes the game state
logic.setState(PIECESELECTED);
}
}
/*
* Wipes the ChessBoard restoring the initial black & white checks.
*/
private void defaultSquares() {
// this is the same code from the constructor earlier
colour = true;
for (int x = 0; x <= 7; x++) {
colour = !colour;
for (int y = 0; y <= 7; y++) {
if (colour) chessSquare[x][y].setBackground(new Color(0, 0, 0));
else chessSquare[x][y].setBackground(new Color(255, 255, 255));
chessSquare[x][y].setState(NONE);
colour = !colour;
}
}
}
/*
* Highlights a given ChessSquare with colours dependent on a number of
* factors.
*/
private boolean highlightSquare(int x, int y, int team,
boolean pawn, boolean selected) {
boolean squareEmpty = false;
// get the piece on the selected square, if applicable
Piece presentPiece = null;
if (chessSquare[x][y].hasPiece())
presentPiece = chessSquare[x][y].getPiece();
else squareEmpty = true;
// if this highlighting is a result of a piece being selected, rather
// than just moused over...
if (selected) {
// if the square is clear...
if (squareEmpty) {
// sets the colour to green, and the state to that of a legal move
chessSquare[x][y].setBackground(new Color(0, 100, 0));
chessSquare[x][y].setState(MOVABLE);
// return that the square doesn't represent an obstacle
return false;
// ...else...
} else {
// ...if the square is occupied by an enemy piece...
if (presentPiece.getTeam() != team) {
// ...and the selected piece isn't a pawn (because their move
// forward can't be used as an attack), and the occupying piece
// isn't the enemy's king (because they can't be taken, only
// put in check)...
if ((!pawn) && (presentPiece.getType() != KING)) {
// sets the colour to red, and the state to that of a legal
// attack
chessSquare[x][y].setBackground(new Color(255, 0, 0));
chessSquare[x][y].setState(ATTACKABLE);
}
}
// return that the square does represent an obstacle
return true;
}
// ...else, if the piece has just been highlighted over, do the same as
// before, but with fainter shades of red and green
} else {
if (squareEmpty) {
chessSquare[x][y].setBackground(new Color(127, 127, 127));
return false;
} else {
if (presentPiece.getTeam() != team) {
if ((pawn)
&& (presentPiece.getType() != KING)) {
// sets the colour to red, and the state to that of a legal
// attack
chessSquare[x][y].setBackground(new Color(127, 0, 0));
}
}
return true;
}
}
}
}