/*
* Copyright (C) 2014 Tim Declercq <caveman1917@hotmail.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 chesstrainer.ui;
import chesstrainer.CPosition;
import chesstrainer.Main;
import chesstrainer.util.EPiece;
import chesstrainer.util.ESide;
import chesstrainer.util.ESquare;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.FileInputStream;
import java.nio.file.Path;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.JPanel;
/**
*
* @author Tim Declercq <caveman1917@hotmail.com>
*/
public class CBoard extends JPanel implements MouseListener, MouseMotionListener {
private CPieceSet pieceset;
private Color darkColor, lightColor, arrowColor, selectedColor;
private ESide perspective = ESide.WHITE;
private CPosition position;
private Set<ESquare> selectedSquares = EnumSet.noneOf(ESquare.class);
private Set<CArrow> arrows = new HashSet<>();
private Set<ESquare> blindfoldSquares = EnumSet.noneOf(ESquare.class);
private boolean dragging = false;
private ESquare draggingSquare = null;
private int mouseX = 0, mouseY = 0;
private Set<IBoardListener> listeners = new HashSet<>();
private boolean animating = false;
private ESquare animateFrom = null, animateTo = null;
private long animateStart, animateEnd;
public CBoard(CPosition position) {
super();
this.position = position;
Properties BOARD_PROPERTIES = new Properties();
Path propFile = Main.DIRECTORY.resolve("config").resolve("board.properties");
try (FileInputStream is = new FileInputStream(propFile.toFile())) {
BOARD_PROPERTIES.load(is);
} catch (Exception e) {
Main.LOG.log(Level.SEVERE, "Cannot load board properties", e);
}
pieceset = new CPieceSet(BOARD_PROPERTIES.getProperty("PieceSet"));
darkColor = Color.decode(BOARD_PROPERTIES.getProperty("DarkColor"));
lightColor = Color.decode(BOARD_PROPERTIES.getProperty("LightColor"));
arrowColor = Color.decode(BOARD_PROPERTIES.getProperty("ArrowColor"));
selectedColor = Color.decode(BOARD_PROPERTIES.getProperty("SelectedColor"));
selectedColor = new Color(selectedColor.getRed(), selectedColor.getGreen(),
selectedColor.getBlue(), 128);
int width = Integer.parseInt(BOARD_PROPERTIES.getProperty("Width"));
int height = Integer.parseInt(BOARD_PROPERTIES.getProperty("Height"));
this.setPreferredSize(new Dimension(width, height));
this.addMouseListener(this);
this.addMouseMotionListener(this);
}
public CBoard() {
this(new CPosition());
}
@Override
public void paintComponent(Graphics g) {
int s = this.getHeight();
if (this.getWidth() < s) {
s = this.getWidth();
}
int size = s / 8;
drawSquares((Graphics2D)g, size);
drawSelectedSquares((Graphics2D)g, size);
drawCoordinates((Graphics2D)g, size);
drawPieces((Graphics2D)g, size);
drawArrows((Graphics2D)g, size);
}
private void drawSquares(Graphics2D g, int size) {
int x, y;
ESide color;
for (ESquare square : ESquare.values()) {
color = square.getColor();
x = getX(square, size);
y = getY(square, size);
if (color == ESide.WHITE) {
g.setColor(lightColor);
} else {
g.setColor(darkColor);
}
g.fillRect(x, y, size, size);
}
}
private void drawSelectedSquares(Graphics2D g, int size) {
g.setColor(selectedColor);
int x, y;
for (ESquare square : selectedSquares) {
x = getX(square, size);
y = getY(square, size);
g.fillRect(x, y, size, size);
}
}
private void drawPieces(Graphics2D g, int size) {
EPiece piece;
int x, y;
for (ESquare square : position.getPosition().keySet()) {
if (blindfoldSquares.contains(square)) {
continue;
}
piece = position.getPosition().get(square);
x = getX(square, size);
y = getY(square, size);
if (dragging == true && square == draggingSquare) {
x = mouseX - (size / 2);
y = mouseY - (size / 2);
}
if (animating == true && square == animateFrom) {
double now = (double)(System.currentTimeMillis() - animateStart);
double total = (double)(animateEnd - animateStart);
double fraction = now / total;
int x1 = x;
int y1 = y;
int x2 = getX(animateTo, size);
int y2 = getY(animateTo, size);
x = x1 + (int)(fraction * (double)(x2 - x1));
y = y1 + (int)(fraction * (double)(y2 - y1));
}
g.drawImage(pieceset.getImage(piece), x, y, size, size, this);
}
}
private void drawCoordinates(Graphics2D g, int size) {
int height = g.getFontMetrics().getHeight();
int width = g.getFontMetrics().stringWidth("8");
String[] ranks, files;
if (perspective == ESide.BLACK) {
files = new String[]{"h", "g", "f", "e", "d", "c", "b", "a"};
ranks = new String[]{"1", "2", "3", "4", "5", "6", "7", "8"};
} else {
files = new String[]{"a", "b", "c", "d", "e", "f", "g", "h"};
ranks = new String[]{"8", "7", "6", "5", "4", "3", "2", "1"};
}
g.setColor(Color.BLACK);
for (int i = 0; i < 8; i++) {
g.drawString(ranks[i], 0, (i * size) + height);
}
for (int i = 0; i < 8; i++) {
g.drawString(files[i], i * size, (8 * size) - g.getFontMetrics().getDescent());
}
}
private void drawArrows(Graphics2D g, int size) {
int middle = size / 2;
int x1, y1, x2, y2;
g.setColor(arrowColor);
for (CArrow arrow : arrows) {
x1 = getX(arrow.getFrom(), size) + middle;
y1 = getY(arrow.getFrom(), size) + middle;
x2 = getX(arrow.getTo(), size) + middle;
y2 = getY(arrow.getTo(), size) + middle;
g.drawLine(x1, y1, x2, y2);
g.fillOval(x2, y2, middle / 3, middle / 3);
}
}
private int getX(ESquare square, int size) {
int file = square.getFile();
if (perspective == ESide.BLACK) {
file = 7 - file;
}
return file * size;
}
private int getY(ESquare square, int size) {
int rank = square.getRank();
if (perspective == ESide.WHITE) {
rank = 7 - rank;
}
return rank * size;
}
private ESquare getSquare(int x, int y) {
int s = this.getHeight();
if (this.getWidth() < s) {
s = this.getWidth();
}
int size = s / 8;
int rank = 7 - (y / size);
int file = x / size;
if (perspective == ESide.BLACK) {
rank = 7 - rank;
file = 7 - file;
}
return ESquare.get(rank, file);
}
public void addBoardListener(IBoardListener listener) {
listeners.add(listener);
}
public void removeBoardListener(IBoardListener listener) {
listeners.remove(listener);
}
@Override
public void mouseClicked(MouseEvent e) {
// nothing, will do by press-release
}
@Override
public void mousePressed(MouseEvent e) {
dragging = true;
mouseX = e.getX();
mouseY = e.getY();
draggingSquare = getSquare(mouseX, mouseY);
repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
dragging = false;
mouseX = e.getX();
mouseY = e.getY();
ESquare sq = getSquare(mouseX, mouseY);
if (draggingSquare == sq) {
for (IBoardListener listener : listeners) {
listener.boardClicked(sq, e.getButton());
}
Main.LOG.log(Level.INFO, "Board clicked: {0}", sq.name());
} else {
for (IBoardListener listener : listeners) {
listener.boardDragged(draggingSquare, sq, e.getButton());
}
Main.LOG.log(Level.INFO, "Board dragged: {0} to {1}", new Object[]{draggingSquare.name(), sq.name()});
}
repaint();
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
dragging = false;
draggingSquare = null;
}
@Override
public void mouseDragged(MouseEvent e) {
mouseX = e.getX();
mouseY = e.getY();
if (dragging) {
repaint();
}
}
@Override
public void mouseMoved(MouseEvent e) {
mouseX = e.getX();
mouseY = e.getY();
if (dragging) {
repaint();
}
}
public void animate(ESquare from, ESquare to, int milliseconds) {
animating = true;
animateFrom = from;
animateTo = to;
animateStart = System.currentTimeMillis();
animateEnd = animateStart + milliseconds;
while (System.currentTimeMillis() < animateEnd) {
repaint();
Thread.yield();
}
animating = false;
}
public Set<ESquare> getSelectedSquares() {
return selectedSquares;
}
public Set<CArrow> getArrows() {
return arrows;
}
public Set<ESquare> getBlindfoldSquares() {
return blindfoldSquares;
}
public CPieceSet getPieceset() {
return pieceset;
}
public void setPieceset(CPieceSet pieceset) {
this.pieceset = pieceset;
}
public Color getDarkColor() {
return darkColor;
}
public void setDarkColor(Color darkColor) {
this.darkColor = darkColor;
}
public Color getLightColor() {
return lightColor;
}
public void setLightColor(Color lightColor) {
this.lightColor = lightColor;
}
public Color getArrowColor() {
return arrowColor;
}
public void setArrowColor(Color arrowColor) {
this.arrowColor = arrowColor;
}
public Color getSelectedColor() {
return selectedColor;
}
public void setSelectedColor(Color selectedColor) {
this.selectedColor = selectedColor;
}
public ESide getPerspective() {
return perspective;
}
public void setPerspective(ESide perspective) {
this.perspective = perspective;
}
public CPosition getPosition() {
return position;
}
public void setPosition(CPosition position) {
this.position = position;
}
}