/*
CuckooChess - A java chess program.
Copyright (C) 2011 Peter Ă–sterlund, peterosterlund2@gmail.com
This program 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.
This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
*/
package gui;
import chess.Move;
import chess.Piece;
import chess.Position;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.InputStream;
import javax.swing.JLabel;
/** Draws a graphical chess board. Also handles user interaction. */
public class ChessBoardPainter extends JLabel {
private static final long serialVersionUID = -1319250011487017825L;
private Position pos;
private int selectedSquare;
private int x0, y0, sqSize;
private boolean flipped;
private Font chessFont;
// For piece animation during dragging
private int activeSquare;
private boolean dragging;
private int dragX;
private int dragY;
private boolean cancelSelection;
ChessBoardPainter() {
pos = new Position();
selectedSquare = -1;
x0 = y0 = sqSize = 0;
flipped = false;
activeSquare = -1;
}
/**
* Set the board to a given state.
* @param pos
*/
final public void setPosition(Position pos) {
this.pos = pos;
repaint();
}
/**
* Set/clear the board flipped status.
* @param flipped
*/
final public void setFlipped(boolean flipped) {
this.flipped = flipped;
repaint();
}
/**
* Set/clear the selected square.
* @param square The square to select, or -1 to clear selection.
*/
final public void setSelection(int square) {
if (square != this.selectedSquare) {
this.selectedSquare = square;
repaint();
}
}
@Override
public void paint(Graphics g0) {
Graphics2D g = (Graphics2D)g0;
Dimension size = getSize();
sqSize = (Math.min(size.width, size.height) - 4) / 8;
x0 = (size.width - sqSize * 8) / 2;
y0 = (size.height - sqSize * 8) / 2;
boolean doDrag = (activeSquare >= 0) && dragging;
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
final int xCrd = getXCrd(x);
final int yCrd = getYCrd(y);
g.setColor(Position.darkSquare(x, y) ? Color.GRAY : new Color(190, 190, 90));
g.fillRect(xCrd, yCrd, sqSize, sqSize);
int sq = Position.getSquare(x, y);
int p = pos.getPiece(sq);
if (doDrag && (sq == activeSquare)) {
// Skip this piece. It will be drawn later at (dragX,dragY)
} else {
drawPiece(g, xCrd + sqSize / 2, yCrd + sqSize / 2, p);
}
}
}
if (selectedSquare >= 0) {
int selX = Position.getX(selectedSquare);
int selY = Position.getY(selectedSquare);
g.setColor(Color.RED);
g.setStroke(new BasicStroke(3));
g.drawRect(getXCrd(selX), getYCrd(selY), sqSize, sqSize);
}
if (doDrag) {
int p = pos.getPiece(activeSquare);
drawPiece(g, dragX, dragY, p);
}
}
private final void drawPiece(Graphics2D g, int xCrd, int yCrd, int p) {
g.setColor(Piece.isWhite(p) ? Color.WHITE : Color.BLACK);
String ps;
switch (p) {
case Piece.EMPTY:
ps = "";
break;
case Piece.WKING:
ps = "k";
break;
case Piece.WQUEEN:
ps = "q";
break;
case Piece.WROOK:
ps = "r";
break;
case Piece.WBISHOP:
ps = "b";
break;
case Piece.WKNIGHT:
ps = "n";
break;
case Piece.WPAWN:
ps = "p";
break;
case Piece.BKING:
ps = "l";
break;
case Piece.BQUEEN:
ps = "w";
break;
case Piece.BROOK:
ps = "t";
break;
case Piece.BBISHOP:
ps = "v";
break;
case Piece.BKNIGHT:
ps = "m";
break;
case Piece.BPAWN:
ps = "o";
break;
default:
ps = "?";
break;
}
if (ps.length() > 0) {
FontRenderContext frc = g.getFontRenderContext();
if ((chessFont == null) || (chessFont.getSize() != sqSize)) {
InputStream inStream = getClass().getResourceAsStream("/gui/casefont.ttf");
try {
Font font = Font.createFont(Font.TRUETYPE_FONT, inStream);
chessFont = font.deriveFont((float)sqSize);
} catch (FontFormatException ex) {
throw new RuntimeException();
} catch (IOException ex) {
throw new RuntimeException();
}
}
g.setFont(chessFont);
Rectangle2D textRect = g.getFont().getStringBounds(ps, frc);
int xCent = (int)textRect.getCenterX();
int yCent = (int)textRect.getCenterY();
g.drawString(ps, xCrd - xCent, yCrd - yCent);
}
}
private final int getXCrd(int x) {
return x0 + sqSize * (flipped ? 7 - x : x);
}
private final int getYCrd(int y) {
return y0 + sqSize * (flipped ? y : (7 - y));
}
/**
* Compute the square corresponding to the coordinates of a mouse event.
* @param evt Details about the mouse event.
* @return The square corresponding to the mouse event, or -1 if outside board.
*/
final int eventToSquare(MouseEvent evt) {
int xCrd = evt.getX();
int yCrd = evt.getY();
int sq = -1;
if ((xCrd >= x0) && (yCrd >= y0) && (sqSize > 0)) {
int x = (xCrd - x0) / sqSize;
int y = 7 - (yCrd - y0) / sqSize;
if ((x >= 0) && (x < 8) && (y >= 0) && (y < 8)) {
if (flipped) {
x = 7 - x;
y = 7 - y;
}
sq = Position.getSquare(x, y);
}
}
return sq;
}
final Move mousePressed(int sq) {
Move m = null;
cancelSelection = false;
int p = pos.getPiece(sq);
if ((selectedSquare >= 0) && (sq == selectedSquare)) {
int fromPiece = pos.getPiece(selectedSquare);
if ((fromPiece == Piece.EMPTY) || (Piece.isWhite(fromPiece) != pos.whiteMove)) {
return m; // Can't move the piece the oppenent just moved.
}
}
if ((selectedSquare < 0) &&
((p == Piece.EMPTY) || (Piece.isWhite(p) != pos.whiteMove))) {
return m; // You must click on one of your own pieces.
}
activeSquare = sq;
dragging = false;
dragX = dragY = -1;
if (selectedSquare >= 0) {
if (sq == selectedSquare) {
cancelSelection = true;
} else {
if ((p == Piece.EMPTY) || (Piece.isWhite(p) != pos.whiteMove)) {
m = new Move(selectedSquare, sq, Piece.EMPTY);
activeSquare = -1;
setSelection(sq);
}
}
}
if (m == null) {
setSelection(-1);
}
return m;
}
final void mouseDragged(MouseEvent evt) {
final int xCrd = evt.getX();
final int yCrd = evt.getY();
if (!dragging || (dragX != xCrd) || (dragY != yCrd)) {
dragging = true;
dragX = xCrd;
dragY = yCrd;
repaint();
}
}
final Move mouseReleased(int sq) {
Move m = null;
if (activeSquare >= 0) {
if (sq != activeSquare) {
m = new Move(activeSquare, sq, Piece.EMPTY);
setSelection(sq);
} else if (!cancelSelection) {
setSelection(sq);
}
activeSquare = -1;
repaint();
}
return m;
}
}