Chess/src/Chess.java

406 lines
13 KiB
Java

import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartPanel;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.renderer.category.BarRenderer;
import org.jfree.chart.renderer.category.CategoryItemRenderer;
import org.jfree.chart.renderer.category.StandardBarPainter;
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.text.NumberFormat;
import java.util.List;
/**
* Main program layout
*/
public class Chess {
/**
* Window frame
*/
static JFrame window;
/**
* Chessboard instance
*/
static Chessboard chessboard;
/**
* Menu bar instance
*/
static JMenuBar menuBar;
/**
* Default chessboard FEN
*/
static final String DEFAULT_FEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
/**
* Create the main window and the chessboard
* @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);
}
/**
* Try to inherit design from the OS
*/
public static void setLookAndFeel() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception e) {
System.out.println("Unable to load system theme!");
}
}
/**
* Create window's menu bar
* @return menu bar
*/
public static JMenuBar createMenuBar() {
menuBar = new JMenuBar();
menuBar.add(createMenuGame());
menuBar.add(createMenuTheme());
menuBar.add(createMenuCPU());
menuBar.add(createMenuExport());
return menuBar;
}
/**
* Create Game menu
* @return Game menu
*/
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;
}
/**
* Create Theme menu
* @return Theme menu
*/
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;
}
/**
* Create CPU menu
* @return CPU menu
*/
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 = () -> {
CPUMode cpu = chessboard.getPlayer1().getCPU();
whiteDisabled.setState(cpu == null);
whiteAutomaticSmart.setState(cpu == CPUMode.SMART);
whiteAutomaticRandom.setState(cpu == CPUMode.RANDOM);
};
Runnable updateBlack = () -> {
CPUMode cpu = chessboard.getPlayer2().getCPU();
blackDisabled.setState(cpu == null);
blackAutomaticSmart.setState(cpu == CPUMode.SMART);
blackAutomaticRandom.setState(cpu == CPUMode.RANDOM);
};
whiteDisabled.addActionListener(l -> {
setCPU(chessboard.getPlayer1(), null);
updateWhite.run();
});
menuCPU.add(whiteDisabled);
whiteAutomaticRandom.addActionListener(l -> {
setCPU(chessboard.getPlayer1(), CPUMode.RANDOM);
updateWhite.run();
});
menuCPU.add(whiteAutomaticRandom);
whiteAutomaticSmart.addActionListener(l -> {
setCPU(chessboard.getPlayer1(), CPUMode.SMART);
updateWhite.run();
});
menuCPU.add(whiteAutomaticSmart);
menuCPU.add(new JPopupMenu.Separator());
blackDisabled.addActionListener(l -> {
setCPU(chessboard.getPlayer2(), null);
updateBlack.run();
});
menuCPU.add(blackDisabled);
blackAutomaticRandom.addActionListener(l -> {
setCPU(chessboard.getPlayer2(), CPUMode.RANDOM);
updateBlack.run();
});
menuCPU.add(blackAutomaticRandom);
blackAutomaticSmart.addActionListener(l -> {
setCPU(chessboard.getPlayer2(), CPUMode.SMART);
updateBlack.run();
});
menuCPU.add(blackAutomaticSmart);
updateWhite.run();
updateBlack.run();
return menuCPU;
}
/**
* Create Export menu
* @return Export menu
*/
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;
}
/**
* Set player's CPU mode
* @param player selected player
* @param mode CPU mode or null
*/
public static void setCPU(Player player, CPUMode mode) {
if(mode == CPUMode.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);
}
/**
* Repaint entire window
*/
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);
}
/**
* Starts a new game with specified chessboard
* @param newChessboard chessboard
*/
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();
}
/**
* Export current chessboard to SVG file
*/
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!");
}
}
/**
* Export current chessboard to PNG file
*/
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!");
}
}
/**
* Show window with statistics
*/
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)/1000.0, playerNames[i], (j+1)+".");
}
}
JFreeChart chart = ChartFactory.createBarChart(
"Turn speed", "Turns", "Time (s)",
dataset
);
CategoryPlot plot = chart.getCategoryPlot();
plot.setBackgroundPaint(Color.LIGHT_GRAY);
plot.setRangeGridlinePaint(Color.GRAY);
CategoryItemRenderer renderer = plot.getRenderer();
renderer.setDefaultItemLabelGenerator(
new StandardCategoryItemLabelGenerator("{2}s", NumberFormat.getInstance())
);
renderer.setDefaultItemLabelFont(new Font("Arial", Font.PLAIN, 9));
renderer.setDefaultItemLabelsVisible(true);
BarRenderer br = (BarRenderer) renderer;
br.setItemMargin(0.05);
br.setBarPainter(new StandardBarPainter());
br.setSeriesPaint(0, Color.WHITE);
br.setSeriesPaint(1, Color.BLACK);
chart.removeLegend();
ChartPanel panel = new ChartPanel(chart);
window.add(panel);
window.pack();
window.setMinimumSize(new Dimension(500, 300));
window.setVisible(true);
}
}