diff --git a/doc/plugin.md b/doc/plugin.md deleted file mode 100644 index e5053390c..000000000 --- a/doc/plugin.md +++ /dev/null @@ -1,115 +0,0 @@ -# Developing plug-ins for Lizzie - - -The Jar file for a plugin should look like this: - -./plugin -./plugin/Plugin.class -./META-INF -./META-INF/MANIFEST.MF -./...Your other classes - -### Start first step - -Create a "HelloWorld" directory and create a "plugin" directory in it, and create a new "Plugin.java" file in the "plugin" directory. - -And write the following - -```java -package plugin; - -import java.io.IOException; - -import IPlugin; - -/** - * Plugin - */ -public class Plugin implements IPlugin { - public static String name = "Hello World"; - public static String version = "0.0.0"; - - @Override - public String getName() { - return name; - } - - @Override - public String getVersion() { - return version; - } -} -``` - -This is the most basic framework of a plugin where ``` name ``` and ``` version ``` are used to generate a hash of this plugin. - -Let's edit Plugin.java to have a message box pop up when we press 'H'. - -```java - @Override - public void onKeyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_H) { - JOptionPane.showConfirmDialog(null, "Hello World!"); - } - } -``` - -And add the import statement at the beginning of the file - -```java -import java.swt.KeyEvent; -import javax.swing.JOptionPane; -``` - -Copy lizzie.jar to "HelloWorld" directory, and execute the command: - -``` -javac -classpath lizzie.jar ./plugin/Plugin.java -jar -cvf HelloWorld.jar ./plugin/Plugin.class -``` - -Copy the generated "HelloWorld.jar" to the "plugin" directory under the Lizzie directory. - -Start the lizzie and press 'H'! - -### More - -The plugin can freely call Lizzie's own classes and interfaces, so it can do a lot of functions. - -In the future, plugins may be able to enable and disable certain functions, so it is possible to do very complete functions with plug-ins. - -### Methods for plug-ins - -In ``` Lizzie.config ```: - -``` public boolean showBranch ``` - -``` public boolean showBestMoves ``` - -``` public void toggleShowBranch() ``` - -``` public void toggleShowBestMoves() ``` - -### Callback functions - -Basic callback function - -``` public abstract String getName() ``` Should be override, return plug-in 's name. -``` public abstract String getVersion() ``` Should be override, return plug-in 's version. - -``` public void onInit() throws IOException ``` Called when the plugin is loaded, the plugin is loaded after loading the configuration file. - -``` public void onMousePressed(MouseEvent e) ``` -``` public void onMouseReleased(MouseEvent e) ``` -``` public void onMouseMoved(MouseEvent e) ``` -``` public void onKeyPressed(KeyEvent e) ``` -``` public void onKeyReleased(KeyEvent e) ``` Like its name, it is called in the corresponding event. - -``` public boolean onDraw(Graphics2D g) ``` Called when drawing, the return value represents whether to submit the drawing result. - -``` public void onShutdown() throws IOException ``` Called when the program is shut down. - - -Special callback function - -``` public void onSgfLoaded() ``` diff --git a/doc/theme.md b/doc/theme.md deleted file mode 100644 index 20f006348..000000000 --- a/doc/theme.md +++ /dev/null @@ -1,19 +0,0 @@ -# Theme -The theme's function is to make Lizzie's display richer, so simply change the assets file can not do things. - -The way to load a theme is to copy the theme's Jar file to the theme directory, and then set the theme's class name in the configuration file. - -Any class that implements the ITheme interface can be used as a theme class. - -Use ```javac -classpath ...``` to ensure that the theme's source files can be imported into the ITheme class. - -A theme class needs to implement the following methods: -```java -public Image getBlackStone(int[] position) throws IOException; - -public Image getWhiteStone(int[] position) throws IOException; - - public Image getBoard() throws IOException; - - public Image getBackground() throws IOException; -``` diff --git a/pom.xml b/pom.xml index be1ec6c0d..699e4d2f2 100644 --- a/pom.xml +++ b/pom.xml @@ -64,18 +64,6 @@ false - - com.coveo - fmt-maven-plugin - 2.5.1 - - - - format - - - - @@ -94,5 +82,12 @@ 4.11 test + + + + com.jhlabs + filters + 2.0.235 + diff --git a/src/main/java/featurecat/lizzie/Config.java b/src/main/java/featurecat/lizzie/Config.java index ee5c70b50..169880605 100644 --- a/src/main/java/featurecat/lizzie/Config.java +++ b/src/main/java/featurecat/lizzie/Config.java @@ -14,6 +14,8 @@ public class Config { public boolean showMoveNumber = false; public boolean showWinrate = true; public boolean showVariationGraph = true; + public boolean showComment = false; + public int commentFontSize = 0; public boolean showRawBoard = false; public boolean showCaptured = true; public boolean handicapInsteadOfWinrate = false; @@ -130,6 +132,8 @@ public Config() throws IOException { showBranch = uiConfig.getBoolean("show-leelaz-variation"); showWinrate = uiConfig.getBoolean("show-winrate"); showVariationGraph = uiConfig.getBoolean("show-variation-graph"); + showComment = uiConfig.optBoolean("show-comment", false); + commentFontSize = uiConfig.optInt("comment-font-size", 0); showCaptured = uiConfig.getBoolean("show-captured"); showBestMoves = uiConfig.getBoolean("show-best-moves"); showNextMoves = uiConfig.getBoolean("show-next-moves"); @@ -181,6 +185,10 @@ public void toggleShowVariationGraph() { this.showVariationGraph = !this.showVariationGraph; } + public void toggleShowComment() { + this.showComment = !this.showComment; + } + public void toggleShowBestMoves() { this.showBestMoves = !this.showBestMoves; } @@ -249,7 +257,6 @@ private JSONObject createDefaultConfig() { JSONObject ui = new JSONObject(); ui.put("board-color", new JSONArray("[217, 152, 77]")); - ui.put("theme", "DefaultTheme"); ui.put("shadows-enabled", true); ui.put("fancy-stones", true); ui.put("fancy-board", true); diff --git a/src/main/java/featurecat/lizzie/Lizzie.java b/src/main/java/featurecat/lizzie/Lizzie.java index 08db4991f..15a3b6de2 100644 --- a/src/main/java/featurecat/lizzie/Lizzie.java +++ b/src/main/java/featurecat/lizzie/Lizzie.java @@ -2,11 +2,11 @@ import featurecat.lizzie.analysis.Leelaz; import featurecat.lizzie.gui.LizzieFrame; -import featurecat.lizzie.plugin.PluginManager; import featurecat.lizzie.rules.Board; import java.io.File; import java.io.IOException; import javax.swing.*; +import org.json.JSONArray; import org.json.JSONException; /** Main class. */ @@ -16,40 +16,51 @@ public class Lizzie { public static Board board; public static Config config; public static String lizzieVersion = "0.5"; + private static String[] args; /** Launches the game window, and runs the game. */ - public static void main(String[] args) - throws IOException, JSONException, ClassNotFoundException, UnsupportedLookAndFeelException, - InstantiationException, IllegalAccessException, InterruptedException { - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + public static void main(String[] args) throws IOException { + setLookAndFeel(); + args = args; config = new Config(); - PluginManager.loadPlugins(); board = new Board(); frame = new LizzieFrame(); + new Thread(Lizzie::run).start(); + } - new Thread( - () -> { - try { - leelaz = new Leelaz(); - if (config.handicapInsteadOfWinrate) { - leelaz.estimatePassWinrate(); - } - if (args.length == 1) { - frame.loadFile(new File(args[0])); - } else if (config.config.getJSONObject("ui").getBoolean("resume-previous-game")) { - board.resumePreviousGame(); - } - leelaz.togglePonder(); - } catch (IOException e) { - e.printStackTrace(); - System.exit(-1); - } - }) - .start(); + public static void run() { + try { + leelaz = new Leelaz(); + if (config.handicapInsteadOfWinrate) { + leelaz.estimatePassWinrate(); + } + if (args.length == 1) { + frame.loadFile(new File(args[0])); + } else if (config.config.getJSONObject("ui").getBoolean("resume-previous-game")) { + board.resumePreviousGame(); + } + leelaz.togglePonder(); + } catch (IOException e) { + e.printStackTrace(); + System.exit(-1); + } + } + + public static void setLookAndFeel() { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } catch (UnsupportedLookAndFeelException e) { + e.printStackTrace(); + } } public static void shutdown() { - PluginManager.onShutdown(); if (board != null && config.config.getJSONObject("ui").getBoolean("confirm-exit")) { int ret = JOptionPane.showConfirmDialog( @@ -64,11 +75,58 @@ public static void shutdown() { try { config.persist(); - } catch (IOException err) { - // Failed to save config + } catch (IOException e) { + e.printStackTrace(); // Failed to save config } if (leelaz != null) leelaz.shutdown(); System.exit(0); } + + /** + * Switch the Engine by index number + * + * @param index engine index + */ + public static void switchEngine(int index) { + + String commandLine = null; + if (index == 0) { + commandLine = Lizzie.config.leelazConfig.getString("engine-command"); + commandLine = + commandLine.replaceAll( + "%network-file", Lizzie.config.leelazConfig.getString("network-file")); + } else { + JSONArray commandList = Lizzie.config.leelazConfig.getJSONArray("engine-command-list"); + if (commandList != null && commandList.length() >= index) { + commandLine = commandList.getString(index - 1); + } else { + index = -1; + } + } + if (index < 0 + || commandLine == null + || commandLine.trim().isEmpty() + || index == Lizzie.leelaz.currentEngineN()) { + return; + } + + // Workaround for leelaz cannot exit when restarting + if (leelaz.isThinking) { + if (Lizzie.frame.isPlayingAgainstLeelaz) { + Lizzie.frame.isPlayingAgainstLeelaz = false; + Lizzie.leelaz.togglePonder(); // we must toggle twice for it to restart pondering + Lizzie.leelaz.isThinking = false; + } + Lizzie.leelaz.togglePonder(); + } + + board.saveMoveNumber(); + try { + leelaz.restartEngine(commandLine, index); + board.restoreMoveNumber(); + } catch (IOException e) { + e.printStackTrace(); + } + } } diff --git a/src/main/java/featurecat/lizzie/Util.java b/src/main/java/featurecat/lizzie/Util.java index 432ebb1f5..94def40a9 100644 --- a/src/main/java/featurecat/lizzie/Util.java +++ b/src/main/java/featurecat/lizzie/Util.java @@ -1,5 +1,6 @@ package featurecat.lizzie; +import java.awt.FontMetrics; import java.io.*; import java.net.URL; import java.nio.channels.Channels; @@ -74,4 +75,36 @@ public static void saveAsFile(URL url, File file) { e.printStackTrace(); } } + + /** + * 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 == null || line.length() == 0) { + 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; + } + } } diff --git a/src/main/java/featurecat/lizzie/analysis/Leelaz.java b/src/main/java/featurecat/lizzie/analysis/Leelaz.java index 5c2774437..f9224919b 100644 --- a/src/main/java/featurecat/lizzie/analysis/Leelaz.java +++ b/src/main/java/featurecat/lizzie/analysis/Leelaz.java @@ -10,6 +10,11 @@ import java.net.URL; import java.util.*; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.swing.*; import org.json.JSONException; import org.json.JSONObject; @@ -55,6 +60,15 @@ public class Leelaz { private boolean isLoaded = false; private boolean isCheckingVersion; + // for Multiple Engine + private String engineCommand = null; + private List commands = null; + private JSONObject config = null; + private String currentWeight = null; + private boolean switching = false; + private int currentEngineN = -1; + private ScheduledExecutorService executor = null; + // dynamic komi and opponent komi as reported by dynamic-komi version of leelaz private float dynamicKomi = Float.NaN, dynamicOppKomi = Float.NaN; /** @@ -73,7 +87,8 @@ public Leelaz() throws IOException, JSONException { currentCmdNum = 0; cmdQueue = new ArrayDeque<>(); - JSONObject config = Lizzie.config.config.getJSONObject("leelaz"); + // Move config to member for other method call + config = Lizzie.config.config.getJSONObject("leelaz"); printCommunication = config.getBoolean("print-comms"); maxAnalyzeTimeMillis = MINUTE * config.getInt("max-analyze-time-minutes"); @@ -82,13 +97,44 @@ public Leelaz() throws IOException, JSONException { updateToLatestNetwork(); } + // command string for starting the engine + engineCommand = config.getString("engine-command"); + // substitute in the weights file + engineCommand = engineCommand.replaceAll("%network-file", config.getString("network-file")); + + // Initialize current engine number and start engine + currentEngineN = 0; + startEngine(engineCommand); + Lizzie.frame.refreshBackground(); + } + + public void startEngine(String engineCommand) throws IOException { + // Check engine command + if (engineCommand == null || engineCommand.trim().isEmpty()) { + return; + } + File startfolder = new File(config.optString("engine-start-location", ".")); - String engineCommand = config.getString("engine-command"); String networkFile = config.getString("network-file"); // substitute in the weights file engineCommand = engineCommand.replaceAll("%network-file", networkFile); // create this as a list which gets passed into the processbuilder - List commands = Arrays.asList(engineCommand.split(" ")); + commands = Arrays.asList(engineCommand.split(" ")); + + // get weight name + if (engineCommand != null) { + Pattern wPattern = Pattern.compile("(?s).*?(--weights |-w )([^ ]+)(?s).*"); + Matcher wMatcher = wPattern.matcher(engineCommand); + if (wMatcher.matches()) { + currentWeight = wMatcher.group(2); + if (currentWeight != null) { + String[] names = currentWeight.split("[\\\\|/]"); + if (names != null && names.length > 1) { + currentWeight = names[names.length - 1]; + } + } + } + } // Check if engine is present File lef = startfolder.toPath().resolve(new File(commands.get(0)).toPath()).toFile(); @@ -123,8 +169,42 @@ public Leelaz() throws IOException, JSONException { sendCommand("version"); // start a thread to continuously read Leelaz output - new Thread(this::read).start(); - Lizzie.frame.refreshBackground(); + // new Thread(this::read).start(); + // can stop engine for switching weights + executor = Executors.newSingleThreadScheduledExecutor(); + executor.execute(this::read); + } + + public void restartEngine(String engineCommand, int index) throws IOException { + if (engineCommand == null || engineCommand.trim().isEmpty()) { + return; + } + switching = true; + this.engineCommand = engineCommand; + // stop the ponder + if (Lizzie.leelaz.isPondering()) { + Lizzie.leelaz.togglePonder(); + } + normalQuit(); + startEngine(engineCommand); + currentEngineN = index; + togglePonder(); + } + + public void normalQuit() { + sendCommand("quit"); + executor.shutdown(); + try { + while (!executor.awaitTermination(1, TimeUnit.SECONDS)) { + executor.shutdownNow(); + } + if (executor.awaitTermination(1, TimeUnit.SECONDS)) { + shutdown(); + } + } catch (InterruptedException e) { + executor.shutdownNow(); + Thread.currentThread().interrupt(); + } } private void updateToLatestNetwork() { @@ -201,6 +281,10 @@ private void parseLine(String line) { // End of response } else if (line.startsWith("info")) { isLoaded = true; + // Clear switching prompt + switching = false; + // Display engine command in the title + if (Lizzie.frame != null) Lizzie.frame.updateTitle(); if (isResponseUpToDate()) { // This should not be stale data when the command number match parseInfo(line.substring(5)); @@ -301,7 +385,8 @@ private void read() { System.out.println("Leelaz process ended."); shutdown(); - System.exit(-1); + // Do no exit for switching weights + // System.exit(-1); } catch (IOException e) { e.printStackTrace(); System.exit(-1); @@ -424,9 +509,8 @@ private void ponder() { + Lizzie.config .config .getJSONObject("leelaz") - .getInt( - "analyze-update-interval-centisec")); // until it responds to this, incoming - // ponder results are obsolete + .getInt("analyze-update-interval-centisec")); // until it responds to this, incoming + // ponder results are obsolete } public void togglePonder() { @@ -484,23 +568,12 @@ public WinrateStats getWinrateStats() { final List moves = new ArrayList(bestMoves); // get the total number of playouts in moves - stats.totalPlayouts = - moves - .stream() - .reduce( - 0, - (Integer result, MoveData move) -> result + move.playouts, - (Integer a, Integer b) -> a + b); + int totalPlayouts = moves.stream().mapToInt(move -> move.playouts).sum(); + stats.totalPlayouts = totalPlayouts; // set maxWinrate to the weighted average winrate of moves stats.maxWinrate = - moves - .stream() - .reduce( - 0d, - (Double result, MoveData move) -> - result + move.winrate * move.playouts / stats.totalPlayouts, - (Double a, Double b) -> a + b); + moves.stream().mapToDouble(move -> move.winrate * move.playouts / totalPlayouts).sum(); } return stats; @@ -580,4 +653,20 @@ private synchronized void notifyBestMoveListeners() { public boolean isLoaded() { return isLoaded; } + + public String currentWeight() { + return currentWeight; + } + + public boolean switching() { + return switching; + } + + public int currentEngineN() { + return currentEngineN; + } + + public String engineCommand() { + return this.engineCommand; + } } diff --git a/src/main/java/featurecat/lizzie/gui/BoardRenderer.java b/src/main/java/featurecat/lizzie/gui/BoardRenderer.java index e3e7e18d4..a30f1cfcf 100644 --- a/src/main/java/featurecat/lizzie/gui/BoardRenderer.java +++ b/src/main/java/featurecat/lizzie/gui/BoardRenderer.java @@ -1,32 +1,38 @@ package featurecat.lizzie.gui; +import static java.awt.RenderingHints.*; +import static java.awt.image.BufferedImage.TYPE_INT_ARGB; +import static java.lang.Math.log; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.lang.Math.round; + import featurecat.lizzie.Lizzie; import featurecat.lizzie.analysis.Branch; import featurecat.lizzie.analysis.MoveData; -import featurecat.lizzie.plugin.PluginManager; import featurecat.lizzie.rules.Board; import featurecat.lizzie.rules.BoardData; import featurecat.lizzie.rules.BoardHistoryNode; import featurecat.lizzie.rules.SGFParser; import featurecat.lizzie.rules.Stone; import featurecat.lizzie.rules.Zobrist; -import featurecat.lizzie.theme.DefaultTheme; -import featurecat.lizzie.theme.ITheme; import java.awt.*; import java.awt.font.TextAttribute; import java.awt.geom.Point2D; import java.awt.image.BufferedImage; +import java.io.IOException; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.imageio.ImageIO; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class BoardRenderer { - private static final double MARGIN = - 0.03; // percentage of the boardLength to offset before drawing black lines + // Percentage of the boardLength to offset before drawing black lines + private static final double MARGIN = 0.03; private static final double MARGIN_WITH_COORDINATES = 0.06; private static final double STARPOINT_DIAMETER = 0.015; @@ -43,6 +49,8 @@ public class BoardRenderer { private int cachedX, cachedY; private BufferedImage cachedStonesImage = null; + private BufferedImage cachedBoardImage = null; + private BufferedImage cachedWallpaperImage = null; private BufferedImage cachedStonesShadowImage = null; private Zobrist cachedZhash = new Zobrist(); // defaults to an empty board @@ -54,7 +62,6 @@ public class BoardRenderer { private boolean lastInScoreMode = false; - public ITheme theme; public List variation; // special values of displayedBranchLength @@ -70,10 +77,6 @@ public class BoardRenderer { public BoardRenderer(boolean isMainBoard) { uiConfig = Lizzie.config.config.getJSONObject("ui"); - theme = ITheme.loadTheme(uiConfig.getString("theme")); - if (theme == null) { - theme = new DefaultTheme(); - } uiPersist = Lizzie.config.persisted.getJSONObject("ui-persist"); try { maxAlpha = uiPersist.getInt("max-alpha"); @@ -89,7 +92,7 @@ public void draw(Graphics2D g) { setupSizeParameters(); // Stopwatch timer = new Stopwatch(); - drawBackground(g); + drawGoban(g); // timer.lap("background"); drawStones(); // timer.lap("stones"); @@ -121,7 +124,6 @@ public void draw(Graphics2D g) { drawStoneMarkup(g); } - PluginManager.onDraw(g); // timer.lap("leelaz"); // timer.print(); @@ -142,7 +144,7 @@ public String bestMoveCoordinateName() { /** Calculate good values for boardLength, scaledMargin, availableLength, and squareLength */ private void setupSizeParameters() { - int originalBoardLength = boardLength; + int boardLength0 = boardLength; int[] calculatedPixelMargins = calculatePixelMargins(); boardLength = calculatedPixelMargins[0]; @@ -153,18 +155,20 @@ private void setupSizeParameters() { stoneRadius = squareLength / 2 - 1; // re-center board - setLocation( - x + (originalBoardLength - boardLength) / 2, y + (originalBoardLength - boardLength) / 2); + setLocation(x + (boardLength0 - boardLength) / 2, y + (boardLength0 - boardLength) / 2); } /** * Draw the green background and go board with lines. We cache the image for a performance boost. */ - private void drawBackground(Graphics2D g0) { + private void drawGoban(Graphics2D g0) { + int width = Lizzie.frame.getWidth(); + int height = Lizzie.frame.getHeight(); + // draw the cached background image if frame size changes if (cachedBackgroundImage == null - || cachedBackgroundImage.getWidth() != Lizzie.frame.getWidth() - || cachedBackgroundImage.getHeight() != Lizzie.frame.getHeight() + || cachedBackgroundImage.getWidth() != width + || cachedBackgroundImage.getHeight() != height || cachedX != x || cachedY != y || cachedBackgroundImageHasCoordinatesEnabled != showCoordinates() @@ -172,9 +176,7 @@ private void drawBackground(Graphics2D g0) { Lizzie.board.setForceRefresh(false); - cachedBackgroundImage = - new BufferedImage( - Lizzie.frame.getWidth(), Lizzie.frame.getHeight(), BufferedImage.TYPE_INT_ARGB); + cachedBackgroundImage = new BufferedImage(width, height, TYPE_INT_ARGB); Graphics2D g = cachedBackgroundImage.createGraphics(); // draw the wooden background @@ -245,7 +247,7 @@ private void drawBackground(Graphics2D g0) { g.dispose(); } - g0.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + g0.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF); g0.drawImage(cachedBackgroundImage, 0, 0, null); cachedX = x; cachedY = y; @@ -258,73 +260,31 @@ private void drawBackground(Graphics2D g0) { */ private void drawStarPoints(Graphics2D g) { if (Board.BOARD_SIZE == 9) { - drawStarPoints9x9(g); + drawStarPoints0(2, 2, 4, true, g); } else if (Board.BOARD_SIZE == 13) { - drawStarPoints13x13(g); + drawStarPoints0(2, 3, 6, true, g); } else { - drawStarPoints19x19(g); + drawStarPoints0(3, 3, 6, false, g); } } - private void drawStarPoints19x19(Graphics2D g) { - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + private void drawStarPoints0( + int nStarpoints, int edgeOffset, int gridDistance, boolean center, Graphics2D g) { + g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); int starPointRadius = (int) (STARPOINT_DIAMETER * boardLength) / 2; - final int NUM_STARPOINTS = 3; - final int STARPOINT_EDGE_OFFSET = 3; - final int STARPOINT_GRID_DISTANCE = 6; - for (int i = 0; i < NUM_STARPOINTS; i++) { - for (int j = 0; j < NUM_STARPOINTS; j++) { - int centerX = - x + scaledMargin + squareLength * (STARPOINT_EDGE_OFFSET + STARPOINT_GRID_DISTANCE * i); - int centerY = - y + scaledMargin + squareLength * (STARPOINT_EDGE_OFFSET + STARPOINT_GRID_DISTANCE * j); + for (int i = 0; i < nStarpoints; i++) { + for (int j = 0; j < nStarpoints; j++) { + int centerX = x + scaledMargin + squareLength * (edgeOffset + gridDistance * i); + int centerY = y + scaledMargin + squareLength * (edgeOffset + gridDistance * j); fillCircle(g, centerX, centerY, starPointRadius); } } - } - private void drawStarPoints13x13(Graphics2D g) { - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - int starPointRadius = (int) (STARPOINT_DIAMETER * boardLength) / 2; - final int NUM_STARPOINTS = 2; - final int STARPOINT_EDGE_OFFSET = 3; - final int STARPOINT_GRID_DISTANCE = 6; - for (int i = 0; i < NUM_STARPOINTS; i++) { - for (int j = 0; j < NUM_STARPOINTS; j++) { - int centerX = - x + scaledMargin + squareLength * (STARPOINT_EDGE_OFFSET + STARPOINT_GRID_DISTANCE * i); - int centerY = - y + scaledMargin + squareLength * (STARPOINT_EDGE_OFFSET + STARPOINT_GRID_DISTANCE * j); - fillCircle(g, centerX, centerY, starPointRadius); - } + if (center) { + int centerX = x + scaledMargin + squareLength * gridDistance; + int centerY = y + scaledMargin + squareLength * gridDistance; + fillCircle(g, centerX, centerY, starPointRadius); } - - // Draw center - int centerX = x + scaledMargin + squareLength * STARPOINT_GRID_DISTANCE; - int centerY = y + scaledMargin + squareLength * STARPOINT_GRID_DISTANCE; - fillCircle(g, centerX, centerY, starPointRadius); - } - - private void drawStarPoints9x9(Graphics2D g) { - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - int starPointRadius = (int) (STARPOINT_DIAMETER * boardLength) / 2; - final int NUM_STARPOINTS = 2; - final int STARPOINT_EDGE_OFFSET = 2; - final int STARPOINT_GRID_DISTANCE = 4; - for (int i = 0; i < NUM_STARPOINTS; i++) { - for (int j = 0; j < NUM_STARPOINTS; j++) { - int centerX = - x + scaledMargin + squareLength * (STARPOINT_EDGE_OFFSET + STARPOINT_GRID_DISTANCE * i); - int centerY = - y + scaledMargin + squareLength * (STARPOINT_EDGE_OFFSET + STARPOINT_GRID_DISTANCE * j); - fillCircle(g, centerX, centerY, starPointRadius); - } - } - - // Draw center - int centerX = x + scaledMargin + squareLength * STARPOINT_GRID_DISTANCE; - int centerY = y + scaledMargin + squareLength * STARPOINT_GRID_DISTANCE; - fillCircle(g, centerX, centerY, starPointRadius); } /** Draw the stones. We cache the image for a performance boost. */ @@ -338,16 +298,15 @@ private void drawStones() { || Lizzie.board.inScoreMode() || lastInScoreMode) { - cachedStonesImage = new BufferedImage(boardLength, boardLength, BufferedImage.TYPE_INT_ARGB); - cachedStonesShadowImage = - new BufferedImage(boardLength, boardLength, BufferedImage.TYPE_INT_ARGB); + cachedStonesImage = new BufferedImage(boardLength, boardLength, TYPE_INT_ARGB); + cachedStonesShadowImage = new BufferedImage(boardLength, boardLength, TYPE_INT_ARGB); Graphics2D g = cachedStonesImage.createGraphics(); Graphics2D gShadow = cachedStonesShadowImage.createGraphics(); // we need antialiasing to make the stones pretty. Java is a bit slow at antialiasing; that's // why we want the cache - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - gShadow.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); + gShadow.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); for (int i = 0; i < Board.BOARD_SIZE; i++) { for (int j = 0; j < Board.BOARD_SIZE; j++) { @@ -402,9 +361,8 @@ private void drawScore(Graphics2D go) { /** Draw the 'ghost stones' which show a variation Leelaz is thinking about */ private void drawBranch() { showingBranch = false; - branchStonesImage = new BufferedImage(boardLength, boardLength, BufferedImage.TYPE_INT_ARGB); - branchStonesShadowImage = - new BufferedImage(boardLength, boardLength, BufferedImage.TYPE_INT_ARGB); + branchStonesImage = new BufferedImage(boardLength, boardLength, TYPE_INT_ARGB); + branchStonesShadowImage = new BufferedImage(boardLength, boardLength, TYPE_INT_ARGB); branch = null; if (Lizzie.frame.isPlayingAgainstLeelaz || Lizzie.leelaz == null) { @@ -422,7 +380,7 @@ private void drawBranch() { Graphics2D g = (Graphics2D) branchStonesImage.getGraphics(); Graphics2D gShadow = (Graphics2D) branchStonesShadowImage.getGraphics(); - MoveData suggestedMove = (isMainBoard ? mouseHoveredMove() : getBestMove()); + MoveData suggestedMove = (isMainBoard ? mouseOveredMove() : getBestMove()); if (suggestedMove == null) return; variation = suggestedMove.variation; branch = new Branch(Lizzie.board, variation); @@ -430,7 +388,7 @@ private void drawBranch() { if (branch == null) return; showingBranch = true; - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); for (int i = 0; i < Board.BOARD_SIZE; i++) { for (int j = 0; j < Board.BOARD_SIZE; j++) { @@ -449,8 +407,8 @@ private void drawBranch() { gShadow.dispose(); } - private MoveData mouseHoveredMove() { - if (Lizzie.frame.mouseHoverCoordinate != null) { + private MoveData mouseOveredMove() { + if (Lizzie.frame.mouseOverCoordinate != null) { for (int i = 0; i < bestMoves.size(); i++) { MoveData move = bestMoves.get(i); int[] coord = Board.convertNameToCoordinates(move.coordinate); @@ -458,8 +416,7 @@ private MoveData mouseHoveredMove() { continue; } - if (coord[0] == Lizzie.frame.mouseHoverCoordinate[0] - && coord[1] == Lizzie.frame.mouseHoverCoordinate[1]) { + if (Lizzie.frame.isMouseOver(coord[0], coord[1])) { return move; } } @@ -473,7 +430,7 @@ private MoveData getBestMove() { /** render the shadows and stones in correct background-foreground order */ private void renderImages(Graphics2D g) { - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF); g.drawImage(cachedStonesShadowImage, x, y, null); if (Lizzie.config.showBranch) { g.drawImage(branchStonesShadowImage, x, y, null); @@ -486,9 +443,9 @@ private void renderImages(Graphics2D g) { /** Draw move numbers and/or mark the last played move */ private void drawMoveNumbers(Graphics2D g) { - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); - - int[] lastMove = branch == null ? Lizzie.board.getLastMove() : branch.data.lastMove; + g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); + Board board = Lizzie.board; + int[] lastMove = branch == null ? board.getLastMove() : branch.data.lastMove; if (!Lizzie.config.showMoveNumber && branch == null) { if (lastMove != null) { // mark the last coordinate @@ -497,27 +454,20 @@ private void drawMoveNumbers(Graphics2D g) { int stoneY = y + scaledMargin + squareLength * lastMove[1]; // set color to the opposite color of whatever is on the board - g.setColor( - Lizzie.board.getStones()[Board.getIndex(lastMove[0], lastMove[1])].isWhite() - ? Color.BLACK - : Color.WHITE); + boolean isWhite = board.getStones()[Board.getIndex(lastMove[0], lastMove[1])].isWhite(); + g.setColor(isWhite ? Color.BLACK : Color.WHITE); + drawCircle(g, stoneX, stoneY, lastMoveMarkerRadius); - } else if (lastMove == null - && Lizzie.board.getData().moveNumber != 0 - && !Lizzie.board.inScoreMode()) { + } else if (lastMove == null && board.getData().moveNumber != 0 && !board.inScoreMode()) { g.setColor( - Lizzie.board.getData().blackToPlay - ? new Color(255, 255, 255, 150) - : new Color(0, 0, 0, 150)); + board.getData().blackToPlay ? new Color(255, 255, 255, 150) : new Color(0, 0, 0, 150)); g.fillOval( x + boardLength / 2 - 4 * stoneRadius, y + boardLength / 2 - 4 * stoneRadius, stoneRadius * 8, stoneRadius * 8); g.setColor( - Lizzie.board.getData().blackToPlay - ? new Color(0, 0, 0, 255) - : new Color(255, 255, 255, 255)); + board.getData().blackToPlay ? new Color(0, 0, 0, 255) : new Color(255, 255, 255, 255)); drawString( g, x + boardLength / 2, @@ -531,12 +481,10 @@ private void drawMoveNumbers(Graphics2D g) { return; } - int[] moveNumberList = - branch == null ? Lizzie.board.getMoveNumberList() : branch.data.moveNumberList; + int[] moveNumberList = branch == null ? board.getMoveNumberList() : branch.data.moveNumberList; // Allow to display only last move number - int lastMoveNumber = - branch == null ? Lizzie.board.getData().moveNumber : branch.data.moveNumber; + int lastMoveNumber = branch == null ? board.getData().moveNumber : branch.data.moveNumber; int onlyLastMoveNumber = Lizzie.config.uiConfig.optInt("only-last-move-number", 9999); for (int i = 0; i < Board.BOARD_SIZE; i++) { @@ -549,28 +497,21 @@ private void drawMoveNumbers(Graphics2D g) { continue; } - Stone stoneAtThisPoint = - branch == null - ? Lizzie.board.getStones()[Board.getIndex(i, j)] - : branch.data.stones[Board.getIndex(i, j)]; + int here = Board.getIndex(i, j); + Stone stoneHere = branch == null ? board.getStones()[here] : branch.data.stones[here]; // don't write the move number if either: the move number is 0, or there will already be // playout information written if (moveNumberList[Board.getIndex(i, j)] > 0 - && !(branch != null - && Lizzie.frame.mouseHoverCoordinate != null - && i == Lizzie.frame.mouseHoverCoordinate[0] - && j == Lizzie.frame.mouseHoverCoordinate[1])) { + && !(branch != null && Lizzie.frame.isMouseOver(i, j))) { if (lastMove != null && i == lastMove[0] && j == lastMove[1]) - g.setColor( - Color.RED - .brighter()); // stoneAtThisPoint.isBlack() ? Color.RED.brighter() : - // Color.BLUE.brighter()); + g.setColor(Color.RED.brighter()); // stoneHere.isBlack() ? Color.RED.brighter() : + // Color.BLUE.brighter()); else { // Draw white letters on black stones nomally. // But use black letters for showing black moves without stones. boolean reverse = (moveNumberList[Board.getIndex(i, j)] > maxBranchMoves()); - g.setColor(stoneAtThisPoint.isBlack() ^ reverse ? Color.WHITE : Color.BLACK); + g.setColor(stoneHere.isBlack() ^ reverse ? Color.WHITE : Color.BLACK); } String moveNumberString = moveNumberList[Board.getIndex(i, j)] + ""; @@ -593,11 +534,11 @@ private void drawMoveNumbers(Graphics2D g) { private void drawLeelazSuggestions(Graphics2D g) { if (Lizzie.leelaz == null) return; - final int MIN_ALPHA = 32; - final double HUE_SCALING_FACTOR = 3.0; - final double ALPHA_SCALING_FACTOR = 5.0; - final float GREEN_HUE = Color.RGBtoHSB(0, 1, 0, null)[0]; - final float CYAN_HUE = Color.RGBtoHSB(0, 1, 1, null)[0]; + int minAlpha = 32; + float hueFactor = 3.0f; + float alphaFactor = 5.0f; + float greenHue = Color.RGBtoHSB(0, 1, 0, null)[0]; + float cyanHue = Color.RGBtoHSB(0, 1, 1, null)[0]; if (!bestMoves.isEmpty()) { @@ -633,44 +574,32 @@ private void drawLeelazSuggestions(Graphics2D g) { if (move.playouts == 0) // this actually can happen continue; - double percentPlayouts = (double) move.playouts / maxPlayouts; + float percentPlayouts = (float) move.playouts / maxPlayouts; int[] coordinates = Board.convertNameToCoordinates(move.coordinate); int suggestionX = x + scaledMargin + squareLength * coordinates[0]; int suggestionY = y + scaledMargin + squareLength * coordinates[1]; // 0 = Reddest hue - float hue = - isBestMove - ? CYAN_HUE - : (float) - (-GREEN_HUE - * Math.max(0, Math.log(percentPlayouts) / HUE_SCALING_FACTOR + 1)); - float saturation = 0.75f; // saturation - float brightness = 0.85f; // brightness - int alpha = - (int) - (MIN_ALPHA - + (maxAlpha - MIN_ALPHA) - * Math.max(0, Math.log(percentPlayouts) / ALPHA_SCALING_FACTOR + 1)); - // if (uiConfig.getBoolean("shadows-enabled")) - // alpha = 255; + float logPlayouts = (float) log(percentPlayouts); + float otherHue = -greenHue * max(0, logPlayouts / hueFactor + 1); + float hue = isBestMove ? cyanHue : otherHue; + float saturation = 0.75f; + float brightness = 0.85f; + float alpha = (minAlpha + (maxAlpha - minAlpha) * max(0, logPlayouts / alphaFactor + 1)); Color hsbColor = Color.getHSBColor(hue, saturation, brightness); Color color = - new Color(hsbColor.getRed(), hsbColor.getBlue(), hsbColor.getGreen(), alpha); + new Color(hsbColor.getRed(), hsbColor.getBlue(), hsbColor.getGreen(), (int) alpha); + boolean isMouseOver = Lizzie.frame.isMouseOver(coordinates[0], coordinates[1]); if (branch == null) { - drawShadow(g, suggestionX, suggestionY, true, (float) alpha / 255); + drawShadow(g, suggestionX, suggestionY, true, alpha / 255.0f); g.setColor(color); fillCircle(g, suggestionX, suggestionY, stoneRadius); } - if (branch == null - || (isBestMove - && Lizzie.frame.mouseHoverCoordinate != null - && coordinates[0] == Lizzie.frame.mouseHoverCoordinate[0] - && coordinates[1] == Lizzie.frame.mouseHoverCoordinate[1])) { + if (branch == null || isBestMove && isMouseOver) { int strokeWidth = 1; if (isBestMove != hasMaxWinrate) { strokeWidth = 2; @@ -683,13 +612,11 @@ private void drawLeelazSuggestions(Graphics2D g) { g.setStroke(new BasicStroke(1)); } - if ((branch == null + if (branch == null && (hasMaxWinrate - || percentPlayouts >= uiConfig.getDouble("min-playout-ratio-for-stats"))) - || (Lizzie.frame.mouseHoverCoordinate != null - && coordinates[0] == Lizzie.frame.mouseHoverCoordinate[0] - && coordinates[1] == Lizzie.frame.mouseHoverCoordinate[1])) { - double roundedWinrate = Math.round(move.winrate * 10) / 10.0; + || percentPlayouts >= uiConfig.getDouble("min-playout-ratio-for-stats")) + || isMouseOver) { + double roundedWinrate = round(move.winrate * 10) / 10.0; if (uiConfig.getBoolean("win-rate-always-black") && !Lizzie.board.getData().blackToPlay) { roundedWinrate = 100.0 - roundedWinrate; @@ -714,6 +641,7 @@ private void drawLeelazSuggestions(Graphics2D g) { stoneRadius, stoneRadius * 1.5, 1); + drawString( g, suggestionX, @@ -754,19 +682,25 @@ private void drawNextMoves(Graphics2D g) { private void drawWoodenBoard(Graphics2D g) { if (uiConfig.getBoolean("fancy-board")) { - // fancy version + if (cachedBoardImage == null) { + try { + cachedBoardImage = ImageIO.read(getClass().getResourceAsStream("/assets/board.png")); + } catch (IOException e) { + e.printStackTrace(); + } + } + int shadowRadius = (int) (boardLength * MARGIN / 6); - BufferedImage boardImage = theme.getBoard(); - // Support seamless texture drawTextureImage( g, - boardImage == null ? theme.getBoard() : boardImage, + cachedBoardImage, x - 2 * shadowRadius, y - 2 * shadowRadius, boardLength + 4 * shadowRadius, boardLength + 4 * shadowRadius); g.setStroke(new BasicStroke(shadowRadius * 2)); + // draw border g.setColor(new Color(0, 0, 0, 50)); g.drawRect( @@ -777,9 +711,8 @@ private void drawWoodenBoard(Graphics2D g) { g.setStroke(new BasicStroke(1)); } else { - // simple version JSONArray boardColor = uiConfig.getJSONArray("board-color"); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF); + g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF); g.setColor(new Color(boardColor.getInt(0), boardColor.getInt(1), boardColor.getInt(2))); g.fillRect(x, y, boardLength, boardLength); } @@ -879,11 +812,10 @@ private void drawShadow( /** Draws a stone centered at (centerX, centerY) */ private void drawStone( Graphics2D g, Graphics2D gShadow, int centerX, int centerY, Stone color, int x, int y) { - // g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, - // RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY); - g.setRenderingHint( - RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + // g.setRenderingHint(KEY_ALPHA_INTERPOLATION, + // VALUE_ALPHA_INTERPOLATION_QUALITY); + g.setRenderingHint(KEY_INTERPOLATION, VALUE_INTERPOLATION_BILINEAR); + g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_ON); // if no shadow graphics is supplied, just draw onto the same graphics if (gShadow == null) gShadow = g; @@ -893,11 +825,9 @@ private void drawStone( boolean isGhost = (color == Stone.BLACK_GHOST || color == Stone.WHITE_GHOST); if (uiConfig.getBoolean("fancy-stones")) { drawShadow(gShadow, centerX, centerY, isGhost); - Image stone = - isBlack ? theme.getBlackStone(new int[] {x, y}) : theme.getWhiteStone(new int[] {x, y}); int size = stoneRadius * 2 + 1; g.drawImage( - getScaleStone(stone, isBlack, size, size), + getScaleStone(isBlack, size), centerX - stoneRadius, centerY - stoneRadius, size, @@ -905,13 +835,12 @@ private void drawStone( null); } else { drawShadow(gShadow, centerX, centerY, true); - g.setColor( - isBlack - ? (isGhost ? new Color(0, 0, 0) : Color.BLACK) - : (isGhost ? new Color(255, 255, 255) : Color.WHITE)); + Color blackColor = isGhost ? new Color(0, 0, 0) : Color.BLACK; + Color whiteColor = isGhost ? new Color(255, 255, 255) : Color.WHITE; + g.setColor(isBlack ? blackColor : whiteColor); fillCircle(g, centerX, centerY, stoneRadius); if (!isBlack) { - g.setColor(isGhost ? new Color(0, 0, 0) : Color.BLACK); + g.setColor(blackColor); drawCircle(g, centerX, centerY, stoneRadius); } } @@ -919,12 +848,19 @@ private void drawStone( } /** Get scaled stone, if cached then return cached */ - public BufferedImage getScaleStone(Image img, boolean isBlack, int width, int height) { + private BufferedImage getScaleStone(boolean isBlack, int size) { BufferedImage stone = isBlack ? cachedBlackStoneImage : cachedWhiteStoneImage; - if (stone == null || stone.getWidth() != width || stone.getHeight() != height) { - stone = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + if (stone == null) { + stone = new BufferedImage(size, size, TYPE_INT_ARGB); + String imgPath = isBlack ? "/assets/black0.png" : "/assets/white0.png"; + Image img = null; + try { + img = ImageIO.read(getClass().getResourceAsStream(imgPath)); + } catch (IOException e) { + e.printStackTrace(); + } Graphics2D g2 = stone.createGraphics(); - g2.drawImage(img.getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH), 0, 0, null); + g2.drawImage(img.getScaledInstance(size, size, java.awt.Image.SCALE_SMOOTH), 0, 0, null); g2.dispose(); if (isBlack) { cachedBlackStoneImage = stone; @@ -935,6 +871,18 @@ public BufferedImage getScaleStone(Image img, boolean isBlack, int width, int he return stone; } + public BufferedImage getWallpaper() { + if (cachedWallpaperImage == null) { + try { + String wallpaperPath = "/assets/background.jpg"; + cachedWallpaperImage = ImageIO.read(getClass().getResourceAsStream(wallpaperPath)); + } catch (IOException e) { + e.printStackTrace(); + } + } + return cachedWallpaperImage; + } + /** * Draw scale smooth image, enhanced display quality (Not use, for future) This function use the * traditional Image.getScaledInstance() method to provide the nice quality, but the performance @@ -942,7 +890,7 @@ public BufferedImage getScaleStone(Image img, boolean isBlack, int width, int he */ // public void drawScaleSmoothImage(Graphics2D g, BufferedImage img, int x, int y, int width, // int height, ImageObserver observer) { - // BufferedImage newstone = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + // BufferedImage newstone = new BufferedImage(width, height, TYPE_INT_ARGB); // Graphics2D g2 = newstone.createGraphics(); // g2.drawImage(img.getScaledInstance(width, height, java.awt.Image.SCALE_SMOOTH), 0, 0, // observer); @@ -973,10 +921,10 @@ public BufferedImage getScaleStone(Image img, boolean isBlack, int width, int he // h = height; // } // } - // BufferedImage tmp = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + // BufferedImage tmp = new BufferedImage(w, h, TYPE_INT_ARGB); // Graphics2D g2 = tmp.createGraphics(); - // g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, - // RenderingHints.VALUE_INTERPOLATION_BICUBIC); + // g2.setRenderingHint(KEY_INTERPOLATION, + // VALUE_INTERPOLATION_BICUBIC); // g2.drawImage(newstone, 0, 0, w, h, null); // g2.dispose(); // newstone = tmp; @@ -1103,33 +1051,25 @@ private void drawString( Font font = makeFont(fontBase, style); // set maximum size of font - font = - font.deriveFont( - (float) - (font.getSize2D() * maximumFontWidth / g.getFontMetrics(font).stringWidth(string))); - font = font.deriveFont(Math.min(maximumFontHeight, font.getSize())); + FontMetrics fm = g.getFontMetrics(font); + font = font.deriveFont((float) (font.getSize2D() * maximumFontWidth / fm.stringWidth(string))); + font = font.deriveFont(min(maximumFontHeight, font.getSize())); g.setFont(font); - FontMetrics metrics = g.getFontMetrics(font); - - int height = metrics.getAscent() - metrics.getDescent(); + int height = fm.getAscent() - fm.getDescent(); int verticalOffset; - switch (aboveOrBelow) { - case -1: - verticalOffset = height / 2; - break; - - case 1: - verticalOffset = -height / 2; - break; - - default: - verticalOffset = 0; + if (aboveOrBelow == -1) { + verticalOffset = height / 2; + } else if (aboveOrBelow == 1) { + verticalOffset = -height / 2; + } else { + verticalOffset = 0; } + // bounding box for debugging // g.drawRect(x-(int)maximumFontWidth/2, y - height/2 + verticalOffset, (int)maximumFontWidth, // height+verticalOffset ); - g.drawString(string, x - metrics.stringWidth(string) / 2, y + height / 2 + verticalOffset); + g.drawString(string, x - fm.stringWidth(string) / 2, y + height / 2 + verticalOffset); } private void drawString( @@ -1158,13 +1098,13 @@ private Font makeFont(Font fontBase, int style) { private String getPlayoutsString(int playouts) { if (playouts >= 1_000_000) { double playoutsDouble = (double) playouts / 100_000; // 1234567 -> 12.34567 - return Math.round(playoutsDouble) / 10.0 + "m"; + return round(playoutsDouble) / 10.0 + "m"; } else if (playouts >= 10_000) { double playoutsDouble = (double) playouts / 1_000; // 13265 -> 13.265 - return Math.round(playoutsDouble) + "k"; + return round(playoutsDouble) + "k"; } else if (playouts >= 1_000) { double playoutsDouble = (double) playouts / 100; // 1265 -> 12.65 - return Math.round(playoutsDouble) / 10.0 + "k"; + return round(playoutsDouble) / 10.0 + "k"; } else { return String.valueOf(playouts); } @@ -1274,13 +1214,13 @@ public boolean incrementDisplayedBranchLength(int n) { return false; default: // force nonnegative - displayedBranchLength = Math.max(0, displayedBranchLength + n); + displayedBranchLength = max(0, displayedBranchLength + n); return true; } } public boolean isInside(int x1, int y1) { - return (x <= x1 && x1 < x + boardLength && y <= y1 && y1 < y + boardLength); + return x <= x1 && x1 < x + boardLength && y <= y1 && y1 < y + boardLength; } private boolean showCoordinates() { @@ -1288,7 +1228,7 @@ private boolean showCoordinates() { } public void increaseMaxAlpha(int k) { - maxAlpha = Math.min(maxAlpha + k, 255); + maxAlpha = min(maxAlpha + k, 255); uiPersist.put("max-alpha", maxAlpha); } } diff --git a/src/main/java/featurecat/lizzie/gui/Input.java b/src/main/java/featurecat/lizzie/gui/Input.java index e576bb8bb..fc36096ad 100644 --- a/src/main/java/featurecat/lizzie/gui/Input.java +++ b/src/main/java/featurecat/lizzie/gui/Input.java @@ -3,7 +3,6 @@ import static java.awt.event.KeyEvent.*; import featurecat.lizzie.Lizzie; -import featurecat.lizzie.plugin.PluginManager; import java.awt.event.*; import javax.swing.*; @@ -13,20 +12,14 @@ public void mouseClicked(MouseEvent e) {} @Override public void mousePressed(MouseEvent e) { - PluginManager.onMousePressed(e); - int x = e.getX(); - int y = e.getY(); - - if (e.getButton() == MouseEvent.BUTTON1) // left mouse click - Lizzie.frame.onClicked(x, y); - else if (e.getButton() == MouseEvent.BUTTON3) // right mouse click + if (e.getButton() == MouseEvent.BUTTON1) // left click + Lizzie.frame.onClicked(e.getX(), e.getY()); + else if (e.getButton() == MouseEvent.BUTTON3) // right click undo(); } @Override - public void mouseReleased(MouseEvent e) { - PluginManager.onMouseReleased(e); - } + public void mouseReleased(MouseEvent e) {} @Override public void mouseEntered(MouseEvent e) {} @@ -36,19 +29,12 @@ public void mouseExited(MouseEvent e) {} @Override public void mouseDragged(MouseEvent e) { - int x = e.getX(); - int y = e.getY(); - - Lizzie.frame.onMouseDragged(x, y); + Lizzie.frame.onMouseDragged(e.getX(), e.getY()); } @Override public void mouseMoved(MouseEvent e) { - PluginManager.onMouseMoved(e); - int x = e.getX(); - int y = e.getY(); - - Lizzie.frame.onMouseMoved(x, y); + Lizzie.frame.onMouseMoved(e.getX(), e.getY()); } @Override @@ -155,9 +141,6 @@ private void toggleShowDynamicKomi() { @Override public void keyPressed(KeyEvent e) { - - PluginManager.onKeyPressed(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; @@ -300,6 +283,10 @@ public void keyPressed(KeyEvent e) { Lizzie.config.toggleShowVariationGraph(); break; + case VK_T: + Lizzie.config.toggleShowComment(); + break; + case VK_C: if (controlIsPressed(e)) { Lizzie.frame.copySgf(); @@ -355,6 +342,21 @@ public void keyPressed(KeyEvent e) { toggleShowDynamicKomi(); break; + // Use Ctrl+Num to switching multiple engine + case VK_0: + case VK_1: + case VK_2: + case VK_3: + case VK_4: + case VK_5: + case VK_6: + case VK_7: + case VK_8: + case VK_9: + if (controlIsPressed(e)) { + Lizzie.switchEngine(e.getKeyCode() - VK_0); + } + break; default: shouldDisableAnalysis = false; } @@ -368,7 +370,6 @@ public void keyPressed(KeyEvent e) { @Override public void keyReleased(KeyEvent e) { - PluginManager.onKeyReleased(e); switch (e.getKeyCode()) { case VK_X: if (wasPonderingWhenControlsShown) Lizzie.leelaz.togglePonder(); @@ -387,6 +388,9 @@ public void keyReleased(KeyEvent e) { @Override public void mouseWheelMoved(MouseWheelEvent e) { + if (Lizzie.frame.processCommentMouseWheelMoved(e)) { + return; + } if (Lizzie.board.inAnalysisMode()) Lizzie.board.toggleAnalysis(); if (e.getWheelRotation() > 0) { redo(); diff --git a/src/main/java/featurecat/lizzie/gui/LizzieFrame.java b/src/main/java/featurecat/lizzie/gui/LizzieFrame.java index e2f44b8fe..1893a6414 100644 --- a/src/main/java/featurecat/lizzie/gui/LizzieFrame.java +++ b/src/main/java/featurecat/lizzie/gui/LizzieFrame.java @@ -23,6 +23,7 @@ import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; +import java.awt.event.MouseWheelEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferStrategy; @@ -64,6 +65,7 @@ public class LizzieFrame extends JFrame { resourceBundle.getString("LizzieFrame.commands.keyV"), resourceBundle.getString("LizzieFrame.commands.keyW"), resourceBundle.getString("LizzieFrame.commands.keyG"), + resourceBundle.getString("LizzieFrame.commands.keyT"), resourceBundle.getString("LizzieFrame.commands.keyHome"), resourceBundle.getString("LizzieFrame.commands.keyEnd"), resourceBundle.getString("LizzieFrame.commands.keyControl"), @@ -79,7 +81,7 @@ public class LizzieFrame extends JFrame { private final BufferStrategy bs; - public int[] mouseHoverCoordinate; + public int[] mouseOverCoordinate; public boolean showControls = false; public boolean showCoordinates = false; public boolean isPlayingAgainstLeelaz = false; @@ -91,6 +93,16 @@ public class LizzieFrame extends JFrame { private long lastAutosaveTime = System.currentTimeMillis(); + // Save the player title + private String playerTitle = null; + + // Display Comment + private JScrollPane scrollPane = null; + private JTextPane commentPane = null; + private BufferedImage commentImage = null; + private String cachedComment = null; + private Rectangle commentRect = null; + static { // load fonts try { @@ -129,6 +141,18 @@ public LizzieFrame() { setExtendedState(Frame.MAXIMIZED_BOTH); // start maximized } + // Comment Pane + commentPane = new JTextPane(); + commentPane.setEditable(false); + commentPane.setMargin(new Insets(5, 5, 5, 5)); + commentPane.setBackground(new Color(0, 0, 0, 200)); + commentPane.setForeground(Color.WHITE); + scrollPane = new JScrollPane(); + scrollPane.setViewportView(commentPane); + scrollPane.setBorder(null); + scrollPane.setVerticalScrollBarPolicy( + javax.swing.ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED); + setVisible(true); createBufferStrategy(2); @@ -415,26 +439,19 @@ public void paint(Graphics g0) { if (Lizzie.leelaz != null && Lizzie.leelaz.isLoaded()) { if (Lizzie.config.showStatus) { - drawPonderingState( - g, - resourceBundle.getString("LizzieFrame.display.pondering") - + (Lizzie.leelaz.isPondering() - ? resourceBundle.getString("LizzieFrame.display.on") - : resourceBundle.getString("LizzieFrame.display.off")), - ponderingX, - ponderingY, - ponderingSize); + String pondKey = "LizzieFrame.display." + (Lizzie.leelaz.isPondering() ? "on" : "off"); + String pondText = resourceBundle.getString(pondKey); + String switchText = resourceBundle.getString("LizzieFrame.prompt.switching"); + String weightText = Lizzie.leelaz.currentWeight().toString(); + String text = pondText + " " + weightText + (Lizzie.leelaz.switching() ? switchText : ""); + drawPonderingState(g, text, ponderingX, ponderingY, ponderingSize); } - if (Lizzie.config.showDynamicKomi && Lizzie.leelaz.getDynamicKomi() != null) { - drawPonderingState( - g, - resourceBundle.getString("LizzieFrame.display.dynamic-komi"), - dynamicKomiLabelX, - dynamicKomiLabelY, - dynamicKomiSize); - drawPonderingState( - g, Lizzie.leelaz.getDynamicKomi(), dynamicKomiX, dynamicKomiY, dynamicKomiSize); + String dynamicKomi = Lizzie.leelaz.getDynamicKomi(); + if (Lizzie.config.showDynamicKomi && dynamicKomi != null) { + String text = resourceBundle.getString("LizzieFrame.display.dynamic-komi"); + drawPonderingState(g, text, dynamicKomiLabelX, dynamicKomiLabelY, dynamicKomiSize); + drawPonderingState(g, dynamicKomi, dynamicKomiX, dynamicKomiY, dynamicKomiSize); } // Todo: Make board move over when there is no space beside the board @@ -446,8 +463,19 @@ public void paint(Graphics g0) { if (Lizzie.config.showVariationGraph) { drawVariationTreeContainer(backgroundG, vx, vy, vw, vh); - variationTree.draw(g, treex, treey, treew, treeh); + int cHeight = 0; + if (Lizzie.config.showComment) { + // Draw the Comment of the Sgf + cHeight = drawComment(g, vx, vy, vw, vh, false); + } + variationTree.draw(g, treex, treey, treew, treeh - cHeight); + } else { + if (Lizzie.config.showComment) { + // Draw the Comment of the Sgf + drawComment(g, vx, topInset, vw, vh - topInset + vy, true); + } } + if (Lizzie.config.showSubBoard) { try { subBoardRenderer.setLocation(subBoardX, subBoardY); @@ -458,12 +486,8 @@ public void paint(Graphics g0) { } } } else if (Lizzie.config.showStatus) { - drawPonderingState( - g, - resourceBundle.getString("LizzieFrame.display.loading"), - loadingX, - loadingY, - loadingSize); + String loadingText = resourceBundle.getString("LizzieFrame.display.loading"); + drawPonderingState(g, loadingText, loadingX, loadingY, loadingSize); } if (Lizzie.config.showCaptured) drawCaptured(g, capx, capy, capw, caph); @@ -503,11 +527,11 @@ private Graphics2D createBackground() { Graphics2D g = cachedBackground.createGraphics(); - BufferedImage background = boardRenderer.theme.getBackground(); - int drawWidth = Math.max(background.getWidth(), getWidth()); - int drawHeight = Math.max(background.getHeight(), getHeight()); + BufferedImage wallpaper = boardRenderer.getWallpaper(); + int drawWidth = Math.max(wallpaper.getWidth(), getWidth()); + int drawHeight = Math.max(wallpaper.getHeight(), getHeight()); // Support seamless texture - boardRenderer.drawTextureImage(g, background, 0, 0, drawWidth, drawHeight); + boardRenderer.drawTextureImage(g, wallpaper, 0, 0, drawWidth, drawHeight); return g; } @@ -523,11 +547,21 @@ private void drawVariationTreeContainer(Graphics2D g, int vx, int vy, int vw, in } private void drawPonderingState(Graphics2D g, String text, int x, int y, double size) { - Font font = - new Font( - systemDefaultFontName, Font.PLAIN, (int) (Math.max(getWidth(), getHeight()) * size)); + int fontSize = (int) (Math.max(getWidth(), getHeight()) * size); + Font font = new Font(systemDefaultFontName, Font.PLAIN, fontSize); FontMetrics fm = g.getFontMetrics(font); int stringWidth = fm.stringWidth(text); + // Truncate too long text when display switching prompt + if (Lizzie.leelaz.isLoaded()) { + int mainBoardX = + (boardRenderer != null && boardRenderer.getLocation() != null) + ? boardRenderer.getLocation().x + : 0; + if ((mainBoardX > x) && stringWidth > (mainBoardX - x)) { + text = Util.truncateStringByWidth(text, fm, mainBoardX - x); + stringWidth = fm.stringWidth(text); + } + } int stringHeight = fm.getAscent() - fm.getDescent(); int width = stringWidth; int height = (int) (stringHeight * 1.2); @@ -580,17 +614,12 @@ void drawControls() { int maxSize = Math.min(getWidth(), getHeight()); Font font = new Font(systemDefaultFontName, Font.PLAIN, (int) (maxSize * 0.034)); g.setFont(font); + FontMetrics metrics = g.getFontMetrics(font); - int maxCommandWidth = - commandsToShow - .stream() - .reduce( - 0, - (Integer i, String command) -> Math.max(i, metrics.stringWidth(command)), - (Integer a, Integer b) -> Math.max(a, b)); + int maxCmdWidth = commandsToShow.stream().mapToInt(c -> metrics.stringWidth(c)).max().orElse(0); int lineHeight = (int) (font.getSize() * 1.15); - int boxWidth = Util.clamp((int) (maxCommandWidth * 1.4), 0, getWidth()); + int boxWidth = Util.clamp((int) (maxCmdWidth * 1.4), 0, getWidth()); int boxHeight = Util.clamp(commandsToShow.size() * lineHeight, 0, getHeight()); int commandsX = Util.clamp(getWidth() / 2 - boxWidth / 2, 0, getWidth()); @@ -733,11 +762,9 @@ private void drawMoveStatistics(Graphics2D g, int posX, int posY, int width, int if (validLastWinrate && validWinrate) { String text; if (Lizzie.config.handicapInsteadOfWinrate) { - text = - String.format( - ": %.2f", - Lizzie.leelaz.winrateToHandicap(100 - curWR) - - Lizzie.leelaz.winrateToHandicap(lastWR)); + 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); } @@ -918,16 +945,17 @@ public void playBestMove() { } public void onMouseMoved(int x, int y) { - int[] newMouseHoverCoordinate = boardRenderer.convertScreenToCoordinates(x, y); - if (mouseHoverCoordinate != null - && newMouseHoverCoordinate != null - && (mouseHoverCoordinate[0] != newMouseHoverCoordinate[0] - || mouseHoverCoordinate[1] != newMouseHoverCoordinate[1])) { - mouseHoverCoordinate = newMouseHoverCoordinate; + int[] c = boardRenderer.convertScreenToCoordinates(x, y); + if (c != null && !isMouseOver(c[0], c[1])) { repaint(); - } else { - mouseHoverCoordinate = newMouseHoverCoordinate; } + mouseOverCoordinate = c; + } + + public boolean isMouseOver(int x, int y) { + return mouseOverCoordinate != null + && mouseOverCoordinate[0] == x + && mouseOverCoordinate[1] == x; } public void onMouseDragged(int x, int y) { @@ -939,6 +967,58 @@ public void onMouseDragged(int x, int y) { } } + /** + * Process Comment Mouse Wheel Moved + * + * @return true when the scroll event was processed by this method + */ + public boolean processCommentMouseWheelMoved(MouseWheelEvent e) { + if (Lizzie.config.showComment + && commentRect != null + && commentRect.contains(e.getX(), e.getY())) { + scrollPane.dispatchEvent(e); + createCommentImage(true, 0, 0); + getGraphics() + .drawImage( + commentImage, + commentRect.x, + commentRect.y, + commentRect.width, + commentRect.height, + null); + return true; + } else { + return false; + } + } + + /** + * Create comment cached image + * + * @param forceRefresh + * @param w + * @param h + */ + public void createCommentImage(boolean forceRefresh, int w, int h) { + if (forceRefresh + || commentImage == null + || scrollPane.getWidth() != w + || scrollPane.getHeight() != h) { + if (w > 0 && h > 0) { + scrollPane.setSize(w, h); + } + commentImage = + new BufferedImage( + scrollPane.getWidth(), scrollPane.getHeight(), BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = commentImage.createGraphics(); + scrollPane.doLayout(); + scrollPane.addNotify(); + scrollPane.validate(); + scrollPane.printAll(g2); + g2.dispose(); + } + } + private void autosaveMaybe() { int interval = Lizzie.config.config.getJSONObject("ui").getInt("autosave-interval-seconds") * 1000; @@ -954,7 +1034,16 @@ public void toggleCoordinates() { } public void setPlayers(String whitePlayer, String blackPlayer) { - setTitle(String.format("%s (%s [W] vs %s [B])", DEFAULT_TITLE, whitePlayer, blackPlayer)); + this.playerTitle = String.format("(%s [W] vs %s [B])", whitePlayer, blackPlayer); + this.updateTitle(); + } + + public void updateTitle() { + StringBuilder sb = new StringBuilder(DEFAULT_TITLE); + sb.append(this.playerTitle != null ? " " + this.playerTitle.trim() : ""); + sb.append( + Lizzie.leelaz.engineCommand() != null ? " [" + Lizzie.leelaz.engineCommand() + "]" : ""); + setTitle(sb.toString()); } private void setDisplayedBranchLength(int n) { @@ -976,7 +1065,8 @@ public boolean incrementDisplayedBranchLength(int n) { } public void resetTitle() { - setTitle(DEFAULT_TITLE); + this.playerTitle = null; + this.updateTitle(); } public void copySgf() { @@ -1017,4 +1107,41 @@ public void pasteSgf() { public void increaseMaxAlpha(int k) { boardRenderer.increaseMaxAlpha(k); } + + /** + * Draw the Comment of the Sgf file + * + * @param g + * @param x + * @param y + * @param w + * @param h + * @param full + * @return + */ + private int drawComment(Graphics2D g, int x, int y, int w, int h, boolean full) { + String comment = + (Lizzie.board.getHistory().getData() != null + && Lizzie.board.getHistory().getData().comment != null) + ? Lizzie.board.getHistory().getData().comment + : ""; + int cHeight = full ? h : (int) (h * 0.5); + int fontSize = (int) (Math.min(getWidth(), getHeight()) * 0.0294); + if (Lizzie.config.commentFontSize > 0) { + fontSize = Lizzie.config.commentFontSize; + } else if (fontSize < 16) { + fontSize = 16; + } + Font font = new Font(systemDefaultFontName, Font.PLAIN, fontSize); + commentPane.setFont(font); + commentPane.setText(comment); + commentPane.setSize(w, cHeight); + createCommentImage(comment != null && !comment.equals(this.cachedComment), w, cHeight); + commentRect = + new Rectangle(x, y + (h - cHeight), scrollPane.getWidth(), scrollPane.getHeight()); + g.drawImage( + commentImage, commentRect.x, commentRect.y, commentRect.width, commentRect.height, null); + cachedComment = comment; + return cHeight; + } } diff --git a/src/main/java/featurecat/lizzie/gui/NewGameDialog.java b/src/main/java/featurecat/lizzie/gui/NewGameDialog.java index 585676d10..668c2a6ed 100644 --- a/src/main/java/featurecat/lizzie/gui/NewGameDialog.java +++ b/src/main/java/featurecat/lizzie/gui/NewGameDialog.java @@ -124,20 +124,11 @@ private void initButtonBar() { okButton.setText("OK"); okButton.addActionListener(e -> apply()); + int center = GridBagConstraints.CENTER; + int both = GridBagConstraints.BOTH; buttonBar.add( okButton, - new GridBagConstraints( - 1, - 0, - 1, - 1, - 0.0, - 0.0, - GridBagConstraints.CENTER, - GridBagConstraints.BOTH, - new Insets(0, 0, 0, 0), - 0, - 0)); + new GridBagConstraints(1, 0, 1, 1, 0.0, 0.0, center, both, new Insets(0, 0, 0, 0), 0, 0)); dialogPane.add(buttonBar, BorderLayout.SOUTH); } diff --git a/src/main/java/featurecat/lizzie/gui/VariationTree.java b/src/main/java/featurecat/lizzie/gui/VariationTree.java index 72f072bf0..aef810e2b 100644 --- a/src/main/java/featurecat/lizzie/gui/VariationTree.java +++ b/src/main/java/featurecat/lizzie/gui/VariationTree.java @@ -126,7 +126,9 @@ public void drawTree( } public void draw(Graphics2D g, int posx, int posy, int width, int height) { - if (width <= 0 || height <= 0) return; // we don't have enough space + if (width <= 0 || height <= 0) { + return; // we don't have enough space + } // Use dense tree for saving space if large-subboard YSPACING = (Lizzie.config.showLargeSubBoard() ? 20 : 30); diff --git a/src/main/java/featurecat/lizzie/gui/WinrateGraph.java b/src/main/java/featurecat/lizzie/gui/WinrateGraph.java index 1b0de91c6..3b4915c96 100644 --- a/src/main/java/featurecat/lizzie/gui/WinrateGraph.java +++ b/src/main/java/featurecat/lizzie/gui/WinrateGraph.java @@ -114,21 +114,19 @@ public void draw(Graphics2D g, int posx, int posy, int width, int height) { wr = bwr; playouts = stats.totalPlayouts; } - { - // Draw a vertical line at the current move - Stroke previousStroke = g.getStroke(); - int x = posx + (movenum * width / numMoves); - g.setStroke(dashed); - g.setColor(Color.white); - g.drawLine(x, posy, x, posy + height); - // Show move number - String moveNumString = "" + node.getData().moveNumber; - int mw = g.getFontMetrics().stringWidth(moveNumString); - int margin = strokeRadius; - int mx = x - posx < width / 2 ? x + margin : x - mw - margin; - g.drawString(moveNumString, mx, posy + height - margin); - g.setStroke(previousStroke); - } + // Draw a vertical line at the current move + Stroke previousStroke = g.getStroke(); + int x = posx + (movenum * width / numMoves); + g.setStroke(dashed); + g.setColor(Color.white); + g.drawLine(x, posy, x, posy + height); + // Show move number + String moveNumString = "" + node.getData().moveNumber; + int mw = g.getFontMetrics().stringWidth(moveNumString); + int margin = strokeRadius; + int mx = x - posx < width / 2 ? x + margin : x - mw - margin; + g.drawString(moveNumString, mx, posy + height - margin); + g.setStroke(previousStroke); } if (playouts > 0) { if (wr < 0) { diff --git a/src/main/java/featurecat/lizzie/rules/Board.java b/src/main/java/featurecat/lizzie/rules/Board.java index 2e4783a70..2a8d38897 100644 --- a/src/main/java/featurecat/lizzie/rules/Board.java +++ b/src/main/java/featurecat/lizzie/rules/Board.java @@ -25,6 +25,9 @@ public class Board implements LeelazListener { private boolean analysisMode = false; private int playoutsAnalysis = 100; + // Save the node for restore move when in the branch + private BoardHistoryNode saveNode = null; + // Force refresh board private boolean forceRefresh = false; @@ -35,28 +38,17 @@ public Board() { /** Initialize the board completely */ private void initialize() { Stone[] stones = new Stone[BOARD_SIZE * BOARD_SIZE]; - for (int i = 0; i < stones.length; i++) stones[i] = Stone.EMPTY; - - boolean blackToPlay = true; - int[] lastMove = null; + for (int i = 0; i < stones.length; i++) { + stones[i] = Stone.EMPTY; + } capturedStones = null; scoreMode = false; - history = - new BoardHistoryList( - new BoardData( - stones, - lastMove, - Stone.EMPTY, - blackToPlay, - new Zobrist(), - 0, - new int[BOARD_SIZE * BOARD_SIZE], - 0, - 0, - 50, - 0)); + int[] boardArray = new int[BOARD_SIZE * BOARD_SIZE]; + BoardData boardData = + new BoardData(stones, null, Stone.EMPTY, true, new Zobrist(), 0, boardArray, 0, 0, 50, 0); + history = new BoardHistoryList(boardData); } /** @@ -627,6 +619,82 @@ public boolean nextMove() { } } + /** + * Goes to the next coordinate, thread safe + * + * @param fromBackChildren by back children branch + * @return true when has next variation + */ + public boolean nextMove(int fromBackChildren) { + synchronized (this) { + // Update win rate statistics + Leelaz.WinrateStats stats = Lizzie.leelaz.getWinrateStats(); + if (stats.totalPlayouts >= history.getData().playouts) { + history.getData().winrate = stats.maxWinrate; + history.getData().playouts = stats.totalPlayouts; + } + return nextVariation(fromBackChildren); + } + } + + /** Save the move number for restore If in the branch, save the back routing from children */ + public void saveMoveNumber() { + BoardHistoryNode curNode = history.getCurrentHistoryNode(); + int curMoveNum = curNode.getData().moveNumber; + if (curMoveNum > 0) { + if (!BoardHistoryList.isMainTrunk(curNode)) { + // If in branch, save the back routing from children + saveBackRouting(curNode); + } + goToMoveNumber(0); + } + saveNode = curNode; + } + + /** Save the back routing from children */ + public void saveBackRouting(BoardHistoryNode node) { + if (node != null && node.previous() != null) { + node.previous().setFromBackChildren(node.previous().getNexts().indexOf(node)); + saveBackRouting(node.previous()); + } + } + + /** Restore move number by saved node */ + public void restoreMoveNumber() { + restoreMoveNumber(saveNode); + } + + /** Restore move number by node */ + public void restoreMoveNumber(BoardHistoryNode node) { + if (node == null) { + return; + } + int moveNumber = node.getData().moveNumber; + if (moveNumber > 0) { + if (BoardHistoryList.isMainTrunk(node)) { + goToMoveNumber(moveNumber); + } else { + // If in Branch, restore by the back routing + goToMoveNumberByBackChildren(moveNumber); + } + } + } + + /** Go to move number by back routing from children when in branch */ + public void goToMoveNumberByBackChildren(int moveNumber) { + int delta = moveNumber - history.getMoveNumber(); + for (int i = 0; i < Math.abs(delta); i++) { + BoardHistoryNode curNode = history.getCurrentHistoryNode(); + if (curNode.numberOfChildren() > 1 && delta > 0) { + nextMove(curNode.getFromBackChildren()); + } else { + if (!(delta > 0 ? nextMove() : previousMove())) { + break; + } + } + } + } + public boolean goToMoveNumber(int moveNumber) { return goToMoveNumberHelper(moveNumber, false); } diff --git a/src/main/java/featurecat/lizzie/rules/BoardData.java b/src/main/java/featurecat/lizzie/rules/BoardData.java index 6bc42c018..fff4fcd07 100644 --- a/src/main/java/featurecat/lizzie/rules/BoardData.java +++ b/src/main/java/featurecat/lizzie/rules/BoardData.java @@ -12,12 +12,10 @@ public class BoardData { public Stone lastMoveColor; public Stone[] stones; public Zobrist zobrist; - public boolean verify; public double winrate; public int playouts; - public int blackCaptures; public int whiteCaptures; diff --git a/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java b/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java index fb2347f2e..7de27a1a2 100644 --- a/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java +++ b/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java @@ -83,18 +83,6 @@ public BoardData next() { return head.getData(); } - /** - * moves the pointer to the right, returns the node stored there - * - * @return the next node, null if there is no next node - */ - public BoardHistoryNode nextNode() { - if (head.next() == null) return null; - else head = head.next(); - - return head; - } - /** * moves the pointer to the variation number idx, returns the data stored there * diff --git a/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java b/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java index 45aa25ff8..d038123d2 100644 --- a/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java +++ b/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java @@ -10,6 +10,9 @@ public class BoardHistoryNode { private BoardData data; + // Save the children for restore to branch + private int fromBackChildren; + /** Initializes a new list node */ public BoardHistoryNode(BoardData data) { previous = null; @@ -181,4 +184,14 @@ public void deleteChild(int idx) { nexts.remove(idx); } } + + /** @param fromBackChildren the fromBackChildren to set */ + public void setFromBackChildren(int fromBackChildren) { + this.fromBackChildren = fromBackChildren; + } + + /** @return the fromBackChildren */ + public int getFromBackChildren() { + return fromBackChildren; + } } diff --git a/src/main/java/featurecat/lizzie/rules/GIBParser.java b/src/main/java/featurecat/lizzie/rules/GIBParser.java index cead794c7..3f5e826ff 100644 --- a/src/main/java/featurecat/lizzie/rules/GIBParser.java +++ b/src/main/java/featurecat/lizzie/rules/GIBParser.java @@ -1,7 +1,6 @@ package featurecat.lizzie.rules; import featurecat.lizzie.Lizzie; -import featurecat.lizzie.plugin.PluginManager; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -35,7 +34,6 @@ public static boolean load(String filename) throws IOException { } boolean returnValue = parse(value); - PluginManager.onSgfLoaded(); return returnValue; } diff --git a/src/main/java/featurecat/lizzie/rules/SGFParser.java b/src/main/java/featurecat/lizzie/rules/SGFParser.java index 2b3a19004..1cc6a931c 100644 --- a/src/main/java/featurecat/lizzie/rules/SGFParser.java +++ b/src/main/java/featurecat/lizzie/rules/SGFParser.java @@ -2,7 +2,6 @@ import featurecat.lizzie.Lizzie; import featurecat.lizzie.analysis.GameInfo; -import featurecat.lizzie.plugin.PluginManager; import java.io.*; import java.text.SimpleDateFormat; import java.util.HashMap; @@ -40,7 +39,6 @@ public static boolean load(String filename) throws IOException { } boolean returnValue = parse(value); - PluginManager.onSgfLoaded(); return returnValue; } @@ -294,8 +292,8 @@ private static void saveToStream(Board board, Writer writer) throws IOException // collect game info BoardHistoryList history = board.getHistory().shallowCopy(); GameInfo gameInfo = history.getGameInfo(); - String playerBlack = gameInfo.getPlayerBlack(); - String playerWhite = gameInfo.getPlayerWhite(); + String playerB = gameInfo.getPlayerBlack(); + String playerW = gameInfo.getPlayerWhite(); Double komi = gameInfo.getKomi(); Integer handicap = gameInfo.getHandicap(); String date = SGF_DATE_FORMAT.format(gameInfo.getDate()); @@ -307,7 +305,7 @@ private static void saveToStream(Board board, Writer writer) throws IOException generalProps.append( String.format( "KM[%s]PW[%s]PB[%s]DT[%s]AP[Lizzie: %s]", - komi, playerWhite, playerBlack, date, Lizzie.lizzieVersion)); + komi, playerW, playerB, date, Lizzie.lizzieVersion)); // move to the first move history.toStart(); diff --git a/src/main/resources/l10n/DisplayStrings.properties b/src/main/resources/l10n/DisplayStrings.properties index 05deb875a..be3e5dce7 100644 --- a/src/main/resources/l10n/DisplayStrings.properties +++ b/src/main/resources/l10n/DisplayStrings.properties @@ -25,6 +25,7 @@ LizzieFrame.commands.keyEnd=end|go to end LizzieFrame.commands.keyEnter=enter|force Leela Zero move LizzieFrame.commands.keyF=f|toggle next move display LizzieFrame.commands.keyG=g|toggle variation graph +LizzieFrame.commands.keyT=t|toggle comment display LizzieFrame.commands.keyHome=home|go to start LizzieFrame.commands.keyI=i|edit game info LizzieFrame.commands.keyA=a|run automatic analysis of game @@ -44,6 +45,7 @@ LizzieFrame.prompt.failedToOpenFile=Failed to open file. LizzieFrame.prompt.failedToSaveFile=Failed to save file. LizzieFrame.prompt.sgfExists=The SGF file already exists, do you want to replace it? LizzieFrame.prompt.showControlsHint=hold x = view controls +LizzieFrame.prompt.switching=switching... LizzieFrame.display.lastMove=Last move LizzieFrame.display.pondering=Pondering LizzieFrame.display.on=on diff --git a/src/main/resources/l10n/DisplayStrings_RO.properties b/src/main/resources/l10n/DisplayStrings_RO.properties index 07b385516..14c54dce5 100644 --- a/src/main/resources/l10n/DisplayStrings_RO.properties +++ b/src/main/resources/l10n/DisplayStrings_RO.properties @@ -24,6 +24,7 @@ LizzieFrame.commands.keyEnd=end|mergi la sfârșit LizzieFrame.commands.keyEnter=enter|forțează mutarea lui Leela Zero LizzieFrame.commands.keyF=f|afișează/ascunde următoarea mutare LizzieFrame.commands.keyG=g|afișează/ascunde graficul cu variante +LizzieFrame.commands.keyT=t|afișează/ascunde comentariul LizzieFrame.commands.keyHome=home|mergi la început LizzieFrame.commands.keyI=i|editează informațiile jocului LizzieFrame.commands.keyA=a|rulează analiza automată a jocului @@ -43,6 +44,7 @@ LizzieFrame.prompt.failedToOpenSgf=Nu s-a reușit deschiderea fișierului SGF LizzieFrame.prompt.failedToSaveSgf=Nu s-a reușit salvarea fișierului SGF LizzieFrame.prompt.sgfExists=Fișierul SGF există deja, doriți să-l înlocuiți? LizzieFrame.prompt.showControlsHint=x apăsat = afișează comenzi +LizzieFrame.prompt.switching=comutare... LizzieFrame.display.lastMove=Ulima mutare LizzieFrame.display.pondering=Analizeză LizzieFrame.display.on=pornit diff --git a/src/main/resources/l10n/DisplayStrings_zh_CN.properties b/src/main/resources/l10n/DisplayStrings_zh_CN.properties index 95d9c079d..c7effab3e 100644 --- a/src/main/resources/l10n/DisplayStrings_zh_CN.properties +++ b/src/main/resources/l10n/DisplayStrings_zh_CN.properties @@ -13,6 +13,7 @@ LizzieFrame.commands.keyEnd=end|\u8DF3\u5230\u68CB\u8C31\u672B\u5C3E LizzieFrame.commands.keyEnter=enter|\u8BA9Leela Zero\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 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 @@ -32,6 +33,7 @@ LizzieFrame.prompt.failedToOpenFile=\u4E0D\u80FD\u6253\u5F00SGF\u6587\u4EF6. LizzieFrame.prompt.failedToSaveFile=\u4E0D\u80FD\u4FDD\u5B58SGF\u6587\u4EF6. LizzieFrame.prompt.sgfExists=SGF\u6587\u4EF6\u5DF2\u7ECF\u5B58\u5728, \u9700\u8981\u66FF\u6362\u5417? LizzieFrame.prompt.showControlsHint=\u6309\u4F4FX\u4E0D\u653E\u67E5\u770B\u5FEB\u6377\u952E\u63D0\u793A +LizzieFrame.prompt.switching=\u5207\u6362\u4E2D... LizzieFrame.display.lastMove=\u6700\u540E\u4E00\u624B LizzieFrame.display.pondering=\u5206\u6790 LizzieFrame.display.on=\u5F00\u542F diff --git a/src/test/java/common/Util.java b/src/test/java/common/Util.java index 2811152d5..7fa429488 100644 --- a/src/test/java/common/Util.java +++ b/src/test/java/common/Util.java @@ -1,6 +1,5 @@ package common; - import featurecat.lizzie.Lizzie; import featurecat.lizzie.rules.Board; import featurecat.lizzie.rules.BoardData;