diff --git a/src/main/java/featurecat/lizzie/Config.java b/src/main/java/featurecat/lizzie/Config.java
index cee1c529b..8cf48c50a 100644
--- a/src/main/java/featurecat/lizzie/Config.java
+++ b/src/main/java/featurecat/lizzie/Config.java
@@ -1,17 +1,31 @@
package featurecat.lizzie;
import featurecat.lizzie.theme.Theme;
+import featurecat.lizzie.util.WindowPosition;
import java.awt.Color;
-import java.io.*;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
import java.nio.file.Files;
import java.nio.file.Paths;
-import java.util.*;
-import javax.swing.*;
-import org.json.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Optional;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.json.JSONTokener;
public class Config {
public String language = "en";
+ public boolean panelUI = false;
public boolean showBorder = false;
public boolean showMoveNumber = false;
public int onlyLastMoveNumber = 0;
@@ -160,6 +174,7 @@ public Config() throws IOException {
theme = new Theme(uiConfig);
+ panelUI = uiConfig.optBoolean("panel-ui", false);
showBorder = uiConfig.optBoolean("show-border", false);
showMoveNumber = uiConfig.getBoolean("show-move-number");
onlyLastMoveNumber = uiConfig.optInt("only-last-move-number");
@@ -185,7 +200,7 @@ public Config() throws IOException {
showCoordinates = uiConfig.optBoolean("show-coordinates");
replayBranchIntervalSeconds = uiConfig.optDouble("replay-branch-interval-seconds", 1.0);
colorByWinrateInsteadOfVisits = uiConfig.optBoolean("color-by-winrate-instead-of-visits");
- boardPositionProportion = uiConfig.optInt("board-postion-proportion", 4);
+ boardPositionProportion = uiConfig.optInt("board-position-proportion", 4);
winrateStrokeWidth = theme.winrateStrokeWidth();
minimumBlunderBarWidth = theme.minimumBlunderBarWidth();
shadowSize = theme.shadowSize();
@@ -414,6 +429,7 @@ private JSONObject createDefaultConfig() {
ui.put("append-winrate-to-comment", false);
ui.put("replay-branch-interval-seconds", 1.0);
ui.put("gtp-console-style", defaultGtpConsoleStyle);
+ ui.put("panel-ui", false);
config.put("ui", ui);
return config;
}
@@ -437,10 +453,8 @@ private JSONObject createPersistConfig() {
// ui.put("window-width", 687);
// ui.put("max-alpha", 240);
- // Main Window Position & Size
- ui.put("main-window-position", new JSONArray("[]"));
- ui.put("gtp-console-position", new JSONArray("[]"));
- ui.put("window-maximized", false);
+ // Window Position & Size
+ ui = WindowPosition.create(ui);
config.put("filesystem", filesys);
@@ -464,24 +478,10 @@ private void writeConfig(JSONObject config, File file) throws IOException, JSONE
}
public void persist() throws IOException {
- boolean windowIsMaximized = Lizzie.frame.getExtendedState() == JFrame.MAXIMIZED_BOTH;
-
- JSONArray mainPos = new JSONArray();
- if (!windowIsMaximized) {
- mainPos.put(Lizzie.frame.getX());
- mainPos.put(Lizzie.frame.getY());
- mainPos.put(Lizzie.frame.getWidth());
- mainPos.put(Lizzie.frame.getHeight());
- }
- persistedUi.put("main-window-position", mainPos);
- JSONArray gtpPos = new JSONArray();
- gtpPos.put(Lizzie.gtpConsole.getX());
- gtpPos.put(Lizzie.gtpConsole.getY());
- gtpPos.put(Lizzie.gtpConsole.getWidth());
- gtpPos.put(Lizzie.gtpConsole.getHeight());
- persistedUi.put("gtp-console-position", gtpPos);
- persistedUi.put("board-postion-propotion", Lizzie.frame.BoardPositionProportion);
- persistedUi.put("window-maximized", windowIsMaximized);
+
+ // Save the window position
+ persistedUi = WindowPosition.save(persistedUi);
+
writeConfig(this.persisted, new File(persistFilename));
}
diff --git a/src/main/java/featurecat/lizzie/Lizzie.java b/src/main/java/featurecat/lizzie/Lizzie.java
index b0306564d..478cfcc50 100644
--- a/src/main/java/featurecat/lizzie/Lizzie.java
+++ b/src/main/java/featurecat/lizzie/Lizzie.java
@@ -3,18 +3,22 @@
import featurecat.lizzie.analysis.Leelaz;
import featurecat.lizzie.gui.GtpConsolePane;
import featurecat.lizzie.gui.LizzieFrame;
+import featurecat.lizzie.gui.LizzieMain;
+import featurecat.lizzie.gui.MainFrame;
import featurecat.lizzie.rules.Board;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
-import javax.swing.*;
+import javax.swing.JOptionPane;
+import javax.swing.UIManager;
+import javax.swing.UnsupportedLookAndFeelException;
import org.json.JSONArray;
/** Main class. */
public class Lizzie {
public static Config config;
+ public static MainFrame frame;
public static GtpConsolePane gtpConsole;
- public static LizzieFrame frame;
public static Board board;
public static Leelaz leelaz;
public static String lizzieVersion = "0.7";
@@ -26,7 +30,7 @@ public static void main(String[] args) throws IOException {
mainArgs = args;
config = new Config();
board = new Board();
- frame = new LizzieFrame();
+ frame = config.panelUI ? new LizzieMain() : new LizzieFrame();
gtpConsole = new GtpConsolePane(frame);
gtpConsole.setVisible(config.leelazConfig.optBoolean("print-comms", false));
try {
@@ -67,7 +71,7 @@ public static void shutdown() {
JOptionPane.showConfirmDialog(
null, "Do you want to save this SGF?", "Save SGF?", JOptionPane.OK_CANCEL_OPTION);
if (ret == JOptionPane.OK_OPTION) {
- LizzieFrame.saveFile();
+ frame.saveFile();
}
}
board.autosaveToMemory();
diff --git a/src/main/java/featurecat/lizzie/analysis/Leelaz.java b/src/main/java/featurecat/lizzie/analysis/Leelaz.java
index caaf956ab..6c2426f01 100644
--- a/src/main/java/featurecat/lizzie/analysis/Leelaz.java
+++ b/src/main/java/featurecat/lizzie/analysis/Leelaz.java
@@ -117,6 +117,7 @@ public void startEngine(String engineCommand) throws IOException {
return;
}
+ isLoaded = false;
commands = splitCommand(engineCommand);
// Get weight name
@@ -247,6 +248,9 @@ private void parseLine(String line) {
} else if (line.equals("\n")) {
// End of response
} else if (line.startsWith("info")) {
+ if (!isLoaded) {
+ Lizzie.frame.refresh();
+ }
isLoaded = true;
// Clear switching prompt
switching = false;
@@ -256,7 +260,7 @@ private void parseLine(String line) {
// This should not be stale data when the command number match
this.bestMoves = parseInfo(line.substring(5));
notifyBestMoveListeners();
- Lizzie.frame.repaint();
+ Lizzie.frame.refresh(1);
// don't follow the maxAnalyzeTime rule if we are in analysis mode
if (System.currentTimeMillis() - startPonderTime > maxAnalyzeTimeMillis
&& !Lizzie.board.inAnalysisMode()) {
@@ -264,13 +268,16 @@ private void parseLine(String line) {
}
}
} else if (line.contains(" -> ")) {
+ if (!isLoaded) {
+ Lizzie.frame.refresh();
+ }
isLoaded = true;
if (isResponseUpToDate()
|| isThinking
&& (!isPondering && Lizzie.frame.isPlayingAgainstLeelaz || isInputCommand)) {
bestMoves.add(MoveData.fromSummary(line));
notifyBestMoveListeners();
- Lizzie.frame.repaint();
+ Lizzie.frame.refresh(1);
}
} else if (line.startsWith("play")) {
// In lz-genmove_analyze
@@ -302,7 +309,8 @@ private void parseLine(String line) {
} else if (isThinking && !isPondering) {
if (Lizzie.frame.isPlayingAgainstLeelaz || isInputCommand) {
Lizzie.board.place(params[1]);
- togglePonder();
+ // TODO Do not ponder when playing against Leela Zero
+ // togglePonder();
if (!isInputCommand) {
isPondering = false;
}
@@ -528,6 +536,7 @@ public void togglePonder() {
} else {
sendCommand("name"); // ends pondering
}
+ Lizzie.frame.updateBasicInfo();
}
/** End the process */
diff --git a/src/main/java/featurecat/lizzie/gui/BasicInfoPane.java b/src/main/java/featurecat/lizzie/gui/BasicInfoPane.java
new file mode 100644
index 000000000..0eab1c084
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/gui/BasicInfoPane.java
@@ -0,0 +1,166 @@
+package featurecat.lizzie.gui;
+
+import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
+
+import featurecat.lizzie.Lizzie;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.image.BufferedImage;
+
+/** The window used to display the game. */
+public class BasicInfoPane extends LizziePane {
+
+ private LizzieMain owner;
+
+ public BasicInfoPane(LizzieMain owner) {
+ super(owner);
+ this.owner = owner;
+ setVisible(true);
+ }
+
+ private BufferedImage cachedImage;
+
+ /**
+ * Draws the game board and interface
+ *
+ * @param g0 not used
+ */
+ @Override
+ protected void paintComponent(Graphics g0) {
+ super.paintComponent(g0);
+
+ int x = 0; // getX();
+ int y = 0; // getY();
+ int width = getWidth();
+ int height = getHeight();
+
+ // initialize
+
+ cachedImage = new BufferedImage(width, height, TYPE_INT_ARGB);
+ Graphics2D g = (Graphics2D) cachedImage.getGraphics();
+ g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ if (Lizzie.config.showCaptured) {
+ if (owner == null) {
+ g.drawImage(owner.getBasicInfoContainer(this), x, y, null);
+ } else {
+ g.drawImage(owner.getBasicInfoContainer(this), x, y, null);
+ }
+ drawCaptured(g, x, y, width, height);
+ }
+
+ // cleanup
+ g.dispose();
+
+ // draw the image
+ Graphics2D bsGraphics = (Graphics2D) g0; // bs.getDrawGraphics();
+ bsGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ bsGraphics.drawImage(cachedImage, 0, 0, null);
+
+ // cleanup
+ bsGraphics.dispose();
+ // bs.show();
+ }
+
+ private void drawCaptured(Graphics2D g, int posX, int posY, int width, int height) {
+ // Draw border
+ g.setColor(new Color(0, 0, 0, 130));
+ g.fillRect(posX, posY, width, height);
+
+ // border. does not include bottom edge
+ int strokeRadius = Lizzie.config.showBorder ? 3 : 1;
+ g.setStroke(new BasicStroke(strokeRadius == 1 ? strokeRadius : 2 * strokeRadius));
+ if (Lizzie.config.showBorder) {
+ g.drawLine(
+ posX + strokeRadius,
+ posY + strokeRadius,
+ posX - strokeRadius + width,
+ posY + strokeRadius);
+ g.drawLine(
+ posX + strokeRadius,
+ posY + 3 * strokeRadius,
+ posX + strokeRadius,
+ posY - strokeRadius + height);
+ g.drawLine(
+ posX - strokeRadius + width,
+ posY + 3 * strokeRadius,
+ posX - strokeRadius + width,
+ posY - strokeRadius + height);
+ }
+
+ // Draw middle line
+ g.drawLine(
+ posX - strokeRadius + width / 2,
+ posY + 3 * strokeRadius,
+ posX - strokeRadius + width / 2,
+ posY - strokeRadius + height);
+ g.setColor(Color.white);
+
+ // Draw black and white "stone"
+ int diam = height / 3;
+ int smallDiam = diam / 2;
+ int bdiam = diam, wdiam = diam;
+ if (Lizzie.board != null) {
+ if (Lizzie.board.inScoreMode()) {
+ // do nothing
+ } else if (Lizzie.board.getHistory().isBlacksTurn()) {
+ wdiam = smallDiam;
+ } else {
+ bdiam = smallDiam;
+ }
+ } else {
+ bdiam = smallDiam;
+ }
+ g.setColor(Color.black);
+ g.fillOval(
+ posX + width / 4 - bdiam / 2, posY + height * 3 / 8 + (diam - bdiam) / 2, bdiam, bdiam);
+
+ g.setColor(Color.WHITE);
+ g.fillOval(
+ posX + width * 3 / 4 - wdiam / 2, posY + height * 3 / 8 + (diam - wdiam) / 2, wdiam, wdiam);
+
+ // Draw captures
+ String bval, wval;
+ setPanelFont(g, (float) (height * 0.18));
+ if (Lizzie.board == null) {
+ return;
+ }
+ if (Lizzie.board.inScoreMode()) {
+ double score[] = Lizzie.board.getScore(Lizzie.board.scoreStones());
+ bval = String.format("%.0f", score[0]);
+ wval = String.format("%.1f", score[1]);
+ } else {
+ bval = String.format("%d", Lizzie.board.getData().blackCaptures);
+ wval = String.format("%d", Lizzie.board.getData().whiteCaptures);
+ }
+
+ g.setColor(Color.WHITE);
+ int bw = g.getFontMetrics().stringWidth(bval);
+ int ww = g.getFontMetrics().stringWidth(wval);
+ boolean largeSubBoard = Lizzie.config.showLargeSubBoard();
+ int bx = (largeSubBoard ? diam : -bw / 2);
+ int wx = (largeSubBoard ? bx : -ww / 2);
+
+ g.drawString(bval, posX + width / 4 + bx, posY + height * 7 / 8);
+ g.drawString(wval, posX + width * 3 / 4 + wx, posY + height * 7 / 8);
+
+ // Status Indicator
+ int statusDiam = height / 8;
+ g.setColor((Lizzie.leelaz != null && Lizzie.leelaz.isPondering()) ? Color.GREEN : Color.RED);
+ g.fillOval(
+ posX - strokeRadius + width / 2 - statusDiam / 2,
+ posY + height * 3 / 8 + (diam - statusDiam) / 2,
+ statusDiam,
+ statusDiam);
+ }
+
+ private void setPanelFont(Graphics2D g, float size) {
+ Font font = new Font(Lizzie.config.fontName, Font.PLAIN, (int) size);
+ g.setFont(font);
+ }
+}
diff --git a/src/main/java/featurecat/lizzie/gui/BasicLizziePaneUI.java b/src/main/java/featurecat/lizzie/gui/BasicLizziePaneUI.java
new file mode 100644
index 000000000..683bb21bc
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/gui/BasicLizziePaneUI.java
@@ -0,0 +1,679 @@
+package featurecat.lizzie.gui;
+
+import featurecat.lizzie.Lizzie;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dialog;
+import java.awt.Dimension;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.IllegalComponentStateException;
+import java.awt.Insets;
+import java.awt.LayoutManager;
+import java.awt.Point;
+import java.awt.Window;
+import java.awt.event.ContainerEvent;
+import java.awt.event.ContainerListener;
+import java.awt.event.FocusEvent;
+import java.awt.event.FocusListener;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import javax.swing.InputMap;
+import javax.swing.JComponent;
+import javax.swing.JDialog;
+import javax.swing.JRootPane;
+import javax.swing.KeyStroke;
+import javax.swing.LookAndFeel;
+import javax.swing.RootPaneContainer;
+import javax.swing.SwingConstants;
+import javax.swing.SwingUtilities;
+import javax.swing.UIManager;
+import javax.swing.event.MouseInputListener;
+import javax.swing.plaf.ComponentUI;
+
+public class BasicLizziePaneUI extends LizziePaneUI implements SwingConstants {
+ protected LizziePane lizziePane;
+ private boolean floating;
+ private int floatingX;
+ private int floatingY;
+ private RootPaneContainer floatingLizziePane;
+ protected DragWindow dragWindow;
+ private Container dockingSource;
+ protected int focusedCompIndex = -1;
+ private Dimension originSize = null;
+
+ protected MouseInputListener dockingListener;
+ protected PropertyChangeListener propertyListener;
+
+ protected ContainerListener lizziePaneContListener;
+ protected FocusListener lizziePaneFocusListener;
+ private Handler handler;
+
+ protected String constraintBeforeFloating;
+
+ private static String FOCUSED_COMP_INDEX = "LizziePane.focusedCompIndex";
+
+ public static ComponentUI createUI(JComponent c) {
+ return new BasicLizziePaneUI();
+ }
+
+ public void installUI(JComponent c) {
+ lizziePane = (LizziePane) c;
+
+ // Set defaults
+ installDefaults();
+ installComponents();
+ // Default disabled drag
+ // installListeners();
+ // installKeyboardActions();
+
+ // Initialize instance vars
+ floating = false;
+ floatingX = floatingY = 0;
+ floatingLizziePane = null;
+
+ LookAndFeel.installProperty(c, "opaque", Boolean.TRUE);
+
+ if (c.getClientProperty(FOCUSED_COMP_INDEX) != null) {
+ focusedCompIndex = ((Integer) (c.getClientProperty(FOCUSED_COMP_INDEX))).intValue();
+ }
+ }
+
+ public void uninstallUI(JComponent c) {
+
+ // Clear defaults
+ uninstallDefaults();
+ uninstallComponents();
+ uninstallListeners();
+ // uninstallKeyboardActions();
+
+ // Clear instance vars
+ if (isFloating()) setFloating(false, null);
+
+ floatingLizziePane = null;
+ dragWindow = null;
+ dockingSource = null;
+
+ c.putClientProperty(FOCUSED_COMP_INDEX, Integer.valueOf(focusedCompIndex));
+ }
+
+ protected void installDefaults() {
+ LookAndFeel.installBorder(lizziePane, "LizziePane.border");
+ LookAndFeel.installColorsAndFont(
+ lizziePane, "LizziePane.background", "LizziePane.foreground", "LizziePane.font");
+ }
+
+ protected void uninstallDefaults() {
+ LookAndFeel.uninstallBorder(lizziePane);
+ }
+
+ protected void installComponents() {}
+
+ protected void uninstallComponents() {}
+
+ public void installListeners() {
+ dockingListener = createDockingListener();
+
+ if (dockingListener != null) {
+ lizziePane.addMouseMotionListener(dockingListener);
+ lizziePane.addMouseListener(dockingListener);
+ }
+
+ propertyListener = createPropertyListener(); // added in setFloating
+ if (propertyListener != null) {
+ lizziePane.addPropertyChangeListener(propertyListener);
+ }
+
+ lizziePaneContListener = createLizziePaneContListener();
+ if (lizziePaneContListener != null) {
+ lizziePane.addContainerListener(lizziePaneContListener);
+ }
+
+ lizziePaneFocusListener = createLizziePaneFocusListener();
+
+ if (lizziePaneFocusListener != null) {
+ // Put focus listener on all components in lizziePane
+ Component[] components = lizziePane.getComponents();
+
+ for (Component component : components) {
+ component.addFocusListener(lizziePaneFocusListener);
+ }
+ }
+ }
+
+ public void uninstallListeners() {
+ if (dockingListener != null) {
+ lizziePane.removeMouseMotionListener(dockingListener);
+ lizziePane.removeMouseListener(dockingListener);
+
+ dockingListener = null;
+ }
+
+ if (propertyListener != null) {
+ lizziePane.removePropertyChangeListener(propertyListener);
+ propertyListener = null; // removed in setFloating
+ }
+
+ if (lizziePaneContListener != null) {
+ lizziePane.removeContainerListener(lizziePaneContListener);
+ lizziePaneContListener = null;
+ }
+
+ if (lizziePaneFocusListener != null) {
+ // Remove focus listener from all components in lizziePane
+ Component[] components = lizziePane.getComponents();
+
+ for (Component component : components) {
+ component.removeFocusListener(lizziePaneFocusListener);
+ }
+
+ lizziePaneFocusListener = null;
+ }
+ handler = null;
+ }
+
+ protected void installKeyboardActions() {
+ InputMap km = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
+
+ SwingUtilities.replaceUIInputMap(lizziePane, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, km);
+ }
+
+ InputMap getInputMap(int condition) {
+ if (condition == JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT) {
+ return (InputMap) UIManager.get("LizziePane.ancestorInputMap", lizziePane.getLocale());
+ }
+ return null;
+ }
+
+ protected void uninstallKeyboardActions() {
+ SwingUtilities.replaceUIActionMap(lizziePane, null);
+ SwingUtilities.replaceUIInputMap(
+ lizziePane, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
+ }
+
+ /**
+ * Creates a window which contains the lizziePane after it has been dragged out from its container
+ *
+ * @return a RootPaneContainer
object, containing the lizziePane.
+ */
+ protected RootPaneContainer createFloatingWindow(LizziePane lizziePane) {
+ class LizziePaneDialog extends JDialog {
+ public LizziePaneDialog(Frame owner, String title, boolean modal) {
+ super(owner, title, modal);
+ }
+
+ public LizziePaneDialog(Dialog owner, String title, boolean modal) {
+ super(owner, title, modal);
+ }
+
+ protected JRootPane createRootPane() {
+ JRootPane rootPane = new JRootPane();
+ rootPane.setOpaque(false);
+
+ rootPane.registerKeyboardAction(
+ e -> {
+ if (Lizzie.frame.isDesignMode()) {
+ Lizzie.frame.toggleDesignMode();
+ }
+ },
+ KeyStroke.getKeyStroke(KeyEvent.VK_W, KeyEvent.ALT_DOWN_MASK),
+ JComponent.WHEN_IN_FOCUSED_WINDOW);
+ return rootPane;
+ }
+ }
+
+ JDialog dialog;
+ Window window = SwingUtilities.getWindowAncestor(lizziePane);
+ if (window instanceof Frame) {
+ dialog = new LizziePaneDialog((Frame) window, lizziePane.getName(), false);
+ } else if (window instanceof Dialog) {
+ dialog = new LizziePaneDialog((Dialog) window, lizziePane.getName(), false);
+ } else {
+ dialog = new LizziePaneDialog((Frame) null, lizziePane.getName(), false);
+ }
+
+ dialog.getRootPane().setName("LizziePane.FloatingWindow");
+ dialog.setTitle(lizziePane.getName());
+ dialog.setResizable(true);
+ // dialog.setSize(lizziePane.getSize());
+ WindowListener wl = createFrameListener();
+ dialog.addWindowListener(wl);
+ return dialog;
+ }
+
+ protected DragWindow createDragWindow(LizziePane lizziePane) {
+ Window frame = null;
+ if (lizziePane != null) {
+ Container p;
+ for (p = lizziePane.getParent(); p != null && !(p instanceof Window); p = p.getParent()) ;
+ if (p != null && p instanceof Window) frame = (Window) p;
+ }
+ if (floatingLizziePane == null) {
+ floatingLizziePane = createFloatingWindow(lizziePane);
+ }
+ if (floatingLizziePane instanceof Window) frame = (Window) floatingLizziePane;
+ DragWindow dragWindow = new DragWindow(frame);
+ return dragWindow;
+ }
+
+ public void setFloatingLocation(int x, int y) {
+ floatingX = x;
+ floatingY = y;
+ }
+
+ public boolean isFloating() {
+ return floating;
+ }
+
+ public void setFloating(boolean b, Point p) {
+ if (lizziePane.isFloatable()) {
+ boolean visible = false;
+ Window ancestor = SwingUtilities.getWindowAncestor(lizziePane);
+ if (ancestor != null) {
+ visible = ancestor.isVisible();
+ }
+ if (dragWindow != null) dragWindow.setVisible(false);
+ this.floating = b;
+ if (floatingLizziePane == null) {
+ floatingLizziePane = createFloatingWindow(lizziePane);
+ }
+ if (b == true) {
+ if (dockingSource == null) {
+ dockingSource = lizziePane.getParent();
+ dockingSource.remove(lizziePane);
+ }
+ constraintBeforeFloating = calculateConstraint();
+ if (propertyListener != null) UIManager.addPropertyChangeListener(propertyListener);
+ floatingLizziePane.getContentPane().add(lizziePane, BorderLayout.CENTER);
+ if (floatingLizziePane instanceof Window) {
+ ((Window) floatingLizziePane).pack();
+ ((Window) floatingLizziePane).setLocation(floatingX, floatingY);
+ Insets insets = ((Window) floatingLizziePane).getInsets();
+ Dimension d =
+ new Dimension(
+ originSize.width + insets.left + insets.right,
+ originSize.height + insets.top + insets.bottom);
+ ((Window) floatingLizziePane).setSize(d);
+ if (visible) {
+ ((Window) floatingLizziePane).setVisible(true);
+ } else {
+ ancestor.addWindowListener(
+ new WindowAdapter() {
+ public void windowOpened(WindowEvent e) {
+ ((Window) floatingLizziePane).setVisible(true);
+ }
+ });
+ }
+ }
+ } else {
+ if (floatingLizziePane == null) floatingLizziePane = createFloatingWindow(lizziePane);
+ if (floatingLizziePane instanceof Window) ((Window) floatingLizziePane).setVisible(false);
+ floatingLizziePane.getContentPane().remove(lizziePane);
+ String constraint = getDockingConstraint(dockingSource, p);
+ if (constraint != null) {
+ if (dockingSource == null) dockingSource = lizziePane.getParent();
+ if (propertyListener != null) UIManager.removePropertyChangeListener(propertyListener);
+ dockingSource.add(constraint, lizziePane);
+ }
+ }
+ dockingSource.invalidate();
+ Container dockingSourceParent = dockingSource.getParent();
+ if (dockingSourceParent != null) dockingSourceParent.validate();
+ dockingSource.repaint();
+ }
+ }
+
+ public boolean canDock(Component c, Point p) {
+ return (p != null && getDockingConstraint(c, p) != null);
+ }
+
+ private String calculateConstraint() {
+ String constraint = null;
+ LayoutManager lm = dockingSource.getLayout();
+ if (lm instanceof LizzieLayout) {
+ constraint = (String) ((LizzieLayout) lm).getConstraints(lizziePane);
+ }
+ return (constraint != null) ? constraint : constraintBeforeFloating;
+ }
+
+ private String getDockingConstraint(Component c, Point p) {
+ if (p == null) return constraintBeforeFloating;
+ return null;
+ }
+
+ protected void dragTo(Point position, Point origin) {
+ originSize = lizziePane.getSize();
+ if (lizziePane.isFloatable()) {
+ try {
+ if (dragWindow == null) dragWindow = createDragWindow(lizziePane);
+ Point offset = dragWindow.getOffset();
+ if (offset == null) {
+ Dimension size = lizziePane.getSize();
+ offset = new Point(size.width / 2, size.height / 2);
+ dragWindow.setOffset(offset);
+ }
+ Point global = new Point(origin.x + position.x, origin.y + position.y);
+ Point dragPoint = new Point(global.x - offset.x, global.y - offset.y);
+ if (dockingSource == null) dockingSource = lizziePane.getParent();
+ constraintBeforeFloating = calculateConstraint();
+ dragWindow.setLocation(dragPoint.x, dragPoint.y);
+ if (dragWindow.isVisible() == false) {
+ Dimension size = lizziePane.getSize();
+ dragWindow.setSize(size.width, size.height);
+ dragWindow.setVisible(true);
+ }
+ } catch (IllegalComponentStateException e) {
+ }
+ }
+ }
+
+ protected void floatAt(Point position, Point origin) {
+ if (lizziePane.isFloatable()) {
+ try {
+ Point offset = dragWindow.getOffset();
+ if (offset == null) {
+ offset = position;
+ dragWindow.setOffset(offset);
+ }
+ Point global = new Point(origin.x + position.x, origin.y + position.y);
+ setFloatingLocation(global.x - offset.x, global.y - offset.y);
+ if (dockingSource != null) {
+ Point dockingPosition = dockingSource.getLocationOnScreen();
+ Point comparisonPoint =
+ new Point(global.x - dockingPosition.x, global.y - dockingPosition.y);
+ if (canDock(dockingSource, comparisonPoint)) {
+ setFloating(false, comparisonPoint);
+ } else {
+ setFloating(true, null);
+ }
+ } else {
+ setFloating(true, null);
+ }
+ dragWindow.setOffset(null);
+ } catch (IllegalComponentStateException e) {
+ }
+ }
+ }
+
+ public void toWindow(Point position, Dimension size) {
+ if (lizziePane.isFloatable()) {
+ try {
+ originSize = size;
+ if (dragWindow == null) dragWindow = createDragWindow(lizziePane);
+ if (dockingSource == null) dockingSource = lizziePane.getParent();
+ constraintBeforeFloating = calculateConstraint();
+ setFloatingLocation(position.x, position.y);
+ setFloating(true, null);
+ } catch (IllegalComponentStateException e) {
+ }
+ }
+ }
+
+ private Handler getHandler() {
+ if (handler == null) {
+ handler = new Handler();
+ }
+ return handler;
+ }
+
+ protected ContainerListener createLizziePaneContListener() {
+ return getHandler();
+ }
+
+ protected FocusListener createLizziePaneFocusListener() {
+ return getHandler();
+ }
+
+ protected PropertyChangeListener createPropertyListener() {
+ return getHandler();
+ }
+
+ protected MouseInputListener createDockingListener() {
+ getHandler().lp = lizziePane;
+ return getHandler();
+ }
+
+ protected WindowListener createFrameListener() {
+ return new FrameListener();
+ }
+
+ /**
+ * Paints the contents of the window used for dragging.
+ *
+ * @param g Graphics to paint to.
+ * @throws NullPointerException is g
is null
+ */
+ protected void paintDragWindow(Graphics g) {
+ g.setColor(dragWindow.getBackground());
+ int w = dragWindow.getWidth();
+ int h = dragWindow.getHeight();
+ g.fillRect(0, 0, w, h);
+ g.setColor(dragWindow.getBorderColor());
+ g.drawRect(0, 0, w - 1, h - 1);
+ }
+
+ private class Handler
+ implements ContainerListener, FocusListener, MouseInputListener, PropertyChangeListener {
+
+ //
+ // ContainerListener
+ //
+ public void componentAdded(ContainerEvent evt) {
+ Component c = evt.getChild();
+
+ if (lizziePaneFocusListener != null) {
+ c.addFocusListener(lizziePaneFocusListener);
+ }
+ }
+
+ public void componentRemoved(ContainerEvent evt) {
+ Component c = evt.getChild();
+
+ if (lizziePaneFocusListener != null) {
+ c.removeFocusListener(lizziePaneFocusListener);
+ }
+ }
+
+ public void focusGained(FocusEvent evt) {
+ Component c = evt.getComponent();
+ focusedCompIndex = lizziePane.getComponentIndex(c);
+ }
+
+ public void focusLost(FocusEvent evt) {}
+
+ LizziePane lp;
+ boolean isDragging = false;
+ Point origin = null;
+
+ public void mousePressed(MouseEvent evt) {
+ if (!lp.isEnabled()) {
+ return;
+ }
+ isDragging = false;
+ }
+
+ public void mouseReleased(MouseEvent evt) {
+ if (!lp.isEnabled()) {
+ return;
+ }
+ if (isDragging) {
+ Point position = evt.getPoint();
+ if (origin == null) origin = evt.getComponent().getLocationOnScreen();
+ floatAt(position, origin);
+ }
+ origin = null;
+ isDragging = false;
+ }
+
+ public void mouseDragged(MouseEvent evt) {
+ if (!lp.isEnabled()) {
+ return;
+ }
+ isDragging = true;
+ Point position = evt.getPoint();
+ if (origin == null) {
+ origin = evt.getComponent().getLocationOnScreen();
+ }
+ dragTo(position, origin);
+ }
+
+ public void mouseClicked(MouseEvent evt) {}
+
+ public void mouseEntered(MouseEvent evt) {}
+
+ public void mouseExited(MouseEvent evt) {}
+
+ public void mouseMoved(MouseEvent evt) {}
+
+ public void propertyChange(PropertyChangeEvent evt) {
+ String propertyName = evt.getPropertyName();
+ if (propertyName == "lookAndFeel") {
+ lizziePane.updateUI();
+ }
+ }
+ }
+
+ protected class FrameListener extends WindowAdapter {
+ public void windowClosing(WindowEvent w) {
+ if (lizziePane.isFloatable()) {
+ if (dragWindow != null) dragWindow.setVisible(false);
+ floating = false;
+ if (floatingLizziePane == null) floatingLizziePane = createFloatingWindow(lizziePane);
+ if (floatingLizziePane instanceof Window) ((Window) floatingLizziePane).setVisible(false);
+ floatingLizziePane.getContentPane().remove(lizziePane);
+ String constraint = constraintBeforeFloating;
+ if (dockingSource == null) dockingSource = lizziePane.getParent();
+ if (propertyListener != null) UIManager.removePropertyChangeListener(propertyListener);
+ dockingSource.add(lizziePane, constraint);
+ dockingSource.invalidate();
+ Container dockingSourceParent = dockingSource.getParent();
+ if (dockingSourceParent != null) dockingSourceParent.validate();
+ dockingSource.repaint();
+ }
+ }
+ }
+
+ protected class LizziePaneContListener implements ContainerListener {
+ public void componentAdded(ContainerEvent e) {
+ getHandler().componentAdded(e);
+ }
+
+ public void componentRemoved(ContainerEvent e) {
+ getHandler().componentRemoved(e);
+ }
+ }
+
+ protected class LizziePaneFocusListener implements FocusListener {
+ public void focusGained(FocusEvent e) {
+ getHandler().focusGained(e);
+ }
+
+ public void focusLost(FocusEvent e) {
+ getHandler().focusLost(e);
+ }
+ }
+
+ protected class PropertyListener implements PropertyChangeListener {
+ public void propertyChange(PropertyChangeEvent e) {
+ getHandler().propertyChange(e);
+ }
+ }
+
+ /**
+ * This class should be treated as a "protected" inner class. Instantiate it only within
+ * subclasses of LizziePaneUI.
+ */
+ public class DockingListener implements MouseInputListener {
+ protected LizziePane lizziePane;
+ protected boolean isDragging = false;
+ protected Point origin = null;
+
+ public DockingListener(LizziePane t) {
+ this.lizziePane = t;
+ getHandler().lp = t;
+ }
+
+ public void mouseClicked(MouseEvent e) {
+ getHandler().mouseClicked(e);
+ }
+
+ public void mousePressed(MouseEvent e) {
+ getHandler().lp = lizziePane;
+ getHandler().mousePressed(e);
+ isDragging = getHandler().isDragging;
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ getHandler().lp = lizziePane;
+ getHandler().isDragging = isDragging;
+ getHandler().origin = origin;
+ getHandler().mouseReleased(e);
+ isDragging = getHandler().isDragging;
+ origin = getHandler().origin;
+ }
+
+ public void mouseEntered(MouseEvent e) {
+ getHandler().mouseEntered(e);
+ }
+
+ public void mouseExited(MouseEvent e) {
+ getHandler().mouseExited(e);
+ }
+
+ public void mouseDragged(MouseEvent e) {
+ getHandler().lp = lizziePane;
+ getHandler().origin = origin;
+ getHandler().mouseDragged(e);
+ isDragging = getHandler().isDragging;
+ origin = getHandler().origin;
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ getHandler().mouseMoved(e);
+ }
+ }
+
+ protected class DragWindow extends Window {
+ Color borderColor = Color.gray;
+ Point offset; // offset of the mouse cursor inside the DragWindow
+
+ DragWindow(Window w) {
+ super(w);
+ }
+
+ public Point getOffset() {
+ return offset;
+ }
+
+ public void setOffset(Point p) {
+ this.offset = p;
+ }
+
+ public void setBorderColor(Color c) {
+ if (this.borderColor == c) return;
+ this.borderColor = c;
+ repaint();
+ }
+
+ public Color getBorderColor() {
+ return this.borderColor;
+ }
+
+ public void paint(Graphics g) {
+ paintDragWindow(g);
+ // Paint the children
+ super.paint(g);
+ }
+
+ public Insets getInsets() {
+ return new Insets(1, 1, 1, 1);
+ }
+ }
+}
diff --git a/src/main/java/featurecat/lizzie/gui/BoardPane.java b/src/main/java/featurecat/lizzie/gui/BoardPane.java
new file mode 100644
index 000000000..7649d99a5
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/gui/BoardPane.java
@@ -0,0 +1,468 @@
+package featurecat.lizzie.gui;
+
+import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+import com.jhlabs.image.GaussianFilter;
+import featurecat.lizzie.Lizzie;
+import featurecat.lizzie.rules.Board;
+import featurecat.lizzie.rules.SGFParser;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionListener;
+import java.awt.image.BufferedImage;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.ResourceBundle;
+import java.util.function.Consumer;
+
+/** The window used to display the game. */
+public class BoardPane extends LizziePane {
+ private static final ResourceBundle resourceBundle =
+ ResourceBundle.getBundle("l10n.DisplayStrings");
+
+ private static final String[] commands = {
+ resourceBundle.getString("LizzieFrame.commands.keyN"),
+ resourceBundle.getString("LizzieFrame.commands.keyEnter"),
+ resourceBundle.getString("LizzieFrame.commands.keySpace"),
+ resourceBundle.getString("LizzieFrame.commands.keyUpArrow"),
+ resourceBundle.getString("LizzieFrame.commands.keyDownArrow"),
+ resourceBundle.getString("LizzieFrame.commands.rightClick"),
+ resourceBundle.getString("LizzieFrame.commands.mouseWheelScroll"),
+ resourceBundle.getString("LizzieFrame.commands.keyC"),
+ resourceBundle.getString("LizzieFrame.commands.keyP"),
+ resourceBundle.getString("LizzieFrame.commands.keyPeriod"),
+ resourceBundle.getString("LizzieFrame.commands.keyA"),
+ resourceBundle.getString("LizzieFrame.commands.keyM"),
+ resourceBundle.getString("LizzieFrame.commands.keyI"),
+ resourceBundle.getString("LizzieFrame.commands.keyO"),
+ resourceBundle.getString("LizzieFrame.commands.keyS"),
+ resourceBundle.getString("LizzieFrame.commands.keyAltC"),
+ resourceBundle.getString("LizzieFrame.commands.keyAltV"),
+ resourceBundle.getString("LizzieFrame.commands.keyF"),
+ resourceBundle.getString("LizzieFrame.commands.keyV"),
+ resourceBundle.getString("LizzieFrame.commands.keyW"),
+ resourceBundle.getString("LizzieFrame.commands.keyCtrlW"),
+ resourceBundle.getString("LizzieFrame.commands.keyG"),
+ resourceBundle.getString("LizzieFrame.commands.keyR"),
+ resourceBundle.getString("LizzieFrame.commands.keyBracket"),
+ resourceBundle.getString("LizzieFrame.commands.keyT"),
+ resourceBundle.getString("LizzieFrame.commands.keyCtrlT"),
+ resourceBundle.getString("LizzieFrame.commands.keyY"),
+ resourceBundle.getString("LizzieFrame.commands.keyZ"),
+ resourceBundle.getString("LizzieFrame.commands.keyShiftZ"),
+ resourceBundle.getString("LizzieFrame.commands.keyHome"),
+ resourceBundle.getString("LizzieFrame.commands.keyEnd"),
+ resourceBundle.getString("LizzieFrame.commands.keyControl"),
+ resourceBundle.getString("LizzieFrame.commands.keyDelete"),
+ resourceBundle.getString("LizzieFrame.commands.keyBackspace"),
+ resourceBundle.getString("LizzieFrame.commands.keyE"),
+ };
+
+ private static BoardRenderer boardRenderer;
+
+ // private final BufferStrategy bs;
+ private static boolean started = false;
+
+ private static final int[] outOfBoundCoordinate = new int[] {-1, -1};
+ public int[] mouseOverCoordinate = outOfBoundCoordinate;
+
+ private long lastAutosaveTime = System.currentTimeMillis();
+ private boolean isReplayVariation = false;
+
+ LizzieMain owner;
+ /** Creates a window */
+ public BoardPane(LizzieMain owner) {
+ super(owner);
+ this.owner = owner;
+
+ boardRenderer = new BoardRenderer(true);
+
+ // createBufferStrategy(2);
+ // bs = getBufferStrategy();
+
+ addMouseListener(
+ new MouseAdapter() {
+ @Override
+ public void mousePressed(MouseEvent e) {
+ if (e.getButton() == MouseEvent.BUTTON1) { // left click
+ if (e.getClickCount() == 2) { // TODO: Maybe need to delay check
+ onDoubleClicked(e.getX(), e.getY());
+ } else {
+ onClicked(e.getX(), e.getY());
+ }
+ } else if (e.getButton() == MouseEvent.BUTTON3) { // right click
+ Input.undo();
+ }
+ }
+ });
+ addMouseMotionListener(
+ new MouseMotionListener() {
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ onMouseMoved(e.getX(), e.getY());
+ }
+
+ @Override
+ public void mouseDragged(MouseEvent e) {}
+ });
+ }
+
+ /** Clears related status from empty board. */
+ public void clear() {
+ if (LizzieMain.winratePane != null) {
+ LizzieMain.winratePane.clear();
+ }
+ started = false;
+ owner.updateStatus();
+ }
+
+ private BufferedImage cachedImage;
+
+ /**
+ * Draws the game board and interface
+ *
+ * @param g0 not used
+ */
+ @Override
+ protected void paintComponent(Graphics g0) {
+ super.paintComponent(g0);
+ autosaveMaybe();
+
+ int width = getWidth();
+ int height = getHeight();
+
+ if (!owner.showControls) {
+ // layout parameters
+ // initialize
+ cachedImage = new BufferedImage(width, height, TYPE_INT_ARGB);
+ Graphics2D g = (Graphics2D) cachedImage.getGraphics();
+ g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+
+ boardRenderer.setLocation(0, 0);
+ boardRenderer.setBoardLength(width);
+ boardRenderer.draw(g);
+
+ owner.repaintSub();
+
+ if (Lizzie.leelaz != null && Lizzie.leelaz.isLoaded() && !started) {
+ started = true;
+ if (Lizzie.config.showVariationGraph || Lizzie.config.showComment) {
+ owner.updateStatus();
+ }
+ }
+
+ // cleanup
+ g.dispose();
+ }
+
+ // draw the image
+ Graphics2D bsGraphics = (Graphics2D) g0; // bs.getDrawGraphics();
+ bsGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ // bsGraphics.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
+ // RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
+ bsGraphics.drawImage(cachedImage, 0, 0, null);
+
+ // cleanup
+ bsGraphics.dispose();
+ // bs.show();
+ }
+
+ private GaussianFilter filter10 = new GaussianFilter(10);
+
+ /** Display the controls */
+ void drawControls() {
+ userAlreadyKnowsAboutCommandString = true;
+
+ cachedImage = new BufferedImage(getWidth(), getHeight(), TYPE_INT_ARGB);
+
+ // redraw background
+ // createBackground();
+
+ List commandsToShow = new ArrayList<>(Arrays.asList(commands));
+ if (Lizzie.leelaz.getDynamicKomi().isPresent()) {
+ commandsToShow.add(resourceBundle.getString("LizzieFrame.commands.keyD"));
+ }
+
+ Graphics2D g = cachedImage.createGraphics();
+
+ int maxSize = min(getWidth(), getHeight());
+ int fontSize = (int) (maxSize * min(0.034, 0.80 / commandsToShow.size()));
+ Font font = new Font(Lizzie.config.fontName, Font.PLAIN, fontSize);
+ g.setFont(font);
+
+ FontMetrics metrics = g.getFontMetrics(font);
+ int maxCmdWidth = commandsToShow.stream().mapToInt(c -> metrics.stringWidth(c)).max().orElse(0);
+ int lineHeight = (int) (font.getSize() * 1.15);
+
+ int boxWidth = min((int) (maxCmdWidth * 1.4), getWidth());
+ int boxHeight =
+ min(commandsToShow.size() * lineHeight, getHeight() - getInsets().top - getInsets().bottom);
+
+ int commandsX = min(getWidth() / 2 - boxWidth / 2, getWidth());
+ int top = this.getInsets().top;
+ int commandsY = top + min((getHeight() - top) / 2 - boxHeight / 2, getHeight() - top);
+
+ BufferedImage result = new BufferedImage(boxWidth, boxHeight, TYPE_INT_ARGB);
+ filter10.filter(
+ owner.cachedBackground.getSubimage(commandsX, commandsY, boxWidth, boxHeight), result);
+ g.drawImage(result, commandsX, commandsY, null);
+
+ g.setColor(new Color(0, 0, 0, 130));
+ g.fillRect(commandsX, commandsY, boxWidth, boxHeight);
+ int strokeRadius = Lizzie.config.showBorder ? 2 : 1;
+ g.setStroke(new BasicStroke(strokeRadius == 1 ? strokeRadius : 2 * strokeRadius));
+ if (Lizzie.config.showBorder) {
+ g.setColor(new Color(0, 0, 0, 60));
+ g.drawRect(
+ commandsX + strokeRadius,
+ commandsY + strokeRadius,
+ boxWidth - 2 * strokeRadius,
+ boxHeight - 2 * strokeRadius);
+ }
+ int verticalLineX = (int) (commandsX + boxWidth * 0.3);
+ g.setColor(new Color(0, 0, 0, 60));
+ g.drawLine(
+ verticalLineX,
+ commandsY + 2 * strokeRadius,
+ verticalLineX,
+ commandsY + boxHeight - 2 * strokeRadius);
+
+ g.setStroke(new BasicStroke(1));
+
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ g.setColor(Color.WHITE);
+ int lineOffset = commandsY;
+ for (String command : commandsToShow) {
+ String[] split = command.split("\\|");
+ g.drawString(
+ split[0],
+ verticalLineX - metrics.stringWidth(split[0]) - strokeRadius * 4,
+ font.getSize() + lineOffset);
+ g.drawString(split[1], verticalLineX + strokeRadius * 4, font.getSize() + lineOffset);
+ lineOffset += lineHeight;
+ }
+
+ // refreshBackground();
+ }
+
+ private boolean userAlreadyKnowsAboutCommandString = false;
+
+ private void drawCommandString(Graphics2D g) {
+ if (userAlreadyKnowsAboutCommandString) return;
+
+ int maxSize = (int) (min(getWidth(), getHeight()) * 0.98);
+
+ Font font = new Font(Lizzie.config.fontName, Font.PLAIN, (int) (maxSize * 0.03));
+ String commandString = resourceBundle.getString("LizzieFrame.prompt.showControlsHint");
+ int strokeRadius = Lizzie.config.showBorder ? 2 : 0;
+
+ int showCommandsHeight = (int) (font.getSize() * 1.1);
+ int showCommandsWidth = g.getFontMetrics(font).stringWidth(commandString) + 4 * strokeRadius;
+ int showCommandsX = this.getInsets().left;
+ int showCommandsY = getHeight() - showCommandsHeight - this.getInsets().bottom;
+ g.setColor(new Color(0, 0, 0, 130));
+ g.fillRect(showCommandsX, showCommandsY, showCommandsWidth, showCommandsHeight);
+ if (Lizzie.config.showBorder) {
+ g.setStroke(new BasicStroke(2 * strokeRadius));
+ g.setColor(new Color(0, 0, 0, 60));
+ g.drawRect(
+ showCommandsX + strokeRadius,
+ showCommandsY + strokeRadius,
+ showCommandsWidth - 2 * strokeRadius,
+ showCommandsHeight - 2 * strokeRadius);
+ }
+ g.setStroke(new BasicStroke(1));
+
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setColor(Color.WHITE);
+ g.setFont(font);
+ g.drawString(commandString, showCommandsX + 2 * strokeRadius, showCommandsY + font.getSize());
+ }
+
+ /**
+ * Checks whether or not something was clicked and performs the appropriate action
+ *
+ * @param x x coordinate
+ * @param y y coordinate
+ */
+ public void onClicked(int x, int y) {
+ // Check for board click
+ Optional boardCoordinates = boardRenderer.convertScreenToCoordinates(x, y);
+ if (boardCoordinates.isPresent()) {
+ int[] coords = boardCoordinates.get();
+ if (Lizzie.board.inAnalysisMode()) Lizzie.board.toggleAnalysis();
+ if (!owner.isPlayingAgainstLeelaz
+ || (owner.playerIsBlack == Lizzie.board.getData().blackToPlay))
+ Lizzie.board.place(coords[0], coords[1]);
+ // repaint();
+ // owner.updateStatus();
+ }
+ }
+
+ public void onDoubleClicked(int x, int y) {
+ // Check for board double click
+ Optional boardCoordinates = boardRenderer.convertScreenToCoordinates(x, y);
+ if (boardCoordinates.isPresent()) {
+ int[] coords = boardCoordinates.get();
+ if (!owner.isPlayingAgainstLeelaz) {
+ int moveNumber = Lizzie.board.moveNumberByCoord(coords);
+ if (moveNumber > 0) {
+ Lizzie.board.goToMoveNumberBeyondBranch(moveNumber);
+ }
+ }
+ }
+ }
+
+ public void onMouseMoved(int x, int y) {
+ mouseOverCoordinate = outOfBoundCoordinate;
+ Optional coords = boardRenderer.convertScreenToCoordinates(x, y);
+ coords.filter(c -> !isMouseOver(c[0], c[1])).ifPresent(c -> repaint());
+ coords.ifPresent(
+ c -> {
+ mouseOverCoordinate = c;
+ isReplayVariation = false;
+ });
+ if (!coords.isPresent() && boardRenderer.isShowingBranch()) {
+ repaint();
+ }
+ }
+
+ private final Consumer placeVariation =
+ v -> Board.asCoordinates(v).ifPresent(c -> Lizzie.board.place(c[0], c[1]));
+
+ public boolean playCurrentVariation() {
+ boardRenderer.variationOpt.ifPresent(vs -> vs.forEach(placeVariation));
+ return boardRenderer.variationOpt.isPresent();
+ }
+
+ public void playBestMove() {
+ boardRenderer.bestMoveCoordinateName().ifPresent(placeVariation);
+ }
+
+ public boolean isMouseOver(int x, int y) {
+ return mouseOverCoordinate[0] == x && mouseOverCoordinate[1] == y;
+ }
+
+ private void autosaveMaybe() {
+ int interval =
+ Lizzie.config.config.getJSONObject("ui").getInt("autosave-interval-seconds") * 1000;
+ long currentTime = System.currentTimeMillis();
+ if (interval > 0 && currentTime - lastAutosaveTime >= interval) {
+ Lizzie.board.autosave();
+ lastAutosaveTime = currentTime;
+ }
+ }
+
+ private void setDisplayedBranchLength(int n) {
+ boardRenderer.setDisplayedBranchLength(n);
+ }
+
+ public void startRawBoard() {
+ boolean onBranch = boardRenderer.isShowingBranch();
+ int n = (onBranch ? 1 : BoardRenderer.SHOW_RAW_BOARD);
+ boardRenderer.setDisplayedBranchLength(n);
+ }
+
+ public void stopRawBoard() {
+ boardRenderer.setDisplayedBranchLength(BoardRenderer.SHOW_NORMAL_BOARD);
+ }
+
+ public boolean incrementDisplayedBranchLength(int n) {
+ return boardRenderer.incrementDisplayedBranchLength(n);
+ }
+
+ public void copySgf() {
+ try {
+ // Get sgf content from game
+ String sgfContent = SGFParser.saveToString();
+
+ // Save to clipboard
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable transferableString = new StringSelection(sgfContent);
+ clipboard.setContents(transferableString, null);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void pasteSgf() {
+ // Get string from clipboard
+ String sgfContent =
+ Optional.ofNullable(Toolkit.getDefaultToolkit().getSystemClipboard().getContents(null))
+ .filter(cc -> cc.isDataFlavorSupported(DataFlavor.stringFlavor))
+ .flatMap(
+ cc -> {
+ try {
+ return Optional.of((String) cc.getTransferData(DataFlavor.stringFlavor));
+ } catch (UnsupportedFlavorException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ return Optional.empty();
+ })
+ .orElse("");
+
+ // Load game contents from sgf string
+ if (!sgfContent.isEmpty()) {
+ SGFParser.loadFromString(sgfContent);
+ }
+ }
+
+ public void increaseMaxAlpha(int k) {
+ boardRenderer.increaseMaxAlpha(k);
+ }
+
+ public void replayBranch() {
+ if (isReplayVariation) return;
+ int replaySteps = boardRenderer.getReplayBranch();
+ if (replaySteps <= 0) return; // Bad steps or no branch
+ int oriBranchLength = boardRenderer.getDisplayedBranchLength();
+ isReplayVariation = true;
+ if (Lizzie.leelaz.isPondering()) Lizzie.leelaz.togglePonder();
+ Runnable runnable =
+ new Runnable() {
+ public void run() {
+ int secs = (int) (Lizzie.config.replayBranchIntervalSeconds * 1000);
+ for (int i = 1; i < replaySteps + 1; i++) {
+ if (!isReplayVariation) break;
+ setDisplayedBranchLength(i);
+ repaint();
+ try {
+ Thread.sleep(secs);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ boardRenderer.setDisplayedBranchLength(oriBranchLength);
+ isReplayVariation = false;
+ if (!Lizzie.leelaz.isPondering()) Lizzie.leelaz.togglePonder();
+ }
+ };
+ Thread thread = new Thread(runnable);
+ thread.start();
+ }
+
+ public void updateStatus() {
+ owner.updateStatus();
+ }
+}
diff --git a/src/main/java/featurecat/lizzie/gui/BoardRenderer.java b/src/main/java/featurecat/lizzie/gui/BoardRenderer.java
index 58ad1db11..2980055a6 100644
--- a/src/main/java/featurecat/lizzie/gui/BoardRenderer.java
+++ b/src/main/java/featurecat/lizzie/gui/BoardRenderer.java
@@ -1,6 +1,10 @@
package featurecat.lizzie.gui;
-import static java.awt.RenderingHints.*;
+import static java.awt.RenderingHints.KEY_ANTIALIASING;
+import static java.awt.RenderingHints.KEY_INTERPOLATION;
+import static java.awt.RenderingHints.VALUE_ANTIALIAS_OFF;
+import static java.awt.RenderingHints.VALUE_ANTIALIAS_ON;
+import static java.awt.RenderingHints.VALUE_INTERPOLATION_BILINEAR;
import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
import static java.lang.Math.log;
import static java.lang.Math.max;
@@ -16,7 +20,19 @@
import featurecat.lizzie.rules.SGFParser;
import featurecat.lizzie.rules.Stone;
import featurecat.lizzie.rules.Zobrist;
-import java.awt.*;
+import featurecat.lizzie.util.Utils;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontMetrics;
+import java.awt.Graphics2D;
+import java.awt.Image;
+import java.awt.Paint;
+import java.awt.Point;
+import java.awt.RadialGradientPaint;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.TexturePaint;
import java.awt.font.TextAttribute;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
@@ -49,6 +65,7 @@ public class BoardRenderer {
private boolean cachedBackgroundImageHasCoordinatesEnabled = false;
private int cachedX, cachedY;
+ private int cachedBoardLength = 0;
private BufferedImage cachedStonesImage = emptyImage;
private BufferedImage cachedBoardImage = emptyImage;
private BufferedImage cachedWallpaperImage = emptyImage;
@@ -95,7 +112,7 @@ public void draw(Graphics2D g) {
// timer.lap("background");
drawStones();
// timer.lap("stones");
- if (Lizzie.board.inScoreMode() && isMainBoard) {
+ if (Lizzie.board != null && Lizzie.board.inScoreMode() && isMainBoard) {
drawScore(g);
} else {
drawBranch();
@@ -137,6 +154,14 @@ public Optional bestMoveCoordinateName() {
return bestMoves.isEmpty() ? Optional.empty() : Optional.of(bestMoves.get(0).coordinate);
}
+ /** Calculate good values for boardLength, scaledMargin, availableLength, and squareLength */
+ public static int availableLength(int boardLength, boolean showCoordinates) {
+ int[] calculatedPixelMargins = calculatePixelMargins(boardLength, showCoordinates);
+ return (calculatedPixelMargins != null && calculatedPixelMargins.length >= 1)
+ ? calculatedPixelMargins[0]
+ : boardLength;
+ }
+
/** Calculate good values for boardLength, scaledMargin, availableLength, and squareLength */
private void setupSizeParameters() {
int boardLength0 = boardLength;
@@ -163,12 +188,14 @@ private void drawGoban(Graphics2D g0) {
// Draw the cached background image if frame size changes
if (cachedBackgroundImage.getWidth() != width
|| cachedBackgroundImage.getHeight() != height
+ || cachedBoardLength != boardLength
|| cachedX != x
|| cachedY != y
|| cachedBackgroundImageHasCoordinatesEnabled != showCoordinates()
- || Lizzie.board.isForceRefresh()) {
+ || Lizzie.frame.isForceRefresh()) {
- Lizzie.board.setForceRefresh(false);
+ cachedBoardLength = boardLength;
+ Lizzie.frame.setForceRefresh(false);
cachedBackgroundImage = new BufferedImage(width, height, TYPE_INT_ARGB);
Graphics2D g = cachedBackgroundImage.createGraphics();
@@ -204,16 +231,16 @@ private void drawGoban(Graphics2D g0) {
drawString(
g,
x + scaledMargin + squareLength * i,
- y + scaledMargin / 2,
- LizzieFrame.uiFont,
+ y + scaledMargin / 3,
+ MainFrame.uiFont,
Board.asName(i),
stoneRadius * 4 / 5,
stoneRadius);
drawString(
g,
x + scaledMargin + squareLength * i,
- y - scaledMargin / 2 + boardLength,
- LizzieFrame.uiFont,
+ y - scaledMargin / 3 + boardLength,
+ MainFrame.uiFont,
Board.asName(i),
stoneRadius * 4 / 5,
stoneRadius);
@@ -221,17 +248,17 @@ private void drawGoban(Graphics2D g0) {
for (int i = 0; i < Board.boardSize; i++) {
drawString(
g,
- x + scaledMargin / 2,
+ x + scaledMargin / 3,
y + scaledMargin + squareLength * i,
- LizzieFrame.uiFont,
+ MainFrame.uiFont,
"" + (Board.boardSize - i),
stoneRadius * 4 / 5,
stoneRadius);
drawString(
g,
- x - scaledMargin / 2 + +boardLength,
+ x - scaledMargin / 3 + boardLength,
y + scaledMargin + squareLength * i,
- LizzieFrame.uiFont,
+ MainFrame.uiFont,
"" + (Board.boardSize - i),
stoneRadius * 4 / 5,
stoneRadius);
@@ -453,6 +480,7 @@ private void renderImages(Graphics2D g) {
/** Draw move numbers and/or mark the last played move */
private void drawMoveNumbers(Graphics2D g) {
g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON);
+ if (Lizzie.board == null) return;
Board board = Lizzie.board;
Optional lastMoveOpt = branchOpt.map(b -> b.data.lastMove).orElse(board.getLastMove());
if (Lizzie.config.allowMoveNumber == 0 && !branchOpt.isPresent()) {
@@ -488,7 +516,7 @@ private void drawMoveNumbers(Graphics2D g) {
g,
x + boardLength / 2,
y + boardLength / 2,
- LizzieFrame.uiFont,
+ MainFrame.uiFont,
"pass",
stoneRadius * 4,
stoneRadius * 6);
@@ -541,7 +569,7 @@ private void drawMoveNumbers(Graphics2D g) {
g,
stoneX,
stoneY,
- LizzieFrame.uiFont,
+ MainFrame.uiFont,
moveNumberString,
(float) (stoneRadius * 1.4),
(int) (stoneRadius * 1.4));
@@ -619,7 +647,8 @@ private void drawLeelazSuggestions(Graphics2D g) {
int suggestionY = y + scaledMargin + squareLength * coords[1];
float hue;
- if (isBestMove && !Lizzie.config.colorByWinrateInsteadOfVisits) {
+ if (isBestMove && !Lizzie.config.colorByWinrateInsteadOfVisits
+ || hasMaxWinrate && Lizzie.config.colorByWinrateInsteadOfVisits) {
hue = cyanHue;
} else {
double fraction;
@@ -717,7 +746,7 @@ private void drawLeelazSuggestions(Graphics2D g) {
g,
suggestionX,
suggestionY,
- LizzieFrame.winrateFont,
+ MainFrame.winrateFont,
Font.PLAIN,
text,
stoneRadius,
@@ -728,8 +757,8 @@ private void drawLeelazSuggestions(Graphics2D g) {
g,
suggestionX,
suggestionY + stoneRadius * 2 / 5,
- LizzieFrame.uiFont,
- Lizzie.frame.getPlayoutsString(move.playouts),
+ MainFrame.uiFont,
+ Utils.getPlayoutsString(move.playouts),
(float) (stoneRadius * 0.8),
stoneRadius * 1.4);
}
@@ -739,6 +768,7 @@ private void drawLeelazSuggestions(Graphics2D g) {
}
private void drawNextMoves(Graphics2D g) {
+ if (Lizzie.board == null) return;
g.setColor(Lizzie.board.getData().blackToPlay ? Color.BLACK : Color.WHITE);
List nexts = Lizzie.board.getHistory().getNexts();
@@ -802,7 +832,7 @@ private void drawWoodenBoard(Graphics2D g) {
* @param boardLength go board's length in pixels; must be boardLength >= BOARD_SIZE - 1
* @return an array containing the three outputs: new boardLength, scaledMargin, availableLength
*/
- private int[] calculatePixelMargins(int boardLength) {
+ private static int[] calculatePixelMargins(int boardLength, boolean showCoordinates) {
// boardLength -= boardLength*MARGIN/3; // account for the shadows we will draw around the edge
// of the board
// if (boardLength < Board.BOARD_SIZE - 1)
@@ -814,7 +844,7 @@ private int[] calculatePixelMargins(int boardLength) {
// decrease boardLength until the availableLength will result in square board intersections
double margin =
- (showCoordinates() ? 1.5 / (Board.boardSize + 2) : 1.0d / (Board.boardSize + 1));
+ (showCoordinates ? (Board.boardSize > 3 ? 0.06 : 0.04) : 0.03) / Board.boardSize * 19.0;
boardLength++;
do {
boardLength--;
@@ -1015,7 +1045,7 @@ public void drawTextureImage(
* @param g
*/
private void drawStoneMarkup(Graphics2D g) {
-
+ if (Lizzie.board == null) return;
BoardData data = Lizzie.board.getHistory().getData();
data.getProperties()
@@ -1044,7 +1074,7 @@ private void drawStoneMarkup(Graphics2D g) {
g,
moveX,
moveY,
- LizzieFrame.uiFont,
+ MainFrame.uiFont,
moves[1],
(float) labelRadius,
labelRadius);
@@ -1156,7 +1186,7 @@ private Font makeFont(Font fontBase, int style) {
}
private int[] calculatePixelMargins() {
- return calculatePixelMargins(boardLength);
+ return calculatePixelMargins(boardLength, showCoordinates());
}
/**
@@ -1214,8 +1244,8 @@ public Optional convertScreenToCoordinates(int x, int y) {
int squareSize = calculateSquareLength(boardLengthWithoutMargins);
// transform the pixel coordinates to board coordinates
- x = Math.floorDiv(x - this.x - marginLength + squareSize / 2, squareSize);
- y = Math.floorDiv(y - this.y - marginLength + squareSize / 2, squareSize);
+ x = squareSize == 0 ? 0 : Math.floorDiv(x - this.x - marginLength + squareSize / 2, squareSize);
+ y = squareSize == 0 ? 0 : Math.floorDiv(y - this.y - marginLength + squareSize / 2, squareSize);
// return these values if they are valid board coordinates
return Board.isValid(x, y) ? Optional.of(new int[] {x, y}) : Optional.empty();
diff --git a/src/main/java/featurecat/lizzie/gui/CommentPane.java b/src/main/java/featurecat/lizzie/gui/CommentPane.java
new file mode 100644
index 000000000..1cf7444f2
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/gui/CommentPane.java
@@ -0,0 +1,158 @@
+package featurecat.lizzie.gui;
+
+import static java.lang.Math.min;
+
+import featurecat.lizzie.Lizzie;
+import java.awt.BorderLayout;
+import java.awt.Font;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.event.MouseMotionListener;
+import java.io.IOException;
+import javax.swing.BorderFactory;
+import javax.swing.JLabel;
+import javax.swing.JScrollPane;
+import javax.swing.JTextPane;
+import javax.swing.text.BadLocationException;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.StyleSheet;
+
+/** The window used to display the game. */
+public class CommentPane extends LizziePane {
+
+ // private final BufferStrategy bs;
+
+ // Display Comment
+ private HTMLDocument htmlDoc;
+ private LizziePane.HtmlKit htmlKit;
+ private StyleSheet htmlStyle;
+ public JScrollPane scrollPane;
+ private JTextPane commentPane;
+ private JLabel dragPane = new JLabel("Drag out");
+ private MouseMotionListener[] mouseMotionListeners;
+ private MouseMotionAdapter mouseMotionAdapter;
+
+ /** Creates a window */
+ public CommentPane(LizzieMain owner) {
+ super(owner);
+ setLayout(new BorderLayout(0, 0));
+
+ htmlKit = new LizziePane.HtmlKit();
+ htmlDoc = (HTMLDocument) htmlKit.createDefaultDocument();
+ htmlStyle = htmlKit.getStyleSheet();
+ String style =
+ "body {background:#"
+ + String.format(
+ "%02x%02x%02x",
+ Lizzie.config.commentBackgroundColor.getRed(),
+ Lizzie.config.commentBackgroundColor.getGreen(),
+ Lizzie.config.commentBackgroundColor.getBlue())
+ + "; color:#"
+ + String.format(
+ "%02x%02x%02x",
+ Lizzie.config.commentFontColor.getRed(),
+ Lizzie.config.commentFontColor.getGreen(),
+ Lizzie.config.commentFontColor.getBlue())
+ + "; font-family:"
+ + Lizzie.config.fontName
+ + ", Consolas, Menlo, Monaco, 'Ubuntu Mono', monospace;"
+ + (Lizzie.config.commentFontSize > 0
+ ? "font-size:" + Lizzie.config.commentFontSize
+ : "")
+ + "}";
+ htmlStyle.addRule(style);
+
+ commentPane = new JTextPane();
+ commentPane.setBorder(BorderFactory.createEmptyBorder());
+ commentPane.setEditorKit(htmlKit);
+ commentPane.setDocument(htmlDoc);
+ commentPane.setText("");
+ commentPane.setEditable(false);
+ commentPane.setFocusable(false);
+ scrollPane = new JScrollPane();
+ scrollPane.setBorder(BorderFactory.createEmptyBorder());
+ scrollPane.setVerticalScrollBarPolicy(
+ javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);
+ add(scrollPane);
+ scrollPane.setViewportView(commentPane);
+ setVisible(false);
+
+ // mouseMotionAdapter = new MouseMotionAdapter() {
+ // @Override
+ // public void mouseDragged(MouseEvent e) {
+ // System.out.println("Mouse Dragged");
+ // owner.dispatchEvent(e);
+ // }
+ // };
+ // commentPane.addMouseMotionListener(mouseMotionAdapter);
+ }
+
+ /** Draw the Comment of the Sgf file */
+ public void drawComment() {
+ if (Lizzie.leelaz != null && Lizzie.leelaz.isLoaded()) {
+ if (Lizzie.config.showComment) {
+ setVisible(true);
+ String comment = Lizzie.board.getHistory().getData().comment;
+ int fontSize = (int) (min(getWidth(), getHeight()) * 0.0294);
+ if (Lizzie.config.commentFontSize > 0) {
+ fontSize = Lizzie.config.commentFontSize;
+ } else if (fontSize < 16) {
+ fontSize = 16;
+ }
+ Font font = new Font(Lizzie.config.fontName, Font.PLAIN, fontSize);
+ commentPane.setFont(font);
+ comment = comment.replaceAll("(\r\n)|(\n)", "
").replaceAll(" ", " ");
+ addText("");
+ }
+ }
+ }
+
+ private void addText(String text) {
+ try {
+ htmlDoc.remove(0, htmlDoc.getLength());
+ htmlKit.insertHTML(htmlDoc, htmlDoc.getLength(), text, 0, 0, null);
+ commentPane.setCaretPosition(htmlDoc.getLength());
+ } catch (BadLocationException | IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void setDesignMode(boolean mode) {
+ // if (mode) {
+ // mouseMotionListeners = commentPane.getMouseMotionListeners();
+ // if (mouseMotionListeners != null) {
+ // for (MouseMotionListener l : mouseMotionListeners) {
+ // commentPane.removeMouseMotionListener(l);
+ // }
+ // }
+ // } else {
+ // if (mouseMotionListeners != null) {
+ // for (MouseMotionListener l : mouseMotionListeners) {
+ // commentPane.addMouseMotionListener(l);
+ // }
+ // }
+ // }
+ if (mode) {
+ remove(scrollPane);
+ add(dragPane);
+ commentPane.setVisible(false);
+ scrollPane.setVisible(false);
+ dragPane.setVisible(true);
+ } else {
+ remove(dragPane);
+ add(scrollPane);
+ commentPane.setVisible(true);
+ scrollPane.setVisible(true);
+ dragPane.setVisible(false);
+ }
+ super.setDesignMode(mode);
+ revalidate();
+ repaint();
+ }
+
+ public void setCommentBounds(int x, int y, int width, int height) {
+ this.setBounds(x, y, width, height);
+ if (scrollPane != null) {
+ scrollPane.setSize(width, height);
+ }
+ }
+}
diff --git a/src/main/java/featurecat/lizzie/gui/ConfigDialog.java b/src/main/java/featurecat/lizzie/gui/ConfigDialog.java
index df089ffd1..11acac6eb 100644
--- a/src/main/java/featurecat/lizzie/gui/ConfigDialog.java
+++ b/src/main/java/featurecat/lizzie/gui/ConfigDialog.java
@@ -37,7 +37,6 @@
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.DocumentFilter;
-import javax.swing.text.DocumentFilter.FilterBypass;
import javax.swing.text.InternationalFormatter;
import org.json.JSONArray;
import org.json.JSONObject;
diff --git a/src/main/java/featurecat/lizzie/gui/GtpConsolePane.java b/src/main/java/featurecat/lizzie/gui/GtpConsolePane.java
index 4515a319b..293305e64 100644
--- a/src/main/java/featurecat/lizzie/gui/GtpConsolePane.java
+++ b/src/main/java/featurecat/lizzie/gui/GtpConsolePane.java
@@ -1,6 +1,7 @@
package featurecat.lizzie.gui;
import featurecat.lizzie.Lizzie;
+import featurecat.lizzie.util.WindowPosition;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
@@ -19,7 +20,6 @@
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.html.HTMLDocument;
-import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
import org.json.JSONArray;
@@ -29,7 +29,7 @@ public class GtpConsolePane extends JDialog {
// Display Comment
private HTMLDocument htmlDoc;
- private HTMLEditorKit htmlKit;
+ private LizziePane.HtmlKit htmlKit;
private StyleSheet htmlStyle;
private JScrollPane scrollPane;
private JTextPane console;
@@ -44,12 +44,8 @@ public GtpConsolePane(Window owner) {
super(owner);
setTitle("Gtp Console");
- boolean persisted =
- Lizzie.config.persistedUi != null
- && Lizzie.config.persistedUi.optJSONArray("gtp-console-position") != null
- && Lizzie.config.persistedUi.optJSONArray("gtp-console-position").length() == 4;
- if (persisted) {
- JSONArray pos = Lizzie.config.persistedUi.getJSONArray("gtp-console-position");
+ JSONArray pos = WindowPosition.gtpWindowPos();
+ if (pos != null) {
this.setBounds(pos.getInt(0), pos.getInt(1), pos.getInt(2), pos.getInt(3));
} else {
Insets oi = owner.getInsets();
@@ -60,7 +56,7 @@ public GtpConsolePane(Window owner) {
Math.max(owner.getHeight() + oi.top + oi.bottom, 300));
}
- htmlKit = new HTMLEditorKit();
+ htmlKit = new LizziePane.HtmlKit();
htmlDoc = (HTMLDocument) htmlKit.createDefaultDocument();
htmlStyle = htmlKit.getStyleSheet();
htmlStyle.addRule(Lizzie.config.gtpConsoleStyle);
diff --git a/src/main/java/featurecat/lizzie/gui/Input.java b/src/main/java/featurecat/lizzie/gui/Input.java
index 636f9f320..cb22f4d7c 100644
--- a/src/main/java/featurecat/lizzie/gui/Input.java
+++ b/src/main/java/featurecat/lizzie/gui/Input.java
@@ -3,8 +3,13 @@
import static java.awt.event.KeyEvent.*;
import featurecat.lizzie.Lizzie;
-import java.awt.event.*;
-import javax.swing.*;
+import java.awt.event.KeyEvent;
+import java.awt.event.KeyListener;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseListener;
+import java.awt.event.MouseMotionListener;
+import java.awt.event.MouseWheelEvent;
+import java.awt.event.MouseWheelListener;
public class Input implements MouseListener, KeyListener, MouseWheelListener, MouseMotionListener {
@Override
@@ -12,9 +17,13 @@ public void mouseClicked(MouseEvent e) {}
@Override
public void mousePressed(MouseEvent e) {
- if (e.getButton() == MouseEvent.BUTTON1) // left click
- Lizzie.frame.onClicked(e.getX(), e.getY());
- else if (e.getButton() == MouseEvent.BUTTON3) // right click
+ if (e.getButton() == MouseEvent.BUTTON1) { // left click
+ if (e.getClickCount() == 2) { // TODO: Maybe need to delay check
+ Lizzie.frame.onDoubleClicked(e.getX(), e.getY());
+ } else {
+ Lizzie.frame.onClicked(e.getX(), e.getY());
+ }
+ } else if (e.getButton() == MouseEvent.BUTTON3) // right click
undo();
}
@@ -171,6 +180,7 @@ public void keyPressed(KeyEvent e) {
// If any controls key is pressed, let's disable analysis mode.
// This is probably the user attempting to exit analysis mode.
boolean shouldDisableAnalysis = true;
+ int refreshType = 1;
switch (e.getKeyCode()) {
case VK_E:
@@ -227,7 +237,7 @@ public void keyPressed(KeyEvent e) {
case VK_N:
// stop the ponder
if (Lizzie.leelaz.isPondering()) Lizzie.leelaz.togglePonder();
- LizzieFrame.startNewGame();
+ Lizzie.frame.startGame();
break;
case VK_SPACE:
if (Lizzie.frame.isPlayingAgainstLeelaz) {
@@ -277,12 +287,12 @@ public void keyPressed(KeyEvent e) {
case VK_S:
// stop the ponder
if (Lizzie.leelaz.isPondering()) Lizzie.leelaz.togglePonder();
- LizzieFrame.saveFile();
+ Lizzie.frame.saveFile();
break;
case VK_O:
if (Lizzie.leelaz.isPondering()) Lizzie.leelaz.togglePonder();
- LizzieFrame.openFile();
+ Lizzie.frame.openFile();
break;
case VK_V:
@@ -325,8 +335,12 @@ public void keyPressed(KeyEvent e) {
case VK_W:
if (controlIsPressed(e)) {
Lizzie.config.toggleLargeWinrate();
+ refreshType = 2;
+ } else if (e.isAltDown()) {
+ Lizzie.frame.toggleDesignMode();
} else {
Lizzie.config.toggleShowWinrate();
+ refreshType = 2;
}
break;
@@ -336,6 +350,7 @@ public void keyPressed(KeyEvent e) {
case VK_G:
Lizzie.config.toggleShowVariationGraph();
+ refreshType = 2;
break;
case VK_T:
@@ -343,6 +358,7 @@ public void keyPressed(KeyEvent e) {
Lizzie.config.toggleShowCommentNodeColor();
} else {
Lizzie.config.toggleShowComment();
+ refreshType = 2;
}
break;
@@ -355,6 +371,7 @@ public void keyPressed(KeyEvent e) {
Lizzie.frame.copySgf();
} else {
Lizzie.config.toggleCoordinates();
+ refreshType = 2;
}
break;
@@ -410,11 +427,17 @@ public void keyPressed(KeyEvent e) {
break;
case VK_OPEN_BRACKET:
- if (Lizzie.frame.BoardPositionProportion > 0) Lizzie.frame.BoardPositionProportion--;
+ if (Lizzie.frame.boardPositionProportion > 0) {
+ Lizzie.frame.boardPositionProportion--;
+ refreshType = 2;
+ }
break;
case VK_CLOSE_BRACKET:
- if (Lizzie.frame.BoardPositionProportion < 8) Lizzie.frame.BoardPositionProportion++;
+ if (Lizzie.frame.boardPositionProportion < 8) {
+ Lizzie.frame.boardPositionProportion++;
+ refreshType = 2;
+ }
break;
case VK_K:
@@ -434,6 +457,7 @@ public void keyPressed(KeyEvent e) {
case VK_9:
if (controlIsPressed(e)) {
Lizzie.switchEngine(e.getKeyCode() - VK_0);
+ refreshType = 0;
}
break;
default:
@@ -442,7 +466,7 @@ public void keyPressed(KeyEvent e) {
if (shouldDisableAnalysis && Lizzie.board.inAnalysisMode()) Lizzie.board.toggleAnalysis();
- Lizzie.frame.repaint();
+ Lizzie.frame.refresh(refreshType);
}
private boolean wasPonderingWhenControlsShown = false;
@@ -453,29 +477,34 @@ public void keyReleased(KeyEvent e) {
case VK_X:
if (wasPonderingWhenControlsShown) Lizzie.leelaz.togglePonder();
Lizzie.frame.showControls = false;
- Lizzie.frame.repaint();
+ Lizzie.frame.refresh(1);
break;
case VK_Z:
stopTemporaryBoard();
- Lizzie.frame.repaint();
+ Lizzie.frame.refresh(1);
break;
default:
}
}
+ private long wheelWhen;
+
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if (Lizzie.frame.processCommentMouseWheelMoved(e)) {
return;
}
- if (Lizzie.board.inAnalysisMode()) Lizzie.board.toggleAnalysis();
- if (e.getWheelRotation() > 0) {
- redo();
- } else if (e.getWheelRotation() < 0) {
- undo();
+ if (e.getWhen() - wheelWhen > 0) {
+ wheelWhen = e.getWhen();
+ if (Lizzie.board.inAnalysisMode()) Lizzie.board.toggleAnalysis();
+ if (e.getWheelRotation() > 0) {
+ redo();
+ } else if (e.getWheelRotation() < 0) {
+ undo();
+ }
+ Lizzie.frame.refresh();
}
- Lizzie.frame.repaint();
}
}
diff --git a/src/main/java/featurecat/lizzie/gui/LizzieFrame.java b/src/main/java/featurecat/lizzie/gui/LizzieFrame.java
index 8a9daf535..5721f1507 100644
--- a/src/main/java/featurecat/lizzie/gui/LizzieFrame.java
+++ b/src/main/java/featurecat/lizzie/gui/LizzieFrame.java
@@ -4,7 +4,6 @@
import static java.awt.image.BufferedImage.TYPE_INT_RGB;
import static java.lang.Math.max;
import static java.lang.Math.min;
-import static java.lang.Math.round;
import com.jhlabs.image.GaussianFilter;
import featurecat.lizzie.Lizzie;
@@ -16,6 +15,7 @@
import featurecat.lizzie.rules.BoardHistoryNode;
import featurecat.lizzie.rules.GIBParser;
import featurecat.lizzie.rules.SGFParser;
+import featurecat.lizzie.util.Utils;
import java.awt.*;
import java.awt.BasicStroke;
import java.awt.Color;
@@ -46,11 +46,13 @@
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
+import javax.swing.text.html.HTMLDocument;
+import javax.swing.text.html.StyleSheet;
import org.json.JSONArray;
import org.json.JSONObject;
/** The window used to display the game. */
-public class LizzieFrame extends JFrame {
+public class LizzieFrame extends MainFrame {
private static final ResourceBundle resourceBundle =
ResourceBundle.getBundle("l10n.DisplayStrings");
@@ -97,18 +99,10 @@ public class LizzieFrame extends JFrame {
private static VariationTree variationTree;
private static WinrateGraph winrateGraph;
- public static Font uiFont;
- public static Font winrateFont;
-
private final BufferStrategy bs;
private static final int[] outOfBoundCoordinate = new int[] {-1, -1};
public int[] mouseOverCoordinate = outOfBoundCoordinate;
- public boolean showControls = false;
- public boolean isPlayingAgainstLeelaz = false;
- public boolean playerIsBlack = true;
- public int winRateGridLines = 3;
- public int BoardPositionProportion = Lizzie.config.boardPositionProportion;
private long lastAutosaveTime = System.currentTimeMillis();
private boolean isReplayVariation = false;
@@ -117,6 +111,9 @@ public class LizzieFrame extends JFrame {
private String playerTitle = "";
// Display Comment
+ private HTMLDocument htmlDoc;
+ private LizziePane.HtmlKit htmlKit;
+ private StyleSheet htmlStyle;
private JScrollPane scrollPane;
private JTextPane commentPane;
private BufferedImage cachedCommentImage = new BufferedImage(1, 1, TYPE_INT_ARGB);
@@ -165,8 +162,9 @@ public LizzieFrame() {
&& Lizzie.config.persistedUi.optJSONArray("main-window-position").length() == 4) {
JSONArray pos = Lizzie.config.persistedUi.getJSONArray("main-window-position");
this.setBounds(pos.getInt(0), pos.getInt(1), pos.getInt(2), pos.getInt(3));
- this.BoardPositionProportion =
- Lizzie.config.persistedUi.optInt("board-postion-propotion", this.BoardPositionProportion);
+ this.boardPositionProportion =
+ Lizzie.config.persistedUi.optInt(
+ "board-position-proportion", this.boardPositionProportion);
} else {
setSize(960, 600);
setLocationRelativeTo(null); // Start centered, needs to be called *after* setSize...
@@ -186,11 +184,35 @@ public LizzieFrame() {
setExtendedState(Frame.MAXIMIZED_BOTH);
}
+ htmlKit = new LizziePane.HtmlKit();
+ htmlDoc = (HTMLDocument) htmlKit.createDefaultDocument();
+ htmlStyle = htmlKit.getStyleSheet();
+ String style =
+ "body {background:#"
+ + String.format(
+ "%02x%02x%02x",
+ Lizzie.config.commentBackgroundColor.getRed(),
+ Lizzie.config.commentBackgroundColor.getGreen(),
+ Lizzie.config.commentBackgroundColor.getBlue())
+ + "; color:#"
+ + String.format(
+ "%02x%02x%02x",
+ Lizzie.config.commentFontColor.getRed(),
+ Lizzie.config.commentFontColor.getGreen(),
+ Lizzie.config.commentFontColor.getBlue())
+ + "; font-family:"
+ + Lizzie.config.fontName
+ + ", Consolas, Menlo, Monaco, 'Ubuntu Mono', monospace;"
+ + (Lizzie.config.commentFontSize > 0
+ ? "font-size:" + Lizzie.config.commentFontSize
+ : "")
+ + "}";
+ htmlStyle.addRule(style);
commentPane = new JTextPane();
+ commentPane.setBorder(BorderFactory.createEmptyBorder());
+ commentPane.setEditorKit(htmlKit);
+ commentPane.setDocument(htmlDoc);
commentPane.setEditable(false);
- commentPane.setMargin(new Insets(5, 5, 5, 5));
- commentPane.setBackground(Lizzie.config.commentBackgroundColor);
- commentPane.setForeground(Lizzie.config.commentFontColor);
scrollPane = new JScrollPane();
scrollPane.setViewportView(commentPane);
scrollPane.setBorder(null);
@@ -263,12 +285,12 @@ public void clear() {
}
}
- public static void openConfigDialog() {
+ public void openConfigDialog() {
ConfigDialog configDialog = new ConfigDialog();
configDialog.setVisible(true);
}
- public static void openChangeMoveDialog() {
+ public void openChangeMoveDialog() {
ChangeMoveDialog changeMoveDialog = new ChangeMoveDialog();
changeMoveDialog.setVisible(true);
}
@@ -283,38 +305,49 @@ public void toggleGtpConsole() {
}
}
- public static void startNewGame() {
+ @Override
+ public void startGame() {
GameInfo gameInfo = Lizzie.board.getHistory().getGameInfo();
- NewGameDialog newGameDialog = new NewGameDialog();
- newGameDialog.setGameInfo(gameInfo);
- newGameDialog.setVisible(true);
- boolean playerIsBlack = newGameDialog.playerIsBlack();
- newGameDialog.dispose();
- if (newGameDialog.isCancelled()) return;
+ NewGameDialog gameDialog = new NewGameDialog();
+ gameDialog.setGameInfo(gameInfo);
+ gameDialog.setVisible(true);
+ boolean playerIsBlack = gameDialog.playerIsBlack();
+ boolean isNewGame = gameDialog.isNewGame();
+ // gameDialog.dispose();
+ if (gameDialog.isCancelled()) return;
- Lizzie.board.clear();
- Lizzie.board.getHistory().setGameInfo(gameInfo);
+ if (isNewGame) {
+ Lizzie.board.clear();
+ }
Lizzie.leelaz.sendCommand("komi " + gameInfo.getKomi());
- Lizzie.leelaz.sendCommand(
- "time_settings 0 "
- + Lizzie.config.config.getJSONObject("leelaz").getInt("max-game-thinking-time-seconds")
- + " 1");
+ Lizzie.leelaz.time_settings();
Lizzie.frame.playerIsBlack = playerIsBlack;
+ Lizzie.frame.isNewGame = isNewGame;
Lizzie.frame.isPlayingAgainstLeelaz = true;
boolean isHandicapGame = gameInfo.getHandicap() != 0;
- if (isHandicapGame) {
- Lizzie.board.getHistory().getData().blackToPlay = false;
- Lizzie.leelaz.sendCommand("fixed_handicap " + gameInfo.getHandicap());
- if (playerIsBlack) Lizzie.leelaz.genmove("W");
- } else if (!playerIsBlack) {
- Lizzie.leelaz.genmove("B");
+ if (isNewGame) {
+ Lizzie.board.getHistory().setGameInfo(gameInfo);
+ if (isHandicapGame) {
+ Lizzie.board.getHistory().getData().blackToPlay = false;
+ Lizzie.leelaz.sendCommand("fixed_handicap " + gameInfo.getHandicap());
+ if (playerIsBlack) Lizzie.leelaz.genmove("W");
+ } else if (!playerIsBlack) {
+ Lizzie.leelaz.genmove("B");
+ }
+ } else {
+ Lizzie.board.getHistory().setGameInfo(gameInfo);
+ if (Lizzie.frame.playerIsBlack != Lizzie.board.getData().blackToPlay) {
+ if (!Lizzie.leelaz.isThinking) {
+ Lizzie.leelaz.genmove((Lizzie.board.getData().blackToPlay ? "B" : "W"));
+ }
+ }
}
}
- public static void editGameInfo() {
+ public void editGameInfo() {
GameInfo gameInfo = Lizzie.board.getHistory().getGameInfo();
GameInfoDialog gameInfoDialog = new GameInfoDialog();
@@ -324,7 +357,7 @@ public static void editGameInfo() {
gameInfoDialog.dispose();
}
- public static void saveFile() {
+ public void saveFile() {
FileNameExtensionFilter filter = new FileNameExtensionFilter("*.sgf", "SGF");
JSONObject filesystem = Lizzie.config.persisted.getJSONObject("filesystem");
JFileChooser chooser = new JFileChooser(filesystem.getString("last-folder"));
@@ -360,7 +393,7 @@ public static void saveFile() {
}
}
- public static void openFile() {
+ public void openFile() {
FileNameExtensionFilter filter = new FileNameExtensionFilter("*.sgf or *.gib", "SGF", "GIB");
JSONObject filesystem = Lizzie.config.persisted.getJSONObject("filesystem");
JFileChooser chooser = new JFileChooser(filesystem.getString("last-folder"));
@@ -371,7 +404,7 @@ public static void openFile() {
if (result == JFileChooser.APPROVE_OPTION) loadFile(chooser.getSelectedFile());
}
- public static void loadFile(File file) {
+ public void loadFile(File file) {
JSONObject filesystem = Lizzie.config.persisted.getJSONObject("filesystem");
if (!(file.getPath().endsWith(".sgf") || file.getPath().endsWith(".gib"))) {
file = new File(file.getPath() + ".sgf");
@@ -404,7 +437,7 @@ public static void loadFile(File file) {
private boolean cachedLargeWinrate = true;
private boolean cachedShowComment = true;
private boolean redrawBackgroundAnyway = false;
- private int cachedBoardPositionProportion = BoardPositionProportion;
+ private int cachedBoardPositionProportion = boardPositionProportion;
/**
* Draws the game board and interface
@@ -420,6 +453,7 @@ public void paint(Graphics g0) {
Optional backgroundG;
if (cachedBackgroundWidth != width
|| cachedBackgroundHeight != height
+ || cachedBoardPositionProportion != boardPositionProportion
|| redrawBackgroundAnyway) {
backgroundG = Optional.of(createBackground());
} else {
@@ -435,10 +469,20 @@ public void paint(Graphics g0) {
int bottomInset = this.getInsets().bottom;
int maxBound = Math.max(width, height);
+ boolean noWinrate = !Lizzie.config.showWinrate;
+ boolean noVariation = !Lizzie.config.showVariationGraph;
+ boolean noBasic = !Lizzie.config.showCaptured;
+ boolean noSubBoard = !Lizzie.config.showSubBoard;
+ boolean noComment = !Lizzie.config.showComment;
// board
int maxSize = (int) (min(width - leftInset - rightInset, height - topInset - bottomInset));
maxSize = max(maxSize, Board.boardSize + 5); // don't let maxWidth become too small
- int boardX = (width - maxSize) / 8 * BoardPositionProportion;
+ int boardX = (width - maxSize) / 8 * boardPositionProportion;
+ if (noBasic && noWinrate && noSubBoard) {
+ boardX = leftInset;
+ } else if (noVariation && noComment) {
+ boardX = (width - maxSize);
+ }
int boardY = topInset + (height - topInset - bottomInset - maxSize) / 2;
int panelMargin = (int) (maxSize * 0.02);
@@ -495,7 +539,7 @@ public void paint(Graphics g0) {
if (width >= height) {
// Landscape mode
- if (Lizzie.config.showLargeSubBoard()) {
+ if (Lizzie.config.showLargeSubBoard() && !noSubBoard) {
boardX = width - maxSize - panelMargin;
int spaceW = boardX - panelMargin - leftInset;
int spaceH = height - topInset - bottomInset;
@@ -503,7 +547,7 @@ public void paint(Graphics g0) {
int panelH = spaceH / 4;
// captured stones
- capw = panelW;
+ capw = (noVariation && noComment) ? spaceW : panelW;
caph = (int) (panelH * 0.2);
// move statistics (winrate bar)
staty = capy + caph;
@@ -522,7 +566,7 @@ public void paint(Graphics g0) {
subBoardWidth = spaceW;
subBoardHeight = ponderingY - subBoardY;
subBoardLength = Math.min(subBoardWidth, subBoardHeight);
- subBoardX = statx + (statw + vw - subBoardLength) / 2;
+ subBoardX = statx + (spaceW - subBoardLength) / 2;
} else if (Lizzie.config.showLargeWinrate()) {
boardX = width - maxSize - panelMargin;
int spaceW = boardX - panelMargin - leftInset;
@@ -555,7 +599,7 @@ public void paint(Graphics g0) {
}
} else {
// Portrait mode
- if (Lizzie.config.showLargeSubBoard()) {
+ if (Lizzie.config.showLargeSubBoard() && !noSubBoard) {
// board
maxSize = (int) (maxSize * 0.8);
boardY = height - maxSize - bottomInset;
@@ -588,7 +632,7 @@ public void paint(Graphics g0) {
subBoardY = capy + (gry + grh - capy - subBoardLength) / 2;
// pondering message
ponderingY = height;
- } else if (Lizzie.config.showLargeWinrate()) {
+ } else if (Lizzie.config.showLargeWinrate() && !noWinrate) {
// board
maxSize = (int) (maxSize * 0.8);
boardY = height - maxSize - bottomInset;
@@ -809,9 +853,9 @@ private Graphics2D createBackground() {
cachedShowLargeSubBoard = Lizzie.config.showLargeSubBoard();
cachedLargeWinrate = Lizzie.config.showLargeWinrate();
cachedShowComment = Lizzie.config.showComment;
- cachedBoardPositionProportion = BoardPositionProportion;
+ cachedBoardPositionProportion = boardPositionProportion;
- redrawBackgroundAnyway = false;
+ // redrawBackgroundAnyway = false;
Graphics2D g = cachedBackground.createGraphics();
g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
@@ -834,7 +878,7 @@ private void drawContainer(Graphics g, int vx, int vy, int vw, int vh) {
|| vy + vh > cachedBackground.getMinY() + cachedBackground.getHeight()) {
return;
}
-
+ redrawBackgroundAnyway = false;
BufferedImage result = new BufferedImage(vw, vh, TYPE_INT_ARGB);
filter20.filter(cachedBackground.getSubimage(vx, vy, vw, vh), result);
g.drawImage(result, vx, vy, null);
@@ -849,7 +893,7 @@ private void drawPonderingState(Graphics2D g, String text, int x, int y, double
if (Lizzie.leelaz != null && Lizzie.leelaz.isLoaded()) {
int mainBoardX = boardRenderer.getLocation().x;
if (getWidth() > getHeight() && (mainBoardX > x) && stringWidth > (mainBoardX - x)) {
- text = truncateStringByWidth(text, fm, mainBoardX - x);
+ text = Utils.truncateStringByWidth(text, fm, mainBoardX - x);
stringWidth = fm.stringWidth(text);
}
}
@@ -878,62 +922,11 @@ private void drawPonderingState(Graphics2D g, String text, int x, int y, double
text, x + (width - stringWidth) / 2, y + stringHeight + (height - stringHeight) / 2);
}
- /**
- * @return a shorter, rounded string version of playouts. e.g. 345 -> 345, 1265 -> 1.3k, 44556 ->
- * 45k, 133523 -> 134k, 1234567 -> 1.2m
- */
- public String getPlayoutsString(int playouts) {
- if (playouts >= 1_000_000) {
- double playoutsDouble = (double) playouts / 100_000; // 1234567 -> 12.34567
- return round(playoutsDouble) / 10.0 + "m";
- } else if (playouts >= 10_000) {
- double playoutsDouble = (double) playouts / 1_000; // 13265 -> 13.265
- return round(playoutsDouble) + "k";
- } else if (playouts >= 1_000) {
- double playoutsDouble = (double) playouts / 100; // 1265 -> 12.65
- return round(playoutsDouble) / 10.0 + "k";
- } else {
- return String.valueOf(playouts);
- }
- }
-
- /**
- * Truncate text that is too long for the given width
- *
- * @param line
- * @param fm
- * @param fitWidth
- * @return fitted
- */
- private static String truncateStringByWidth(String line, FontMetrics fm, int fitWidth) {
- if (line.isEmpty()) {
- return "";
- }
- int width = fm.stringWidth(line);
- if (width > fitWidth) {
- int guess = line.length() * fitWidth / width;
- String before = line.substring(0, guess).trim();
- width = fm.stringWidth(before);
- if (width > fitWidth) {
- int diff = width - fitWidth;
- int i = 0;
- for (; (diff > 0 && i < 5); i++) {
- diff = diff - fm.stringWidth(line.substring(guess - i - 1, guess - i));
- }
- return line.substring(0, guess - i).trim();
- } else {
- return before;
- }
- } else {
- return line;
- }
- }
-
private GaussianFilter filter20 = new GaussianFilter(20);
private GaussianFilter filter10 = new GaussianFilter(10);
/** Display the controls */
- void drawControls() {
+ public void drawControls() {
userAlreadyKnowsAboutCommandString = true;
cachedImage = new BufferedImage(getWidth(), getHeight(), TYPE_INT_ARGB);
@@ -1210,10 +1203,14 @@ private void drawCaptured(Graphics2D g, int posX, int posY, int width, int heigh
int diam = height / 3;
int smallDiam = diam / 2;
int bdiam = diam, wdiam = diam;
- if (Lizzie.board.inScoreMode()) {
- // do nothing
- } else if (Lizzie.board.getHistory().isBlacksTurn()) {
- wdiam = smallDiam;
+ if (Lizzie.board != null) {
+ if (Lizzie.board.inScoreMode()) {
+ // do nothing
+ } else if (Lizzie.board.getHistory().isBlacksTurn()) {
+ wdiam = smallDiam;
+ } else {
+ bdiam = smallDiam;
+ }
} else {
bdiam = smallDiam;
}
@@ -1228,6 +1225,9 @@ private void drawCaptured(Graphics2D g, int posX, int posY, int width, int heigh
// Draw captures
String bval, wval;
setPanelFont(g, (float) (height * 0.18));
+ if (Lizzie.board == null) {
+ return;
+ }
if (Lizzie.board.inScoreMode()) {
double score[] = Lizzie.board.getScore(Lizzie.board.scoreStones());
bval = String.format("%.0f", score[0]);
@@ -1283,6 +1283,20 @@ public void onClicked(int x, int y) {
repaint();
}
+ public void onDoubleClicked(int x, int y) {
+ // Check for board double click
+ Optional boardCoordinates = boardRenderer.convertScreenToCoordinates(x, y);
+ if (boardCoordinates.isPresent()) {
+ int[] coords = boardCoordinates.get();
+ if (!isPlayingAgainstLeelaz) {
+ int moveNumber = Lizzie.board.moveNumberByCoord(coords);
+ if (moveNumber > 0) {
+ Lizzie.board.goToMoveNumberBeyondBranch(moveNumber);
+ }
+ }
+ }
+ }
+
private final Consumer placeVariation =
v -> Board.asCoordinates(v).ifPresent(c -> Lizzie.board.place(c[0], c[1]));
@@ -1327,6 +1341,7 @@ public void onMouseDragged(int x, int y) {
*
* @return true when the scroll event was processed by this method
*/
+ @Override
public boolean processCommentMouseWheelMoved(MouseWheelEvent e) {
if (Lizzie.config.showComment && commentRect.contains(e.getX(), e.getY())) {
scrollPane.dispatchEvent(e);
@@ -1475,6 +1490,7 @@ private void drawComment(Graphics2D g, int x, int y, int w, int h) {
}
Font font = new Font(Lizzie.config.fontName, Font.PLAIN, fontSize);
commentPane.setFont(font);
+ comment = comment.replaceAll("(\r\n)|(\n)", "
").replaceAll(" ", " ");
commentPane.setText(comment);
commentPane.setSize(w, h);
createCommentImage(!comment.equals(this.cachedComment), w, h);
@@ -1489,57 +1505,6 @@ private void drawComment(Graphics2D g, int x, int y, int w, int h) {
cachedComment = comment;
}
- public double lastWinrateDiff(BoardHistoryNode node) {
-
- // Last winrate
- Optional lastNode = node.previous().flatMap(n -> Optional.of(n.getData()));
- boolean validLastWinrate = lastNode.map(d -> d.getPlayouts() > 0).orElse(false);
- double lastWR = validLastWinrate ? lastNode.get().winrate : 50;
-
- // Current winrate
- BoardData data = node.getData();
- boolean validWinrate = false;
- double curWR = 50;
- if (data == Lizzie.board.getHistory().getData()) {
- Leelaz.WinrateStats stats = Lizzie.leelaz.getWinrateStats();
- curWR = stats.maxWinrate;
- validWinrate = (stats.totalPlayouts > 0);
- if (isPlayingAgainstLeelaz
- && playerIsBlack == !Lizzie.board.getHistory().getData().blackToPlay) {
- validWinrate = false;
- }
- } else {
- validWinrate = (data.getPlayouts() > 0);
- curWR = validWinrate ? data.winrate : 100 - lastWR;
- }
-
- // Last move difference winrate
- if (validLastWinrate && validWinrate) {
- return 100 - lastWR - curWR;
- } else {
- return 0;
- }
- }
-
- public Color getBlunderNodeColor(BoardHistoryNode node) {
- if (Lizzie.config.nodeColorMode == 1 && node.getData().blackToPlay
- || Lizzie.config.nodeColorMode == 2 && !node.getData().blackToPlay) {
- return Color.WHITE;
- }
- double diffWinrate = lastWinrateDiff(node);
- Optional st =
- diffWinrate >= 0
- ? Lizzie.config.blunderWinrateThresholds.flatMap(
- l -> l.stream().filter(t -> (t > 0 && t <= diffWinrate)).reduce((f, s) -> s))
- : Lizzie.config.blunderWinrateThresholds.flatMap(
- l -> l.stream().filter(t -> (t < 0 && t >= diffWinrate)).reduce((f, s) -> f));
- if (st.isPresent()) {
- return Lizzie.config.blunderNodeColors.map(m -> m.get(st.get())).get();
- } else {
- return Color.WHITE;
- }
- }
-
public void replayBranch() {
if (isReplayVariation) return;
int replaySteps = boardRenderer.getReplayBranch();
diff --git a/src/main/java/featurecat/lizzie/gui/LizzieLayout.java b/src/main/java/featurecat/lizzie/gui/LizzieLayout.java
new file mode 100644
index 000000000..9a5f03d5b
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/gui/LizzieLayout.java
@@ -0,0 +1,642 @@
+package featurecat.lizzie.gui;
+
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+import featurecat.lizzie.Lizzie;
+import featurecat.lizzie.rules.Board;
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.awt.LayoutManager2;
+
+public class LizzieLayout implements LayoutManager2, java.io.Serializable {
+ int hgap;
+ int vgap;
+
+ private Component mainBoard;
+ private Component subBoard;
+ private Component winratePane;
+ private Component variationPane;
+ private Component basicInfoPane;
+ private Component commentPane;
+ private Component consolePane;
+
+ public static final String MAIN_BOARD = "mainBoard";
+ public static final String SUB_BOARD = "subBoard";
+ public static final String WINRATE = "winratePane";
+ public static final String VARIATION = "variationPane";
+ public static final String BASIC_INFO = "basicInfoPane";
+ public static final String COMMENT = "commentPane";
+ public static final String CONSOLE = "consolePane";
+
+ public static final int MODE_FUSION = 0;
+ public static final int MODE_SEPARATION = 1;
+
+ private int mode = 0;
+
+ public LizzieLayout() {
+ this(3, 0);
+ }
+
+ public LizzieLayout(int hgap, int vgap) {
+ this.hgap = hgap;
+ this.vgap = vgap;
+ }
+
+ public int getHgap() {
+ return hgap;
+ }
+
+ public void setHgap(int hgap) {
+ this.hgap = hgap;
+ }
+
+ public int getVgap() {
+ return vgap;
+ }
+
+ public void setVgap(int vgap) {
+ this.vgap = vgap;
+ }
+
+ public void addLayoutComponent(Component comp, Object constraints) {
+ synchronized (comp.getTreeLock()) {
+ if ((constraints == null) || (constraints instanceof String)) {
+ addLayoutComponent((String) constraints, comp);
+ } else {
+ throw new IllegalArgumentException(
+ "cannot add to layout: constraint must be a string (or null)");
+ }
+ }
+ }
+
+ /** @deprecated replaced by addLayoutComponent(Component, Object)
. */
+ @Deprecated
+ public void addLayoutComponent(String name, Component comp) {
+ synchronized (comp.getTreeLock()) {
+ if (name == null) {
+ name = MAIN_BOARD;
+ }
+
+ if (BASIC_INFO.equals(name)) {
+ basicInfoPane = comp;
+ } else if (MAIN_BOARD.equals(name)) {
+ mainBoard = comp;
+ } else if (VARIATION.equals(name)) {
+ variationPane = comp;
+ } else if (WINRATE.equals(name)) {
+ winratePane = comp;
+ } else if (SUB_BOARD.equals(name)) {
+ subBoard = comp;
+ } else if (COMMENT.equals(name)) {
+ commentPane = comp;
+ } else if (CONSOLE.equals(name)) {
+ consolePane = comp;
+ } else {
+ throw new IllegalArgumentException("cannot add to layout: unknown constraint: " + name);
+ }
+ }
+ }
+
+ public void removeLayoutComponent(Component comp) {
+ synchronized (comp.getTreeLock()) {
+ if (comp == basicInfoPane) {
+ basicInfoPane = null;
+ } else if (comp == mainBoard) {
+ mainBoard = null;
+ } else if (comp == variationPane) {
+ variationPane = null;
+ } else if (comp == winratePane) {
+ winratePane = null;
+ } else if (comp == subBoard) {
+ subBoard = null;
+ }
+ if (comp == commentPane) {
+ commentPane = null;
+ } else if (comp == consolePane) {
+ consolePane = null;
+ }
+ }
+ }
+
+ public Component getLayoutComponent(Object constraints) {
+ if (BASIC_INFO.equals(constraints)) {
+ return basicInfoPane;
+ } else if (MAIN_BOARD.equals(constraints)) {
+ return mainBoard;
+ } else if (SUB_BOARD.equals(constraints)) {
+ return subBoard;
+ } else if (VARIATION.equals(constraints)) {
+ return variationPane;
+ } else if (WINRATE.equals(constraints)) {
+ return winratePane;
+ } else if (COMMENT.equals(constraints)) {
+ return commentPane;
+ } else if (CONSOLE.equals(constraints)) {
+ return consolePane;
+ } else {
+ throw new IllegalArgumentException(
+ "cannot get component: unknown constraint: " + constraints);
+ }
+ }
+
+ public Component getLayoutComponent(Container target, Object constraints) {
+ boolean ltr = target.getComponentOrientation().isLeftToRight();
+ Component result = null;
+
+ if (MAIN_BOARD.equals(constraints)) {
+ result = mainBoard;
+ } else if (SUB_BOARD.equals(constraints)) {
+ result = subBoard;
+ } else if (COMMENT.equals(constraints)) {
+ result = commentPane;
+ } else if (VARIATION.equals(constraints)) {
+ result = variationPane;
+ } else if (WINRATE.equals(constraints)) {
+ result = winratePane;
+ } else if (BASIC_INFO.equals(constraints)) {
+ result = basicInfoPane;
+ } else {
+ throw new IllegalArgumentException(
+ "cannot get component: invalid constraint: " + constraints);
+ }
+
+ return result;
+ }
+
+ public Object getConstraints(Component comp) {
+ if (comp == null) {
+ return null;
+ }
+ if (comp == basicInfoPane) {
+ return BASIC_INFO;
+ } else if (comp == mainBoard) {
+ return MAIN_BOARD;
+ } else if (comp == subBoard) {
+ return SUB_BOARD;
+ } else if (comp == variationPane) {
+ return VARIATION;
+ } else if (comp == winratePane) {
+ return WINRATE;
+ } else if (comp == commentPane) {
+ return COMMENT;
+ } else if (comp == consolePane) {
+ return CONSOLE;
+ }
+ return null;
+ }
+
+ public Dimension minimumLayoutSize(Container target) {
+ synchronized (target.getTreeLock()) {
+ Dimension dim = new Dimension(0, 0);
+
+ boolean ltr = target.getComponentOrientation().isLeftToRight();
+ Component c = null;
+
+ if ((c = getChild(WINRATE, ltr)) != null) {
+ Dimension d = c.getMinimumSize();
+ dim.width += d.width + hgap;
+ dim.height = Math.max(d.height, dim.height);
+ }
+ if ((c = getChild(VARIATION, ltr)) != null) {
+ Dimension d = c.getMinimumSize();
+ dim.width += d.width + hgap;
+ dim.height = Math.max(d.height, dim.height);
+ }
+ if ((c = getChild(BASIC_INFO, ltr)) != null) {
+ Dimension d = c.getMinimumSize();
+ dim.width += d.width;
+ dim.height = Math.max(d.height, dim.height);
+ }
+ if ((c = getChild(MAIN_BOARD, ltr)) != null) {
+ Dimension d = c.getMinimumSize();
+ dim.width = Math.max(d.width, dim.width);
+ dim.height += d.height + vgap;
+ }
+ if ((c = getChild(SUB_BOARD, ltr)) != null) {
+ Dimension d = c.getMinimumSize();
+ dim.width = Math.max(d.width, dim.width);
+ dim.height += d.height + vgap;
+ }
+
+ Insets insets = target.getInsets();
+ dim.width += insets.left + insets.right;
+ dim.height += insets.top + insets.bottom;
+
+ return dim;
+ }
+ }
+
+ public Dimension preferredLayoutSize(Container target) {
+ synchronized (target.getTreeLock()) {
+ Dimension dim = new Dimension(0, 0);
+
+ boolean ltr = target.getComponentOrientation().isLeftToRight();
+ Component c = null;
+
+ if ((c = getChild(WINRATE, ltr)) != null) {
+ Dimension d = c.getPreferredSize();
+ dim.width += d.width + hgap;
+ dim.height = Math.max(d.height, dim.height);
+ }
+ if ((c = getChild(VARIATION, ltr)) != null) {
+ Dimension d = c.getPreferredSize();
+ dim.width += d.width + hgap;
+ dim.height = Math.max(d.height, dim.height);
+ }
+ if ((c = getChild(BASIC_INFO, ltr)) != null) {
+ Dimension d = c.getPreferredSize();
+ dim.width += d.width;
+ dim.height = Math.max(d.height, dim.height);
+ }
+ if ((c = getChild(MAIN_BOARD, ltr)) != null) {
+ Dimension d = c.getPreferredSize();
+ dim.width = Math.max(d.width, dim.width);
+ dim.height += d.height + vgap;
+ }
+ if ((c = getChild(SUB_BOARD, ltr)) != null) {
+ Dimension d = c.getPreferredSize();
+ dim.width = Math.max(d.width, dim.width);
+ dim.height += d.height + vgap;
+ }
+
+ Insets insets = target.getInsets();
+ dim.width += insets.left + insets.right;
+ dim.height += insets.top + insets.bottom;
+
+ return dim;
+ }
+ }
+
+ public Dimension maximumLayoutSize(Container target) {
+ return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
+ }
+
+ public float getLayoutAlignmentX(Container parent) {
+ return 0.5f;
+ }
+
+ public float getLayoutAlignmentY(Container parent) {
+ return 0.5f;
+ }
+
+ public void invalidateLayout(Container target) {}
+
+ public void layoutContainer(Container target) {
+ if (mode != MODE_FUSION) {
+ return;
+ }
+ synchronized (target.getTreeLock()) {
+ Container main = getMain(target);
+ Insets insets = target.getInsets();
+
+ int x = target.getX();
+ int y = target.getY();
+ int width = target.getWidth();
+ int height = target.getHeight();
+
+ // layout parameters
+
+ int topInset = insets.top;
+ int leftInset = insets.left;
+ int rightInset = insets.right;
+ int bottomInset = insets.bottom;
+ int maxBound = Math.max(width, height);
+
+ boolean ltr = target.getComponentOrientation().isLeftToRight();
+ Component c = null;
+ boolean noWinrate = (getChild(WINRATE, ltr) == null || !Lizzie.config.showWinrate);
+ boolean noVariation = (getChild(VARIATION, ltr) == null || !Lizzie.config.showVariationGraph);
+ boolean noBasic = (getChild(BASIC_INFO, ltr) == null || !Lizzie.config.showCaptured);
+ boolean noSubBoard = (getChild(SUB_BOARD, ltr) == null || !Lizzie.config.showSubBoard);
+ boolean noComment = (getChild(COMMENT, ltr) == null || !Lizzie.config.showComment);
+ // boolean onlyMainBoard = noWinrate && noVariation && noBasic && noSubBoard &&
+ // noComment;
+
+ // board
+ int maxSize = (int) (min(width - leftInset - rightInset, height - topInset - bottomInset));
+ maxSize =
+ BoardRenderer.availableLength(
+ max(maxSize, Board.boardSize + 5),
+ Lizzie.config.showCoordinates); // don't let maxWidth become too small
+ int boardX =
+ (width - maxSize)
+ / 8
+ * ((main == null || !(main instanceof LizzieMain))
+ ? Lizzie.config.boardPositionProportion
+ : ((LizzieMain) main).boardPositionProportion);
+ if (noBasic && noWinrate && noSubBoard) {
+ boardX = leftInset;
+ } else if (noVariation && noComment) {
+ boardX = (width - maxSize);
+ }
+ int boardY = topInset + (height - topInset - bottomInset - maxSize) / 2;
+
+ int panelMargin = (int) (maxSize * 0.02);
+
+ // captured stones
+ int capx = leftInset;
+ int capy = topInset;
+ int capw = boardX - panelMargin - leftInset;
+ int caph = maxSize / 8;
+
+ // move statistics (winrate bar)
+ // boardX equals width of space on each side
+ int statx = capx;
+ int staty = capy + caph;
+ int statw = capw;
+ int stath = maxSize / 10;
+
+ // winrate graph
+ int grx = statx;
+ int gry = staty + stath;
+ int grw = statw;
+ int grh = maxSize / 3;
+
+ // variation tree container
+ int vx = boardX + maxSize + panelMargin;
+ int vy = capy;
+ int vw = width - vx - rightInset;
+ int vh = height - vy - bottomInset;
+
+ // pondering message
+ double ponderingSize = .02;
+ int ponderingY =
+ height - bottomInset - (int) (maxSize * 0.033) - (int) (maxBound * ponderingSize);
+
+ // subboard
+ int subBoardY = gry + grh + 1;
+ int subBoardWidth = grw;
+ int subBoardHeight = ponderingY - subBoardY;
+ int subBoardLength = BoardRenderer.availableLength(min(subBoardWidth, subBoardHeight), false);
+ int subBoardX = statx + (statw - subBoardLength) / 2;
+
+ if (width >= height) {
+ // Landscape mode
+ if (Lizzie.config.showLargeSubBoard() && !noSubBoard) {
+ boardX = width - maxSize - panelMargin;
+ int spaceW = boardX - panelMargin - leftInset;
+ int spaceH = height - topInset - bottomInset;
+ int panelW = spaceW / 2;
+ int panelH = spaceH / 4;
+
+ // captured stones
+ capw = (noVariation && noComment) ? spaceW : panelW;
+ caph = (int) (panelH * 0.2);
+ // move statistics (winrate bar)
+ staty = capy + caph;
+ statw = capw;
+ stath = (int) (panelH * 0.4);
+ // winrate graph
+ gry = staty + stath;
+ grw = statw;
+ grh = panelH - caph - stath;
+ // variation tree container
+ vx = statx + statw;
+ vw = panelW;
+ vh = panelH;
+ // subboard
+ subBoardY = gry + grh;
+ subBoardWidth = spaceW;
+ subBoardHeight = ponderingY - subBoardY;
+ subBoardLength =
+ BoardRenderer.availableLength(Math.min(subBoardWidth, subBoardHeight), false);
+ subBoardX = statx + (spaceW - subBoardLength) / 2;
+ } else if (Lizzie.config.showLargeWinrate() && !noWinrate) {
+ boardX = width - maxSize - panelMargin;
+ int spaceW = boardX - panelMargin - leftInset;
+ int spaceH = height - topInset - bottomInset;
+ int panelW = spaceW / 2;
+ int panelH = spaceH / 4;
+
+ // captured stones
+ capy = topInset + panelH + 1;
+ capw = spaceW;
+ caph = (int) ((ponderingY - topInset - panelH) * 0.15);
+ // move statistics (winrate bar)
+ staty = capy + caph;
+ statw = capw;
+ stath = caph;
+ // winrate graph
+ gry = staty + stath;
+ grw = statw;
+ grh = ponderingY - gry;
+ // variation tree container
+ vx = leftInset + panelW;
+ vw = panelW;
+ vh = panelH;
+ // subboard
+ subBoardY = topInset;
+ subBoardWidth = panelW - leftInset;
+ subBoardHeight = panelH;
+ subBoardLength =
+ BoardRenderer.availableLength(Math.min(subBoardWidth, subBoardHeight), false);
+ subBoardX = statx + (vw - subBoardLength) / 2;
+ }
+ } else {
+ // Portrait mode
+ if (Lizzie.config.showLargeSubBoard() && !noSubBoard) {
+ // board
+ maxSize =
+ BoardRenderer.availableLength((int) (maxSize * 0.8), Lizzie.config.showCoordinates);
+ boardY = height - maxSize - bottomInset;
+ int spaceW = width - leftInset - rightInset;
+ int spaceH = boardY - panelMargin - topInset;
+ int panelW = spaceW / 2;
+ int panelH = spaceH / 2;
+ boardX = (spaceW - maxSize) / 2 + leftInset;
+
+ // captured stones
+ capw = panelW / 2;
+ caph = panelH / 2;
+ // move statistics (winrate bar)
+ staty = capy + caph;
+ statw = capw;
+ stath = caph;
+ // winrate graph
+ gry = staty + stath;
+ grw = statw;
+ grh = spaceH - caph - stath;
+ // variation tree container
+ vx = capx + capw;
+ vw = panelW / 2;
+ vh = spaceH;
+ // subboard
+ subBoardX = vx + vw;
+ subBoardWidth = panelW;
+ subBoardHeight = boardY - topInset;
+ subBoardLength =
+ BoardRenderer.availableLength(Math.min(subBoardWidth, subBoardHeight), false);
+ subBoardY = capy + (gry + grh - capy - subBoardLength) / 2;
+ // pondering message
+ ponderingY = height;
+ } else if (Lizzie.config.showLargeWinrate() && !noWinrate) {
+ // board
+ maxSize =
+ BoardRenderer.availableLength((int) (maxSize * 0.8), Lizzie.config.showCoordinates);
+ boardY = height - maxSize - bottomInset;
+ int spaceW = width - leftInset - rightInset;
+ int spaceH = boardY - panelMargin - topInset;
+ int panelW = spaceW / 2;
+ int panelH = spaceH / 2;
+ boardX = (spaceW - maxSize) / 2 + leftInset;
+
+ // captured stones
+ capw = panelW / 2;
+ caph = panelH / 4;
+ // move statistics (winrate bar)
+ statx = capx + capw;
+ staty = capy;
+ statw = capw;
+ stath = caph;
+ // winrate graph
+ gry = staty + stath;
+ grw = spaceW;
+ grh = boardY - gry - 1;
+ // variation tree container
+ vx = statx + statw;
+ vy = capy;
+ vw = panelW / 2;
+ vh = caph;
+ // subboard
+ subBoardY = topInset;
+ subBoardWidth = panelW / 2;
+ subBoardHeight = gry - topInset;
+ subBoardLength =
+ BoardRenderer.availableLength(Math.min(subBoardWidth, subBoardHeight), false);
+ subBoardX = vx + vw;
+ // pondering message
+ ponderingY = height;
+ } else {
+ // Normal
+ // board
+ boardY = (height - maxSize + topInset - bottomInset) / 2;
+ int spaceW = width - leftInset - rightInset;
+ int spaceH = boardY - panelMargin - topInset;
+ int panelW = spaceW / 2;
+ int panelH = spaceH / 2;
+
+ // captured stones
+ capw = panelW * 3 / 4;
+ caph = panelH / 2;
+ // move statistics (winrate bar)
+ statx = capx + capw;
+ staty = capy;
+ statw = capw;
+ stath = caph;
+ // winrate graph
+ grx = capx;
+ gry = staty + stath;
+ grw = capw + statw;
+ grh = boardY - gry;
+ // subboard
+ subBoardX = grx + grw;
+ subBoardWidth = panelW / 2;
+ subBoardHeight = boardY - topInset;
+ subBoardLength =
+ BoardRenderer.availableLength(Math.min(subBoardWidth, subBoardHeight), false);
+ subBoardY = capy + (boardY - topInset - subBoardLength) / 2;
+ // variation tree container
+ vx = leftInset + panelW;
+ vy = boardY + maxSize;
+ vw = panelW;
+ vh = height - vy - bottomInset;
+ }
+ }
+
+ // variation tree
+ int treex = vx;
+ int treey = vy;
+ int treew = vw;
+ int treeh = vh;
+
+ // comment panel
+ int cx = vx, cy = vy, cw = vw, ch = vh;
+ if (Lizzie.config.showComment) {
+ if (width >= height) {
+ if (Lizzie.config.showVariationGraph) {
+ treeh = vh / 2;
+ cy = vy + treeh;
+ ch = treeh;
+ }
+ } else {
+ if (Lizzie.config.showVariationGraph) {
+ if (Lizzie.config.showLargeSubBoard()) {
+ treeh = vh / 2;
+ cy = vy + treeh;
+ ch = treeh;
+ } else {
+ treew = vw / 2;
+ cx = vx + treew;
+ cw = treew;
+ }
+ }
+ }
+ }
+
+ if ((c = getChild(MAIN_BOARD, ltr)) != null) {
+ c.setBounds(x + boardX, y + boardY, maxSize, maxSize);
+ // c.repaint();
+ }
+ if ((c = getChild(SUB_BOARD, ltr)) != null) {
+ c.setBounds(x + subBoardX, y + subBoardY, subBoardLength, subBoardLength);
+ // c.repaint();
+ }
+ if ((c = getChild(BASIC_INFO, ltr)) != null) {
+ c.setBounds(x + capx, y + capy, capw, caph);
+ }
+ if ((c = getChild(WINRATE, ltr)) != null) {
+ c.setBounds(x + statx, y + staty, statw, stath + grh);
+ }
+ if ((c = getChild(VARIATION, ltr)) != null) {
+ c.setBounds(x + treex, y + treey, treew, treeh);
+ }
+ if ((c = getChild(COMMENT, ltr)) != null) {
+ // ((CommentPane)c).setCommentBounds(x + cx, y + cy, cw, ch);
+ c.setBounds(x + cx, y + cy, cw, ch);
+ c.repaint();
+ }
+ }
+ }
+
+ private Component getChild(String key, boolean ltr) {
+ Component result = null;
+
+ if (key == MAIN_BOARD) {
+ result = mainBoard;
+ } else if (key == SUB_BOARD) {
+ result = subBoard;
+ } else if (key == VARIATION) {
+ result = variationPane;
+ } else if (key == WINRATE) {
+ result = winratePane;
+ } else if (key == COMMENT) {
+ result = commentPane;
+ } else if (key == BASIC_INFO) {
+ result = basicInfoPane;
+ }
+ return result;
+ }
+
+ public String toString() {
+ return getClass().getName() + "[hgap=" + hgap + ",vgap=" + vgap + "]";
+ }
+
+ private Container getMain(Container target) {
+ Container p = (target != null) ? target.getParent() : null;
+ while (p != null && !(p instanceof LizzieMain)) {
+ p = p.getParent();
+ }
+ return p;
+ }
+
+ public int getMode() {
+ return mode;
+ }
+
+ public void setMode(int mode) {
+ this.mode = mode;
+ }
+}
diff --git a/src/main/java/featurecat/lizzie/gui/LizzieMain.java b/src/main/java/featurecat/lizzie/gui/LizzieMain.java
new file mode 100644
index 000000000..72a70afde
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/gui/LizzieMain.java
@@ -0,0 +1,746 @@
+package featurecat.lizzie.gui;
+
+import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
+import static java.awt.image.BufferedImage.TYPE_INT_RGB;
+import static java.lang.Math.max;
+
+import com.jhlabs.image.GaussianFilter;
+import featurecat.lizzie.Lizzie;
+import featurecat.lizzie.analysis.GameInfo;
+import featurecat.lizzie.analysis.MoveData;
+import featurecat.lizzie.rules.GIBParser;
+import featurecat.lizzie.rules.SGFParser;
+import featurecat.lizzie.util.Utils;
+import featurecat.lizzie.util.WindowPosition;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.FontFormatException;
+import java.awt.FontMetrics;
+import java.awt.Frame;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.TexturePaint;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.awt.image.BufferedImage;
+import java.io.File;
+import java.io.IOException;
+import java.util.Optional;
+import java.util.ResourceBundle;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import javax.imageio.ImageIO;
+import javax.swing.JFileChooser;
+import javax.swing.JOptionPane;
+import javax.swing.JPanel;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+public class LizzieMain extends MainFrame {
+ public static final ResourceBundle resourceBundle =
+ ResourceBundle.getBundle("l10n.DisplayStrings");
+
+ public static Input input;
+ public static BasicInfoPane basicInfoPane;
+ private static final String DEFAULT_TITLE = resourceBundle.getString("LizzieFrame.title");
+
+ public static BoardPane boardPane;
+ public static SubBoardPane subBoardPane;
+ public static WinratePane winratePane;
+ public static VariationTreePane variationTreePane;
+ public static CommentPane commentPane;
+ public static boolean designMode;
+ private LizzieLayout layout;
+
+ private static final BufferedImage emptyImage = new BufferedImage(1, 1, TYPE_INT_ARGB);
+
+ public BufferedImage cachedBackground;
+ private BufferedImage cachedBasicInfoContainer = emptyImage;
+ private BufferedImage cachedWinrateContainer = emptyImage;
+ private BufferedImage cachedVariationContainer = emptyImage;
+
+ private BufferedImage cachedWallpaperImage = emptyImage;
+ private int cachedBackgroundWidth = 0, cachedBackgroundHeight = 0;
+ private boolean redrawBackgroundAnyway = false;
+
+ private static final int[] outOfBoundCoordinate = new int[] {-1, -1};
+ public int[] mouseOverCoordinate = outOfBoundCoordinate;
+
+ // Save the player title
+ private String playerTitle = "";
+
+ // Show the playouts in the title
+ private ScheduledExecutorService showPlayouts = Executors.newScheduledThreadPool(1);
+ private long lastPlayouts = 0;
+ private String visitsString = "";
+ public boolean isDrawVisitsInTitle = true;
+
+ static {
+ // load fonts
+ try {
+ uiFont = new Font("SansSerif", Font.TRUETYPE_FONT, 12);
+ // Font.createFont(
+ // Font.TRUETYPE_FONT,
+ // Thread.currentThread()
+ // .getContextClassLoader()
+ // .getResourceAsStream("fonts/OpenSans-Regular.ttf"));
+ winrateFont =
+ Font.createFont(
+ Font.TRUETYPE_FONT,
+ Thread.currentThread()
+ .getContextClassLoader()
+ .getResourceAsStream("fonts/OpenSans-Semibold.ttf"));
+ } catch (IOException | FontFormatException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /** Creates a window */
+ public LizzieMain() {
+ super(DEFAULT_TITLE);
+
+ // TODO
+ // setMinimumSize(new Dimension(640, 400));
+ boolean persisted = Lizzie.config.persistedUi != null;
+ if (persisted)
+ boardPositionProportion =
+ Lizzie.config.persistedUi.optInt("board-position-proportion", boardPositionProportion);
+ JSONArray pos = WindowPosition.mainWindowPos();
+ if (pos != null) {
+ this.setBounds(pos.getInt(0), pos.getInt(1), pos.getInt(2), pos.getInt(3));
+ } else {
+ setSize(960, 600);
+ setLocationRelativeTo(null); // Start centered, needs to be called *after* setSize...
+ }
+
+ // Allow change font in the config
+ if (Lizzie.config.uiFontName != null) {
+ uiFont = new Font(Lizzie.config.uiFontName, Font.PLAIN, 12);
+ }
+ if (Lizzie.config.winrateFontName != null) {
+ winrateFont = new Font(Lizzie.config.winrateFontName, Font.BOLD, 12);
+ }
+
+ if (Lizzie.config.startMaximized && !persisted) {
+ setExtendedState(Frame.MAXIMIZED_BOTH);
+ } else if (persisted && Lizzie.config.persistedUi.getBoolean("window-maximized")) {
+ setExtendedState(Frame.MAXIMIZED_BOTH);
+ }
+
+ JPanel panel =
+ new JPanel() {
+ @Override
+ protected void paintComponent(Graphics g) {
+ if (g instanceof Graphics2D) {
+ int width = getWidth();
+ int height = getHeight();
+ Optional backgroundG;
+ if (cachedBackgroundWidth != width
+ || cachedBackgroundHeight != height
+ || redrawBackgroundAnyway) {
+ backgroundG = Optional.of(createBackground(width, height));
+ } else {
+ backgroundG = Optional.empty();
+ }
+ // draw the image
+ Graphics2D bsGraphics = (Graphics2D) g; // bs.getDrawGraphics();
+ bsGraphics.setRenderingHint(
+ RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ bsGraphics.setRenderingHint(
+ RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ bsGraphics.drawImage(cachedBackground, 0, 0, null);
+
+ // pondering message
+ int maxBound = Math.max(width, height);
+ double ponderingSize = .02;
+ int ponderingX = 0;
+ int ponderingY = height - (int) (maxBound * ponderingSize);
+
+ // dynamic komi
+ double dynamicKomiSize = .02;
+ int dynamicKomiX = 0;
+ int dynamicKomiY = ponderingY - (int) (maxBound * dynamicKomiSize);
+ int dynamicKomiLabelX = 0;
+ int dynamicKomiLabelY = dynamicKomiY - (int) (maxBound * dynamicKomiSize);
+
+ // loading message;
+ double loadingSize = 0.03;
+ int loadingX = ponderingX;
+ int loadingY = ponderingY - (int) (maxBound * (loadingSize - ponderingSize));
+
+ if (Lizzie.leelaz != null && Lizzie.leelaz.isLoaded()) {
+ if (Lizzie.config.showStatus) {
+ String statusKey =
+ "LizzieFrame.display." + (Lizzie.leelaz.isPondering() ? "on" : "off");
+ String statusText = resourceBundle.getString(statusKey);
+ String ponderingText = resourceBundle.getString("LizzieFrame.display.pondering");
+ String switching = resourceBundle.getString("LizzieFrame.prompt.switching");
+ String switchingText = Lizzie.leelaz.switching() ? switching : "";
+ String weightText = Lizzie.leelaz.currentWeight();
+ String text =
+ ponderingText + " " + statusText + " " + weightText + " " + switchingText;
+ drawPonderingState(bsGraphics, text, ponderingX, ponderingY, ponderingSize);
+ }
+
+ Optional dynamicKomi = Lizzie.leelaz.getDynamicKomi();
+ if (Lizzie.config.showDynamicKomi && dynamicKomi.isPresent()) {
+ String text = resourceBundle.getString("LizzieFrame.display.dynamic-komi");
+ drawPonderingState(
+ bsGraphics, text, dynamicKomiLabelX, dynamicKomiLabelY, dynamicKomiSize);
+ drawPonderingState(
+ bsGraphics, dynamicKomi.get(), dynamicKomiX, dynamicKomiY, dynamicKomiSize);
+ }
+ } else if (Lizzie.config.showStatus) {
+ String loadingText = resourceBundle.getString("LizzieFrame.display.loading");
+ drawPonderingState(bsGraphics, loadingText, loadingX, loadingY, loadingSize);
+ }
+ }
+ }
+ };
+ setContentPane(panel);
+
+ layout = new LizzieLayout();
+ getContentPane().setLayout(layout);
+ basicInfoPane = new BasicInfoPane(this);
+ boardPane = new BoardPane(this);
+ subBoardPane = new SubBoardPane(this);
+ winratePane = new WinratePane(this);
+ variationTreePane = new VariationTreePane(this);
+ commentPane = new CommentPane(this);
+ getContentPane().add(boardPane, LizzieLayout.MAIN_BOARD);
+ getContentPane().add(basicInfoPane, LizzieLayout.BASIC_INFO);
+ getContentPane().add(winratePane, LizzieLayout.WINRATE);
+ getContentPane().add(subBoardPane, LizzieLayout.SUB_BOARD);
+ getContentPane().add(variationTreePane, LizzieLayout.VARIATION);
+ getContentPane().add(commentPane, LizzieLayout.COMMENT);
+ WindowPosition.restorePane(Lizzie.config.persistedUi, boardPane);
+ WindowPosition.restorePane(Lizzie.config.persistedUi, basicInfoPane);
+ WindowPosition.restorePane(Lizzie.config.persistedUi, winratePane);
+ WindowPosition.restorePane(Lizzie.config.persistedUi, subBoardPane);
+ WindowPosition.restorePane(Lizzie.config.persistedUi, variationTreePane);
+ WindowPosition.restorePane(Lizzie.config.persistedUi, commentPane);
+
+ try {
+ this.setIconImage(ImageIO.read(getClass().getResourceAsStream("/assets/logo.png")));
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ setVisible(true);
+
+ input = new Input();
+ // addMouseListener(input);
+ addKeyListener(input);
+ addMouseWheelListener(input);
+ // addMouseMotionListener(input);
+
+ // When the window is closed: save the SGF file, then run shutdown()
+ this.addWindowListener(
+ new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {
+ Lizzie.shutdown();
+ }
+ });
+
+ // Show the playouts in the title
+ showPlayouts.scheduleAtFixedRate(
+ new Runnable() {
+ @Override
+ public void run() {
+ if (!isDrawVisitsInTitle) {
+ visitsString = "";
+ return;
+ }
+ if (Lizzie.leelaz == null) return;
+ try {
+ int totalPlayouts = MoveData.getPlayouts(Lizzie.leelaz.getBestMoves());
+ if (totalPlayouts <= 0) return;
+ visitsString =
+ String.format(
+ " %d visits/second",
+ (totalPlayouts > lastPlayouts) ? totalPlayouts - lastPlayouts : 0);
+ updateTitle();
+ lastPlayouts = totalPlayouts;
+ } catch (Exception e) {
+ }
+ }
+ },
+ 1,
+ 1,
+ TimeUnit.SECONDS);
+
+ setFocusable(true);
+ setFocusTraversalKeysEnabled(false);
+ }
+
+ /**
+ * temporary measure to refresh background. ideally we shouldn't need this (but we want to release
+ * Lizzie 0.5 today, not tomorrow!). Refactor me out please! (you need to get blurring to work
+ * properly on startup).
+ */
+ public void refreshBackground() {
+ redrawBackgroundAnyway = true;
+ }
+
+ public BufferedImage getWallpaper() {
+ if (cachedWallpaperImage == emptyImage) {
+ cachedWallpaperImage = Lizzie.config.theme.background();
+ }
+ return cachedWallpaperImage;
+ }
+
+ private Graphics2D createBackground(int width, int height) {
+ cachedBackground = new BufferedImage(width, height, TYPE_INT_RGB);
+ cachedBackgroundWidth = cachedBackground.getWidth();
+ cachedBackgroundHeight = cachedBackground.getHeight();
+
+ redrawBackgroundAnyway = false;
+
+ Graphics2D g = cachedBackground.createGraphics();
+ g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+
+ BufferedImage wallpaper = getWallpaper();
+ int drawWidth = max(wallpaper.getWidth(), width);
+ int drawHeight = max(wallpaper.getHeight(), height);
+ // Support seamless texture
+ drawTextureImage(g, wallpaper, 0, 0, drawWidth, drawHeight);
+
+ return g;
+ }
+
+ private void drawPonderingState(Graphics2D g, String text, int x, int y, double size) {
+ int fontSize = (int) (max(getWidth(), getHeight()) * size);
+ Font font = new Font(Lizzie.config.fontName, Font.PLAIN, fontSize);
+ FontMetrics fm = g.getFontMetrics(font);
+ int stringWidth = fm.stringWidth(text);
+ // Truncate too long text when display switching prompt
+ if (Lizzie.leelaz != null && Lizzie.leelaz.isLoaded()) {
+ int mainBoardX = boardPane.getLocation().x;
+ if (getWidth() > getHeight() && (mainBoardX > x) && stringWidth > (mainBoardX - x)) {
+ text = Utils.truncateStringByWidth(text, fm, mainBoardX - x);
+ stringWidth = fm.stringWidth(text);
+ }
+ }
+ // Do nothing when no text
+ if (stringWidth <= 0) {
+ return;
+ }
+ int stringHeight = fm.getAscent() - fm.getDescent();
+ int width = max(stringWidth, 1);
+ int height = max((int) (stringHeight * 1.2), 1);
+
+ BufferedImage result = new BufferedImage(width, height, TYPE_INT_ARGB);
+ // commenting this out for now... always causing an exception on startup. will fix in the
+ // upcoming refactoring
+ // filter20.filter(cachedBackground.getSubimage(x, y, result.getWidth(),
+ // result.getHeight()), result);
+ g.drawImage(result, x, y, null);
+
+ g.setColor(new Color(0, 0, 0, 130));
+ g.fillRect(x, y, width, height);
+ g.drawRect(x, y, width, height);
+
+ g.setColor(Color.white);
+ g.setFont(font);
+ g.drawString(
+ text, x + (width - stringWidth) / 2, y + stringHeight + (height - stringHeight) / 2);
+ }
+
+ private GaussianFilter filter20 = new GaussianFilter(20);
+
+ public BufferedImage getBasicInfoContainer(LizziePane pane) {
+ if (cachedBackground == null
+ || (cachedBasicInfoContainer != null
+ && cachedBasicInfoContainer.getWidth() == pane.getWidth()
+ && cachedBasicInfoContainer.getHeight() == pane.getHeight())) {
+ return cachedBasicInfoContainer;
+ }
+ int vx = pane.getX();
+ int vy = pane.getY();
+ int vw = pane.getWidth();
+ int vh = pane.getHeight();
+ BufferedImage result = cachedBackground.getSubimage(vx, vy, vw, vh);
+ // BufferedImage result = new BufferedImage(vw, vh, TYPE_INT_ARGB);
+ // filter10.filter(cachedBackground.getSubimage(vx, vy, vw, vh), result);
+ cachedBasicInfoContainer = result;
+ return result;
+ }
+
+ public BufferedImage getWinrateContainer(LizziePane pane) {
+ if (cachedBackground == null
+ || (cachedWinrateContainer != null
+ && cachedWinrateContainer.getWidth() == pane.getWidth()
+ && cachedWinrateContainer.getHeight() == pane.getHeight())) {
+ return cachedWinrateContainer;
+ }
+ int vx = pane.getX();
+ int vy = pane.getY();
+ int vw = pane.getWidth();
+ int vh = pane.getHeight();
+ BufferedImage result = new BufferedImage(vw, vh, TYPE_INT_ARGB);
+ filter20.filter(cachedBackground.getSubimage(vx, vy, vw, vh), result);
+ cachedWinrateContainer = result;
+ return result;
+ }
+
+ public BufferedImage getVariationContainer(LizziePane pane) {
+ if (cachedBackground == null
+ || (cachedVariationContainer != null
+ && cachedVariationContainer.getWidth() == pane.getWidth()
+ && cachedVariationContainer.getHeight() == pane.getHeight())) {
+ return cachedVariationContainer;
+ }
+ int vx = pane.getX();
+ int vy = pane.getY();
+ int vw = pane.getWidth();
+ int vh = pane.getHeight();
+ BufferedImage result = new BufferedImage(vw, vh, TYPE_INT_ARGB);
+ filter20.filter(cachedBackground.getSubimage(vx, vy, vw, vh), result);
+ cachedVariationContainer = result;
+ return result;
+ }
+
+ public void drawContainer(Graphics g, int vx, int vy, int vw, int vh) {
+ if (vw <= 0
+ || vh <= 0
+ || vx < cachedBackground.getMinX()
+ || vx + vw > cachedBackground.getMinX() + cachedBackground.getWidth()
+ || vy < cachedBackground.getMinY()
+ || vy + vh > cachedBackground.getMinY() + cachedBackground.getHeight()) {
+ return;
+ }
+ redrawBackgroundAnyway = false;
+ BufferedImage result = new BufferedImage(vw, vh, TYPE_INT_ARGB);
+ filter20.filter(cachedBackground.getSubimage(vx, vy, vw, vh), result);
+ g.drawImage(result, vx, vy, null);
+ }
+
+ /** Draw texture image */
+ public void drawTextureImage(
+ Graphics2D g, BufferedImage img, int x, int y, int width, int height) {
+ TexturePaint paint =
+ new TexturePaint(img, new Rectangle(0, 0, img.getWidth(), img.getHeight()));
+ g.setPaint(paint);
+ g.fill(new Rectangle(x, y, width, height));
+ }
+
+ @Override
+ public boolean isDesignMode() {
+ return designMode;
+ }
+
+ @Override
+ public void toggleDesignMode() {
+ this.designMode = !this.designMode;
+ // boardPane.setDesignMode(designMode);
+ basicInfoPane.setDesignMode(designMode);
+ winratePane.setDesignMode(designMode);
+ subBoardPane.setDesignMode(designMode);
+ variationTreePane.setDesignMode(designMode);
+ commentPane.setDesignMode(designMode);
+ }
+
+ @Override
+ public void updateBasicInfo() {
+ if (basicInfoPane != null) {
+ basicInfoPane.repaint();
+ }
+ }
+
+ public void invalidLayout() {
+ // TODO
+ layout.layoutContainer(getContentPane());
+ layout.invalidateLayout(getContentPane());
+ repaint();
+ }
+
+ @Override
+ public void refresh() {
+ refresh(0);
+ }
+
+ @Override
+ public void refresh(int type) {
+ if (type == 2) {
+ invalidLayout();
+ } else {
+ boardPane.repaint();
+ if (type != 1) {
+ updateStatus();
+ }
+ }
+ }
+
+ public void repaintSub() {
+ if (Lizzie.leelaz != null && Lizzie.leelaz.isLoaded()) {
+ if (Lizzie.config.showSubBoard && !subBoardPane.isVisible()) {
+ subBoardPane.setVisible(true);
+ }
+ if (Lizzie.config.showWinrate && !winratePane.isVisible()) {
+ winratePane.setVisible(true);
+ }
+ }
+ subBoardPane.repaint();
+ winratePane.repaint();
+ }
+
+ public void updateStatus() {
+ // basicInfoPane.revalidate();
+ basicInfoPane.repaint();
+ if (Lizzie.leelaz != null && Lizzie.leelaz.isLoaded()) {
+ if (Lizzie.config.showVariationGraph && !variationTreePane.isVisible()) {
+ variationTreePane.setVisible(true);
+ }
+ }
+ // variationTreePane.revalidate();
+ variationTreePane.repaint();
+ commentPane.drawComment();
+ // commentPane.revalidate();
+ commentPane.repaint();
+ invalidLayout();
+ }
+
+ public void openConfigDialog() {
+ ConfigDialog configDialog = new ConfigDialog();
+ configDialog.setVisible(true);
+ // configDialog.dispose();
+ }
+
+ public void openChangeMoveDialog() {
+ ChangeMoveDialog changeMoveDialog = new ChangeMoveDialog();
+ changeMoveDialog.setVisible(true);
+ }
+
+ public void toggleGtpConsole() {
+ Lizzie.leelaz.toggleGtpConsole();
+ if (Lizzie.gtpConsole != null) {
+ Lizzie.gtpConsole.setVisible(!Lizzie.gtpConsole.isVisible());
+ } else {
+ Lizzie.gtpConsole = new GtpConsolePane(this);
+ Lizzie.gtpConsole.setVisible(true);
+ }
+ }
+
+ @Override
+ public void startGame() {
+ GameInfo gameInfo = Lizzie.board.getHistory().getGameInfo();
+
+ NewGameDialog gameDialog = new NewGameDialog();
+ gameDialog.setGameInfo(gameInfo);
+ gameDialog.setVisible(true);
+ boolean playerIsBlack = gameDialog.playerIsBlack();
+ boolean isNewGame = gameDialog.isNewGame();
+ // gameDialog.dispose();
+ if (gameDialog.isCancelled()) return;
+
+ if (isNewGame) {
+ Lizzie.board.clear();
+ }
+ Lizzie.leelaz.sendCommand("komi " + gameInfo.getKomi());
+
+ Lizzie.leelaz.time_settings();
+ Lizzie.frame.playerIsBlack = playerIsBlack;
+ Lizzie.frame.isNewGame = isNewGame;
+ Lizzie.frame.isPlayingAgainstLeelaz = true;
+
+ boolean isHandicapGame = gameInfo.getHandicap() != 0;
+ if (isNewGame) {
+ Lizzie.board.getHistory().setGameInfo(gameInfo);
+ if (isHandicapGame) {
+ Lizzie.board.getHistory().getData().blackToPlay = false;
+ Lizzie.leelaz.sendCommand("fixed_handicap " + gameInfo.getHandicap());
+ if (playerIsBlack) Lizzie.leelaz.genmove("W");
+ } else if (!playerIsBlack) {
+ Lizzie.leelaz.genmove("B");
+ }
+ } else {
+ Lizzie.board.getHistory().setGameInfo(gameInfo);
+ if (Lizzie.frame.playerIsBlack != Lizzie.board.getData().blackToPlay) {
+ if (!Lizzie.leelaz.isThinking) {
+ Lizzie.leelaz.genmove((Lizzie.board.getData().blackToPlay ? "B" : "W"));
+ }
+ }
+ }
+ }
+
+ public void editGameInfo() {
+ GameInfo gameInfo = Lizzie.board.getHistory().getGameInfo();
+
+ GameInfoDialog gameInfoDialog = new GameInfoDialog();
+ gameInfoDialog.setGameInfo(gameInfo);
+ gameInfoDialog.setVisible(true);
+
+ gameInfoDialog.dispose();
+ }
+
+ public void saveFile() {
+ FileNameExtensionFilter filter = new FileNameExtensionFilter("*.sgf", "SGF");
+ JSONObject filesystem = Lizzie.config.persisted.getJSONObject("filesystem");
+ JFileChooser chooser = new JFileChooser(filesystem.getString("last-folder"));
+ chooser.setFileFilter(filter);
+ chooser.setMultiSelectionEnabled(false);
+ int result = chooser.showSaveDialog(null);
+ if (result == JFileChooser.APPROVE_OPTION) {
+ File file = chooser.getSelectedFile();
+ if (file.exists()) {
+ int ret =
+ JOptionPane.showConfirmDialog(
+ null,
+ resourceBundle.getString("LizzieFrame.prompt.sgfExists"),
+ "Warning",
+ JOptionPane.OK_CANCEL_OPTION);
+ if (ret == JOptionPane.CANCEL_OPTION) {
+ return;
+ }
+ }
+ if (!file.getPath().endsWith(".sgf")) {
+ file = new File(file.getPath() + ".sgf");
+ }
+ try {
+ SGFParser.save(Lizzie.board, file.getPath());
+ filesystem.put("last-folder", file.getParent());
+ } catch (IOException err) {
+ JOptionPane.showConfirmDialog(
+ null,
+ resourceBundle.getString("LizzieFrame.prompt.failedTosaveFile"),
+ "Error",
+ JOptionPane.ERROR);
+ }
+ }
+ }
+
+ public void openFile() {
+ FileNameExtensionFilter filter = new FileNameExtensionFilter("*.sgf or *.gib", "SGF", "GIB");
+ JSONObject filesystem = Lizzie.config.persisted.getJSONObject("filesystem");
+ JFileChooser chooser = new JFileChooser(filesystem.getString("last-folder"));
+
+ chooser.setFileFilter(filter);
+ chooser.setMultiSelectionEnabled(false);
+ int result = chooser.showOpenDialog(null);
+ if (result == JFileChooser.APPROVE_OPTION) loadFile(chooser.getSelectedFile());
+ }
+
+ public void loadFile(File file) {
+ JSONObject filesystem = Lizzie.config.persisted.getJSONObject("filesystem");
+ if (!(file.getPath().endsWith(".sgf") || file.getPath().endsWith(".gib"))) {
+ file = new File(file.getPath() + ".sgf");
+ }
+ try {
+ System.out.println(file.getPath());
+ if (file.getPath().endsWith(".sgf")) {
+ SGFParser.load(file.getPath());
+ } else {
+ GIBParser.load(file.getPath());
+ }
+ filesystem.put("last-folder", file.getParent());
+ } catch (IOException err) {
+ JOptionPane.showConfirmDialog(
+ null,
+ resourceBundle.getString("LizzieFrame.prompt.failedToOpenFile"),
+ "Error",
+ JOptionPane.ERROR);
+ }
+ }
+
+ public void setPlayers(String whitePlayer, String blackPlayer) {
+ playerTitle = String.format("(%s [W] vs %s [B])", whitePlayer, blackPlayer);
+ updateTitle();
+ }
+
+ public void updateTitle() {
+ StringBuilder sb = new StringBuilder(DEFAULT_TITLE);
+ sb.append(playerTitle);
+ sb.append(" [" + Lizzie.leelaz.engineCommand() + "]");
+ sb.append(visitsString);
+ setTitle(sb.toString());
+ }
+
+ public void resetTitle() {
+ playerTitle = "";
+ updateTitle();
+ }
+
+ @Override
+ public void drawControls() {
+ boardPane.drawControls();
+ }
+
+ @Override
+ public void replayBranch() {
+ boardPane.replayBranch();
+ }
+
+ @Override
+ public boolean isMouseOver(int x, int y) {
+ return boardPane.isMouseOver(x, y);
+ }
+
+ @Override
+ public void onClicked(int x, int y) {
+ boardPane.onClicked(x, y);
+ }
+
+ @Override
+ public void onDoubleClicked(int x, int y) {
+ boardPane.onDoubleClicked(x, y);
+ }
+
+ @Override
+ public void onMouseDragged(int x, int y) {
+ winratePane.onMouseDragged(x, y);
+ }
+
+ @Override
+ public void onMouseMoved(int x, int y) {
+ boardPane.onMouseMoved(x, y);
+ }
+
+ @Override
+ public void startRawBoard() {
+ boardPane.startRawBoard();
+ }
+
+ @Override
+ public void stopRawBoard() {
+ boardPane.stopRawBoard();
+ }
+
+ @Override
+ public boolean incrementDisplayedBranchLength(int n) {
+ return boardPane.incrementDisplayedBranchLength(n);
+ }
+
+ @Override
+ public void increaseMaxAlpha(int k) {
+ boardPane.increaseMaxAlpha(k);
+ }
+
+ @Override
+ public void copySgf() {
+ boardPane.copySgf();
+ }
+
+ @Override
+ public void pasteSgf() {
+ boardPane.pasteSgf();
+ }
+
+ @Override
+ public boolean playCurrentVariation() {
+ return boardPane.playCurrentVariation();
+ }
+
+ @Override
+ public void playBestMove() {
+ boardPane.playBestMove();
+ }
+
+ @Override
+ public void clear() {
+ boardPane.clear();
+ }
+}
diff --git a/src/main/java/featurecat/lizzie/gui/LizziePane.java b/src/main/java/featurecat/lizzie/gui/LizziePane.java
new file mode 100644
index 000000000..5d47e12f8
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/gui/LizziePane.java
@@ -0,0 +1,537 @@
+package featurecat.lizzie.gui;
+
+import java.awt.Component;
+import java.awt.Container;
+import java.awt.Cursor;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.awt.LayoutManager;
+import java.awt.LayoutManager2;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.Window;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.io.Serializable;
+import javax.swing.BorderFactory;
+import javax.swing.JDialog;
+import javax.swing.JPanel;
+import javax.swing.JWindow;
+import javax.swing.UIManager;
+import javax.swing.plaf.UIResource;
+import javax.swing.text.html.HTMLEditorKit;
+import javax.swing.text.html.StyleSheet;
+
+/** The window used to display the game. */
+public class LizziePane extends JPanel {
+
+ private static final String uiClassID = "LizziePaneUI";
+
+ static {
+ UIManager.put(uiClassID, BasicLizziePaneUI.class.getName());
+ }
+
+ private boolean floatable = true;
+
+ /** Keys to lookup borders in defaults table. */
+ private static final int[] cursorMapping =
+ new int[] {
+ Cursor.NW_RESIZE_CURSOR,
+ Cursor.NW_RESIZE_CURSOR,
+ Cursor.N_RESIZE_CURSOR,
+ Cursor.NE_RESIZE_CURSOR,
+ Cursor.NE_RESIZE_CURSOR,
+ Cursor.NW_RESIZE_CURSOR,
+ 0,
+ 0,
+ 0,
+ Cursor.NE_RESIZE_CURSOR,
+ Cursor.W_RESIZE_CURSOR,
+ 0,
+ 0,
+ 0,
+ Cursor.E_RESIZE_CURSOR,
+ Cursor.SW_RESIZE_CURSOR,
+ 0,
+ 0,
+ 0,
+ Cursor.SE_RESIZE_CURSOR,
+ Cursor.SW_RESIZE_CURSOR,
+ Cursor.SW_RESIZE_CURSOR,
+ Cursor.S_RESIZE_CURSOR,
+ Cursor.SE_RESIZE_CURSOR,
+ Cursor.SE_RESIZE_CURSOR
+ };
+
+ /** The amount of space (in pixels) that the cursor is changed on. */
+ private static final int CORNER_DRAG_WIDTH = 16;
+
+ /** Region from edges that dragging is active from. */
+ private static final int BORDER_DRAG_THICKNESS = 5;
+
+ /**
+ * Cursor
used to track the cursor set by the user. This is initially
+ * Cursor.DEFAULT_CURSOR
.
+ */
+ private Cursor lastCursor = Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR);
+
+ protected PaneDragListener dragListener;
+ protected Input input;
+
+ public LizziePane() {
+ super();
+ }
+
+ /** Creates a window */
+ public LizziePane(LizzieMain owner) {
+ // super(owner);
+ // initCompotents();
+ // input = owner.input;
+ // installInputListeners();
+ setOpaque(false);
+ }
+
+ @Override
+ public LizziePaneUI getUI() {
+ return (LizziePaneUI) ui;
+ }
+
+ public void setUI(LizziePaneUI ui) {
+ super.setUI(ui);
+ }
+
+ public void updateUI() {
+ setUI((LizziePaneUI) UIManager.getUI(this));
+ if (getLayout() == null) {
+ setLayout(new DefaultLizziePaneLayout());
+ }
+ invalidate();
+ }
+
+ public String getUIClassID() {
+ return uiClassID;
+ }
+
+ public void toWindow(Point position, Dimension size) {
+ if (getUI() != null) {
+ getUI().toWindow(position, size);
+ }
+ }
+
+ public int getComponentIndex(Component c) {
+ int ncomponents = this.getComponentCount();
+ Component[] component = this.getComponents();
+ for (int i = 0; i < ncomponents; i++) {
+ Component comp = component[i];
+ if (comp == c) return i;
+ }
+ return -1;
+ }
+
+ public Component getComponentAtIndex(int i) {
+ int ncomponents = this.getComponentCount();
+ if (i >= 0 && i < ncomponents) {
+ Component[] component = this.getComponents();
+ return component[i];
+ }
+ return null;
+ }
+
+ public boolean isFloatable() {
+ return floatable;
+ }
+
+ public void setFloatable(boolean b) {
+ if (floatable != b) {
+ boolean old = floatable;
+ floatable = b;
+
+ firePropertyChange("floatable", old, b);
+ revalidate();
+ repaint();
+ }
+ }
+
+ private void initCompotents() {
+ setBorder(BorderFactory.createEmptyBorder());
+ setVisible(true);
+ }
+
+ private class PaneDragListener extends MouseAdapter {
+
+ /** Set to true if the drag operation is moving the window. */
+ private boolean isMovingWindow;
+
+ /** Used to determine the corner the resize is occurring from. */
+ private int dragCursor;
+
+ /** X location the mouse went down on for a drag operation. */
+ private int dragOffsetX;
+
+ /** Y location the mouse went down on for a drag operation. */
+ private int dragOffsetY;
+
+ /** Width of the window when the drag started. */
+ private int dragWidth;
+
+ /** Height of the window when the drag started. */
+ private int dragHeight;
+
+ /** Window the JRootPane
is in. */
+ private Window window;
+
+ public PaneDragListener(Window window) {
+ this.window = window;
+ }
+
+ public void mouseMoved(MouseEvent e) {
+ Window w = (Window) e.getSource();
+
+ JWindow f = null;
+ JDialog d = null;
+
+ if (w instanceof JWindow) {
+ f = (JWindow) w;
+ } else if (w instanceof JDialog) {
+ d = (JDialog) w;
+ }
+
+ // Update the cursor
+ int cursor = getCursor(calculateCorner(w, e.getX(), e.getY()));
+
+ if (cursor != 0
+ && ((f != null) // && (f.isResizable() && (f.getExtendedState() & Frame.MAXIMIZED_BOTH)
+ // == 0))
+ || (d != null && d.isResizable()))) {
+ w.setCursor(Cursor.getPredefinedCursor(cursor));
+ } else {
+ w.setCursor(lastCursor);
+ }
+ }
+
+ public void mouseReleased(MouseEvent e) {
+ if (dragCursor != 0 && window != null && !window.isValid()) {
+ // Some Window systems validate as you resize, others won't,
+ // thus the check for validity before repainting.
+ window.validate();
+ getRootPane().repaint();
+ }
+ isMovingWindow = false;
+ dragCursor = 0;
+ }
+
+ public void mousePressed(MouseEvent e) {
+ Point dragWindowOffset = e.getPoint();
+ Window w = (Window) e.getSource();
+ if (w != null) {
+ w.toFront();
+ }
+
+ JWindow f = null;
+ JDialog d = null;
+
+ if (w instanceof JWindow) {
+ f = (JWindow) w;
+ } else if (w instanceof JDialog) {
+ d = (JDialog) w;
+ }
+
+ // int frameState = (f != null) ? f.getExtendedState() : 0;
+
+ if (((f != null) // && ((frameState & Frame.MAXIMIZED_BOTH) == 0)
+ || (d != null))
+ && dragWindowOffset.y >= BORDER_DRAG_THICKNESS
+ && dragWindowOffset.x >= BORDER_DRAG_THICKNESS
+ && dragWindowOffset.x < w.getWidth() - BORDER_DRAG_THICKNESS) {
+ isMovingWindow = true;
+ dragOffsetX = dragWindowOffset.x;
+ dragOffsetY = dragWindowOffset.y;
+ } else if (f != null // && f.isResizable() && ((frameState & Frame.MAXIMIZED_BOTH) == 0)
+ || (d != null && d.isResizable())) {
+ dragOffsetX = dragWindowOffset.x;
+ dragOffsetY = dragWindowOffset.y;
+ dragWidth = w.getWidth();
+ dragHeight = w.getHeight();
+ dragCursor = getCursor(calculateCorner(w, dragWindowOffset.x, dragWindowOffset.y));
+ }
+ }
+
+ public void mouseDragged(MouseEvent e) {
+ Window w = (Window) e.getSource();
+ Point pt = e.getPoint();
+
+ if (isMovingWindow) {
+ Point eventLocationOnScreen = e.getLocationOnScreen();
+ w.setLocation(eventLocationOnScreen.x - dragOffsetX, eventLocationOnScreen.y - dragOffsetY);
+ } else if (dragCursor != 0) {
+ Rectangle r = w.getBounds();
+ Rectangle startBounds = new Rectangle(r);
+ Dimension min = w.getMinimumSize();
+
+ switch (dragCursor) {
+ case Cursor.E_RESIZE_CURSOR:
+ adjust(r, min, 0, 0, pt.x + (dragWidth - dragOffsetX) - r.width, 0);
+ break;
+ case Cursor.S_RESIZE_CURSOR:
+ adjust(r, min, 0, 0, 0, pt.y + (dragHeight - dragOffsetY) - r.height);
+ break;
+ case Cursor.N_RESIZE_CURSOR:
+ adjust(r, min, 0, pt.y - dragOffsetY, 0, -(pt.y - dragOffsetY));
+ break;
+ case Cursor.W_RESIZE_CURSOR:
+ adjust(r, min, pt.x - dragOffsetX, 0, -(pt.x - dragOffsetX), 0);
+ break;
+ case Cursor.NE_RESIZE_CURSOR:
+ adjust(
+ r,
+ min,
+ 0,
+ pt.y - dragOffsetY,
+ pt.x + (dragWidth - dragOffsetX) - r.width,
+ -(pt.y - dragOffsetY));
+ break;
+ case Cursor.SE_RESIZE_CURSOR:
+ adjust(
+ r,
+ min,
+ 0,
+ 0,
+ pt.x + (dragWidth - dragOffsetX) - r.width,
+ pt.y + (dragHeight - dragOffsetY) - r.height);
+ break;
+ case Cursor.NW_RESIZE_CURSOR:
+ adjust(
+ r,
+ min,
+ pt.x - dragOffsetX,
+ pt.y - dragOffsetY,
+ -(pt.x - dragOffsetX),
+ -(pt.y - dragOffsetY));
+ break;
+ case Cursor.SW_RESIZE_CURSOR:
+ adjust(
+ r,
+ min,
+ pt.x - dragOffsetX,
+ 0,
+ -(pt.x - dragOffsetX),
+ pt.y + (dragHeight - dragOffsetY) - r.height);
+ break;
+ default:
+ break;
+ }
+ if (!r.equals(startBounds)) {
+ w.setBounds(r);
+ // Defer repaint/validate on mouseReleased unless dynamic
+ // layout is active.
+ if (Toolkit.getDefaultToolkit().isDynamicLayoutActive()) {
+ w.validate();
+ getRootPane().repaint();
+ }
+ }
+ }
+ }
+
+ private int calculateCorner(Window c, int x, int y) {
+ Insets insets = c.getInsets();
+ int xPosition = calculatePosition(x - insets.left, c.getWidth() - insets.left - insets.right);
+ int yPosition = calculatePosition(y - insets.top, c.getHeight() - insets.top - insets.bottom);
+
+ if (xPosition == -1 || yPosition == -1) {
+ return -1;
+ }
+ return yPosition * 5 + xPosition;
+ }
+
+ private int getCursor(int corner) {
+ if (corner == -1) {
+ return 0;
+ }
+ return cursorMapping[corner];
+ }
+
+ private int calculatePosition(int spot, int width) {
+ if (spot < BORDER_DRAG_THICKNESS) {
+ return 0;
+ }
+ if (spot < CORNER_DRAG_WIDTH) {
+ return 1;
+ }
+ if (spot >= (width - BORDER_DRAG_THICKNESS)) {
+ return 4;
+ }
+ if (spot >= (width - CORNER_DRAG_WIDTH)) {
+ return 3;
+ }
+ return 2;
+ }
+
+ private void adjust(
+ Rectangle bounds, Dimension min, int deltaX, int deltaY, int deltaWidth, int deltaHeight) {
+ bounds.x += deltaX;
+ bounds.y += deltaY;
+ bounds.width += deltaWidth;
+ bounds.height += deltaHeight;
+ if (min != null) {
+ if (bounds.width < min.width) {
+ int correction = min.width - bounds.width;
+ if (deltaX != 0) {
+ bounds.x -= correction;
+ }
+ bounds.width = min.width;
+ }
+ if (bounds.height < min.height) {
+ int correction = min.height - bounds.height;
+ if (deltaY != 0) {
+ bounds.y -= correction;
+ }
+ bounds.height = min.height;
+ }
+ }
+ }
+ }
+
+ protected void installDesignListeners() {
+ LizziePaneUI ui = getUI();
+ if (ui != null && ui instanceof BasicLizziePaneUI) {
+ ((BasicLizziePaneUI) ui).installListeners();
+ }
+ }
+
+ protected void uninstallDesignListeners() {
+ LizziePaneUI ui = getUI();
+ if (ui != null && ui instanceof BasicLizziePaneUI) {
+ ((BasicLizziePaneUI) ui).uninstallListeners();
+ }
+ }
+
+ protected void installInputListeners() {
+ // addMouseListener(input);
+ // addKeyListener(input);
+ // addMouseWheelListener(input);
+ // addMouseMotionListener(input);
+ }
+
+ protected void uninstallInputListeners() {
+ // removeMouseListener(input);
+ // removeKeyListener(input);
+ // removeMouseWheelListener(input);
+ // removeMouseMotionListener(input);
+ }
+
+ public void setDesignMode(boolean mode) {
+ if (mode) {
+ uninstallInputListeners();
+ installDesignListeners();
+ } else {
+ uninstallDesignListeners();
+ installInputListeners();
+ }
+ }
+
+ private class DefaultLizziePaneLayout
+ implements LayoutManager2, Serializable, PropertyChangeListener, UIResource {
+
+ LizzieLayout lm;
+
+ DefaultLizziePaneLayout() {
+ lm = new LizzieLayout();
+ }
+
+ /** @deprecated replaced by addLayoutComponent(Component, Object)
. */
+ @Deprecated
+ public void addLayoutComponent(String name, Component comp) {
+ lm.addLayoutComponent(name, comp);
+ }
+
+ public void addLayoutComponent(Component comp, Object constraints) {
+ lm.addLayoutComponent(comp, constraints);
+ }
+
+ public void removeLayoutComponent(Component comp) {
+ lm.removeLayoutComponent(comp);
+ }
+
+ public Dimension preferredLayoutSize(Container target) {
+ return lm.preferredLayoutSize(target);
+ }
+
+ public Dimension minimumLayoutSize(Container target) {
+ return lm.minimumLayoutSize(target);
+ }
+
+ public Dimension maximumLayoutSize(Container target) {
+ return lm.maximumLayoutSize(target);
+ }
+
+ public void layoutContainer(Container target) {
+ lm.layoutContainer(target);
+ }
+
+ public float getLayoutAlignmentX(Container target) {
+ return lm.getLayoutAlignmentX(target);
+ }
+
+ public float getLayoutAlignmentY(Container target) {
+ return lm.getLayoutAlignmentY(target);
+ }
+
+ public void invalidateLayout(Container target) {
+ lm.invalidateLayout(target);
+ }
+
+ public void propertyChange(PropertyChangeEvent e) {
+ // TODO
+ // String name = e.getPropertyName();
+ // if (name.equals("orientation")) {
+ // int o = ((Integer) e.getNewValue()).intValue();
+ // if (o == LizziePane.VERTICAL)
+ // lm = new LizzieLayout(LizziePane.this, LizzieLayout.PAGE_AXIS);
+ // else {
+ // lm = new LizzieLayout(LizziePane.this, LizzieLayout.LINE_AXIS);
+ // }
+ // }
+ }
+ }
+
+ public void setLayout(LayoutManager mgr) {
+ LayoutManager oldMgr = getLayout();
+ if (oldMgr instanceof PropertyChangeListener) {
+ removePropertyChangeListener((PropertyChangeListener) oldMgr);
+ }
+ super.setLayout(mgr);
+ }
+
+ private void writeObject(ObjectOutputStream s) throws IOException {
+ s.defaultWriteObject();
+ if (getUIClassID().equals(uiClassID)) {
+ // byte count = JComponent.getWriteObjCounter(this);
+ // JComponent.setWriteObjCounter(this, --count);
+ // if (count == 0 && ui != null) {
+ ui.installUI(this);
+ // }
+ }
+ }
+
+ public static class HtmlKit extends HTMLEditorKit {
+ private StyleSheet style = new StyleSheet();
+
+ @Override
+ public void setStyleSheet(StyleSheet styleSheet) {
+ style = styleSheet;
+ }
+
+ @Override
+ public StyleSheet getStyleSheet() {
+ if (style == null) {
+ style = super.getStyleSheet();
+ }
+ return style;
+ }
+ }
+}
diff --git a/src/main/java/featurecat/lizzie/gui/LizziePaneUI.java b/src/main/java/featurecat/lizzie/gui/LizziePaneUI.java
new file mode 100644
index 000000000..8386785c1
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/gui/LizziePaneUI.java
@@ -0,0 +1,10 @@
+package featurecat.lizzie.gui;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import javax.swing.plaf.PanelUI;
+
+public abstract class LizziePaneUI extends PanelUI {
+
+ public abstract void toWindow(Point position, Dimension size);
+}
diff --git a/src/main/java/featurecat/lizzie/gui/MainFrame.java b/src/main/java/featurecat/lizzie/gui/MainFrame.java
new file mode 100644
index 000000000..a7ceab40d
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/gui/MainFrame.java
@@ -0,0 +1,115 @@
+package featurecat.lizzie.gui;
+
+import featurecat.lizzie.Lizzie;
+import java.awt.Font;
+import java.awt.HeadlessException;
+import java.awt.event.MouseWheelEvent;
+import java.io.File;
+import javax.swing.JFrame;
+
+public abstract class MainFrame extends JFrame {
+
+ public boolean isPlayingAgainstLeelaz = false;
+ public boolean playerIsBlack = true;
+ public boolean isNewGame = false;
+ public int boardPositionProportion = Lizzie.config.boardPositionProportion;
+ public int winRateGridLines = 3;
+ public boolean showControls = false;
+ public static Font uiFont;
+ public static Font winrateFont;
+ // Force refresh board
+ private boolean forceRefresh;
+
+ public MainFrame(String title) throws HeadlessException {
+ super(title);
+ }
+
+ public boolean isDesignMode() {
+ return false;
+ }
+
+ public void toggleDesignMode() {}
+
+ public void updateBasicInfo() {}
+
+ public void refresh() {
+ repaint();
+ }
+
+ /**
+ * Refresh
+ *
+ * @param type: 0-All, 1-Only Board, 2-Invalid Layout
+ */
+ public void refresh(int type) {
+ repaint();
+ }
+
+ public boolean isForceRefresh() {
+ return forceRefresh;
+ }
+
+ public void setForceRefresh(boolean forceRefresh) {
+ this.forceRefresh = forceRefresh;
+ }
+
+ public boolean processCommentMouseWheelMoved(MouseWheelEvent e) {
+ return false;
+ }
+
+ public abstract void drawControls();
+
+ public abstract void replayBranch();
+
+ public abstract void refreshBackground();
+
+ public abstract void updateTitle();
+
+ public abstract void setPlayers(String whitePlayer, String blackPlayer);
+
+ public abstract void resetTitle();
+
+ public abstract void clear();
+
+ public abstract boolean isMouseOver(int x, int y);
+
+ public abstract void onClicked(int x, int y);
+
+ public abstract void onDoubleClicked(int x, int y);
+
+ public abstract void onMouseDragged(int x, int y);
+
+ public abstract void onMouseMoved(int x, int y);
+
+ public abstract void startRawBoard();
+
+ public abstract void stopRawBoard();
+
+ public abstract boolean incrementDisplayedBranchLength(int n);
+
+ public abstract void increaseMaxAlpha(int k);
+
+ public abstract void loadFile(File file);
+
+ public abstract void openFile();
+
+ public abstract void saveFile();
+
+ public abstract void copySgf();
+
+ public abstract void pasteSgf();
+
+ public abstract void openConfigDialog();
+
+ public abstract void toggleGtpConsole();
+
+ public abstract void startGame();
+
+ public abstract void editGameInfo();
+
+ public abstract void openChangeMoveDialog();
+
+ public abstract boolean playCurrentVariation();
+
+ public abstract void playBestMove();
+}
diff --git a/src/main/java/featurecat/lizzie/gui/NewGameDialog.java b/src/main/java/featurecat/lizzie/gui/NewGameDialog.java
index 395800aa6..c26455a26 100644
--- a/src/main/java/featurecat/lizzie/gui/NewGameDialog.java
+++ b/src/main/java/featurecat/lizzie/gui/NewGameDialog.java
@@ -5,19 +5,32 @@
package featurecat.lizzie.gui;
import featurecat.lizzie.analysis.GameInfo;
-import java.awt.*;
+import java.awt.BorderLayout;
+import java.awt.Container;
+import java.awt.Dimension;
+import java.awt.GridBagConstraints;
+import java.awt.GridBagLayout;
+import java.awt.GridLayout;
+import java.awt.Insets;
import java.text.DecimalFormat;
import java.text.ParseException;
import java.util.ResourceBundle;
-import javax.swing.*;
+import javax.swing.JButton;
+import javax.swing.JCheckBox;
+import javax.swing.JDialog;
+import javax.swing.JFormattedTextField;
+import javax.swing.JLabel;
+import javax.swing.JPanel;
+import javax.swing.JTextField;
import javax.swing.border.EmptyBorder;
/** @author unknown */
public class NewGameDialog extends JDialog {
+ private static final ResourceBundle resourceBundle =
+ ResourceBundle.getBundle("l10n.DisplayStrings");
// create formatters
public static final DecimalFormat FORMAT_KOMI = new DecimalFormat("#0.0");
public static final DecimalFormat FORMAT_HANDICAP = new DecimalFormat("0");
- public static final JLabel PLACEHOLDER = new JLabel("");
static {
FORMAT_HANDICAP.setMaximumIntegerDigits(1);
@@ -36,14 +49,12 @@ public class NewGameDialog extends JDialog {
private boolean cancelled = true;
private GameInfo gameInfo;
+ private JCheckBox chkNewGame;
public NewGameDialog() {
initComponents();
}
- private static final ResourceBundle resourceBundle =
- ResourceBundle.getBundle("l10n.DisplayStrings");
-
private void initComponents() {
setMinimumSize(new Dimension(100, 100));
setResizable(false);
@@ -80,10 +91,14 @@ private void initContentPanel() {
textFieldBlack = new JTextField();
textFieldKomi = new JFormattedTextField(FORMAT_KOMI);
textFieldHandicap = new JFormattedTextField(FORMAT_HANDICAP);
+ textFieldHandicap.setEnabled(false);
textFieldHandicap.addPropertyChangeListener(evt -> modifyHandicap());
contentPanel.add(checkBoxPlayerIsBlack);
- contentPanel.add(PLACEHOLDER);
+
+ chkNewGame = new JCheckBox(resourceBundle.getString("NewGameDialog.NewGame"), false);
+ chkNewGame.addChangeListener(evt -> toggleNewGame());
+ contentPanel.add(chkNewGame);
contentPanel.add(new JLabel(resourceBundle.getString("NewGameDialog.Black")));
contentPanel.add(textFieldBlack);
contentPanel.add(new JLabel(resourceBundle.getString("NewGameDialog.White")));
@@ -93,7 +108,7 @@ private void initContentPanel() {
contentPanel.add(new JLabel(resourceBundle.getString("NewGameDialog.Handicap")));
contentPanel.add(textFieldHandicap);
- textFieldKomi.setEnabled(false);
+ textFieldKomi.setEnabled(true);
dialogPane.add(contentPanel, BorderLayout.CENTER);
}
@@ -126,7 +141,7 @@ private void initButtonBar() {
((GridBagLayout) buttonBar.getLayout()).columnWeights = new double[] {1.0, 0.0};
// ---- okButton ----
- okButton.setText("OK");
+ okButton.setText(resourceBundle.getString("NewGameDialog.OK"));
okButton.addActionListener(e -> apply());
int center = GridBagConstraints.CENTER;
@@ -172,6 +187,14 @@ public void setGameInfo(GameInfo gameInfo) {
togglePlayerIsBlack();
}
+ private void toggleNewGame() {
+ textFieldHandicap.setEnabled(chkNewGame.isSelected());
+ }
+
+ public boolean isNewGame() {
+ return chkNewGame.isSelected();
+ }
+
public boolean playerIsBlack() {
return checkBoxPlayerIsBlack.isSelected();
}
@@ -180,15 +203,15 @@ public boolean isCancelled() {
return cancelled;
}
- public static void main(String[] args) {
- EventQueue.invokeLater(
- () -> {
- try {
- NewGameDialog window = new NewGameDialog();
- window.setVisible(true);
- } catch (Exception e) {
- e.printStackTrace();
- }
- });
- }
+ // public static void main(String[] args) {
+ // EventQueue.invokeLater(
+ // () -> {
+ // try {
+ // NewGameDialog window = new NewGameDialog();
+ // window.setVisible(true);
+ // } catch (Exception e) {
+ // e.printStackTrace();
+ // }
+ // });
+ // }
}
diff --git a/src/main/java/featurecat/lizzie/gui/SubBoardPane.java b/src/main/java/featurecat/lizzie/gui/SubBoardPane.java
new file mode 100644
index 000000000..28dcc8d89
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/gui/SubBoardPane.java
@@ -0,0 +1,112 @@
+package featurecat.lizzie.gui;
+
+import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
+
+import featurecat.lizzie.Lizzie;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+
+/** The window used to display the game. */
+public class SubBoardPane extends LizziePane {
+
+ private static BoardRenderer subBoardRenderer;
+ private BufferedImage cachedImage;
+
+ // private final BufferStrategy bs;
+
+ /** Creates a window */
+ public SubBoardPane(LizzieMain owner) {
+ super(owner);
+
+ subBoardRenderer = new BoardRenderer(false);
+
+ setVisible(false);
+
+ // TODO BufferStrategy does not support transparent background?
+ // createBufferStrategy(2);
+ // bs = getBufferStrategy();
+
+ addMouseListener(
+ new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getButton() == MouseEvent.BUTTON1) { // left click
+ if (Lizzie.config.showSubBoard) {
+ Lizzie.config.toggleLargeSubBoard();
+ owner.invalidLayout();
+ }
+ }
+ }
+ });
+ }
+
+ /**
+ * Draws the game board and interface
+ *
+ * @param g0 not used
+ */
+ @Override
+ protected void paintComponent(Graphics g0) {
+ super.paintComponent(g0);
+
+ int x = 0; // getX();
+ int y = 0; // getY();
+ int width = getWidth();
+ int height = getHeight();
+ // layout parameters
+
+ // initialize
+ cachedImage = new BufferedImage(width, height, TYPE_INT_ARGB);
+ Graphics2D g = (Graphics2D) cachedImage.getGraphics();
+ g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+
+ if (Lizzie.leelaz != null) { // && Lizzie.leelaz.isLoaded()) {
+
+ if (Lizzie.config.showSubBoard) {
+ try {
+ subBoardRenderer.setLocation(x, y);
+ subBoardRenderer.setBoardLength(width);
+ subBoardRenderer.draw(g);
+ } catch (Exception e) {
+ // This can happen when no space is left for subboard.
+ }
+ }
+ }
+
+ // cleanup
+ g.dispose();
+
+ // draw the image
+ // TODO BufferStrategy does not support transparent background?
+ Graphics2D bsGraphics = (Graphics2D) g0; // bs.getDrawGraphics();
+ bsGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ bsGraphics.drawImage(cachedImage, 0, 0, null);
+
+ // cleanup
+ bsGraphics.dispose();
+ // TODO BufferStrategy does not support transparent background?
+ // bs.show();
+ }
+
+ /**
+ * Checks whether or not something was clicked and performs the appropriate action
+ *
+ * @param x x coordinate
+ * @param y y coordinate
+ */
+ public void onClicked(int x, int y) {
+
+ if (Lizzie.config.showSubBoard && subBoardRenderer.isInside(x, y)) {
+ Lizzie.config.toggleLargeSubBoard();
+ }
+ repaint();
+ }
+
+ public boolean isInside(int x1, int y1) {
+ return subBoardRenderer.isInside(x1, y1);
+ }
+}
diff --git a/src/main/java/featurecat/lizzie/gui/VariationTree.java b/src/main/java/featurecat/lizzie/gui/VariationTree.java
index 09f4fee0e..3a6789017 100644
--- a/src/main/java/featurecat/lizzie/gui/VariationTree.java
+++ b/src/main/java/featurecat/lizzie/gui/VariationTree.java
@@ -2,7 +2,13 @@
import featurecat.lizzie.Lizzie;
import featurecat.lizzie.rules.BoardHistoryNode;
-import java.awt.*;
+import featurecat.lizzie.util.Utils;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.Point;
+import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Optional;
@@ -124,7 +130,7 @@ public Optional drawTree(
RING_DIAM,
RING_DIAM);
}
- g.setColor(Lizzie.frame.getBlunderNodeColor(cur));
+ g.setColor(Utils.getBlunderNodeColor(cur));
g.fillOval(curposx + diff, posy + diff, diam, diam);
if (startNode == curMove) {
g.setColor(Color.BLACK);
@@ -144,9 +150,12 @@ public Optional drawTree(
}
// Draw main line
- while (cur.next().isPresent() && posy + YSPACING < maxposy) {
+ while (cur.next(true).isPresent() && posy + YSPACING < maxposy) {
posy += YSPACING;
- cur = cur.next().get();
+ cur = cur.next(true).get();
+ if (cur.isEndDummay()) {
+ continue;
+ }
if (calc) {
if (inNode(curposx + dotoffset, posy + dotoffset)) {
return Optional.of(cur);
@@ -168,7 +177,7 @@ public Optional drawTree(
RING_DIAM,
RING_DIAM);
}
- g.setColor(Lizzie.frame.getBlunderNodeColor(cur));
+ g.setColor(Utils.getBlunderNodeColor(cur));
g.fillOval(curposx + diff, posy + diff, diam, diam);
if (cur == curMove) {
g.setColor(Color.BLACK);
diff --git a/src/main/java/featurecat/lizzie/gui/VariationTreePane.java b/src/main/java/featurecat/lizzie/gui/VariationTreePane.java
new file mode 100644
index 000000000..bd1495275
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/gui/VariationTreePane.java
@@ -0,0 +1,101 @@
+package featurecat.lizzie.gui;
+
+import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
+
+import featurecat.lizzie.Lizzie;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.image.BufferedImage;
+
+/** The window used to display the game. */
+public class VariationTreePane extends LizziePane {
+ private static VariationTree variationTree;
+
+ private LizzieMain owner;
+
+ // private final BufferStrategy bs;
+
+ /** Creates a window */
+ public VariationTreePane(LizzieMain owner) {
+ super(owner);
+ this.owner = owner;
+
+ variationTree = new VariationTree();
+
+ setVisible(false);
+
+ // createBufferStrategy(2);
+ // bs = getBufferStrategy();
+
+ addMouseListener(
+ new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getButton() == MouseEvent.BUTTON1) { // left click
+ onClicked(e.getX(), e.getY());
+ }
+ }
+ });
+ }
+
+ private BufferedImage cachedImage;
+
+ /**
+ * Draws the game board and interface
+ *
+ * @param g0 not used
+ */
+ @Override
+ protected void paintComponent(Graphics g0) {
+ super.paintComponent(g0);
+
+ int x = 0; // getX();
+ int y = 0; // getY();
+ int width = getWidth();
+ int height = getHeight();
+
+ // layout parameters
+ // initialize
+
+ cachedImage = new BufferedImage(width, height, TYPE_INT_ARGB);
+ Graphics2D g = (Graphics2D) cachedImage.getGraphics();
+ g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+
+ if (Lizzie.leelaz != null) { // && Lizzie.leelaz.isLoaded()) {
+ if (Lizzie.config.showVariationGraph) {
+ g.drawImage(owner.getVariationContainer(this), x, y, null);
+ if (Lizzie.config.showVariationGraph) {
+ variationTree.draw(g, x, y, width, height);
+ }
+ }
+ }
+
+ // cleanup
+ g.dispose();
+
+ // draw the image
+ Graphics2D bsGraphics = (Graphics2D) g0; // bs.getDrawGraphics();
+ bsGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ bsGraphics.drawImage(cachedImage, 0, 0, null);
+
+ // cleanup
+ bsGraphics.dispose();
+ // bs.show();
+ }
+
+ /**
+ * Checks whether or not something was clicked and performs the appropriate action
+ *
+ * @param x x coordinate
+ * @param y y coordinate
+ */
+ public void onClicked(int x, int y) {
+ if (Lizzie.config.showVariationGraph) {
+ variationTree.onClicked(x, y);
+ }
+ }
+}
diff --git a/src/main/java/featurecat/lizzie/gui/WinrateGraph.java b/src/main/java/featurecat/lizzie/gui/WinrateGraph.java
index 057feaed3..9a8bc0711 100644
--- a/src/main/java/featurecat/lizzie/gui/WinrateGraph.java
+++ b/src/main/java/featurecat/lizzie/gui/WinrateGraph.java
@@ -3,7 +3,12 @@
import featurecat.lizzie.Lizzie;
import featurecat.lizzie.analysis.Leelaz;
import featurecat.lizzie.rules.BoardHistoryNode;
-import java.awt.*;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.GradientPaint;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.Stroke;
import java.awt.geom.Point2D;
import java.util.Optional;
diff --git a/src/main/java/featurecat/lizzie/gui/WinratePane.java b/src/main/java/featurecat/lizzie/gui/WinratePane.java
new file mode 100644
index 000000000..297d350e7
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/gui/WinratePane.java
@@ -0,0 +1,269 @@
+package featurecat.lizzie.gui;
+
+import static java.awt.image.BufferedImage.TYPE_INT_ARGB;
+import static java.lang.Math.min;
+
+import featurecat.lizzie.Lizzie;
+import featurecat.lizzie.analysis.Leelaz;
+import featurecat.lizzie.rules.BoardData;
+import java.awt.BasicStroke;
+import java.awt.Color;
+import java.awt.Font;
+import java.awt.Graphics;
+import java.awt.Graphics2D;
+import java.awt.RenderingHints;
+import java.awt.Stroke;
+import java.awt.event.MouseAdapter;
+import java.awt.event.MouseEvent;
+import java.awt.event.MouseMotionAdapter;
+import java.awt.image.BufferedImage;
+import java.util.Optional;
+
+/** The window used to display the game. */
+public class WinratePane extends LizziePane {
+
+ private LizzieMain owner;
+ private static WinrateGraph winrateGraph;
+ private BufferedImage cachedImage;
+ public int winRateGridLines = 3;
+
+ // private final BufferStrategy bs;
+
+ /** Creates a window */
+ public WinratePane(LizzieMain owner) {
+ super(owner);
+ this.owner = owner;
+
+ winrateGraph = new WinrateGraph();
+
+ setVisible(false);
+
+ // createBufferStrategy(2);
+ // bs = getBufferStrategy();
+
+ addMouseListener(
+ new MouseAdapter() {
+ @Override
+ public void mouseClicked(MouseEvent e) {
+ if (e.getButton() == MouseEvent.BUTTON1) { // left click
+ onClicked(e.getX(), e.getY());
+ }
+ }
+ });
+
+ addMouseMotionListener(
+ new MouseMotionAdapter() {
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ onMouseDragged(e.getX(), e.getY());
+ }
+ });
+ }
+
+ /** Clears related status from empty board. */
+ public void clear() {
+ if (winrateGraph != null) {
+ winrateGraph.clear();
+ }
+ }
+
+ /**
+ * Draws the game board and interface
+ *
+ * @param g0 not used
+ */
+ @Override
+ protected void paintComponent(Graphics g0) {
+ super.paintComponent(g0);
+
+ int x = 0; // getX();
+ int y = 0; // getY();
+ int width = getWidth();
+ int height = getHeight();
+
+ // initialize
+
+ cachedImage = new BufferedImage(width, height, TYPE_INT_ARGB);
+ Graphics2D g = (Graphics2D) cachedImage.getGraphics();
+ g.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+
+ if (Lizzie.leelaz != null) { // && Lizzie.leelaz.isLoaded()) {
+ if (Lizzie.config.showWinrate) {
+ g.drawImage(owner.getWinrateContainer(this), x, y, null);
+ int hh = height * 3 / 13;
+ drawMoveStatistics(g, x, y, width, hh);
+ winrateGraph.draw(g, x, y + hh, width, height - hh);
+ }
+ }
+
+ // cleanup
+ g.dispose();
+
+ // draw the image
+ Graphics2D bsGraphics = (Graphics2D) g0; // bs.getDrawGraphics();
+ bsGraphics.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
+ bsGraphics.drawImage(cachedImage, 0, 0, null);
+
+ // cleanup
+ bsGraphics.dispose();
+ // bs.show();
+ }
+
+ private void drawMoveStatistics(Graphics2D g, int posX, int posY, int width, int height) {
+ if (width < 0 || height < 0) return; // we don't have enough space
+
+ double lastWR = 50; // winrate the previous move
+ boolean validLastWinrate = false; // whether it was actually calculated
+ Optional previous = Lizzie.board.getHistory().getPrevious();
+ if (previous.isPresent() && previous.get().getPlayouts() > 0) {
+ lastWR = previous.get().winrate;
+ validLastWinrate = true;
+ }
+
+ Leelaz.WinrateStats stats = Lizzie.leelaz.getWinrateStats();
+ double curWR = stats.maxWinrate; // winrate on this move
+ boolean validWinrate = (stats.totalPlayouts > 0); // and whether it was actually calculated
+ if (Lizzie.frame.isPlayingAgainstLeelaz
+ && Lizzie.frame.playerIsBlack == !Lizzie.board.getHistory().getData().blackToPlay) {
+ validWinrate = false;
+ }
+
+ if (!validWinrate) {
+ curWR = 100 - lastWR; // display last move's winrate for now (with color difference)
+ }
+ double whiteWR, blackWR;
+ if (Lizzie.board.getData().blackToPlay) {
+ blackWR = curWR;
+ } else {
+ blackWR = 100 - curWR;
+ }
+
+ whiteWR = 100 - blackWR;
+
+ // Background rectangle
+ g.setColor(new Color(0, 0, 0, 130));
+ g.fillRect(posX, posY, width, height);
+
+ // border. does not include bottom edge
+ int strokeRadius = Lizzie.config.showBorder ? 3 : 1;
+ g.setStroke(new BasicStroke(strokeRadius == 1 ? strokeRadius : 2 * strokeRadius));
+ g.drawLine(
+ posX + strokeRadius, posY + strokeRadius,
+ posX - strokeRadius + width, posY + strokeRadius);
+ if (Lizzie.config.showBorder) {
+ g.drawLine(
+ posX + strokeRadius, posY + 3 * strokeRadius,
+ posX + strokeRadius, posY - strokeRadius + height);
+ g.drawLine(
+ posX - strokeRadius + width, posY + 3 * strokeRadius,
+ posX - strokeRadius + width, posY - strokeRadius + height);
+ }
+
+ // resize the box now so it's inside the border
+ posX += 2 * strokeRadius;
+ posY += 2 * strokeRadius;
+ width -= 4 * strokeRadius;
+ height -= 4 * strokeRadius;
+
+ // Title
+ strokeRadius = 2;
+ g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ g.setColor(Color.WHITE);
+ setPanelFont(g, (int) (min(width, height) * 0.2));
+
+ // Last move
+ if (validLastWinrate && validWinrate) {
+ String text;
+ if (Lizzie.config.handicapInsteadOfWinrate) {
+ double currHandicapedWR = Lizzie.leelaz.winrateToHandicap(100 - curWR);
+ double lastHandicapedWR = Lizzie.leelaz.winrateToHandicap(lastWR);
+ text = String.format(": %.2f", currHandicapedWR - lastHandicapedWR);
+ } else {
+ text = String.format(": %.1f%%", 100 - lastWR - curWR);
+ }
+
+ g.drawString(
+ LizzieMain.resourceBundle.getString("LizzieFrame.display.lastMove") + text,
+ posX + 2 * strokeRadius,
+ posY + height - 2 * strokeRadius); // - font.getSize());
+ } else {
+ // I think it's more elegant to just not display anything when we don't have
+ // valid data --dfannius
+ // g.drawString(resourceBundle.getString("LizzieFrame.display.lastMove") + ": ?%",
+ // posX + 2 * strokeRadius, posY + height - 2 * strokeRadius);
+ }
+
+ if (validWinrate || validLastWinrate) {
+ int maxBarwidth = (int) (width);
+ int barWidthB = (int) (blackWR * maxBarwidth / 100);
+ int barWidthW = (int) (whiteWR * maxBarwidth / 100);
+ int barPosY = posY + height / 3;
+ int barPosxB = (int) (posX);
+ int barPosxW = barPosxB + barWidthB;
+ int barHeight = height / 3;
+
+ // Draw winrate bars
+ g.fillRect(barPosxW, barPosY, barWidthW, barHeight);
+ g.setColor(Color.BLACK);
+ g.fillRect(barPosxB, barPosY, barWidthB, barHeight);
+
+ // Show percentage above bars
+ g.setColor(Color.WHITE);
+ g.drawString(
+ String.format("%.1f%%", blackWR),
+ barPosxB + 2 * strokeRadius,
+ posY + barHeight - 2 * strokeRadius);
+ String winString = String.format("%.1f%%", whiteWR);
+ int sw = g.getFontMetrics().stringWidth(winString);
+ g.drawString(
+ winString,
+ barPosxB + maxBarwidth - sw - 2 * strokeRadius,
+ posY + barHeight - 2 * strokeRadius);
+
+ g.setColor(Color.GRAY);
+ Stroke oldstroke = g.getStroke();
+ Stroke dashed =
+ new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] {4}, 0);
+ g.setStroke(dashed);
+
+ for (int i = 1; i <= winRateGridLines; i++) {
+ int x = barPosxB + (int) (i * (maxBarwidth / (winRateGridLines + 1)));
+ g.drawLine(x, barPosY, x, barPosY + barHeight);
+ }
+ g.setStroke(oldstroke);
+ }
+ }
+
+ private void setPanelFont(Graphics2D g, float size) {
+ Font font = new Font(Lizzie.config.fontName, Font.PLAIN, (int) size);
+ g.setFont(font);
+ }
+
+ /**
+ * Checks whether or not something was clicked and performs the appropriate action
+ *
+ * @param x x coordinate
+ * @param y y coordinate
+ */
+ public void onClicked(int x, int y) {
+ int moveNumber = winrateGraph.moveNumber(x, y);
+ if (Lizzie.config.showWinrate && moveNumber >= 0) {
+ Lizzie.frame.isPlayingAgainstLeelaz = false;
+ Lizzie.board.goToMoveNumberBeyondBranch(moveNumber);
+ repaint();
+ }
+ }
+
+ public void onMouseDragged(int x, int y) {
+ int moveNumber = winrateGraph.moveNumber(x, y);
+ if (Lizzie.config.showWinrate && moveNumber >= 0) {
+ if (Lizzie.board.goToMoveNumberWithinBranch(moveNumber)) {
+ repaint();
+ }
+ }
+ }
+
+ public int moveNumber(int x, int y) {
+ return winrateGraph.moveNumber(x, y);
+ }
+}
diff --git a/src/main/java/featurecat/lizzie/rules/Board.java b/src/main/java/featurecat/lizzie/rules/Board.java
index b4adc0b73..5f91d6f5f 100644
--- a/src/main/java/featurecat/lizzie/rules/Board.java
+++ b/src/main/java/featurecat/lizzie/rules/Board.java
@@ -21,7 +21,7 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
-import javax.swing.*;
+import javax.swing.JOptionPane;
import org.json.JSONException;
public class Board implements LeelazListener {
@@ -37,9 +37,6 @@ public class Board implements LeelazListener {
// Save the node for restore move when in the branch
private Optional saveNode;
- // Force refresh board
- private boolean forceRefresh;
-
public Board() {
initialize();
}
@@ -51,7 +48,7 @@ private void initialize() {
analysisMode = false;
playoutsAnalysis = 100;
saveNode = Optional.empty();
- forceRefresh = false;
+ Lizzie.frame.setForceRefresh(false);
history = new BoardHistoryList(BoardData.empty(boardSize));
}
@@ -166,18 +163,10 @@ public void reopen(int size) {
Zobrist.init();
clear();
Lizzie.leelaz.sendCommand("boardsize " + boardSize);
- forceRefresh = true;
+ Lizzie.frame.setForceRefresh(true);
}
}
- public boolean isForceRefresh() {
- return forceRefresh;
- }
-
- public void setForceRefresh(boolean forceRefresh) {
- this.forceRefresh = forceRefresh;
- }
-
/**
* The comment. Thread safe
*
@@ -214,6 +203,37 @@ public void moveNumber(int moveNumber) {
}
}
+ public int moveNumberByCoord(int[] coord) {
+ int moveNumber = 0;
+ if (Lizzie.board.isValid(coord)) {
+ int index = Lizzie.board.getIndex(coord[0], coord[1]);
+ if (Lizzie.board.getHistory().getStones()[index] != Stone.EMPTY) {
+ BoardHistoryNode cur = Lizzie.board.getHistory().getCurrentHistoryNode();
+ moveNumber = cur.getData().moveNumberList[index];
+ if (!cur.isMainTrunk()) {
+ if (moveNumber > 0) {
+ moveNumber = cur.getData().moveNumber - cur.getData().moveMNNumber + moveNumber;
+ } else {
+ BoardHistoryNode p = cur.firstParentWithVariations().orElse(cur);
+ while (p != cur && moveNumber == 0) {
+ moveNumber = p.getData().moveNumberList[index];
+ if (moveNumber > 0) {
+ BoardHistoryNode topOfTop = p.firstParentWithVariations().orElse(p);
+ if (topOfTop != p) {
+ moveNumber = p.getData().moveNumber - p.getData().moveMNNumber + moveNumber;
+ }
+ } else {
+ cur = p;
+ p = cur.firstParentWithVariations().orElse(cur);
+ }
+ }
+ }
+ }
+ }
+ }
+ return moveNumber;
+ }
+
/**
* Add a stone to the board representation. Thread safe
*
@@ -232,7 +252,7 @@ public void addStone(int x, int y, Stone color) {
stones[getIndex(x, y)] = color;
zobrist.toggleStone(x, y, color);
- Lizzie.frame.repaint();
+ Lizzie.frame.refresh();
}
}
@@ -257,7 +277,7 @@ public void removeStone(int x, int y, Stone color) {
zobrist.toggleStone(x, y, oriColor);
data.moveNumberList[Board.getIndex(x, y)] = 0;
- Lizzie.frame.repaint();
+ Lizzie.frame.refresh();
}
}
@@ -329,7 +349,7 @@ public void pass(Stone color, boolean newBranch, boolean dummy, boolean changeMo
Zobrist zobrist = history.getZobrist();
int moveNumber = history.getMoveNumber() + 1;
int[] moveNumberList =
- newBranch && history.getNext().isPresent()
+ newBranch && history.getNext(true).isPresent()
? new int[Board.boardSize * Board.boardSize]
: history.getMoveNumberList().clone();
@@ -357,7 +377,7 @@ public void pass(Stone color, boolean newBranch, boolean dummy, boolean changeMo
// update history with pass
history.addOrGoto(newState, newBranch, changeMove);
- Lizzie.frame.repaint();
+ Lizzie.frame.refresh();
}
}
@@ -378,7 +398,7 @@ public void place(int x, int y, Stone color) {
}
public void place(int x, int y, Stone color, boolean newBranch) {
- place(x, y, color, false, false);
+ place(x, y, color, newBranch, false);
}
/**
@@ -434,7 +454,7 @@ public void place(int x, int y, Stone color, boolean newBranch, boolean changeMo
int moveMNNumber =
history.getMoveMNNumber() > -1 && !newBranch ? history.getMoveMNNumber() + 1 : -1;
int[] moveNumberList =
- newBranch && history.getNext().isPresent()
+ newBranch && history.getNext(true).isPresent()
? new int[Board.boardSize * Board.boardSize]
: history.getMoveNumberList().clone();
@@ -478,6 +498,7 @@ public void place(int x, int y, Stone color, boolean newBranch, boolean changeMo
nextWinrate,
0);
newState.moveMNNumber = moveMNNumber;
+ newState.dummy = false;
// don't make this coordinate if it is suicidal or violates superko
if (isSuicidal > 0 || history.violatesKoRule(newState)) return;
@@ -494,7 +515,7 @@ public void place(int x, int y, Stone color, boolean newBranch, boolean changeMo
// update history with this coordinate
history.addOrGoto(newState, newBranch, changeMove);
- Lizzie.frame.repaint();
+ Lizzie.frame.refresh();
}
}
@@ -556,7 +577,7 @@ public void flatten() {
* @param zobrist the zobrist object to modify
* @return number of removed stones
*/
- private int removeDeadChain(int x, int y, Stone color, Stone[] stones, Zobrist zobrist) {
+ public static int removeDeadChain(int x, int y, Stone color, Stone[] stones, Zobrist zobrist) {
if (!isValid(x, y) || stones[getIndex(x, y)] != color) return 0;
boolean hasLiberties = hasLibertiesHelper(x, y, color, stones);
@@ -575,7 +596,7 @@ private int removeDeadChain(int x, int y, Stone color, Stone[] stones, Zobrist z
* @param stones the stones array to modify
* @return whether or not this chain has liberties
*/
- private boolean hasLibertiesHelper(int x, int y, Stone color, Stone[] stones) {
+ private static boolean hasLibertiesHelper(int x, int y, Stone color, Stone[] stones) {
if (!isValid(x, y)) return false;
if (stones[getIndex(x, y)] == Stone.EMPTY) return true; // a liberty was found
@@ -607,7 +628,7 @@ else if (stones[getIndex(x, y)] != color)
* their unrecursed version
* @return number of removed stones
*/
- private int cleanupHasLibertiesHelper(
+ private static int cleanupHasLibertiesHelper(
int x, int y, Stone color, Stone[] stones, Zobrist zobrist, boolean removeStones) {
int removed = 0;
if (!isValid(x, y) || stones[getIndex(x, y)] != color) return 0;
@@ -676,7 +697,7 @@ public boolean nextMove() {
} else {
Lizzie.leelaz.playMove(history.getLastMoveColor(), "pass");
}
- Lizzie.frame.repaint();
+ Lizzie.frame.refresh();
return true;
}
return false;
@@ -807,7 +828,7 @@ public boolean nextVariation(int idx) {
} else {
Lizzie.leelaz.playMove(history.getLastMoveColor(), "pass");
}
- Lizzie.frame.repaint();
+ Lizzie.frame.refresh();
return true;
}
return false;
@@ -963,7 +984,7 @@ public void moveBranchDown() {
public void deleteMove() {
synchronized (this) {
BoardHistoryNode currentNode = history.getCurrentHistoryNode();
- if (currentNode.next().isPresent()) {
+ if (currentNode.next(true).isPresent()) {
// Will delete more than one move, ask for confirmation
int ret =
JOptionPane.showConfirmDialog(
@@ -1020,7 +1041,7 @@ public boolean previousMove() {
updateWinrate();
if (history.previous().isPresent()) {
Lizzie.leelaz.undo();
- Lizzie.frame.repaint();
+ Lizzie.frame.refresh();
return true;
}
return false;
@@ -1115,7 +1136,7 @@ private void toggleLiveStatus(Stone[] stones, int stonex, int stoney) {
x++;
}
}
- Lizzie.frame.repaint();
+ Lizzie.frame.refresh();
}
/*
diff --git a/src/main/java/featurecat/lizzie/rules/BoardData.java b/src/main/java/featurecat/lizzie/rules/BoardData.java
index 0f757a57d..6b9a092e8 100644
--- a/src/main/java/featurecat/lizzie/rules/BoardData.java
+++ b/src/main/java/featurecat/lizzie/rules/BoardData.java
@@ -2,7 +2,11 @@
import featurecat.lizzie.Lizzie;
import featurecat.lizzie.analysis.MoveData;
-import java.util.*;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
public class BoardData {
public int moveNumber;
@@ -188,4 +192,33 @@ public void setPlayouts(int playouts) {
public int getPlayouts() {
return playouts;
}
+
+ public void sync(BoardData data) {
+ this.moveMNNumber = data.moveMNNumber;
+ this.moveNumber = data.moveNumber;
+ this.lastMove = data.lastMove;
+ this.moveNumberList = data.moveNumberList;
+ this.blackToPlay = data.blackToPlay;
+ this.dummy = data.dummy;
+ this.lastMoveColor = data.lastMoveColor;
+ this.stones = data.stones;
+ this.zobrist = data.zobrist;
+ this.verify = data.verify;
+ this.blackCaptures = data.blackCaptures;
+ this.whiteCaptures = data.whiteCaptures;
+ this.comment = data.comment;
+ }
+
+ public BoardData clone() {
+ BoardData data = BoardData.empty(19);
+ data.sync(this);
+ return data;
+ }
+
+ public boolean isSameCoord(int[] coord) {
+ if (coord == null || coord.length < 2 || !this.lastMove.isPresent()) {
+ return false;
+ }
+ return this.lastMove.map(m -> (m[0] == coord[0] && m[1] == coord[1])).orElse(false);
+ }
}
diff --git a/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java b/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java
index ce54886d3..bbf8598f4 100644
--- a/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java
+++ b/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java
@@ -76,13 +76,29 @@ public void toStart() {
while (previous().isPresent()) ;
}
+ public void toBranchTop() {
+ BoardHistoryNode start = head;
+ while (start.previous().isPresent()) {
+ BoardHistoryNode pre = start.previous().get();
+ if (pre.next(true).isPresent() && pre.next(true).get() != start) {
+ previous();
+ break;
+ }
+ previous();
+ start = pre;
+ }
+ }
/**
* moves the pointer to the right, returns the data stored there
*
* @return the data of next node, Optional.empty if there is no next node
*/
public Optional next() {
- Optional n = head.next();
+ return next(false);
+ }
+
+ public Optional next(boolean includeDummay) {
+ Optional n = head.next(includeDummay);
n.ifPresent(x -> head = x);
return n.map(x -> x.getData());
}
@@ -104,7 +120,11 @@ public Optional nextVariation(int idx) {
* @return the data stored at the next index, if any, Optional.empty otherwise.
*/
public Optional getNext() {
- return head.next().map(x -> x.getData());
+ return getNext(false);
+ }
+
+ public Optional getNext(boolean includeDummy) {
+ return head.next(includeDummy).map(x -> x.getData());
}
/** @return nexts for display */
@@ -240,4 +260,285 @@ public BoardHistoryNode getEnd() {
}
return e;
}
+
+ public void pass(Stone color) {
+ pass(color, false, false, false);
+ }
+
+ public void pass(Stone color, boolean newBranch) {
+ pass(color, newBranch, false, false);
+ }
+
+ public void pass(Stone color, boolean newBranch, boolean dummy) {
+ pass(color, newBranch, dummy, false);
+ }
+
+ public void pass(Stone color, boolean newBranch, boolean dummy, boolean changeMove) {
+ synchronized (this) {
+
+ // check to see if this move is being replayed in history
+ if (this.getNext().map(n -> !n.lastMove.isPresent()).orElse(false) && !newBranch) {
+ // this is the next move in history. Just increment history so that we don't erase the
+ // redo's
+ this.next();
+ return;
+ }
+
+ Stone[] stones = this.getStones().clone();
+ Zobrist zobrist = this.getZobrist();
+ int moveNumber = this.getMoveNumber() + 1;
+ int[] moveNumberList =
+ newBranch && this.getNext(true).isPresent()
+ ? new int[Board.boardSize * Board.boardSize]
+ : this.getMoveNumberList().clone();
+
+ // build the new game state
+ BoardData newState =
+ new BoardData(
+ stones,
+ Optional.empty(),
+ color,
+ color.equals(Stone.WHITE),
+ zobrist,
+ moveNumber,
+ moveNumberList,
+ this.getData().blackCaptures,
+ this.getData().whiteCaptures,
+ 0,
+ 0);
+ newState.dummy = dummy;
+
+ // update history with pass
+ this.addOrGoto(newState, newBranch, changeMove);
+ }
+ }
+
+ public void place(int x, int y, Stone color) {
+ place(x, y, color, false);
+ }
+
+ public void place(int x, int y, Stone color, boolean newBranch) {
+ place(x, y, color, newBranch, false);
+ }
+
+ public void place(int x, int y, Stone color, boolean newBranch, boolean changeMove) {
+ synchronized (this) {
+ if (!Board.isValid(x, y)
+ || (this.getStones()[Board.getIndex(x, y)] != Stone.EMPTY && !newBranch)) return;
+
+ double nextWinrate = -100;
+ if (this.getData().winrate >= 0) nextWinrate = 100 - this.getData().winrate;
+
+ // check to see if this coordinate is being replayed in history
+ Optional nextLast = this.getNext().flatMap(n -> n.lastMove);
+ if (nextLast.isPresent()
+ && nextLast.get()[0] == x
+ && nextLast.get()[1] == y
+ && !newBranch
+ && !changeMove) {
+ // this is the next coordinate in history. Just increment history so that we don't erase the
+ // redo's
+ this.next();
+ return;
+ }
+
+ // load a copy of the data at the current node of history
+ Stone[] stones = this.getStones().clone();
+ Zobrist zobrist = this.getZobrist();
+ Optional lastMove = Optional.of(new int[] {x, y});
+ int moveNumber = this.getMoveNumber() + 1;
+ int moveMNNumber =
+ this.getMoveMNNumber() > -1 && !newBranch ? this.getMoveMNNumber() + 1 : -1;
+ int[] moveNumberList =
+ newBranch && this.getNext(true).isPresent()
+ ? new int[Board.boardSize * Board.boardSize]
+ : this.getMoveNumberList().clone();
+
+ moveNumberList[Board.getIndex(x, y)] = moveMNNumber > -1 ? moveMNNumber : moveNumber;
+
+ // set the stone at (x, y) to color
+ stones[Board.getIndex(x, y)] = color;
+ zobrist.toggleStone(x, y, color);
+
+ // remove enemy stones
+ int capturedStones = 0;
+ capturedStones += Board.removeDeadChain(x + 1, y, color.opposite(), stones, zobrist);
+ capturedStones += Board.removeDeadChain(x, y + 1, color.opposite(), stones, zobrist);
+ capturedStones += Board.removeDeadChain(x - 1, y, color.opposite(), stones, zobrist);
+ capturedStones += Board.removeDeadChain(x, y - 1, color.opposite(), stones, zobrist);
+
+ // check to see if the player made a suicidal coordinate
+ int isSuicidal = Board.removeDeadChain(x, y, color, stones, zobrist);
+
+ for (int i = 0; i < Board.boardSize * Board.boardSize; i++) {
+ if (stones[i].equals(Stone.EMPTY)) {
+ moveNumberList[i] = 0;
+ }
+ }
+
+ int bc = this.getData().blackCaptures;
+ int wc = this.getData().whiteCaptures;
+ if (color.isBlack()) bc += capturedStones;
+ else wc += capturedStones;
+ BoardData newState =
+ new BoardData(
+ stones,
+ lastMove,
+ color,
+ color.equals(Stone.WHITE),
+ zobrist,
+ moveNumber,
+ moveNumberList,
+ bc,
+ wc,
+ nextWinrate,
+ 0);
+ newState.moveMNNumber = moveMNNumber;
+
+ // don't make this coordinate if it is suicidal or violates superko
+ if (isSuicidal > 0 || this.violatesKoRule(newState)) return;
+
+ // update history with this coordinate
+ this.addOrGoto(newState, newBranch, changeMove);
+ }
+ }
+
+ public void addNodeProperty(String key, String value) {
+ synchronized (this) {
+ this.getData().addProperty(key, value);
+ if ("MN".equals(key)) {
+ moveNumber(Integer.parseInt(value));
+ }
+ }
+ }
+
+ public void moveNumber(int moveNumber) {
+ synchronized (this) {
+ BoardData data = this.getData();
+ if (data.lastMove.isPresent()) {
+ int[] moveNumberList = this.getMoveNumberList();
+ moveNumberList[Board.getIndex(data.lastMove.get()[0], data.lastMove.get()[1])] = moveNumber;
+ Optional node = this.getCurrentHistoryNode().previous();
+ while (node.isPresent() && node.get().numberOfChildren() <= 1) {
+ BoardData nodeData = node.get().getData();
+ if (nodeData.lastMove.isPresent() && nodeData.moveNumber >= moveNumber) {
+ moveNumber = (moveNumber > 1) ? moveNumber - 1 : 0;
+ moveNumberList[Board.getIndex(nodeData.lastMove.get()[0], nodeData.lastMove.get()[1])] =
+ moveNumber;
+ }
+ node = node.get().previous();
+ }
+ }
+ }
+ }
+
+ public void addStone(int x, int y, Stone color) {
+ synchronized (this) {
+ if (!Board.isValid(x, y) || this.getStones()[Board.getIndex(x, y)] != Stone.EMPTY) return;
+
+ Stone[] stones = this.getData().stones;
+ Zobrist zobrist = this.getData().zobrist;
+
+ // set the stone at (x, y) to color
+ stones[Board.getIndex(x, y)] = color;
+ zobrist.toggleStone(x, y, color);
+ }
+ }
+
+ public void removeStone(int x, int y, Stone color) {
+ synchronized (this) {
+ if (!Board.isValid(x, y) || this.getStones()[Board.getIndex(x, y)] == Stone.EMPTY) return;
+
+ BoardData data = this.getData();
+ Stone[] stones = data.stones;
+ Zobrist zobrist = data.zobrist;
+
+ // set the stone at (x, y) to empty
+ Stone oriColor = stones[Board.getIndex(x, y)];
+ stones[Board.getIndex(x, y)] = Stone.EMPTY;
+ zobrist.toggleStone(x, y, oriColor);
+ data.moveNumberList[Board.getIndex(x, y)] = 0;
+ }
+ }
+
+ public void flatten() {
+ Stone[] stones = this.getStones();
+ boolean blackToPlay = this.isBlacksTurn();
+ Zobrist zobrist = this.getZobrist().clone();
+ BoardHistoryList oldHistory = this;
+
+ head =
+ new BoardHistoryNode(
+ new BoardData(
+ stones,
+ Optional.empty(),
+ Stone.EMPTY,
+ blackToPlay,
+ zobrist,
+ 0,
+ new int[Board.boardSize * Board.boardSize],
+ 0,
+ 0,
+ 0.0,
+ 0));
+ this.setGameInfo(oldHistory.getGameInfo());
+ }
+
+ public boolean goToMoveNumber(int moveNumber, boolean withinBranch) {
+ int delta = moveNumber - this.getMoveNumber();
+ boolean moved = false;
+ for (int i = 0; i < Math.abs(delta); i++) {
+ if (withinBranch && delta < 0) {
+ BoardHistoryNode currentNode = this.getCurrentHistoryNode();
+ if (!currentNode.isFirstChild()) {
+ break;
+ }
+ }
+ if (!(delta > 0 ? next().isPresent() : previous().isPresent())) {
+ break;
+ }
+ moved = true;
+ }
+ return moved;
+ }
+
+ public int sync(BoardHistoryList newList) {
+ int diffMoveNo = -1;
+
+ BoardHistoryNode node = this.getCurrentHistoryNode();
+ BoardHistoryNode prev = node.previous().map(p -> p).orElse(null);
+ // From begin
+ while (prev != null) {
+ node = prev;
+ prev = node.previous().map(p -> p).orElse(null);
+ }
+ // Compare
+ BoardHistoryNode newNode = newList.getCurrentHistoryNode();
+
+ while (newNode != null) {
+ if (node == null) {
+ // Add
+ prev.addOrGoto(newNode.getData().clone());
+ node = prev.next().map(n -> n).orElse(null);
+ if (diffMoveNo < 0) {
+ diffMoveNo = newNode.getData().moveNumber;
+ }
+ node.sync(newNode);
+ break;
+ } else {
+ if (!node.compare(newNode)) {
+ if (diffMoveNo < 0) {
+ diffMoveNo = newNode.getData().moveNumber;
+ }
+ node.sync(newNode);
+ break;
+ }
+ }
+ prev = node;
+ node = node.next(true).map(n -> n).orElse(null);
+ newNode = newNode.next(true).map(n -> n).orElse(null);
+ }
+
+ return diffMoveNo;
+ }
}
diff --git a/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java b/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java
index dd7c778d4..6a8058f37 100644
--- a/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java
+++ b/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java
@@ -53,7 +53,7 @@ public BoardHistoryNode addOrGoto(BoardData data) {
}
public BoardHistoryNode addOrGoto(BoardData data, boolean newBranch) {
- return addOrGoto(data, false, false);
+ return addOrGoto(data, newBranch, false);
}
/**
@@ -89,6 +89,9 @@ public BoardHistoryNode addOrGoto(BoardData data, boolean newBranch, boolean cha
// variations.set(i, variations.get(0));
// variations.set(0, currentNext);
// }
+ if (i != 0 && changeMove) {
+ break;
+ }
return variations.get(i);
}
}
@@ -109,7 +112,7 @@ public BoardHistoryNode addOrGoto(BoardData data, boolean newBranch, boolean cha
}
BoardHistoryNode node = new BoardHistoryNode(data);
if (changeMove) {
- Optional next = next();
+ Optional next = next(true);
next.ifPresent(
n -> {
node.variations = n.variations;
@@ -141,7 +144,17 @@ public Optional previous() {
}
public Optional next() {
- return variations.isEmpty() ? Optional.empty() : Optional.of(variations.get(0));
+ return next(false);
+ }
+
+ public Optional next(boolean includeDummy) {
+ return variations.isEmpty() || (!includeDummy && variations.get(0).isEndDummay())
+ ? Optional.empty()
+ : Optional.of(variations.get(0));
+ }
+
+ public boolean isEndDummay() {
+ return this.data.dummy && variations.isEmpty();
}
public BoardHistoryNode topOfBranch() {
@@ -368,7 +381,7 @@ public Optional findChildOfPreviousWithVariation() {
* @return index of child node, -1 if child node not a child of parent
*/
public int indexOfNode(BoardHistoryNode childNode) {
- if (!next().isPresent()) {
+ if (!next(true).isPresent()) {
return -1;
}
for (int i = 0; i < numberOfChildren(); i++) {
@@ -459,6 +472,40 @@ public boolean isMainTrunk() {
return true;
}
+ /**
+ * Finds the next node with the comment.
+ *
+ * @return the first next node with comment, if any, Optional.empty otherwise
+ */
+ public Optional findNextNodeWithComment() {
+ BoardHistoryNode node = this;
+ while (node.next().isPresent()) {
+ BoardHistoryNode next = node.next().get();
+ if (!next.getData().comment.isEmpty()) {
+ return Optional.ofNullable(next);
+ }
+ node = next;
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Finds the previous node with the comment.
+ *
+ * @return the first previous node with comment, if any, Optional.empty otherwise
+ */
+ public Optional findPreviousNodeWithComment() {
+ BoardHistoryNode node = this;
+ while (node.previous().isPresent()) {
+ BoardHistoryNode previous = node.previous().get();
+ if (!previous.getData().comment.isEmpty()) {
+ return Optional.ofNullable(previous);
+ }
+ node = previous;
+ }
+ return Optional.empty();
+ }
+
/**
* Go to the next node with the comment.
*
@@ -496,4 +543,55 @@ public int goToPreviousNodeWithComment() {
}
return moves;
}
+
+ public boolean compare(BoardHistoryNode node) {
+ BoardData sData = this.getData();
+ BoardData dData = node.getData();
+
+ boolean dMove =
+ sData.lastMove.isPresent() && dData.lastMove.isPresent()
+ || !sData.lastMove.isPresent() && !dData.lastMove.isPresent();
+ if (dMove && sData.lastMove.isPresent()) {
+ int[] sM = sData.lastMove.get();
+ int[] dM = dData.lastMove.get();
+ dMove =
+ (sM != null
+ && sM.length == 2
+ && dM != null
+ && dM.length == 2
+ && sM[0] == dM[0]
+ && sM[1] == dM[1]);
+ }
+ return dMove
+ && sData.comment != null
+ && sData.comment.equals(dData.comment)
+ && this.numberOfChildren() == node.numberOfChildren();
+ }
+
+ public void sync(BoardHistoryNode node) {
+ if (node == null) return;
+
+ BoardHistoryNode cur = this;
+ // Compare
+ while (node != null) {
+ if (!cur.compare(node)) {
+ BoardData sData = cur.getData();
+ sData.sync(node.getData());
+ if (node.numberOfChildren() > 0) {
+ for (int i = 0; i < node.numberOfChildren(); i++) {
+ if (node.getVariation(i).isPresent()) {
+ if (cur.variations.size() <= i) {
+ cur.addOrGoto(node.getVariation(i).get().getData().clone(), (i > 0));
+ }
+ if (i > 0) {
+ cur.variations.get(i).sync(node.getVariation(i).get());
+ }
+ }
+ }
+ }
+ }
+ cur = cur.next(true).map(n -> n).orElse(null);
+ node = node.next(true).map(n -> n).orElse(null);
+ }
+ }
}
diff --git a/src/main/java/featurecat/lizzie/rules/SGFParser.java b/src/main/java/featurecat/lizzie/rules/SGFParser.java
index d15a3feab..4226d5e53 100644
--- a/src/main/java/featurecat/lizzie/rules/SGFParser.java
+++ b/src/main/java/featurecat/lizzie/rules/SGFParser.java
@@ -6,7 +6,15 @@
import featurecat.lizzie.analysis.GameInfo;
import featurecat.lizzie.analysis.Leelaz;
import featurecat.lizzie.util.EncodingDetector;
-import java.io.*;
+import featurecat.lizzie.util.Utils;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.StringWriter;
+import java.io.Writer;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
@@ -190,7 +198,7 @@ private static boolean parse(String value) {
Lizzie.board.place(move[0], move[1], color, newBranch);
}
if (newBranch) {
- processPendingPros(pendingProps);
+ processPendingPros(Lizzie.board.getHistory(), pendingProps);
}
} else if (tag.equals("C")) {
// Support comment
@@ -217,7 +225,7 @@ private static boolean parse(String value) {
.replaceAll("[^0-9]", ""));
Lizzie.board.getData().setPlayouts(numPlayouts);
if (numPlayouts > 0 && !line2.isEmpty()) {
- Lizzie.board.getData().bestMoves = Leelaz.parseInfo(line2);
+ Lizzie.board.getData().bestMoves = Lizzie.leelaz.parseInfo(line2);
}
} else if (tag.equals("AB") || tag.equals("AW")) {
int[] move = convertSgfPosToCoord(tagContent);
@@ -231,7 +239,7 @@ private static boolean parse(String value) {
boolean newBranch = (subTreeStepMap.get(subTreeDepth) == 1);
Lizzie.board.pass(color, newBranch, true);
if (newBranch) {
- processPendingPros(pendingProps);
+ processPendingPros(Lizzie.board.getHistory(), pendingProps);
}
addPassForMove = false;
}
@@ -275,7 +283,7 @@ private static boolean parse(String value) {
boolean newBranch = (subTreeStepMap.get(subTreeDepth) == 1);
Lizzie.board.pass(color, newBranch, true);
if (newBranch) {
- processPendingPros(pendingProps);
+ processPendingPros(Lizzie.board.getHistory(), pendingProps);
}
addPassForMove = false;
}
@@ -326,6 +334,7 @@ private static boolean parse(String value) {
// Set AW/AB Comment
if (!headComment.isEmpty()) {
Lizzie.board.comment(headComment);
+ Lizzie.frame.refresh();
}
if (gameProperties.size() > 0) {
Lizzie.board.addNodeProperties(gameProperties);
@@ -504,7 +513,7 @@ private static String formatComment(BoardHistoryNode node) {
String engine = Lizzie.leelaz.currentWeight();
// Playouts
- String playouts = Lizzie.frame.getPlayoutsString(data.getPlayouts());
+ String playouts = Utils.getPlayoutsString(data.getPlayouts());
// Last winrate
Optional lastNode = node.previous().flatMap(n -> Optional.of(n.getData()));
@@ -569,7 +578,7 @@ private static String formatNodeData(BoardHistoryNode node) {
BoardData data = node.getData();
// Playouts
- String playouts = Lizzie.frame.getPlayoutsString(data.getPlayouts());
+ String playouts = Utils.getPlayoutsString(data.getPlayouts());
// Last winrate
Optional lastNode = node.previous().flatMap(n -> Optional.of(n.getData()));
@@ -726,8 +735,8 @@ public static String nodeString(String key, String value) {
return sb.toString();
}
- private static void processPendingPros(Map props) {
- props.forEach((key, value) -> Lizzie.board.addNodeProperty(key, value));
+ private static void processPendingPros(BoardHistoryList history, Map props) {
+ props.forEach((key, value) -> history.addNodeProperty(key, value));
props = new HashMap();
}
diff --git a/src/main/java/featurecat/lizzie/util/Utils.java b/src/main/java/featurecat/lizzie/util/Utils.java
new file mode 100644
index 000000000..b2ab56f25
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/util/Utils.java
@@ -0,0 +1,162 @@
+package featurecat.lizzie.util;
+
+import static java.lang.Math.round;
+
+import featurecat.lizzie.Lizzie;
+import featurecat.lizzie.analysis.Leelaz;
+import featurecat.lizzie.rules.BoardData;
+import featurecat.lizzie.rules.BoardHistoryNode;
+import java.awt.Color;
+import java.awt.FontMetrics;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import javax.swing.JTextField;
+
+public class Utils {
+
+ public static boolean isBlank(String str) {
+ return str == null || str.trim().isEmpty();
+ }
+
+ /**
+ * @return a shorter, rounded string version of playouts. e.g. 345 -> 345, 1265 -> 1.3k, 44556 ->
+ * 45k, 133523 -> 134k, 1234567 -> 1.2m
+ */
+ public static String getPlayoutsString(int playouts) {
+ if (playouts >= 1_000_000) {
+ double playoutsDouble = (double) playouts / 100_000; // 1234567 -> 12.34567
+ return round(playoutsDouble) / 10.0 + "m";
+ } else if (playouts >= 10_000) {
+ double playoutsDouble = (double) playouts / 1_000; // 13265 -> 13.265
+ return round(playoutsDouble) + "k";
+ } else if (playouts >= 1_000) {
+ double playoutsDouble = (double) playouts / 100; // 1265 -> 12.65
+ return round(playoutsDouble) / 10.0 + "k";
+ } else {
+ return String.valueOf(playouts);
+ }
+ }
+
+ /**
+ * Truncate text that is too long for the given width
+ *
+ * @param line
+ * @param fm
+ * @param fitWidth
+ * @return fitted
+ */
+ public static String truncateStringByWidth(String line, FontMetrics fm, int fitWidth) {
+ if (line.isEmpty()) {
+ return "";
+ }
+ int width = fm.stringWidth(line);
+ if (width > fitWidth) {
+ int guess = line.length() * fitWidth / width;
+ String before = line.substring(0, guess).trim();
+ width = fm.stringWidth(before);
+ if (width > fitWidth) {
+ int diff = width - fitWidth;
+ int i = 0;
+ for (; (diff > 0 && i < 5); i++) {
+ diff = diff - fm.stringWidth(line.substring(guess - i - 1, guess - i));
+ }
+ return line.substring(0, guess - i).trim();
+ } else {
+ return before;
+ }
+ } else {
+ return line;
+ }
+ }
+
+ public static double lastWinrateDiff(BoardHistoryNode node) {
+
+ // Last winrate
+ Optional lastNode = node.previous().flatMap(n -> Optional.of(n.getData()));
+ boolean validLastWinrate = lastNode.map(d -> d.getPlayouts() > 0).orElse(false);
+ double lastWR = validLastWinrate ? lastNode.get().winrate : 50;
+
+ // Current winrate
+ BoardData data = node.getData();
+ boolean validWinrate = false;
+ double curWR = 50;
+ if (data == Lizzie.board.getHistory().getData()) {
+ Leelaz.WinrateStats stats = Lizzie.leelaz.getWinrateStats();
+ curWR = stats.maxWinrate;
+ validWinrate = (stats.totalPlayouts > 0);
+ if (Lizzie.frame.isPlayingAgainstLeelaz
+ && Lizzie.frame.playerIsBlack == !Lizzie.board.getHistory().getData().blackToPlay) {
+ validWinrate = false;
+ }
+ } else {
+ validWinrate = (data.getPlayouts() > 0);
+ curWR = validWinrate ? data.winrate : 100 - lastWR;
+ }
+
+ // Last move difference winrate
+ if (validLastWinrate && validWinrate) {
+ return 100 - lastWR - curWR;
+ } else {
+ return 0;
+ }
+ }
+
+ public static Color getBlunderNodeColor(BoardHistoryNode node) {
+ if (Lizzie.config.nodeColorMode == 1 && node.getData().blackToPlay
+ || Lizzie.config.nodeColorMode == 2 && !node.getData().blackToPlay) {
+ return Color.WHITE;
+ }
+ double diffWinrate = lastWinrateDiff(node);
+ Optional st =
+ diffWinrate >= 0
+ ? Lizzie.config.blunderWinrateThresholds.flatMap(
+ l -> l.stream().filter(t -> (t > 0 && t <= diffWinrate)).reduce((f, s) -> s))
+ : Lizzie.config.blunderWinrateThresholds.flatMap(
+ l -> l.stream().filter(t -> (t < 0 && t >= diffWinrate)).reduce((f, s) -> f));
+ if (st.isPresent()) {
+ return Lizzie.config.blunderNodeColors.map(m -> m.get(st.get())).get();
+ } else {
+ return Color.WHITE;
+ }
+ }
+
+ public static Integer txtFieldValue(JTextField txt) {
+ if (txt.getText().trim().isEmpty()
+ || txt.getText().trim().length() >= String.valueOf(Integer.MAX_VALUE).length()) {
+ return 0;
+ } else {
+ return Integer.parseInt(txt.getText().trim());
+ }
+ }
+
+ public static int intOfMap(Map map, String key) {
+ if (map == null) {
+ return 0;
+ }
+ List s = (List) map.get(key);
+ if (s == null || s.size() <= 0) {
+ return 0;
+ }
+ try {
+ return Integer.parseInt((String) s.get(0));
+ } catch (NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ public static String stringOfMap(Map map, String key) {
+ if (map == null) {
+ return "";
+ }
+ List s = (List) map.get(key);
+ if (s == null || s.size() <= 0) {
+ return "";
+ }
+ try {
+ return (String) s.get(0);
+ } catch (NumberFormatException e) {
+ return "";
+ }
+ }
+}
diff --git a/src/main/java/featurecat/lizzie/util/WindowPosition.java b/src/main/java/featurecat/lizzie/util/WindowPosition.java
new file mode 100644
index 000000000..ca37a91f3
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/util/WindowPosition.java
@@ -0,0 +1,156 @@
+package featurecat.lizzie.util;
+
+import featurecat.lizzie.Lizzie;
+import featurecat.lizzie.gui.BasicInfoPane;
+import featurecat.lizzie.gui.BoardPane;
+import featurecat.lizzie.gui.CommentPane;
+import featurecat.lizzie.gui.LizzieMain;
+import featurecat.lizzie.gui.LizziePane;
+import featurecat.lizzie.gui.SubBoardPane;
+import featurecat.lizzie.gui.VariationTreePane;
+import featurecat.lizzie.gui.WinratePane;
+import java.awt.Dimension;
+import java.awt.Insets;
+import java.awt.Point;
+import java.awt.Window;
+import javax.swing.JFrame;
+import javax.swing.SwingUtilities;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+public class WindowPosition {
+
+ public static JSONObject create(JSONObject ui) {
+ if (ui == null) {
+ ui = new JSONObject();
+ }
+
+ // Main Window Position & Size
+ ui.put("main-window-position", new JSONArray("[]"));
+ ui.put("gtp-console-position", new JSONArray("[]"));
+ ui.put("board-position-proportion", 4);
+ // Panes
+ ui.put("main-board-position", new JSONArray("[]"));
+ ui.put("sub-board-position", new JSONArray("[]"));
+ ui.put("basic-info-position", new JSONArray("[]"));
+ ui.put("winrate-position", new JSONArray("[]"));
+ ui.put("variation-tree-position", new JSONArray("[]"));
+ ui.put("comment-position", new JSONArray("[]"));
+
+ ui.put("window-maximized", false);
+
+ return ui;
+ }
+
+ public static JSONObject save(JSONObject ui) {
+ if (ui == null) {
+ ui = new JSONObject();
+ }
+
+ boolean windowIsMaximized = Lizzie.frame.getExtendedState() == JFrame.MAXIMIZED_BOTH;
+ ui.put("window-maximized", windowIsMaximized);
+ ui.put("board-position-proportion", Lizzie.frame.boardPositionProportion);
+
+ JSONArray mainPos = new JSONArray();
+ if (!windowIsMaximized) {
+ mainPos.put(Lizzie.frame.getX());
+ mainPos.put(Lizzie.frame.getY());
+ mainPos.put(Lizzie.frame.getWidth());
+ mainPos.put(Lizzie.frame.getHeight());
+ }
+ ui.put("main-window-position", mainPos);
+
+ JSONArray gtpPos = new JSONArray();
+ gtpPos.put(Lizzie.gtpConsole.getX());
+ gtpPos.put(Lizzie.gtpConsole.getY());
+ gtpPos.put(Lizzie.gtpConsole.getWidth());
+ gtpPos.put(Lizzie.gtpConsole.getHeight());
+ ui.put("gtp-console-position", gtpPos);
+
+ // Panes
+ if (Lizzie.frame instanceof LizzieMain) {
+ LizzieMain main = (LizzieMain) Lizzie.frame;
+ ui.put("main-board-position", getWindowPos(main.boardPane));
+ ui.put("sub-board-position", getWindowPos(main.subBoardPane));
+ ui.put("basic-info-position", getWindowPos(main.basicInfoPane));
+ ui.put("winrate-position", getWindowPos(main.winratePane));
+ ui.put("variation-tree-position", getWindowPos(main.variationTreePane));
+ ui.put("comment-position", getWindowPos(main.commentPane));
+ }
+
+ return ui;
+ }
+
+ public static JSONArray mainWindowPos() {
+
+ // Main
+ boolean persisted = (Lizzie.config.persistedUi != null);
+ if (persisted
+ && Lizzie.config.persistedUi.optJSONArray("main-window-position") != null
+ && Lizzie.config.persistedUi.optJSONArray("main-window-position").length() == 4) {
+ return Lizzie.config.persistedUi.getJSONArray("main-window-position");
+ } else {
+ return null;
+ }
+ }
+
+ public static JSONArray gtpWindowPos() {
+
+ boolean persisted = Lizzie.config.persistedUi != null;
+ if (persisted
+ && Lizzie.config.persistedUi.optJSONArray("gtp-console-position") != null
+ && Lizzie.config.persistedUi.optJSONArray("gtp-console-position").length() == 4) {
+ return Lizzie.config.persistedUi.getJSONArray("gtp-console-position");
+ } else {
+ return null;
+ }
+ }
+
+ public static void restorePane(JSONObject ui, LizziePane pane) {
+ if (ui == null) {
+ return;
+ }
+ JSONArray pos = getPersistedPanePos(pane, ui);
+ if (pos != null) {
+ pane.toWindow(
+ new Point(pos.getInt(0), pos.getInt(1)), new Dimension(pos.getInt(2), pos.getInt(3)));
+ }
+ }
+
+ public static JSONArray getWindowPos(LizziePane pane) {
+ JSONArray panePos = new JSONArray("[]");
+ Window paneWindow = SwingUtilities.getWindowAncestor(pane);
+ if (!(paneWindow instanceof LizzieMain)) {
+ Insets insets = paneWindow.getInsets();
+ panePos.put(paneWindow.getX());
+ panePos.put(paneWindow.getY());
+ panePos.put(paneWindow.getWidth() - insets.left - insets.right);
+ panePos.put(paneWindow.getHeight() - insets.top - insets.bottom);
+ }
+ return panePos;
+ }
+
+ public static JSONArray getPersistedPanePos(LizziePane pane, JSONObject ui) {
+ String key = "";
+ if (pane instanceof BoardPane) {
+ key = "main-board-position";
+ } else if (pane instanceof SubBoardPane) {
+ key = "sub-board-position";
+ } else if (pane instanceof BasicInfoPane) {
+ key = "basic-info-position";
+ } else if (pane instanceof WinratePane) {
+ key = "winrate-position";
+ } else if (pane instanceof VariationTreePane) {
+ key = "variation-tree-position";
+ } else if (pane instanceof CommentPane) {
+ key = "comment-position";
+ }
+ JSONArray pos = null;
+ if (!key.isEmpty()) {
+ if (ui.optJSONArray(key) != null && ui.optJSONArray(key).length() == 4) {
+ pos = ui.getJSONArray(key);
+ }
+ }
+ return pos;
+ }
+}
diff --git a/src/main/resources/l10n/DisplayStrings.properties b/src/main/resources/l10n/DisplayStrings.properties
index cec15df04..c6ae04c26 100644
--- a/src/main/resources/l10n/DisplayStrings.properties
+++ b/src/main/resources/l10n/DisplayStrings.properties
@@ -15,7 +15,7 @@
#
# You can directly use the original display texts as the key, but it is recommended to name the key properly.
-LizzieFrame.title=Lizzie - Leela Zero Interface
+LizzieFrame.title=Lizzie - AI Interface
LizzieFrame.commands.keyAltC=ctrl-c|copy SGF to clipboard
LizzieFrame.commands.keyAltV=ctrl-v|paste SGF from clipboard
LizzieFrame.commands.keyC=c|toggle coordinates
@@ -24,7 +24,7 @@ LizzieFrame.commands.keyD=d|show/hide dynamic komi
LizzieFrame.commands.keyDownArrow=down arrow|redo
LizzieFrame.commands.keyE=e|toggle evaluation coloring
LizzieFrame.commands.keyEnd=end|go to end
-LizzieFrame.commands.keyEnter=enter|force Leela Zero move
+LizzieFrame.commands.keyEnter=enter|force AI move
LizzieFrame.commands.keyF=f|toggle next move display
LizzieFrame.commands.keyG=g|toggle variation graph
LizzieFrame.commands.keyT=t|toggle comment display
@@ -33,7 +33,7 @@ LizzieFrame.commands.keyHome=home|go to start
LizzieFrame.commands.keyI=i|edit game info
LizzieFrame.commands.keyA=a|run automatic analysis of game
LizzieFrame.commands.keyM=m|show/hide move number
-LizzieFrame.commands.keyN=n|start game against Leela Zero
+LizzieFrame.commands.keyN=n|start game against AI
LizzieFrame.commands.keyO=o|open SGF
LizzieFrame.commands.keyP=p|pass
LizzieFrame.commands.keyS=s|save SGF
@@ -61,7 +61,7 @@ LizzieFrame.display.lastMove=Last move
LizzieFrame.display.pondering=Pondering
LizzieFrame.display.on=on
LizzieFrame.display.off=off
-LizzieFrame.display.loading=Leela Zero is loading...
+LizzieFrame.display.loading=Engine is loading...
LizzieFrame.display.leelaz-missing=Did not find Leela Zero, update config.txt or download from Leela Zero homepage
LizzieFrame.display.network-missing=Did not find network weights.\nUpdate config.txt (network-file) or download from Leela Zero homepage
LizzieFrame.display.dynamic-komi=dyn. komi:
@@ -97,9 +97,11 @@ LizzieChangeMove.txtMoveNumber.error=Invalid move number!
LizzieChangeMove.txtChangeCoord.error=Invalid move coordinate!
LizzieChangeMove.button.ok=OK
LizzieChangeMove.button.cancel=Cancel
-NewGameDialog.title=New Game
+NewGameDialog.title=Play against AI
NewGameDialog.PlayBlack=Play black?
NewGameDialog.Black=Black
NewGameDialog.White=White
NewGameDialog.Komi=Komi
NewGameDialog.Handicap=Handicap
+NewGameDialog.NewGame=New game?
+NewGameDialog.OK=OK
diff --git a/src/main/resources/l10n/DisplayStrings_zh_CN.properties b/src/main/resources/l10n/DisplayStrings_zh_CN.properties
index 02e60e999..465341c52 100644
--- a/src/main/resources/l10n/DisplayStrings_zh_CN.properties
+++ b/src/main/resources/l10n/DisplayStrings_zh_CN.properties
@@ -3,6 +3,7 @@
# to encode native translations in raw unicode codes, such as u001 u002 if the native strings
# cannot be encoded in ascii. Typically east Asian translations, such as Chinese(both simplified and traditional),
# Korean or Japanese, need the encoding process.
+LizzieFrame.title=Lizzie-AI\u63A5\u53E3
LizzieFrame.commands.keyAltC=ctrl-c|\u62F7\u8D1DSGF\u5230\u526A\u8D34\u677F
LizzieFrame.commands.keyAltV=ctrl-v|\u4ECE\u526A\u8D34\u677F\u7C98\u8D34SGF\u5C40\u9762
LizzieFrame.commands.keyC=c|\u663E\u793A/\u9690\u85CF\u5750\u6807
@@ -10,7 +11,7 @@ LizzieFrame.commands.keyD=d|show/hide dynamic komi
LizzieFrame.commands.keyControl=ctrl|\u5411\u524D/\u5411\u540E10\u6B65
LizzieFrame.commands.keyDownArrow=\u4E0B\u65B9\u5411\u952E|\u5411\u540E\u4E00\u6B65
LizzieFrame.commands.keyEnd=end|\u8DF3\u5230\u68CB\u8C31\u672B\u5C3E
-LizzieFrame.commands.keyEnter=enter|\u8BA9Leela Zero\u843D\u5B50
+LizzieFrame.commands.keyEnter=enter|\u8BA9AI\u843D\u5B50
LizzieFrame.commands.keyF=f|\u663E\u793A/\u9690\u85CF\u4E0B\u4E00\u624B
LizzieFrame.commands.keyG=g|\u663E\u793A/\u9690\u85CF\u5206\u652F\u56FE
LizzieFrame.commands.keyT=t|\u663E\u793A/\u9690\u85CF\u8BC4\u8BBA
@@ -19,7 +20,7 @@ LizzieFrame.commands.keyHome=home|\u8DF3\u8F6C\u5230\u68CB\u8C31\u5F00\u5934
LizzieFrame.commands.keyI=i|\u7F16\u8F91\u68CB\u5C40\u4FE1\u606F
LizzieFrame.commands.keyA=a|\u8FD0\u884C\u7B80\u5355\u7684\u81EA\u52A8\u5168\u76D8\u5206\u6790
LizzieFrame.commands.keyM=m|\u663E\u793A/\u9690\u85CF\u624B\u6570
-LizzieFrame.commands.keyN=n|\u5F00\u59CB\u4E0ELeela Zero\u7684\u5BF9\u5F08
+LizzieFrame.commands.keyN=n|\u5F00\u59CB\u4E0EAI\u7684\u5BF9\u5F08
LizzieFrame.commands.keyO=o|\u6253\u5F00SGF\u6587\u4EF6
LizzieFrame.commands.keyP=p|\u505C\u4E00\u624B
LizzieFrame.commands.keyS=s|\u4FDD\u5B58SGF
@@ -43,7 +44,7 @@ LizzieFrame.display.lastMove=\u6700\u540E\u4E00\u624B
LizzieFrame.display.pondering=\u5206\u6790
LizzieFrame.display.on=\u5F00\u542F
LizzieFrame.display.off=\u6682\u505C
-LizzieFrame.display.loading=Leela Zero\u6B63\u5728\u8F7D\u5165\u4E2D...
+LizzieFrame.display.loading=\u5F15\u64CE\u8F7D\u5165\u4E2D...
LizzieFrame.display.leelaz-missing=Did not find Leela Zero, update config.txt or download from Leela Zero homepage
LizzieFrame.display.network-missing=Did not find network weights.\nUpdate config.txt (network-file) or download from Leela Zero homepage
LizzieFrame.display.dynamic-komi=dyn. komi:
@@ -79,3 +80,11 @@ LizzieChangeMove.txtMoveNumber.error=\u65E0\u6548\u7684\u624B\u6570\uFF01
LizzieChangeMove.txtChangeCoord.error=\u65E0\u6548\u7684\u5750\u6807\uFF01
LizzieChangeMove.button.ok=\u786E\u5B9A
LizzieChangeMove.button.cancel=\u53D6\u6D88
+NewGameDialog.title=\u548CAI\u5BF9\u6218
+NewGameDialog.PlayBlack=\u6267\u9ED1\uFF1F
+NewGameDialog.Black=\u9ED1\u65B9
+NewGameDialog.White=\u767D\u65B9
+NewGameDialog.Komi=\u8D34\u76EE
+NewGameDialog.Handicap=\u8BA9\u5B50
+NewGameDialog.NewGame=\u65B0\u5C40\uFF1F
+NewGameDialog.OK=\u786E\u5B9A
diff --git a/src/test/java/featurecat/lizzie/rules/SGFParserTest.java b/src/test/java/featurecat/lizzie/rules/SGFParserTest.java
index d42bab753..91f3f59ab 100644
--- a/src/test/java/featurecat/lizzie/rules/SGFParserTest.java
+++ b/src/test/java/featurecat/lizzie/rules/SGFParserTest.java
@@ -8,7 +8,6 @@
import featurecat.lizzie.Config;
import featurecat.lizzie.Lizzie;
import featurecat.lizzie.analysis.Leelaz;
-import featurecat.lizzie.gui.LizzieFrame;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
@@ -23,7 +22,7 @@ public void run() throws IOException {
lizzie = new Lizzie();
lizzie.config = new Config();
lizzie.board = new Board();
- lizzie.frame = new LizzieFrame();
+ // lizzie.frame = new LizzieFrame();
// new Thread( () -> {
lizzie.leelaz = new Leelaz();
// }).start();