324 lines
11 KiB
Java
324 lines
11 KiB
Java
import org.jfree.chart.ChartFactory;
|
|
import org.jfree.chart.ChartPanel;
|
|
import org.jfree.chart.JFreeChart;
|
|
import org.jfree.data.category.DefaultCategoryDataset;
|
|
import org.jfree.svg.SVGGraphics2D;
|
|
|
|
import javax.imageio.ImageIO;
|
|
import javax.swing.*;
|
|
import java.awt.*;
|
|
import java.awt.image.BufferedImage;
|
|
import java.io.File;
|
|
import java.io.FileWriter;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
/**
|
|
* Main program layout
|
|
*/
|
|
public class Chess {
|
|
|
|
static JFrame window;
|
|
|
|
static Chessboard chessboard;
|
|
|
|
static JMenuBar menuBar;
|
|
|
|
static final String DEFAULT_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
|
|
|
|
/**
|
|
* Create the main window and chessboard with pieces
|
|
* @param args command-line arguments
|
|
*/
|
|
public static void main(String[] args) {
|
|
|
|
setLookAndFeel();
|
|
|
|
window = new JFrame();
|
|
window.setTitle("Chess");
|
|
window.setSize(800, 600);
|
|
window.setMinimumSize(new Dimension(800, 600));
|
|
|
|
newGame(Chessboard.fromFEN(DEFAULT_FEN));
|
|
|
|
window.pack();
|
|
|
|
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
|
|
window.setLocationRelativeTo(null);
|
|
window.setVisible(true);
|
|
|
|
}
|
|
|
|
public static void setLookAndFeel() {
|
|
try {
|
|
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
|
|
} catch (Exception e) {
|
|
System.out.println("Unable to load system theme!");
|
|
}
|
|
}
|
|
|
|
public static JMenuBar createMenuBar() {
|
|
menuBar = new JMenuBar();
|
|
|
|
menuBar.add(createMenuGame());
|
|
menuBar.add(createMenuTheme());
|
|
menuBar.add(createMenuCPU());
|
|
menuBar.add(createMenuExport());
|
|
|
|
return menuBar;
|
|
}
|
|
|
|
public static JMenu createMenuGame() {
|
|
|
|
JMenu menuGame = new JMenu("Game");
|
|
|
|
JMenuItem startNewGame = new JMenuItem("Start new game");
|
|
startNewGame.addActionListener(l -> {
|
|
int response = JOptionPane.showConfirmDialog(chessboard, "Do you really want to start a new game?");
|
|
if(response == 0) newGame(Chessboard.fromFEN(DEFAULT_FEN));
|
|
});
|
|
menuGame.add(startNewGame);
|
|
|
|
JMenuItem newGameFromFEN = new JMenuItem("Load from FEN");
|
|
newGameFromFEN.addActionListener(l -> {
|
|
String fen = JOptionPane.showInputDialog("Please input FEN: ");
|
|
if(fen == null) return;
|
|
Chessboard c = Chessboard.fromFEN(fen);
|
|
if(c != null) newGame(c);
|
|
else JOptionPane.showMessageDialog(window, "Invalid FEN!");
|
|
});
|
|
menuGame.add(newGameFromFEN);
|
|
|
|
JMenuItem getFEN = new JMenuItem("Get current FEN");
|
|
getFEN.addActionListener(l -> {
|
|
JOptionPane.showInputDialog("Here is current FEN: ", chessboard.toFEN());
|
|
});
|
|
menuGame.add(getFEN);
|
|
|
|
JMenuItem showStats = new JMenuItem("Statistics");
|
|
showStats.addActionListener(l -> {
|
|
showStatistics();
|
|
});
|
|
menuGame.add(showStats);
|
|
|
|
menuGame.add(new JPopupMenu.Separator());
|
|
|
|
JCheckBoxMenuItem toggleBlindMode = new JCheckBoxMenuItem("Blind mode");
|
|
toggleBlindMode.addActionListener(l -> {
|
|
chessboard.blindMode = !chessboard.blindMode;
|
|
toggleBlindMode.setState(chessboard.blindMode);
|
|
chessboard.repaint();
|
|
});
|
|
menuGame.add(toggleBlindMode);
|
|
|
|
return menuGame;
|
|
|
|
}
|
|
|
|
public static JMenu createMenuTheme() {
|
|
|
|
JMenu menuTheme = new JMenu("Theme");
|
|
|
|
Theme[] themes = new Theme[]{Theme.CLASSIC, Theme.GREEN, Theme.YELLOW, Theme.RED};
|
|
String[] themeNames = new String[]{"Classic", "Green", "Yellow", "Red"};
|
|
JRadioButtonMenuItem[] items = new JRadioButtonMenuItem[themes.length];
|
|
|
|
Runnable deselectRadios = () -> {
|
|
for (JRadioButtonMenuItem item : items) {
|
|
item.setSelected(false);
|
|
}
|
|
};
|
|
|
|
for (int i = 0; i < themes.length; i++) {
|
|
items[i] = new JRadioButtonMenuItem(themeNames[i]);
|
|
int themeIndex = i;
|
|
items[i].addActionListener(l -> {
|
|
Theme.setTheme(themes[themeIndex]);
|
|
deselectRadios.run();
|
|
items[themeIndex].setSelected(true);
|
|
chessboard.repaintRootPane();
|
|
});
|
|
menuTheme.add(items[i]);
|
|
if(themes[i] == Theme.activeTheme) items[i].setSelected(true);
|
|
}
|
|
|
|
return menuTheme;
|
|
|
|
}
|
|
|
|
public static JMenu createMenuCPU() {
|
|
|
|
JMenu menuCPU = new JMenu("CPU");
|
|
|
|
JCheckBoxMenuItem whiteDisabled = new JCheckBoxMenuItem("White / Disabled");
|
|
JCheckBoxMenuItem whiteAutomaticRandom = new JCheckBoxMenuItem("White / Automatic random");
|
|
JCheckBoxMenuItem whiteAutomaticSmart = new JCheckBoxMenuItem("White / Automatic smart");
|
|
|
|
JCheckBoxMenuItem blackDisabled = new JCheckBoxMenuItem("Black / Disabled");
|
|
JCheckBoxMenuItem blackAutomaticRandom = new JCheckBoxMenuItem("Black / Automatic random");
|
|
JCheckBoxMenuItem blackAutomaticSmart = new JCheckBoxMenuItem("Black / Automatic smart");
|
|
|
|
Runnable updateWhite = () -> {
|
|
String cpu = chessboard.getPlayer1().getCPU();
|
|
whiteDisabled.setState(cpu == null);
|
|
whiteAutomaticSmart.setState(cpu != null && cpu.equals("smart"));
|
|
whiteAutomaticRandom.setState(cpu != null && cpu.equals("random"));
|
|
};
|
|
|
|
Runnable updateBlack = () -> {
|
|
String cpu = chessboard.getPlayer2().getCPU();
|
|
blackDisabled.setState(cpu == null);
|
|
blackAutomaticSmart.setState(cpu != null && cpu.equals("smart"));
|
|
blackAutomaticRandom.setState(cpu != null && cpu.equals("random"));
|
|
};
|
|
|
|
whiteDisabled.addActionListener(l -> {
|
|
setCPU(chessboard.getPlayer1(), null, updateWhite);
|
|
});
|
|
menuCPU.add(whiteDisabled);
|
|
|
|
whiteAutomaticRandom.addActionListener(l -> {
|
|
setCPU(chessboard.getPlayer1(), "random", updateWhite);
|
|
});
|
|
menuCPU.add(whiteAutomaticRandom);
|
|
|
|
whiteAutomaticSmart.addActionListener(l -> {
|
|
setCPU(chessboard.getPlayer1(), "smart", updateWhite);
|
|
});
|
|
menuCPU.add(whiteAutomaticSmart);
|
|
|
|
menuCPU.add(new JPopupMenu.Separator());
|
|
|
|
blackDisabled.addActionListener(l -> {
|
|
setCPU(chessboard.getPlayer2(), null, updateBlack);
|
|
});
|
|
menuCPU.add(blackDisabled);
|
|
|
|
blackAutomaticRandom.addActionListener(l -> {
|
|
setCPU(chessboard.getPlayer2(), "random", updateBlack);
|
|
});
|
|
menuCPU.add(blackAutomaticRandom);
|
|
|
|
blackAutomaticSmart.addActionListener(l -> {
|
|
setCPU(chessboard.getPlayer2(), "smart", updateBlack);
|
|
});
|
|
menuCPU.add(blackAutomaticSmart);
|
|
|
|
updateWhite.run();
|
|
updateBlack.run();
|
|
|
|
return menuCPU;
|
|
|
|
}
|
|
|
|
public static JMenu createMenuExport() {
|
|
|
|
JMenu menuExport = new JMenu("Export");
|
|
|
|
JMenuItem exportAsSvg = new JMenuItem("Export as SVG");
|
|
exportAsSvg.addActionListener(l -> exportSVG());
|
|
menuExport.add(exportAsSvg);
|
|
|
|
JMenuItem exportAsPng = new JMenuItem("Export as PNG");
|
|
exportAsPng.addActionListener(l -> exportPNG());
|
|
menuExport.add(exportAsPng);
|
|
|
|
return menuExport;
|
|
|
|
}
|
|
|
|
public static void setCPU(Player player, String mode, Runnable update) {
|
|
if(mode != null && mode.equals("smart")) {
|
|
String binary = Stockfish.findBinary();
|
|
if(binary == null) {
|
|
JOptionPane.showMessageDialog(chessboard, "Stockfish engine not found! (stockfishchess.org)\nDownload 'stockfish' or 'stockfish.exe' to the program's folder or the system's path.");
|
|
return;
|
|
}
|
|
if(Stockfish.getInstance() == null) {
|
|
try {
|
|
Stockfish.create(binary);
|
|
} catch (IOException e) {
|
|
JOptionPane.showMessageDialog(chessboard, "Unable to start Stockfish engine!");
|
|
e.printStackTrace();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
player.setCPU(mode);
|
|
update.run();
|
|
}
|
|
|
|
public static void repaintWindow() {
|
|
// Hack to force repaint of the chessboard (repaint is not working)
|
|
int winWidth = window.getWidth();
|
|
int winHeight = window.getHeight();
|
|
window.setSize(winWidth+1, winHeight+1);
|
|
window.setSize(winWidth, winHeight);
|
|
}
|
|
|
|
public static void newGame(Chessboard newChessboard) {
|
|
if(chessboard != null) window.remove(chessboard);
|
|
chessboard = newChessboard;
|
|
Stockfish sf = Stockfish.getInstance();
|
|
if(sf != null) sf.stopEngine();
|
|
window.setJMenuBar(createMenuBar());
|
|
window.add(chessboard, BorderLayout.CENTER);
|
|
repaintWindow();
|
|
}
|
|
|
|
public static void exportSVG() {
|
|
SVGGraphics2D g2 = new SVGGraphics2D(1000, 1000);
|
|
chessboard.paint(g2, 1000, 1000);
|
|
String svg = g2.getSVGElement();
|
|
try {
|
|
FileWriter bw = new FileWriter("chess.svg");
|
|
bw.append(svg);
|
|
bw.close();
|
|
JOptionPane.showMessageDialog(chessboard, "Exported SVG file 'chess.svg' was saved to program's folder.");
|
|
} catch (IOException e) {
|
|
JOptionPane.showMessageDialog(chessboard, "Saving SVG file failed!");
|
|
}
|
|
}
|
|
|
|
public static void exportPNG() {
|
|
BufferedImage image = new BufferedImage(1000, 1000, BufferedImage.TYPE_INT_RGB);
|
|
Graphics2D g = image.createGraphics();
|
|
chessboard.paint(g, 1000, 1000);
|
|
g.dispose();
|
|
try {
|
|
ImageIO.write(image, "png", new File("chess.png"));
|
|
JOptionPane.showMessageDialog(chessboard, "Exported PNG file 'chess.png' was saved to program's folder.");
|
|
} catch (IOException e) {
|
|
JOptionPane.showMessageDialog(chessboard, "Saving PNG file failed!");
|
|
}
|
|
}
|
|
|
|
public static void showStatistics() {
|
|
JFrame window = new JFrame();
|
|
window.setTitle("Statistics");
|
|
|
|
DefaultCategoryDataset dataset = new DefaultCategoryDataset();
|
|
Player[] players = new Player[]{chessboard.getPlayer1(), chessboard.getPlayer2()};
|
|
String[] playerNames = new String[]{"White", "Black"};
|
|
for (int i = 0; i < players.length; i++) {
|
|
List<Integer> delays = players[i].getDelays();
|
|
for (int j = 0; j < delays.size(); j++) {
|
|
dataset.addValue(delays.get(j), playerNames[i], (j+1)+".");
|
|
}
|
|
}
|
|
JFreeChart chart = ChartFactory.createLineChart(
|
|
"Turn speed", "Turns", "Time (ms)",
|
|
dataset
|
|
);
|
|
|
|
ChartPanel panel = new ChartPanel(chart);
|
|
window.add(panel);
|
|
window.pack();
|
|
window.setMinimumSize(new Dimension(500, 300));
|
|
|
|
window.setVisible(true);
|
|
}
|
|
|
|
}
|