variation;
- /**
- * Parses a leelaz ponder output line
- * @param line line of ponder output
- */
- public MoveData(String line) throws ArrayIndexOutOfBoundsException {
- String[] data = line.trim().split(" ");
+ private MoveData() {}
- // Todo: Proper tag parsing in case gtp protocol is extended(?)/changed
- coordinate = data[1];
- playouts = Integer.parseInt(data[3]);
- winrate = Integer.parseInt(data[5])/100.0;
- order = Integer.parseInt(data[7]);
+ /**
+ * Parses a leelaz ponder output line. For example:
+ *
+ * info move R5 visits 38 winrate 5404 order 0 pv R5 Q5 R6 S4 Q10 C3 D3 C4 C6 C5 D5
+ *
+ * @param line line of ponder output
+ */
+ public static MoveData fromInfo(String line) throws ArrayIndexOutOfBoundsException {
+ MoveData result = new MoveData();
+ String[] data = line.trim().split(" ");
- variation = new ArrayList<>(Arrays.asList(data));
- variation = variation.subList(9, variation.size());
+ // Todo: Proper tag parsing in case gtp protocol is extended(?)/changed
+ for (int i = 0; i < data.length; i++) {
+ String key = data[i];
+ if (key.equals("pv")) {
+ // Read variation to the end of line
+ result.variation = new ArrayList<>(Arrays.asList(data));
+ result.variation = result.variation.subList(i + 1, data.length);
+ break;
+ } else {
+ String value = data[++i];
+ if (key.equals("move")) {
+ result.coordinate = value;
+ }
+ if (key.equals("visits")) {
+ result.playouts = Integer.parseInt(value);
+ }
+ if (key.equals("winrate")) {
+ result.winrate = Integer.parseInt(value) / 100.0;
+ }
+ }
}
+ return result;
+ }
- public int compareTo(MoveData b) {
- return order - b.order;
+ /**
+ * Parses a leelaz summary output line. For example:
+ *
+ *
P16 -> 4 (V: 50.94%) (N: 5.79%) PV: P16 N18 R5 Q5
+ *
+ * @param line line of summary output
+ */
+ public static MoveData fromSummary(String summary) {
+ Matcher match = summaryPattern.matcher(summary.trim());
+ if (!match.matches()) {
+ throw new IllegalArgumentException("Unexpected summary format: " + summary);
+ } else {
+ MoveData result = new MoveData();
+ result.coordinate = match.group(1);
+ result.playouts = Integer.parseInt(match.group(2));
+ result.winrate = Double.parseDouble(match.group(3));
+ result.variation = Arrays.asList(match.group(4).split(" "));
+ return result;
}
+ }
+
+ private static Pattern summaryPattern =
+ Pattern.compile("^ *(\\w\\d*) -> *(\\d+) \\(V: ([^%)]+)%\\) \\([^\\)]+\\) PV: (.+).*$");
}
diff --git a/src/main/java/featurecat/lizzie/gui/BoardRenderer.java b/src/main/java/featurecat/lizzie/gui/BoardRenderer.java
index 8a85a4e51..efff7f5a5 100644
--- a/src/main/java/featurecat/lizzie/gui/BoardRenderer.java
+++ b/src/main/java/featurecat/lizzie/gui/BoardRenderer.java
@@ -1,1005 +1,1149 @@
package featurecat.lizzie.gui;
-import org.json.JSONArray;
-import org.json.JSONObject;
+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.BoardHistoryNode;
import featurecat.lizzie.rules.Stone;
import featurecat.lizzie.rules.Zobrist;
-
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.HashMap;
import java.util.List;
import java.util.Map;
-
-import featurecat.lizzie.theme.DefaultTheme;
-import featurecat.lizzie.theme.ITheme;
+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
- private static final double MARGIN_WITH_COORDINATES = 0.06;
- private static final double STARPOINT_DIAMETER = 0.015;
+ // 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;
- private int x, y;
- private int boardLength;
+ private int x, y;
+ private int boardLength;
- private JSONObject uiConfig;
+ private JSONObject uiConfig, uiPersist;
+ private int scaledMargin, availableLength, squareLength, stoneRadius;
+ private Branch branch;
+ private List bestMoves;
- private int scaledMargin, availableLength, squareLength, stoneRadius;
- private Branch branch;
- private List bestMoves;
+ private BufferedImage cachedBackgroundImage = null;
+ private boolean cachedBackgroundImageHasCoordinatesEnabled = false;
+ private int cachedX, cachedY;
- private BufferedImage cachedBackgroundImage = null;
- private boolean cachedBackgroundImageHasCoordinatesEnabled = false;
- 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
- private BufferedImage cachedStonesImage = null;
- private BufferedImage cachedStonesShadowImage = null;
- private Zobrist cachedZhash = new Zobrist(); // defaults to an empty board
+ private BufferedImage cachedBlackStoneImage = null;
+ private BufferedImage cachedWhiteStoneImage = null;
- private BufferedImage branchStonesImage = null;
- private BufferedImage branchStonesShadowImage = null;
+ private BufferedImage branchStonesImage = null;
+ private BufferedImage branchStonesShadowImage = null;
- private boolean lastInScoreMode = false;
+ private boolean lastInScoreMode = false;
- public ITheme theme;
- public List variation;
+ public List variation;
- // special values of displayedBranchLength
- public static final int SHOW_RAW_BOARD = -1;
- public static final int SHOW_NORMAL_BOARD = -2;
+ // special values of displayedBranchLength
+ public static final int SHOW_RAW_BOARD = -1;
+ public static final int SHOW_NORMAL_BOARD = -2;
- private int displayedBranchLength = SHOW_NORMAL_BOARD;
- private int cachedDisplayedBranchLength = SHOW_RAW_BOARD;
- private boolean showingBranch = false;
- private boolean isMainBoard = false;
+ private int displayedBranchLength = SHOW_NORMAL_BOARD;
+ private int cachedDisplayedBranchLength = SHOW_RAW_BOARD;
+ private boolean showingBranch = false;
+ private boolean isMainBoard = false;
- public BoardRenderer(boolean isMainBoard) {
- uiConfig = Lizzie.config.config.getJSONObject("ui");
- theme = ITheme.loadTheme(uiConfig.getString("theme"));
- if (theme == null) {
- theme = new DefaultTheme();
- }
- this.isMainBoard = isMainBoard;
- }
+ private int maxAlpha = 240;
- /**
- * Draw a go board
- */
- public void draw(Graphics2D g) {
- if (Lizzie.frame == null || Lizzie.board == null)
- return;
-
- setupSizeParameters();
-
-// Stopwatch timer = new Stopwatch();
- drawBackground(g);
-// timer.lap("background");
- drawStones();
-// timer.lap("stones");
- if (Lizzie.board.inScoreMode() && isMainBoard) {
- drawScore(g);
- } else {
- drawBranch();
- }
-// timer.lap("branch");
-
- renderImages(g);
-// timer.lap("rendering images");
-
- if (!isMainBoard) {
- drawMoveNumbers(g);
- return;
- }
-
- if (!isShowingRawBoard()) {
- drawMoveNumbers(g);
-// timer.lap("movenumbers");
- if (!Lizzie.frame.isPlayingAgainstLeelaz && Lizzie.config.showBestMoves)
- drawLeelazSuggestions(g);
-
- if (Lizzie.config.showNextMoves) {
- drawNextMoves(g);
- }
- }
-
- PluginManager.onDraw(g);
-// timer.lap("leelaz");
-
-// timer.print();
+ public BoardRenderer(boolean isMainBoard) {
+ uiConfig = Lizzie.config.config.getJSONObject("ui");
+ uiPersist = Lizzie.config.persisted.getJSONObject("ui-persist");
+ try {
+ maxAlpha = uiPersist.getInt("max-alpha");
+ } catch (JSONException e) {
}
-
- /**
- * Return the best move of Leelaz's suggestions
- *
- * @return the coordinate name of the best move
- */
- public String bestMoveCoordinateName() {
- if (bestMoves == null || bestMoves.size() == 0) {
- return null;
- } else {
- return bestMoves.get(0).coordinate;
- }
+ this.isMainBoard = isMainBoard;
+ }
+
+ /** Draw a go board */
+ public void draw(Graphics2D g) {
+ if (Lizzie.frame == null || Lizzie.board == null) return;
+
+ setupSizeParameters();
+
+ // Stopwatch timer = new Stopwatch();
+ drawGoban(g);
+ // timer.lap("background");
+ drawStones();
+ // timer.lap("stones");
+ if (Lizzie.board.inScoreMode() && isMainBoard) {
+ drawScore(g);
+ } else {
+ drawBranch();
}
+ // timer.lap("branch");
- /**
- * Calculate good values for boardLength, scaledMargin, availableLength, and squareLength
- */
- private void setupSizeParameters() {
- int originalBoardLength = boardLength;
+ renderImages(g);
+ // timer.lap("rendering images");
- int[] calculatedPixelMargins = calculatePixelMargins();
- boardLength = calculatedPixelMargins[0];
- scaledMargin = calculatedPixelMargins[1];
- availableLength = calculatedPixelMargins[2];
-
- squareLength = calculateSquareLength(availableLength);
- stoneRadius = squareLength / 2 - 1;
-
- // re-center board
- setLocation(x + (originalBoardLength - boardLength) / 2, y + (originalBoardLength - boardLength) / 2);
+ if (!isMainBoard) {
+ drawMoveNumbers(g);
+ return;
}
- /**
- * Draw the green background and go board with lines. We cache the image for a performance boost.
- */
- private void drawBackground(Graphics2D g0) {
- // draw the cached background image if frame size changes
- if (cachedBackgroundImage == null || cachedBackgroundImage.getWidth() != Lizzie.frame.getWidth() ||
- cachedBackgroundImage.getHeight() != Lizzie.frame.getHeight() ||
- cachedX != x || cachedY != y ||
- cachedBackgroundImageHasCoordinatesEnabled != showCoordinates()) {
-
- cachedBackgroundImage = new BufferedImage(Lizzie.frame.getWidth(), Lizzie.frame.getHeight(),
- BufferedImage.TYPE_INT_ARGB);
- Graphics2D g = cachedBackgroundImage.createGraphics();
-
- // draw the wooden background
- drawWoodenBoard(g);
+ if (!isShowingRawBoard()) {
+ drawMoveNumbers(g);
+ // timer.lap("movenumbers");
+ if (!Lizzie.frame.isPlayingAgainstLeelaz && Lizzie.config.showBestMoves)
+ drawLeelazSuggestions(g);
- // draw the lines
- g.setColor(Color.BLACK);
- for (int i = 0; i < Board.BOARD_SIZE; i++) {
- g.drawLine(x + scaledMargin, y + scaledMargin + squareLength * i,
- x + scaledMargin + availableLength - 1, y + scaledMargin + squareLength * i);
- }
- for (int i = 0; i < Board.BOARD_SIZE; i++) {
- g.drawLine(x + scaledMargin + squareLength * i, y + scaledMargin,
- x + scaledMargin + squareLength * i, y + scaledMargin + availableLength - 1);
- }
-
- // draw the star points
- drawStarPoints(g);
-
- // draw coordinates if enabled
- if (showCoordinates()) {
- g.setColor(Color.BLACK);
- String alphabet = "ABCDEFGHJKLMNOPQRST";
- for (int i = 0; i < Board.BOARD_SIZE; i++) {
- drawString(g, x + scaledMargin + squareLength * i, y + scaledMargin / 2, LizzieFrame.OpenSansRegularBase, "" + alphabet.charAt(i), stoneRadius * 4 / 5, stoneRadius);
- drawString(g, x + scaledMargin + squareLength * i, y - scaledMargin / 2 + boardLength, LizzieFrame.OpenSansRegularBase, "" + alphabet.charAt(i), stoneRadius * 4 / 5, stoneRadius);
- }
- for (int i = 0; i < Board.BOARD_SIZE; i++) {
- drawString(g, x + scaledMargin / 2, y + scaledMargin + squareLength * i, LizzieFrame.OpenSansRegularBase, "" + (Board.BOARD_SIZE - i), stoneRadius * 4 / 5, stoneRadius);
- drawString(g, x - scaledMargin / 2 + +boardLength, y + scaledMargin + squareLength * i, LizzieFrame.OpenSansRegularBase, "" + (Board.BOARD_SIZE - i), stoneRadius * 4 / 5, stoneRadius);
- }
- }
- cachedBackgroundImageHasCoordinatesEnabled = showCoordinates();
- g.dispose();
- }
-
- g0.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
- g0.drawImage(cachedBackgroundImage, 0, 0, null);
- cachedX = x;
- cachedY = y;
+ if (Lizzie.config.showNextMoves) {
+ drawNextMoves(g);
+ }
}
- /**
- * Draw the star points on the board, according to board size
- *
- * @param g graphics2d object to draw
- */
- private void drawStarPoints(Graphics2D g) {
- if (Board.BOARD_SIZE == 9) {
- drawStarPoints9x9(g);
- } else if (Board.BOARD_SIZE == 13) {
- drawStarPoints13x13(g);
- } else {
- drawStarPoints19x19(g);
- }
+ // timer.lap("leelaz");
+
+ // timer.print();
+ }
+
+ /**
+ * Return the best move of Leelaz's suggestions
+ *
+ * @return the coordinate name of the best move
+ */
+ public String bestMoveCoordinateName() {
+ if (bestMoves == null || bestMoves.size() == 0) {
+ return null;
+ } else {
+ return bestMoves.get(0).coordinate;
}
-
- private void drawStarPoints19x19(Graphics2D g) {
- g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.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);
- fillCircle(g, centerX, centerY, starPointRadius);
- }
+ }
+
+ /** Calculate good values for boardLength, scaledMargin, availableLength, and squareLength */
+ private void setupSizeParameters() {
+ int boardLength0 = boardLength;
+
+ int[] calculatedPixelMargins = calculatePixelMargins();
+ boardLength = calculatedPixelMargins[0];
+ scaledMargin = calculatedPixelMargins[1];
+ availableLength = calculatedPixelMargins[2];
+
+ squareLength = calculateSquareLength(availableLength);
+ stoneRadius = squareLength / 2 - 1;
+
+ // re-center board
+ 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 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() != width
+ || cachedBackgroundImage.getHeight() != height
+ || cachedX != x
+ || cachedY != y
+ || cachedBackgroundImageHasCoordinatesEnabled != showCoordinates()) {
+
+ cachedBackgroundImage = new BufferedImage(width, height, TYPE_INT_ARGB);
+ Graphics2D g = cachedBackgroundImage.createGraphics();
+
+ // draw the wooden background
+ drawWoodenBoard(g);
+
+ // draw the lines
+ g.setColor(Color.BLACK);
+ for (int i = 0; i < Board.BOARD_SIZE; i++) {
+ g.drawLine(
+ x + scaledMargin,
+ y + scaledMargin + squareLength * i,
+ x + scaledMargin + availableLength - 1,
+ y + scaledMargin + squareLength * i);
+ }
+ for (int i = 0; i < Board.BOARD_SIZE; i++) {
+ g.drawLine(
+ x + scaledMargin + squareLength * i,
+ y + scaledMargin,
+ x + scaledMargin + squareLength * i,
+ y + scaledMargin + availableLength - 1);
+ }
+
+ // draw the star points
+ drawStarPoints(g);
+
+ // draw coordinates if enabled
+ if (showCoordinates()) {
+ g.setColor(Color.BLACK);
+ String alphabet = "ABCDEFGHJKLMNOPQRST";
+ for (int i = 0; i < Board.BOARD_SIZE; i++) {
+ drawString(
+ g,
+ x + scaledMargin + squareLength * i,
+ y + scaledMargin / 2,
+ LizzieFrame.OpenSansRegularBase,
+ "" + alphabet.charAt(i),
+ stoneRadius * 4 / 5,
+ stoneRadius);
+ drawString(
+ g,
+ x + scaledMargin + squareLength * i,
+ y - scaledMargin / 2 + boardLength,
+ LizzieFrame.OpenSansRegularBase,
+ "" + alphabet.charAt(i),
+ stoneRadius * 4 / 5,
+ stoneRadius);
}
- }
-
- 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);
- }
+ for (int i = 0; i < Board.BOARD_SIZE; i++) {
+ drawString(
+ g,
+ x + scaledMargin / 2,
+ y + scaledMargin + squareLength * i,
+ LizzieFrame.OpenSansRegularBase,
+ "" + (Board.BOARD_SIZE - i),
+ stoneRadius * 4 / 5,
+ stoneRadius);
+ drawString(
+ g,
+ x - scaledMargin / 2 + +boardLength,
+ y + scaledMargin + squareLength * i,
+ LizzieFrame.OpenSansRegularBase,
+ "" + (Board.BOARD_SIZE - i),
+ stoneRadius * 4 / 5,
+ stoneRadius);
}
-
- // Draw center
- int centerX = x + scaledMargin + squareLength * STARPOINT_GRID_DISTANCE;
- int centerY = y + scaledMargin + squareLength * STARPOINT_GRID_DISTANCE;
- fillCircle(g, centerX, centerY, starPointRadius);
+ }
+ cachedBackgroundImageHasCoordinatesEnabled = showCoordinates();
+ g.dispose();
}
- 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;
+ g0.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF);
+ g0.drawImage(cachedBackgroundImage, 0, 0, null);
+ cachedX = x;
+ cachedY = y;
+ }
+
+ /**
+ * Draw the star points on the board, according to board size
+ *
+ * @param g graphics2d object to draw
+ */
+ private void drawStarPoints(Graphics2D g) {
+ if (Board.BOARD_SIZE == 9) {
+ drawStarPoints0(2, 2, 4, true, g);
+ } else if (Board.BOARD_SIZE == 13) {
+ drawStarPoints0(2, 3, 6, true, g);
+ } else {
+ drawStarPoints0(3, 3, 6, false, g);
+ }
+ }
+
+ 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;
+ 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);
+ }
}
- /**
- * Draw the stones. We cache the image for a performance boost.
- */
- private void drawStones() {
- // draw a new image if frame size changes or board state changes
- if (cachedStonesImage == null || cachedStonesImage.getWidth() != boardLength ||
- cachedStonesImage.getHeight() != boardLength ||
- cachedDisplayedBranchLength != displayedBranchLength ||
- !cachedZhash.equals(Lizzie.board.getData().zobrist)
- || Lizzie.board.inScoreMode() || lastInScoreMode) {
-
- cachedStonesImage = new BufferedImage(boardLength, boardLength, BufferedImage.TYPE_INT_ARGB);
- cachedStonesShadowImage = new BufferedImage(boardLength, boardLength, BufferedImage.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);
-
- for (int i = 0; i < Board.BOARD_SIZE; i++) {
- for (int j = 0; j < Board.BOARD_SIZE; j++) {
- int stoneX = scaledMargin + squareLength * i;
- int stoneY = scaledMargin + squareLength * j;
- drawStone(g, gShadow, stoneX, stoneY, Lizzie.board.getStones()[Board.getIndex(i, j)], i, j);
- }
- }
-
- cachedZhash = Lizzie.board.getData().zobrist.clone();
- cachedDisplayedBranchLength = displayedBranchLength;
- g.dispose();
- gShadow.dispose();
- lastInScoreMode = false;
+ if (center) {
+ int centerX = x + scaledMargin + squareLength * gridDistance;
+ int centerY = y + scaledMargin + squareLength * gridDistance;
+ fillCircle(g, centerX, centerY, starPointRadius);
+ }
+ }
+
+ /** Draw the stones. We cache the image for a performance boost. */
+ private void drawStones() {
+ // draw a new image if frame size changes or board state changes
+ if (cachedStonesImage == null
+ || cachedStonesImage.getWidth() != boardLength
+ || cachedStonesImage.getHeight() != boardLength
+ || cachedDisplayedBranchLength != displayedBranchLength
+ || !cachedZhash.equals(Lizzie.board.getData().zobrist)
+ || Lizzie.board.inScoreMode()
+ || lastInScoreMode) {
+
+ 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(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++) {
+ int stoneX = scaledMargin + squareLength * i;
+ int stoneY = scaledMargin + squareLength * j;
+ drawStone(
+ g, gShadow, stoneX, stoneY, Lizzie.board.getStones()[Board.getIndex(i, j)], i, j);
}
- if (Lizzie.board.inScoreMode()) lastInScoreMode = true;
+ }
+ cachedZhash = Lizzie.board.getData().zobrist.clone();
+ cachedDisplayedBranchLength = displayedBranchLength;
+ g.dispose();
+ gShadow.dispose();
+ lastInScoreMode = false;
}
-
- /*
- * Draw a white/black dot on territory and captured stones. Dame is drawn as red dot.
- */
- private void drawScore(Graphics2D go) {
- Graphics2D g = cachedStonesImage.createGraphics();
- Stone scorestones[] = Lizzie.board.scoreStones();
- int scoreRadius = stoneRadius / 4;
- for (int i = 0; i < Board.BOARD_SIZE; i++) {
- for (int j = 0; j < Board.BOARD_SIZE; j++) {
- int stoneX = scaledMargin + squareLength * i;
- int stoneY = scaledMargin + squareLength * j;
- switch (scorestones[Board.getIndex(i, j)]) {
- case WHITE_POINT:
- case BLACK_CAPTURED:
- g.setColor(Color.white);
- fillCircle(g, stoneX, stoneY, scoreRadius);
- break;
- case BLACK_POINT:
- case WHITE_CAPTURED:
- g.setColor(Color.black);
- fillCircle(g, stoneX, stoneY, scoreRadius);
- break;
- case DAME:
- g.setColor(Color.red);
- fillCircle(g, stoneX, stoneY, scoreRadius);
- break;
- }
- }
+ if (Lizzie.board.inScoreMode()) lastInScoreMode = true;
+ }
+
+ /*
+ * Draw a white/black dot on territory and captured stones. Dame is drawn as red dot.
+ */
+ private void drawScore(Graphics2D go) {
+ Graphics2D g = cachedStonesImage.createGraphics();
+ Stone scorestones[] = Lizzie.board.scoreStones();
+ int scoreRadius = stoneRadius / 4;
+ for (int i = 0; i < Board.BOARD_SIZE; i++) {
+ for (int j = 0; j < Board.BOARD_SIZE; j++) {
+ int stoneX = scaledMargin + squareLength * i;
+ int stoneY = scaledMargin + squareLength * j;
+ switch (scorestones[Board.getIndex(i, j)]) {
+ case WHITE_POINT:
+ case BLACK_CAPTURED:
+ g.setColor(Color.white);
+ fillCircle(g, stoneX, stoneY, scoreRadius);
+ break;
+ case BLACK_POINT:
+ case WHITE_CAPTURED:
+ g.setColor(Color.black);
+ fillCircle(g, stoneX, stoneY, scoreRadius);
+ break;
+ case DAME:
+ g.setColor(Color.red);
+ fillCircle(g, stoneX, stoneY, scoreRadius);
+ break;
}
- g.dispose();
+ }
}
+ g.dispose();
+ }
+
+ /** Draw the 'ghost stones' which show a variation Leelaz is thinking about */
+ private void drawBranch() {
+ showingBranch = false;
+ 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) {
+ return;
+ }
+ // calculate best moves and branch
+ bestMoves = Lizzie.leelaz.getBestMoves();
+ branch = null;
+ variation = null;
- /**
- * 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);
- branch = null;
-
- if (Lizzie.frame.isPlayingAgainstLeelaz || Lizzie.leelaz == null) {
- return;
- }
- // calculate best moves and branch
- bestMoves = Lizzie.leelaz.getBestMoves();
- branch = null;
- variation = null;
-
- if (isMainBoard && (isShowingRawBoard() || !Lizzie.config.showBranch)) {
- return;
- }
+ if (isMainBoard && (isShowingRawBoard() || !Lizzie.config.showBranch)) {
+ return;
+ }
- Graphics2D g = (Graphics2D) branchStonesImage.getGraphics();
- Graphics2D gShadow = (Graphics2D) branchStonesShadowImage.getGraphics();
+ Graphics2D g = (Graphics2D) branchStonesImage.getGraphics();
+ Graphics2D gShadow = (Graphics2D) branchStonesShadowImage.getGraphics();
- MoveData suggestedMove = (isMainBoard ? mouseHoveredMove() : getBestMove());
- if (suggestedMove == null)
- return;
- variation = suggestedMove.variation;
- branch = new Branch(Lizzie.board, variation);
+ MoveData suggestedMove = (isMainBoard ? mouseOveredMove() : getBestMove());
+ if (suggestedMove == null) return;
+ variation = suggestedMove.variation;
+ branch = new Branch(Lizzie.board, variation);
- if (branch == null)
- return;
- showingBranch = true;
+ 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++) {
- if (Lizzie.board.getData().stones[Board.getIndex(i,j)] != Stone.EMPTY)
- continue;
- if (branch.data.moveNumberList[Board.getIndex(i,j)] > maxBranchMoves())
- continue;
+ for (int i = 0; i < Board.BOARD_SIZE; i++) {
+ for (int j = 0; j < Board.BOARD_SIZE; j++) {
+ if (Lizzie.board.getData().stones[Board.getIndex(i, j)] != Stone.EMPTY) continue;
+ if (branch.data.moveNumberList[Board.getIndex(i, j)] > maxBranchMoves()) continue;
- int stoneX = scaledMargin + squareLength * i;
- int stoneY = scaledMargin + squareLength * j;
+ int stoneX = scaledMargin + squareLength * i;
+ int stoneY = scaledMargin + squareLength * j;
- drawStone(g, gShadow, stoneX, stoneY, branch.data.stones[Board.getIndex(i, j)].unGhosted(), i, j);
+ drawStone(
+ g, gShadow, stoneX, stoneY, branch.data.stones[Board.getIndex(i, j)].unGhosted(), i, j);
+ }
+ }
- }
+ g.dispose();
+ gShadow.dispose();
+ }
+
+ 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);
+ if (coord == null) {
+ continue;
}
- g.dispose();
- gShadow.dispose();
- }
-
- private MoveData mouseHoveredMove() {
- if (Lizzie.frame.mouseHoverCoordinate != null) {
- for (int i = 0; i < bestMoves.size(); i++) {
- MoveData move = bestMoves.get(i);
- int[] coord = Board.convertNameToCoordinates(move.coordinate);
- if (coord == null) {
- continue;
- }
-
- if (coord[0] == Lizzie.frame.mouseHoverCoordinate[0] && coord[1] == Lizzie.frame.mouseHoverCoordinate[1]) {
- return move;
- }
- }
+ if (Lizzie.frame.isMouseOver(coord[0], coord[1])) {
+ return move;
}
- return null;
+ }
}
-
- private MoveData getBestMove() {
- return bestMoves.isEmpty() ? null : bestMoves.get(0);
+ return null;
+ }
+
+ private MoveData getBestMove() {
+ return bestMoves.isEmpty() ? null : bestMoves.get(0);
+ }
+
+ /** render the shadows and stones in correct background-foreground order */
+ private void renderImages(Graphics2D g) {
+ g.setRenderingHint(KEY_ANTIALIASING, VALUE_ANTIALIAS_OFF);
+ g.drawImage(cachedStonesShadowImage, x, y, null);
+ if (Lizzie.config.showBranch) {
+ g.drawImage(branchStonesShadowImage, x, y, null);
}
-
- /**
- * 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.drawImage(cachedStonesShadowImage, x, y, null);
- if (Lizzie.config.showBranch) {
- g.drawImage(branchStonesShadowImage, x, y, null);
- }
- g.drawImage(cachedStonesImage, x, y, null);
- if (Lizzie.config.showBranch) {
- g.drawImage(branchStonesImage, x, y, null);
- }
+ g.drawImage(cachedStonesImage, x, y, null);
+ if (Lizzie.config.showBranch) {
+ g.drawImage(branchStonesImage, x, y, null);
+ }
+ }
+
+ /** Draw move numbers and/or mark the last played move */
+ private void drawMoveNumbers(Graphics2D g) {
+ 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
+ int lastMoveMarkerRadius = stoneRadius / 2;
+ int stoneX = x + scaledMargin + squareLength * lastMove[0];
+ int stoneY = y + scaledMargin + squareLength * lastMove[1];
+
+ // set color to the opposite color of whatever is on the board
+ 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 && board.getData().moveNumber != 0 && !board.inScoreMode()) {
+ g.setColor(
+ 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(
+ board.getData().blackToPlay ? new Color(0, 0, 0, 255) : new Color(255, 255, 255, 255));
+ drawString(
+ g,
+ x + boardLength / 2,
+ y + boardLength / 2,
+ LizzieFrame.OpenSansRegularBase,
+ "pass",
+ stoneRadius * 4,
+ stoneRadius * 6);
+ }
+
+ return;
}
- /**
- * 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;
- if (!Lizzie.config.showMoveNumber && branch == null) {
- if (lastMove != null) {
- // mark the last coordinate
- int lastMoveMarkerRadius = stoneRadius / 2;
- int stoneX = x + scaledMargin + squareLength * lastMove[0];
- 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);
- drawCircle(g, stoneX, stoneY, lastMoveMarkerRadius);
- } else if (lastMove == null && Lizzie.board.getData().moveNumber != 0 && !Lizzie.board.inScoreMode()) {
- g.setColor(Lizzie.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));
- drawString(g, x + boardLength / 2, y + boardLength / 2, LizzieFrame.OpenSansRegularBase, "pass", stoneRadius * 4, stoneRadius * 6);
- }
+ int[] moveNumberList = branch == null ? board.getMoveNumberList() : branch.data.moveNumberList;
- return;
- }
+ // Allow to display only last move number
+ int lastMoveNumber = branch == null ? board.getData().moveNumber : branch.data.moveNumber;
+ int onlyLastMoveNumber = Lizzie.config.uiConfig.optInt("only-last-move-number", 9999);
- int[] moveNumberList = branch == null ? Lizzie.board.getMoveNumberList() : branch.data.moveNumberList;
+ for (int i = 0; i < Board.BOARD_SIZE; i++) {
+ for (int j = 0; j < Board.BOARD_SIZE; j++) {
+ int stoneX = x + scaledMargin + squareLength * i;
+ int stoneY = y + scaledMargin + squareLength * j;
- for (int i = 0; i < Board.BOARD_SIZE; i++) {
- for (int j = 0; j < Board.BOARD_SIZE; j++) {
- int stoneX = x + scaledMargin + squareLength * i;
- int stoneY = y + scaledMargin + squareLength * j;
-
- Stone stoneAtThisPoint = branch == null ? Lizzie.board.getStones()[Board.getIndex(i, j)] :
- branch.data.stones[Board.getIndex(i, j)];
-
- // 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])) {
- if (lastMove != null && i == lastMove[0] && j == lastMove[1])
- g.setColor(Color.RED.brighter());//stoneAtThisPoint.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);
- }
-
- String moveNumberString = moveNumberList[Board.getIndex(i, j)] + "";
- drawString(g, stoneX, stoneY, LizzieFrame.OpenSansRegularBase, moveNumberString, (float) (stoneRadius * 1.4), (int) (stoneRadius * 1.4));
- }
- }
+ // Allow to display only last move number
+ if (lastMoveNumber - moveNumberList[Board.getIndex(i, j)] >= onlyLastMoveNumber) {
+ continue;
}
- }
-
- /**
- * Draw all of Leelaz's suggestions as colored stones with winrate/playout statistics overlayed
- */
- private void drawLeelazSuggestions(Graphics2D g) {
- if (Lizzie.leelaz == null)
- return;
-
- final int MIN_ALPHA = 32;
- final int MIN_ALPHA_TO_DISPLAY_TEXT = 64;
- final int MAX_ALPHA = 240;
- 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];
-
- if (!bestMoves.isEmpty()) {
-
- int maxPlayouts = 0;
- double maxWinrate = 0;
- for (MoveData move : bestMoves) {
- if (move.playouts > maxPlayouts)
- maxPlayouts = move.playouts;
- if (move.winrate > maxWinrate)
- maxWinrate = move.winrate;
- }
-
- for (int i = 0; i < Board.BOARD_SIZE; i++) {
- for (int j = 0; j < Board.BOARD_SIZE; j++) {
- MoveData move = null;
-
- // this is inefficient but it looks better with shadows
- for (MoveData m : bestMoves) {
- int[] coord = Board.convertNameToCoordinates(m.coordinate);
- // Handle passes
- if (coord == null) {
- continue;
- }
- if (coord[0] == i && coord[1] == j) {
- move = m;
- break;
- }
- }
-
- if (move == null)
- continue;
-
- boolean isBestMove = bestMoves.get(0) == move;
-
- if (move.playouts == 0) // this actually can happen
- continue;
-
- double percentPlayouts = (double) 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 + (MAX_ALPHA - MIN_ALPHA) * Math.max(0, Math.log(percentPlayouts) /
- ALPHA_SCALING_FACTOR + 1));
-// if (uiConfig.getBoolean("shadows-enabled"))
-// alpha = 255;
-
- Color hsbColor = Color.getHSBColor(hue, saturation, brightness);
- Color color = new Color(hsbColor.getRed(), hsbColor.getBlue(), hsbColor.getGreen(), alpha);
-
- if (branch == null) {
- drawShadow(g, suggestionX, suggestionY, true, (float) alpha / 255);
- 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])) {
- int strokeWidth = 1;
- if (isBestMove != (move.winrate == maxWinrate)) {
- strokeWidth = 2;
- g.setColor(isBestMove ? Color.RED : Color.BLUE);
- g.setStroke(new BasicStroke(strokeWidth));
- } else {
- g.setColor(color.darker());
- }
- drawCircle(g, suggestionX, suggestionY, stoneRadius - strokeWidth / 2);
- g.setStroke(new BasicStroke(1));
- }
-
-
- if (branch == null && alpha >= MIN_ALPHA_TO_DISPLAY_TEXT || (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;
- if (uiConfig.getBoolean("win-rate-always-black") && !Lizzie.board.getData().blackToPlay) {
- roundedWinrate = 100.0 - roundedWinrate;
- }
- g.setColor(Color.BLACK);
- if (branch != null && Lizzie.board.getData().blackToPlay)
- g.setColor(Color.WHITE);
-
- String text;
- if (Lizzie.config.handicapInsteadOfWinrate) {
- text=String.format("%.2f", Lizzie.leelaz.winrateToHandicap(move.winrate));
- } else {
- text=String.format("%.1f", roundedWinrate);
- }
-
- drawString(g, suggestionX, suggestionY, LizzieFrame.OpenSansSemiboldBase, Font.PLAIN, text, stoneRadius, stoneRadius * 1.5, 1);
- drawString(g, suggestionX, suggestionY + stoneRadius * 2 / 5, LizzieFrame.OpenSansRegularBase, getPlayoutsString(move.playouts), (float) (stoneRadius * 0.8), stoneRadius * 1.4);
- }
- }
- }
-
+ 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.isMouseOver(i, j))) {
+ if (lastMove != null && i == lastMove[0] && j == lastMove[1])
+ 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(stoneHere.isBlack() ^ reverse ? Color.WHITE : Color.BLACK);
+ }
+
+ String moveNumberString = moveNumberList[Board.getIndex(i, j)] + "";
+ drawString(
+ g,
+ stoneX,
+ stoneY,
+ LizzieFrame.OpenSansRegularBase,
+ moveNumberString,
+ (float) (stoneRadius * 1.4),
+ (int) (stoneRadius * 1.4));
}
+ }
}
-
- private void drawNextMoves(Graphics2D g) {
-
- List nexts = Lizzie.board.getHistory().getNexts();
-
- for (int i = 0; i < nexts.size(); i++) {
- int[] nextMove = nexts.get(i).getData().lastMove;
- if (nextMove == null) continue;
- if (Lizzie.board.getData().blackToPlay) {
- g.setColor(Color.BLACK);
- } else {
- g.setColor(Color.WHITE);
+ }
+
+ /**
+ * Draw all of Leelaz's suggestions as colored stones with winrate/playout statistics overlayed
+ */
+ private void drawLeelazSuggestions(Graphics2D g) {
+ if (Lizzie.leelaz == null) return;
+
+ 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()) {
+
+ int maxPlayouts = 0;
+ double maxWinrate = 0;
+ for (MoveData move : bestMoves) {
+ if (move.playouts > maxPlayouts) maxPlayouts = move.playouts;
+ if (move.winrate > maxWinrate) maxWinrate = move.winrate;
+ }
+
+ for (int i = 0; i < Board.BOARD_SIZE; i++) {
+ for (int j = 0; j < Board.BOARD_SIZE; j++) {
+ MoveData move = null;
+
+ // this is inefficient but it looks better with shadows
+ for (MoveData m : bestMoves) {
+ int[] coord = Board.convertNameToCoordinates(m.coordinate);
+ // Handle passes
+ if (coord == null) {
+ continue;
}
- int moveX = x + scaledMargin + squareLength * nextMove[0];
- int moveY = y + scaledMargin + squareLength * nextMove[1];
- if (i == 0) {
- g.setStroke(new BasicStroke(3.0f));
+ if (coord[0] == i && coord[1] == j) {
+ move = m;
+ break;
}
- drawCircle(g, moveX, moveY, stoneRadius + 1); // slightly outside best move circle
- if (i == 0) {
- g.setStroke(new BasicStroke(1.0f));
+ }
+
+ if (move == null) continue;
+
+ boolean isBestMove = bestMoves.get(0) == move;
+ boolean hasMaxWinrate = move.winrate == maxWinrate;
+
+ if (move.playouts == 0) // this actually can happen
+ continue;
+
+ 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 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(), (int) alpha);
+
+ boolean isMouseOver = Lizzie.frame.isMouseOver(coordinates[0], coordinates[1]);
+ if (branch == null) {
+ drawShadow(g, suggestionX, suggestionY, true, alpha / 255.0f);
+ g.setColor(color);
+ fillCircle(g, suggestionX, suggestionY, stoneRadius);
+ }
+
+ if (branch == null || isBestMove && isMouseOver) {
+ int strokeWidth = 1;
+ if (isBestMove != hasMaxWinrate) {
+ strokeWidth = 2;
+ g.setColor(isBestMove ? Color.RED : Color.BLUE);
+ g.setStroke(new BasicStroke(strokeWidth));
+ } else {
+ g.setColor(color.darker());
}
- }
- }
-
- private void drawWoodenBoard(Graphics2D g) {
- if (uiConfig.getBoolean("fancy-board")) {
- // fancy version
- int shadowRadius = (int) (boardLength * MARGIN / 6);
- Image boardImage = theme.getBoard();
- g.drawImage(boardImage == null ? theme.getBoard() : boardImage, x - 2 * shadowRadius, y - 2 * shadowRadius, boardLength + 4 * shadowRadius, boardLength + 4 * shadowRadius, null);
- g.setStroke(new BasicStroke(shadowRadius * 2));
- // draw border
- g.setColor(new Color(0, 0, 0, 50));
- g.drawRect(x - shadowRadius, y - shadowRadius, boardLength + 2 * shadowRadius, boardLength + 2 * shadowRadius);
+ drawCircle(g, suggestionX, suggestionY, stoneRadius - strokeWidth / 2);
g.setStroke(new BasicStroke(1));
+ }
+
+ if (branch == null
+ && (hasMaxWinrate
+ || 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;
+ }
+ g.setColor(Color.BLACK);
+ if (branch != null && Lizzie.board.getData().blackToPlay) g.setColor(Color.WHITE);
- } else {
- // simple version
- JSONArray boardColor = uiConfig.getJSONArray("board-color");
- g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
- g.setColor(new Color(boardColor.getInt(0), boardColor.getInt(1), boardColor.getInt(2)));
- g.fillRect(x, y, boardLength, boardLength);
- }
- }
+ String text;
+ if (Lizzie.config.handicapInsteadOfWinrate) {
+ text = String.format("%.2f", Lizzie.leelaz.winrateToHandicap(move.winrate));
+ } else {
+ text = String.format("%.1f", roundedWinrate);
+ }
- /**
- * Calculates the lengths and pixel margins from a given boardLength.
- *
- * @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) {
- //boardLength -= boardLength*MARGIN/3; // account for the shadows we will draw around the edge of the board
-// if (boardLength < Board.BOARD_SIZE - 1)
-// throw new IllegalArgumentException("boardLength may not be less than " + (Board.BOARD_SIZE - 1) + ", but was " + boardLength);
-
- int scaledMargin;
- int availableLength;
-
- // decrease boardLength until the availableLength will result in square board intersections
- double margin = showCoordinates() ? MARGIN_WITH_COORDINATES : MARGIN;
- boardLength++;
- do {
- boardLength--;
- scaledMargin = (int) (margin * boardLength);
- availableLength = boardLength - 2 * scaledMargin;
+ drawString(
+ g,
+ suggestionX,
+ suggestionY,
+ LizzieFrame.OpenSansSemiboldBase,
+ Font.PLAIN,
+ text,
+ stoneRadius,
+ stoneRadius * 1.5,
+ 1);
+
+ drawString(
+ g,
+ suggestionX,
+ suggestionY + stoneRadius * 2 / 5,
+ LizzieFrame.OpenSansRegularBase,
+ getPlayoutsString(move.playouts),
+ (float) (stoneRadius * 0.8),
+ stoneRadius * 1.4);
+ }
}
- while (!((availableLength - 1) % (Board.BOARD_SIZE - 1) == 0));
- // this will be true if BOARD_SIZE - 1 square intersections, plus one line, will fit
-
- return new int[]{boardLength, scaledMargin, availableLength};
+ }
}
-
- private void drawShadow(Graphics2D g, int centerX, int centerY, boolean isGhost) {
- drawShadow(g, centerX, centerY, isGhost, 1);
+ }
+
+ private void drawNextMoves(Graphics2D g) {
+
+ List nexts = Lizzie.board.getHistory().getNexts();
+
+ for (int i = 0; i < nexts.size(); i++) {
+ int[] nextMove = nexts.get(i).getData().lastMove;
+ if (nextMove == null) continue;
+ if (Lizzie.board.getData().blackToPlay) {
+ g.setColor(Color.BLACK);
+ } else {
+ g.setColor(Color.WHITE);
+ }
+ int moveX = x + scaledMargin + squareLength * nextMove[0];
+ int moveY = y + scaledMargin + squareLength * nextMove[1];
+ if (i == 0) {
+ g.setStroke(new BasicStroke(3.0f));
+ }
+ drawCircle(g, moveX, moveY, stoneRadius + 1); // slightly outside best move circle
+ if (i == 0) {
+ g.setStroke(new BasicStroke(1.0f));
+ }
}
-
- private void drawShadow(Graphics2D g, int centerX, int centerY, boolean isGhost, float shadowStrength) {
- if (!uiConfig.getBoolean("shadows-enabled"))
- return;
-
- final int shadowSize = (int) (stoneRadius * 0.3 * uiConfig.getInt("shadow-size") / 100);
- final int fartherShadowSize = (int) (stoneRadius * 0.17 * uiConfig.getInt("shadow-size") / 100);
-
-
- final Paint TOP_GRADIENT_PAINT;
- final Paint LOWER_RIGHT_GRADIENT_PAINT;
-
- if (isGhost) {
- TOP_GRADIENT_PAINT = new RadialGradientPaint(new Point2D.Float(centerX, centerY),
- stoneRadius + shadowSize, new float[]{((float) stoneRadius / (stoneRadius + shadowSize)) - 0.0001f, ((float) stoneRadius / (stoneRadius + shadowSize)), 1.0f}, new Color[]{
- new Color(0, 0, 0, 0), new Color(50, 50, 50, (int) (120 * shadowStrength)), new Color(0, 0, 0, 0)
- });
-
- LOWER_RIGHT_GRADIENT_PAINT = new RadialGradientPaint(new Point2D.Float(centerX + shadowSize * 2 / 3, centerY + shadowSize * 2 / 3),
- stoneRadius + fartherShadowSize, new float[]{0.6f, 1.0f}, new Color[]{
- new Color(0, 0, 0, 180), new Color(0, 0, 0, 0)
- });
- } else {
- TOP_GRADIENT_PAINT = new RadialGradientPaint(new Point2D.Float(centerX, centerY),
- stoneRadius + shadowSize, new float[]{0.3f, 1.0f}, new Color[]{
- new Color(50, 50, 50, 150), new Color(0, 0, 0, 0)
- });
- LOWER_RIGHT_GRADIENT_PAINT = new RadialGradientPaint(new Point2D.Float(centerX + shadowSize, centerY + shadowSize),
- stoneRadius + fartherShadowSize, new float[]{0.6f, 1.0f}, new Color[]{
- new Color(0, 0, 0, 140), new Color(0, 0, 0, 0)
- });
+ }
+
+ private void drawWoodenBoard(Graphics2D g) {
+ if (uiConfig.getBoolean("fancy-board")) {
+ if (cachedBoardImage == null) {
+ try {
+ cachedBoardImage = ImageIO.read(getClass().getResourceAsStream("/assets/board.png"));
+ } catch (IOException e) {
+ e.printStackTrace();
}
-
- final Paint originalPaint = g.getPaint();
-
- g.setPaint(TOP_GRADIENT_PAINT);
- fillCircle(g, centerX, centerY, stoneRadius + shadowSize);
- if (!isGhost) {
- g.setPaint(LOWER_RIGHT_GRADIENT_PAINT);
- fillCircle(g, centerX + shadowSize, centerY + shadowSize, stoneRadius + fartherShadowSize);
- }
- g.setPaint(originalPaint);
+ }
+
+ int shadowRadius = (int) (boardLength * MARGIN / 6);
+ drawTextureImage(
+ g,
+ 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(
+ x - shadowRadius,
+ y - shadowRadius,
+ boardLength + 2 * shadowRadius,
+ boardLength + 2 * shadowRadius);
+ g.setStroke(new BasicStroke(1));
+
+ } else {
+ JSONArray boardColor = uiConfig.getJSONArray("board-color");
+ 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);
}
-
- /**
- * 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);
-
- // if no shadow graphics is supplied, just draw onto the same graphics
- if (gShadow == null)
- gShadow = g;
-
- switch (color) {
- case BLACK:
- case BLACK_CAPTURED:
- if (uiConfig.getBoolean("fancy-stones")) {
- drawShadow(gShadow, centerX, centerY, false);
- Image stone = theme.getBlackStone(new int[]{x, y});
- g.drawImage(stone, centerX - stoneRadius, centerY - stoneRadius, stoneRadius * 2 + 1, stoneRadius * 2 + 1, null);
- } else {
- drawShadow(gShadow, centerX, centerY, true);
- g.setColor(Color.BLACK);
- fillCircle(g, centerX, centerY, stoneRadius);
- }
- break;
-
- case WHITE:
- case WHITE_CAPTURED:
- if (uiConfig.getBoolean("fancy-stones")) {
- drawShadow(gShadow, centerX, centerY, false);
- Image stone = theme.getWhiteStone(new int[]{x, y});
- g.drawImage(stone, centerX - stoneRadius, centerY - stoneRadius, stoneRadius * 2 + 1, stoneRadius * 2 + 1, null);
- } else {
- drawShadow(gShadow, centerX, centerY, true);
- g.setColor(Color.WHITE);
- fillCircle(g, centerX, centerY, stoneRadius);
- g.setColor(Color.BLACK);
- drawCircle(g, centerX, centerY, stoneRadius);
- }
- break;
-
- case BLACK_GHOST:
- if (uiConfig.getBoolean("fancy-stones")) {
- drawShadow(gShadow, centerX, centerY, true);
- Image stone = theme.getBlackStone(new int[]{x, y});
- g.drawImage(stone, centerX - stoneRadius, centerY - stoneRadius, stoneRadius * 2 + 1, stoneRadius * 2 + 1, null);
- } else {
- drawShadow(gShadow, centerX, centerY, true);
- g.setColor(new Color(0, 0, 0));//, uiConfig.getInt("branch-stone-alpha")));
- fillCircle(g, centerX, centerY, stoneRadius);
- }
- break;
-
- case WHITE_GHOST:
- if (uiConfig.getBoolean("fancy-stones")) {
- drawShadow(gShadow, centerX, centerY, true);
- Image stone = theme.getWhiteStone(new int[]{x, y});
- g.drawImage(stone, centerX - stoneRadius, centerY - stoneRadius, stoneRadius * 2 + 1, stoneRadius * 2 + 1, null);
- } else {
- drawShadow(gShadow, centerX, centerY, true);
- g.setColor(new Color(255, 255, 255));//, uiConfig.getInt("branch-stone-alpha")));
- fillCircle(g, centerX, centerY, stoneRadius);
- g.setColor(new Color(0, 0, 0));//, uiConfig.getInt("branch-stone-alpha")));
- drawCircle(g, centerX, centerY, stoneRadius);
- }
- break;
-
- default:
- }
- }
-
- /**
- * Fills in a circle centered at (centerX, centerY) with radius $radius$
- */
- private void fillCircle(Graphics2D g, int centerX, int centerY, int radius) {
- g.fillOval(centerX - radius, centerY - radius, 2 * radius + 1, 2 * radius + 1);
- }
-
- /**
- * Draws the outline of a circle centered at (centerX, centerY) with radius $radius$
- */
- private void drawCircle(Graphics2D g, int centerX, int centerY, int radius) {
- g.drawOval(centerX - radius, centerY - radius, 2 * radius + 1, 2 * radius + 1);
- }
-
- /**
- * Draws a string centered at (x, y) of font $fontString$, whose contents are $string$.
- * The maximum/default fontsize will be $maximumFontHeight$, and the length of the drawn string will be at most maximumFontWidth.
- * The resulting actual size depends on the length of $string$.
- * aboveOrBelow is a param that lets you set:
- * aboveOrBelow = -1 -> y is the top of the string
- * aboveOrBelow = 0 -> y is the vertical center of the string
- * aboveOrBelow = 1 -> y is the bottom of the string
- */
- private void drawString(Graphics2D g, int x, int y, Font fontBase, int style, String string, float maximumFontHeight, double maximumFontWidth, int aboveOrBelow) {
-
- 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()));
- g.setFont(font);
-
- FontMetrics metrics = g.getFontMetrics(font);
-
- int height = metrics.getAscent() - metrics.getDescent();
- int verticalOffset;
- switch (aboveOrBelow) {
- case -1:
- verticalOffset = height / 2;
- break;
-
- case 1:
- verticalOffset = -height / 2;
- break;
-
- default:
- 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);
+ }
+
+ /**
+ * Calculates the lengths and pixel margins from a given boardLength.
+ *
+ * @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) {
+ // boardLength -= boardLength*MARGIN/3; // account for the shadows we will draw around the edge
+ // of the board
+ // if (boardLength < Board.BOARD_SIZE - 1)
+ // throw new IllegalArgumentException("boardLength may not be less than " +
+ // (Board.BOARD_SIZE - 1) + ", but was " + boardLength);
+
+ int scaledMargin;
+ int availableLength;
+
+ // decrease boardLength until the availableLength will result in square board intersections
+ double margin = showCoordinates() ? MARGIN_WITH_COORDINATES : MARGIN;
+ boardLength++;
+ do {
+ boardLength--;
+ scaledMargin = (int) (margin * boardLength);
+ availableLength = boardLength - 2 * scaledMargin;
+ } while (!((availableLength - 1) % (Board.BOARD_SIZE - 1) == 0));
+ // this will be true if BOARD_SIZE - 1 square intersections, plus one line, will fit
+
+ return new int[] {boardLength, scaledMargin, availableLength};
+ }
+
+ private void drawShadow(Graphics2D g, int centerX, int centerY, boolean isGhost) {
+ drawShadow(g, centerX, centerY, isGhost, 1);
+ }
+
+ private void drawShadow(
+ Graphics2D g, int centerX, int centerY, boolean isGhost, float shadowStrength) {
+ if (!uiConfig.getBoolean("shadows-enabled")) return;
+
+ final int shadowSize = (int) (stoneRadius * 0.3 * uiConfig.getInt("shadow-size") / 100);
+ final int fartherShadowSize = (int) (stoneRadius * 0.17 * uiConfig.getInt("shadow-size") / 100);
+
+ final Paint TOP_GRADIENT_PAINT;
+ final Paint LOWER_RIGHT_GRADIENT_PAINT;
+
+ if (isGhost) {
+ TOP_GRADIENT_PAINT =
+ new RadialGradientPaint(
+ new Point2D.Float(centerX, centerY),
+ stoneRadius + shadowSize,
+ new float[] {
+ ((float) stoneRadius / (stoneRadius + shadowSize)) - 0.0001f,
+ ((float) stoneRadius / (stoneRadius + shadowSize)),
+ 1.0f
+ },
+ new Color[] {
+ new Color(0, 0, 0, 0),
+ new Color(50, 50, 50, (int) (120 * shadowStrength)),
+ new Color(0, 0, 0, 0)
+ });
+
+ LOWER_RIGHT_GRADIENT_PAINT =
+ new RadialGradientPaint(
+ new Point2D.Float(centerX + shadowSize * 2 / 3, centerY + shadowSize * 2 / 3),
+ stoneRadius + fartherShadowSize,
+ new float[] {0.6f, 1.0f},
+ new Color[] {new Color(0, 0, 0, 180), new Color(0, 0, 0, 0)});
+ } else {
+ TOP_GRADIENT_PAINT =
+ new RadialGradientPaint(
+ new Point2D.Float(centerX, centerY),
+ stoneRadius + shadowSize,
+ new float[] {0.3f, 1.0f},
+ new Color[] {new Color(50, 50, 50, 150), new Color(0, 0, 0, 0)});
+ LOWER_RIGHT_GRADIENT_PAINT =
+ new RadialGradientPaint(
+ new Point2D.Float(centerX + shadowSize, centerY + shadowSize),
+ stoneRadius + fartherShadowSize,
+ new float[] {0.6f, 1.0f},
+ new Color[] {new Color(0, 0, 0, 140), new Color(0, 0, 0, 0)});
}
- private void drawString(Graphics2D g, int x, int y, Font fontBase, String string, float maximumFontHeight, double maximumFontWidth) {
- drawString(g, x, y, fontBase, Font.PLAIN, string, maximumFontHeight, maximumFontWidth, 0);
- }
+ final Paint originalPaint = g.getPaint();
- /**
- * @return a font with kerning enabled
- */
- private Font makeFont(Font fontBase, int style) {
- Font font = fontBase.deriveFont(style, 100);
- Map atts = new HashMap<>();
- atts.put(TextAttribute.KERNING, TextAttribute.KERNING_ON);
- return font.deriveFont(atts);
+ g.setPaint(TOP_GRADIENT_PAINT);
+ fillCircle(g, centerX, centerY, stoneRadius + shadowSize);
+ if (!isGhost) {
+ g.setPaint(LOWER_RIGHT_GRADIENT_PAINT);
+ fillCircle(g, centerX + shadowSize, centerY + shadowSize, stoneRadius + fartherShadowSize);
}
-
-
- /**
- * @return a shorter, rounded string version of playouts. e.g. 345 -> 345, 1265 -> 1.3k, 44556 -> 45k, 133523 -> 134k, 1234567 -> 1.2m
- */
- 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";
- } else if (playouts >= 10_000) {
- double playoutsDouble = (double) playouts / 1_000; // 13265 -> 13.265
- return Math.round(playoutsDouble) + "k";
- } else if (playouts >= 1_000) {
- double playoutsDouble = (double) playouts / 100; // 1265 -> 12.65
- return Math.round(playoutsDouble) / 10.0 + "k";
- } else {
- return String.valueOf(playouts);
+ g.setPaint(originalPaint);
+ }
+
+ /** 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(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;
+
+ if (color.isBlack() || color.isWhite()) {
+ boolean isBlack = color.isBlack();
+ boolean isGhost = (color == Stone.BLACK_GHOST || color == Stone.WHITE_GHOST);
+ if (uiConfig.getBoolean("fancy-stones")) {
+ drawShadow(gShadow, centerX, centerY, isGhost);
+ int size = stoneRadius * 2 + 1;
+ g.drawImage(
+ getScaleStone(isBlack, size),
+ centerX - stoneRadius,
+ centerY - stoneRadius,
+ size,
+ size,
+ null);
+ } else {
+ drawShadow(gShadow, centerX, centerY, true);
+ 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(blackColor);
+ drawCircle(g, centerX, centerY, stoneRadius);
}
+ }
}
-
-
- private int[] calculatePixelMargins() {
- return calculatePixelMargins(boardLength);
- }
-
- /**
- * Set the location to render the board
- *
- * @param x x coordinate
- * @param y y coordinate
- */
- public void setLocation(int x, int y) {
- this.x = x;
- this.y = y;
- }
-
- public Point getLocation() {
- return new Point(x, y);
- }
-
- /**
- * Set the maximum boardLength to render the board
- *
- * @param boardLength the boardLength of the board
- */
- public void setBoardLength(int boardLength) {
- this.boardLength = boardLength;
- }
-
- /**
- * @return the actual board length, including the shadows drawn at the edge of the wooden board
- */
- public int getActualBoardLength() {
- return (int) (boardLength * (1 + MARGIN / 3));
- }
-
- /**
- * Converts a location on the screen to a location on the board
- *
- * @param x x pixel coordinate
- * @param y y pixel coordinate
- * @return if there is a valid coordinate, an array (x, y) where x and y are between 0 and BOARD_SIZE - 1. Otherwise, returns null
- */
- public int[] convertScreenToCoordinates(int x, int y) {
- int marginLength; // the pixel width of the margins
- int boardLengthWithoutMargins; // the pixel width of the game board without margins
-
- // calculate a good set of boardLength, scaledMargin, and boardLengthWithoutMargins to use
- int[] calculatedPixelMargins = calculatePixelMargins();
- setBoardLength(calculatedPixelMargins[0]);
- marginLength = calculatedPixelMargins[1];
- boardLengthWithoutMargins = calculatedPixelMargins[2];
-
- int squareSize = calculateSquareLength(boardLengthWithoutMargins);
-
- // transform the pixel coordinates to board coordinates
- x = (x - this.x - marginLength + squareSize / 2) / squareSize;
- y = (y - this.y - marginLength + squareSize / 2) / squareSize;
-
- // return these values if they are valid board coordinates
- if (Board.isValid(x, y))
- return new int[]{x, y};
- else
- return null;
+ }
+
+ /** Get scaled stone, if cached then return cached */
+ private BufferedImage getScaleStone(boolean isBlack, int size) {
+ BufferedImage stone = isBlack ? cachedBlackStoneImage : cachedWhiteStoneImage;
+ 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(size, size, java.awt.Image.SCALE_SMOOTH), 0, 0, null);
+ g2.dispose();
+ if (isBlack) {
+ cachedBlackStoneImage = stone;
+ } else {
+ cachedWhiteStoneImage = stone;
+ }
}
-
- /**
- * Calculate the boardLength of each intersection square
- *
- * @param availableLength the pixel board length of the game board without margins
- * @return the board length of each intersection square
- */
- private int calculateSquareLength(int availableLength) {
- return availableLength / (Board.BOARD_SIZE - 1);
+ 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();
+ }
}
-
- private boolean isShowingRawBoard() {
- return (displayedBranchLength == SHOW_RAW_BOARD || displayedBranchLength == 0);
+ 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
+ * is poor. Recommended for use in a few drawings
+ */
+ // public void drawScaleSmoothImage(Graphics2D g, BufferedImage img, int x, int y, int width,
+ // int height, ImageObserver observer) {
+ // 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);
+ // g2.dispose();
+ // g.drawImage(newstone, x, y, width, height, observer);
+ // }
+
+ /**
+ * Draw scale smooth image, enhanced display quality (Not use, for future) This functions use a
+ * multi-step approach to prevent the information loss and produces a much higher quality that is
+ * close to the Image.getScaledInstance() and faster than Image.getScaledInstance() method.
+ */
+ // public void drawScaleImage(Graphics2D g, BufferedImage img, int x, int y, int width, int
+ // height, ImageObserver observer) {
+ // BufferedImage newstone = (BufferedImage)img;
+ // int w = img.getWidth();
+ // int h = img.getHeight();
+ // do {
+ // if (w > width) {
+ // w /= 2;
+ // if (w < width) {
+ // w = width;
+ // }
+ // }
+ // if (h > height) {
+ // h /= 2;
+ // if (h < height) {
+ // h = height;
+ // }
+ // }
+ // BufferedImage tmp = new BufferedImage(w, h, TYPE_INT_ARGB);
+ // Graphics2D g2 = tmp.createGraphics();
+ // g2.setRenderingHint(KEY_INTERPOLATION,
+ // VALUE_INTERPOLATION_BICUBIC);
+ // g2.drawImage(newstone, 0, 0, w, h, null);
+ // g2.dispose();
+ // newstone = tmp;
+ // }
+ // while (w != width || h != height);
+ // g.drawImage(newstone, x, y, width, height, observer);
+ // }
+
+ /** 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));
+ }
+
+ /** Fills in a circle centered at (centerX, centerY) with radius $radius$ */
+ private void fillCircle(Graphics2D g, int centerX, int centerY, int radius) {
+ g.fillOval(centerX - radius, centerY - radius, 2 * radius + 1, 2 * radius + 1);
+ }
+
+ /** Draws the outline of a circle centered at (centerX, centerY) with radius $radius$ */
+ private void drawCircle(Graphics2D g, int centerX, int centerY, int radius) {
+ g.drawOval(centerX - radius, centerY - radius, 2 * radius + 1, 2 * radius + 1);
+ }
+
+ /**
+ * Draws a string centered at (x, y) of font $fontString$, whose contents are $string$. The
+ * maximum/default fontsize will be $maximumFontHeight$, and the length of the drawn string will
+ * be at most maximumFontWidth. The resulting actual size depends on the length of $string$.
+ * aboveOrBelow is a param that lets you set: aboveOrBelow = -1 -> y is the top of the string
+ * aboveOrBelow = 0 -> y is the vertical center of the string aboveOrBelow = 1 -> y is the bottom
+ * of the string
+ */
+ private void drawString(
+ Graphics2D g,
+ int x,
+ int y,
+ Font fontBase,
+ int style,
+ String string,
+ float maximumFontHeight,
+ double maximumFontWidth,
+ int aboveOrBelow) {
+
+ Font font = makeFont(fontBase, style);
+
+ // set maximum size of font
+ 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);
+
+ int height = fm.getAscent() - fm.getDescent();
+ int verticalOffset;
+ if (aboveOrBelow == -1) {
+ verticalOffset = height / 2;
+ } else if (aboveOrBelow == 1) {
+ verticalOffset = -height / 2;
+ } else {
+ verticalOffset = 0;
}
- private int maxBranchMoves() {
- switch (displayedBranchLength) {
- case SHOW_NORMAL_BOARD:
- return Integer.MAX_VALUE;
- case SHOW_RAW_BOARD:
- return -1;
- default:
- return displayedBranchLength;
- }
+ // bounding box for debugging
+ // g.drawRect(x-(int)maximumFontWidth/2, y - height/2 + verticalOffset, (int)maximumFontWidth,
+ // height+verticalOffset );
+ g.drawString(string, x - fm.stringWidth(string) / 2, y + height / 2 + verticalOffset);
+ }
+
+ private void drawString(
+ Graphics2D g,
+ int x,
+ int y,
+ Font fontBase,
+ String string,
+ float maximumFontHeight,
+ double maximumFontWidth) {
+ drawString(g, x, y, fontBase, Font.PLAIN, string, maximumFontHeight, maximumFontWidth, 0);
+ }
+
+ /** @return a font with kerning enabled */
+ private Font makeFont(Font fontBase, int style) {
+ Font font = fontBase.deriveFont(style, 100);
+ Map atts = new HashMap<>();
+ atts.put(TextAttribute.KERNING, TextAttribute.KERNING_ON);
+ return font.deriveFont(atts);
+ }
+
+ /**
+ * @return a shorter, rounded string version of playouts. e.g. 345 -> 345, 1265 -> 1.3k, 44556 ->
+ * 45k, 133523 -> 134k, 1234567 -> 1.2m
+ */
+ private 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);
}
-
- public boolean isShowingBranch() {
- return showingBranch;
+ }
+
+ private int[] calculatePixelMargins() {
+ return calculatePixelMargins(boardLength);
+ }
+
+ /**
+ * Set the location to render the board
+ *
+ * @param x x coordinate
+ * @param y y coordinate
+ */
+ public void setLocation(int x, int y) {
+ this.x = x;
+ this.y = y;
+ }
+
+ public Point getLocation() {
+ return new Point(x, y);
+ }
+
+ /**
+ * Set the maximum boardLength to render the board
+ *
+ * @param boardLength the boardLength of the board
+ */
+ public void setBoardLength(int boardLength) {
+ this.boardLength = boardLength;
+ }
+
+ /**
+ * @return the actual board length, including the shadows drawn at the edge of the wooden board
+ */
+ public int getActualBoardLength() {
+ return (int) (boardLength * (1 + MARGIN / 3));
+ }
+
+ /**
+ * Converts a location on the screen to a location on the board
+ *
+ * @param x x pixel coordinate
+ * @param y y pixel coordinate
+ * @return if there is a valid coordinate, an array (x, y) where x and y are between 0 and
+ * BOARD_SIZE - 1. Otherwise, returns null
+ */
+ public int[] convertScreenToCoordinates(int x, int y) {
+ int marginLength; // the pixel width of the margins
+ int boardLengthWithoutMargins; // the pixel width of the game board without margins
+
+ // calculate a good set of boardLength, scaledMargin, and boardLengthWithoutMargins to use
+ int[] calculatedPixelMargins = calculatePixelMargins();
+ setBoardLength(calculatedPixelMargins[0]);
+ marginLength = calculatedPixelMargins[1];
+ boardLengthWithoutMargins = calculatedPixelMargins[2];
+
+ int squareSize = calculateSquareLength(boardLengthWithoutMargins);
+
+ // transform the pixel coordinates to board coordinates
+ x = (x - this.x - marginLength + squareSize / 2) / squareSize;
+ y = (y - this.y - marginLength + squareSize / 2) / squareSize;
+
+ // return these values if they are valid board coordinates
+ if (Board.isValid(x, y)) return new int[] {x, y};
+ else return null;
+ }
+
+ /**
+ * Calculate the boardLength of each intersection square
+ *
+ * @param availableLength the pixel board length of the game board without margins
+ * @return the board length of each intersection square
+ */
+ private int calculateSquareLength(int availableLength) {
+ return availableLength / (Board.BOARD_SIZE - 1);
+ }
+
+ private boolean isShowingRawBoard() {
+ return (displayedBranchLength == SHOW_RAW_BOARD || displayedBranchLength == 0);
+ }
+
+ private int maxBranchMoves() {
+ switch (displayedBranchLength) {
+ case SHOW_NORMAL_BOARD:
+ return Integer.MAX_VALUE;
+ case SHOW_RAW_BOARD:
+ return -1;
+ default:
+ return displayedBranchLength;
}
-
- public void setDisplayedBranchLength(int n) {
- displayedBranchLength = n;
+ }
+
+ public boolean isShowingBranch() {
+ return showingBranch;
+ }
+
+ public void setDisplayedBranchLength(int n) {
+ displayedBranchLength = n;
+ }
+
+ public boolean incrementDisplayedBranchLength(int n) {
+ switch (displayedBranchLength) {
+ case SHOW_NORMAL_BOARD:
+ case SHOW_RAW_BOARD:
+ return false;
+ default:
+ // force nonnegative
+ displayedBranchLength = max(0, displayedBranchLength + n);
+ return true;
}
+ }
- public boolean incrementDisplayedBranchLength(int n) {
- switch (displayedBranchLength) {
- case SHOW_NORMAL_BOARD:
- case SHOW_RAW_BOARD:
- return false;
- default:
- // force nonnegative
- displayedBranchLength = Math.max(0, displayedBranchLength + n);
- return true;
- }
- }
+ public boolean isInside(int x1, int y1) {
+ return x <= x1 && x1 < x + boardLength && y <= y1 && y1 < y + boardLength;
+ }
- public boolean isInside(int x1, int y1) {
- return (x <= x1 && x1 < x + boardLength && y <= y1 && y1 < y + boardLength);
- }
+ private boolean showCoordinates() {
+ return isMainBoard && Lizzie.frame.showCoordinates;
+ }
- private boolean showCoordinates() {
- return isMainBoard && Lizzie.frame.showCoordinates;
- }
+ public void increaseMaxAlpha(int k) {
+ maxAlpha = min(maxAlpha + k, 255);
+ uiPersist.put("max-alpha", maxAlpha);
+ }
}
diff --git a/src/main/java/featurecat/lizzie/gui/GameInfoDialog.java b/src/main/java/featurecat/lizzie/gui/GameInfoDialog.java
index 3c5c1a655..871a4cfa5 100644
--- a/src/main/java/featurecat/lizzie/gui/GameInfoDialog.java
+++ b/src/main/java/featurecat/lizzie/gui/GameInfoDialog.java
@@ -5,127 +5,135 @@
package featurecat.lizzie.gui;
import featurecat.lizzie.analysis.GameInfo;
-
import java.awt.*;
import java.text.DecimalFormat;
import javax.swing.*;
import javax.swing.border.*;
-/**
- * @author unknown
- */
+/** @author unknown */
public class GameInfoDialog extends JDialog {
- // create formatters
- public static final DecimalFormat FORMAT_KOMI = new DecimalFormat("#0.0");
- public static final DecimalFormat FORMAT_HANDICAP = new DecimalFormat("0");
-
- static {
- FORMAT_HANDICAP.setMaximumIntegerDigits(1);
- }
-
- private JPanel dialogPane = new JPanel();
- private JPanel contentPanel = new JPanel();
- private JPanel buttonBar = new JPanel();
- private JButton okButton = new JButton();
-
- private JTextField textFieldBlack;
- private JTextField textFieldWhite;
- private JTextField textFieldKomi;
- private JTextField textFieldHandicap;
-
- private GameInfo gameInfo;
-
- public GameInfoDialog() {
- initComponents();
- }
-
- private void initComponents() {
- setMinimumSize(new Dimension(100, 100));
- setResizable(false);
- setTitle("Game Info");
- setModal(true);
-
- Container contentPane = getContentPane();
- contentPane.setLayout(new BorderLayout());
-
- initDialogPane(contentPane);
-
- pack();
- setLocationRelativeTo(getOwner());
- }
-
- private void initDialogPane(Container contentPane) {
- dialogPane.setBorder(new EmptyBorder(12, 12, 12, 12));
- dialogPane.setLayout(new BorderLayout());
-
- initContentPanel();
- initButtonBar();
-
- contentPane.add(dialogPane, BorderLayout.CENTER);
- }
-
- private void initContentPanel() {
- GridLayout gridLayout = new GridLayout(4, 2, 4, 4);
- contentPanel.setLayout(gridLayout);
-
- // editable
- textFieldWhite = new JTextField();
- textFieldBlack = new JTextField();
-
- // read-only
- textFieldKomi = new JFormattedTextField(FORMAT_KOMI);
- textFieldHandicap = new JFormattedTextField(FORMAT_HANDICAP);
- textFieldKomi.setEditable(false);
- textFieldHandicap.setEditable(false);
-
- contentPanel.add(new JLabel("Black"));
- contentPanel.add(textFieldBlack);
- contentPanel.add(new JLabel("White"));
- contentPanel.add(textFieldWhite);
- contentPanel.add(new JLabel("Komi"));
- contentPanel.add(textFieldKomi);
- contentPanel.add(new JLabel("Handicap"));
- contentPanel.add(textFieldHandicap);
-
- dialogPane.add(contentPanel, BorderLayout.CENTER);
- }
-
- private void initButtonBar() {
- buttonBar.setBorder(new EmptyBorder(12, 0, 0, 0));
- buttonBar.setLayout(new GridBagLayout());
- ((GridBagLayout) buttonBar.getLayout()).columnWidths = new int[]{0, 80};
- ((GridBagLayout) buttonBar.getLayout()).columnWeights = new double[]{1.0, 0.0};
-
- //---- okButton ----
- okButton.setText("OK");
- okButton.addActionListener(e -> apply());
-
- 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));
-
- dialogPane.add(buttonBar, BorderLayout.SOUTH);
- }
-
- public void setGameInfo(GameInfo gameInfo) {
- this.gameInfo = gameInfo;
-
- textFieldBlack.setText(gameInfo.getPlayerBlack());
- textFieldWhite.setText(gameInfo.getPlayerWhite());
- textFieldHandicap.setText(FORMAT_HANDICAP.format(gameInfo.getHandicap()));
- textFieldKomi.setText(FORMAT_KOMI.format(gameInfo.getKomi()));
- }
-
- public void apply() {
- // validate data
- String playerBlack = textFieldBlack.getText();
- String playerWhite = textFieldWhite.getText();
-
- // apply new values
- gameInfo.setPlayerBlack(playerBlack);
- gameInfo.setPlayerWhite(playerWhite);
-
- // close window
- setVisible(false);
- }
+ // create formatters
+ public static final DecimalFormat FORMAT_KOMI = new DecimalFormat("#0.0");
+ public static final DecimalFormat FORMAT_HANDICAP = new DecimalFormat("0");
+
+ static {
+ FORMAT_HANDICAP.setMaximumIntegerDigits(1);
+ }
+
+ private JPanel dialogPane = new JPanel();
+ private JPanel contentPanel = new JPanel();
+ private JPanel buttonBar = new JPanel();
+ private JButton okButton = new JButton();
+
+ private JTextField textFieldBlack;
+ private JTextField textFieldWhite;
+ private JTextField textFieldKomi;
+ private JTextField textFieldHandicap;
+
+ private GameInfo gameInfo;
+
+ public GameInfoDialog() {
+ initComponents();
+ }
+
+ private void initComponents() {
+ setMinimumSize(new Dimension(100, 100));
+ setResizable(false);
+ setTitle("Game Info");
+ setModal(true);
+
+ Container contentPane = getContentPane();
+ contentPane.setLayout(new BorderLayout());
+
+ initDialogPane(contentPane);
+
+ pack();
+ setLocationRelativeTo(getOwner());
+ }
+
+ private void initDialogPane(Container contentPane) {
+ dialogPane.setBorder(new EmptyBorder(12, 12, 12, 12));
+ dialogPane.setLayout(new BorderLayout());
+
+ initContentPanel();
+ initButtonBar();
+
+ contentPane.add(dialogPane, BorderLayout.CENTER);
+ }
+
+ private void initContentPanel() {
+ GridLayout gridLayout = new GridLayout(4, 2, 4, 4);
+ contentPanel.setLayout(gridLayout);
+
+ // editable
+ textFieldWhite = new JTextField();
+ textFieldBlack = new JTextField();
+
+ // read-only
+ textFieldKomi = new JFormattedTextField(FORMAT_KOMI);
+ textFieldHandicap = new JFormattedTextField(FORMAT_HANDICAP);
+ textFieldKomi.setEditable(false);
+ textFieldHandicap.setEditable(false);
+
+ contentPanel.add(new JLabel("Black"));
+ contentPanel.add(textFieldBlack);
+ contentPanel.add(new JLabel("White"));
+ contentPanel.add(textFieldWhite);
+ contentPanel.add(new JLabel("Komi"));
+ contentPanel.add(textFieldKomi);
+ contentPanel.add(new JLabel("Handicap"));
+ contentPanel.add(textFieldHandicap);
+
+ dialogPane.add(contentPanel, BorderLayout.CENTER);
+ }
+
+ private void initButtonBar() {
+ buttonBar.setBorder(new EmptyBorder(12, 0, 0, 0));
+ buttonBar.setLayout(new GridBagLayout());
+ ((GridBagLayout) buttonBar.getLayout()).columnWidths = new int[] {0, 80};
+ ((GridBagLayout) buttonBar.getLayout()).columnWeights = new double[] {1.0, 0.0};
+
+ // ---- okButton ----
+ okButton.setText("OK");
+ okButton.addActionListener(e -> apply());
+
+ 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));
+
+ dialogPane.add(buttonBar, BorderLayout.SOUTH);
+ }
+
+ public void setGameInfo(GameInfo gameInfo) {
+ this.gameInfo = gameInfo;
+
+ textFieldBlack.setText(gameInfo.getPlayerBlack());
+ textFieldWhite.setText(gameInfo.getPlayerWhite());
+ textFieldHandicap.setText(FORMAT_HANDICAP.format(gameInfo.getHandicap()));
+ textFieldKomi.setText(FORMAT_KOMI.format(gameInfo.getKomi()));
+ }
+
+ public void apply() {
+ // validate data
+ String playerBlack = textFieldBlack.getText();
+ String playerWhite = textFieldWhite.getText();
+
+ // apply new values
+ gameInfo.setPlayerBlack(playerBlack);
+ gameInfo.setPlayerWhite(playerWhite);
+
+ // close window
+ setVisible(false);
+ }
}
diff --git a/src/main/java/featurecat/lizzie/gui/Input.java b/src/main/java/featurecat/lizzie/gui/Input.java
index 044146db5..fc36096ad 100644
--- a/src/main/java/featurecat/lizzie/gui/Input.java
+++ b/src/main/java/featurecat/lizzie/gui/Input.java
@@ -1,395 +1,402 @@
package featurecat.lizzie.gui;
-import featurecat.lizzie.Lizzie;
-
-import java.awt.event.*;
-
import static java.awt.event.KeyEvent.*;
-import featurecat.lizzie.plugin.PluginManager;
+import featurecat.lizzie.Lizzie;
+import java.awt.event.*;
import javax.swing.*;
public class Input implements MouseListener, KeyListener, MouseWheelListener, MouseMotionListener {
- @Override
- public void mouseClicked(MouseEvent e) {
-
+ @Override
+ 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
+ undo();
+ }
+
+ @Override
+ public void mouseReleased(MouseEvent e) {}
+
+ @Override
+ public void mouseEntered(MouseEvent e) {}
+
+ @Override
+ public void mouseExited(MouseEvent e) {}
+
+ @Override
+ public void mouseDragged(MouseEvent e) {
+ Lizzie.frame.onMouseDragged(e.getX(), e.getY());
+ }
+
+ @Override
+ public void mouseMoved(MouseEvent e) {
+ Lizzie.frame.onMouseMoved(e.getX(), e.getY());
+ }
+
+ @Override
+ public void keyTyped(KeyEvent e) {}
+
+ private void undo() {
+ undo(1);
+ }
+
+ private void undo(int movesToAdvance) {
+ if (Lizzie.board.inAnalysisMode()) Lizzie.board.toggleAnalysis();
+ if (Lizzie.frame.isPlayingAgainstLeelaz) {
+ Lizzie.frame.isPlayingAgainstLeelaz = false;
}
-
- @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
- undo();
- }
-
- @Override
- public void mouseReleased(MouseEvent e) {
- PluginManager.onMouseReleased(e);
- }
-
- @Override
- public void mouseEntered(MouseEvent e) {
-
+ if (Lizzie.frame.incrementDisplayedBranchLength(-movesToAdvance)) {
+ return;
}
- @Override
- public void mouseExited(MouseEvent e) {
-
+ for (int i = 0; i < movesToAdvance; i++) Lizzie.board.previousMove();
+ }
+
+ private void undoToChildOfPreviousWithVariation() {
+ // Undo until the position just after the junction position.
+ // If we are already on such a position, we go to
+ // the junction position for convenience.
+ // Use cases:
+ // [Delete branch] Call this function and then deleteMove.
+ // [Go to junction] Call this function twice.
+ if (!Lizzie.board.undoToChildOfPreviousWithVariation()) Lizzie.board.previousMove();
+ }
+
+ private void redo() {
+ redo(1);
+ }
+
+ private void redo(int movesToAdvance) {
+ if (Lizzie.board.inAnalysisMode()) Lizzie.board.toggleAnalysis();
+ if (Lizzie.frame.isPlayingAgainstLeelaz) {
+ Lizzie.frame.isPlayingAgainstLeelaz = false;
}
-
- @Override
- public void mouseDragged(MouseEvent e) {
- int x = e.getX();
- int y = e.getY();
-
- Lizzie.frame.onMouseDragged(x, y);
+ if (Lizzie.frame.incrementDisplayedBranchLength(movesToAdvance)) {
+ return;
}
- @Override
- public void mouseMoved(MouseEvent e) {
- PluginManager.onMouseMoved(e);
- int x = e.getX();
- int y = e.getY();
+ for (int i = 0; i < movesToAdvance; i++) Lizzie.board.nextMove();
+ }
- Lizzie.frame.onMouseMoved(x, y);
+ private void startRawBoard() {
+ if (!Lizzie.config.showRawBoard) {
+ Lizzie.frame.startRawBoard();
}
-
- @Override
- public void keyTyped(KeyEvent e) {
-
+ Lizzie.config.showRawBoard = true;
+ }
+
+ private void stopRawBoard() {
+ Lizzie.frame.stopRawBoard();
+ Lizzie.config.showRawBoard = false;
+ }
+
+ private void toggleHints() {
+ Lizzie.config.toggleShowBranch();
+ Lizzie.config.showSubBoard =
+ Lizzie.config.showNextMoves = Lizzie.config.showBestMoves = Lizzie.config.showBranch;
+ }
+
+ private void nextBranch() {
+ if (Lizzie.frame.isPlayingAgainstLeelaz) {
+ Lizzie.frame.isPlayingAgainstLeelaz = false;
}
+ Lizzie.board.nextBranch();
+ }
- private void undo() {
- undo(1);
+ private void previousBranch() {
+ if (Lizzie.frame.isPlayingAgainstLeelaz) {
+ Lizzie.frame.isPlayingAgainstLeelaz = false;
}
+ Lizzie.board.previousBranch();
+ }
+
+ private void moveBranchUp() {
+ Lizzie.board.moveBranchUp();
+ }
+
+ private void moveBranchDown() {
+ Lizzie.board.moveBranchDown();
+ }
+
+ private void deleteMove() {
+ Lizzie.board.deleteMove();
+ }
+
+ private void deleteBranch() {
+ Lizzie.board.deleteBranch();
+ }
+
+ private boolean controlIsPressed(KeyEvent e) {
+ boolean mac = System.getProperty("os.name", "").toUpperCase().startsWith("MAC");
+ return e.isControlDown() || (mac && e.isMetaDown());
+ }
+
+ private void toggleShowDynamicKomi() {
+ Lizzie.config.showDynamicKomi = !Lizzie.config.showDynamicKomi;
+ }
+
+ @Override
+ 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;
+
+ switch (e.getKeyCode()) {
+ case VK_RIGHT:
+ if (e.isShiftDown()) {
+ moveBranchDown();
+ } else {
+ nextBranch();
+ }
+ break;
- private void undo(int movesToAdvance) {
- if (Lizzie.board.inAnalysisMode())
- Lizzie.board.toggleAnalysis();
- if (Lizzie.frame.isPlayingAgainstLeelaz) {
- Lizzie.frame.isPlayingAgainstLeelaz = false;
+ case VK_LEFT:
+ if (e.isShiftDown()) {
+ moveBranchUp();
+ } else {
+ previousBranch();
}
- if (Lizzie.frame.incrementDisplayedBranchLength(- movesToAdvance)) {
- return;
+ break;
+
+ case VK_UP:
+ if (e.isShiftDown()) {
+ undoToChildOfPreviousWithVariation();
+ } else if (controlIsPressed(e)) {
+ undo(10);
+ } else {
+ undo();
}
+ break;
- for (int i = 0; i < movesToAdvance; i++)
- Lizzie.board.previousMove();
- }
-
- private void undoToChildOfPreviousWithVariation() {
- // Undo until the position just after the junction position.
- // If we are already on such a position, we go to
- // the junction position for convenience.
- // Use cases:
- // [Delete branch] Call this function and then deleteMove.
- // [Go to junction] Call this function twice.
- if (!Lizzie.board.undoToChildOfPreviousWithVariation())
- Lizzie.board.previousMove();
- }
-
- private void redo() {
- redo(1);
- }
+ case VK_PAGE_DOWN:
+ if (controlIsPressed(e) && e.isShiftDown()) {
+ Lizzie.frame.increaseMaxAlpha(-5);
+ } else {
+ redo(10);
+ }
+ break;
- private void redo(int movesToAdvance) {
- if (Lizzie.board.inAnalysisMode())
- Lizzie.board.toggleAnalysis();
+ case VK_DOWN:
+ if (controlIsPressed(e)) {
+ redo(10);
+ } else {
+ redo();
+ }
+ break;
+
+ case VK_N:
+ // stop the ponder
+ if (Lizzie.leelaz.isPondering()) Lizzie.leelaz.togglePonder();
+ LizzieFrame.startNewGame();
+ break;
+ case VK_SPACE:
if (Lizzie.frame.isPlayingAgainstLeelaz) {
- Lizzie.frame.isPlayingAgainstLeelaz = false;
+ Lizzie.frame.isPlayingAgainstLeelaz = false;
+ Lizzie.leelaz.togglePonder(); // we must toggle twice for it to restart pondering
+ Lizzie.leelaz.isThinking = false;
}
- if (Lizzie.frame.incrementDisplayedBranchLength(movesToAdvance)) {
- return;
+ Lizzie.leelaz.togglePonder();
+ break;
+
+ case VK_P:
+ Lizzie.board.pass();
+ break;
+
+ case VK_COMMA:
+ if (!Lizzie.frame.playCurrentVariation()) Lizzie.frame.playBestMove();
+ break;
+
+ case VK_M:
+ Lizzie.config.toggleShowMoveNumber();
+ break;
+
+ case VK_F:
+ Lizzie.config.toggleShowNextMoves();
+ break;
+
+ case VK_H:
+ Lizzie.config.toggleHandicapInsteadOfWinrate();
+ break;
+
+ case VK_PAGE_UP:
+ if (controlIsPressed(e) && e.isShiftDown()) {
+ Lizzie.frame.increaseMaxAlpha(5);
+ } else {
+ undo(10);
}
-
- for (int i = 0; i < movesToAdvance; i++)
- Lizzie.board.nextMove();
- }
-
- private void startRawBoard() {
- if (!Lizzie.config.showRawBoard) {
- Lizzie.frame.startRawBoard();
+ break;
+
+ case VK_I:
+ // stop the ponder
+ if (Lizzie.leelaz.isPondering()) Lizzie.leelaz.togglePonder();
+ Lizzie.frame.editGameInfo();
+ break;
+ case VK_S:
+ // stop the ponder
+ if (Lizzie.leelaz.isPondering()) Lizzie.leelaz.togglePonder();
+ LizzieFrame.saveFile();
+ break;
+
+ case VK_O:
+ if (Lizzie.leelaz.isPondering()) Lizzie.leelaz.togglePonder();
+ LizzieFrame.openFile();
+ break;
+
+ case VK_V:
+ if (controlIsPressed(e)) {
+ Lizzie.frame.pasteSgf();
+ } else {
+ Lizzie.config.toggleShowBranch();
}
- Lizzie.config.showRawBoard = true;
- }
-
- private void stopRawBoard() {
- Lizzie.frame.stopRawBoard();
- Lizzie.config.showRawBoard = false;
- }
-
- private void toggleHints() {
- Lizzie.config.toggleShowBranch();
- Lizzie.config.showSubBoard = Lizzie.config.showNextMoves = Lizzie.config.showBestMoves
- = Lizzie.config.showBranch;
- }
-
- private void nextBranch() {
- if (Lizzie.frame.isPlayingAgainstLeelaz) {
- Lizzie.frame.isPlayingAgainstLeelaz = false;
+ break;
+
+ case VK_HOME:
+ while (Lizzie.board.previousMove()) ;
+ break;
+
+ case VK_END:
+ while (Lizzie.board.nextMove()) ;
+ break;
+
+ case VK_X:
+ if (!Lizzie.frame.showControls) {
+ if (Lizzie.leelaz.isPondering()) {
+ wasPonderingWhenControlsShown = true;
+ Lizzie.leelaz.togglePonder();
+ } else {
+ wasPonderingWhenControlsShown = false;
+ }
+ Lizzie.frame.drawControls();
}
- Lizzie.board.nextBranch();
- }
+ Lizzie.frame.showControls = true;
+ break;
+
+ case VK_W:
+ Lizzie.config.toggleShowWinrate();
+ break;
+
+ case VK_G:
+ Lizzie.config.toggleShowVariationGraph();
+ break;
+
+ case VK_T:
+ Lizzie.config.toggleShowComment();
+ break;
+
+ case VK_C:
+ if (controlIsPressed(e)) {
+ Lizzie.frame.copySgf();
+ } else {
+ Lizzie.frame.toggleCoordinates();
+ }
+ break;
+
+ case VK_ENTER:
+ if (!Lizzie.leelaz.isThinking) {
+ Lizzie.leelaz.sendCommand(
+ "time_settings 0 "
+ + Lizzie.config
+ .config
+ .getJSONObject("leelaz")
+ .getInt("max-game-thinking-time-seconds")
+ + " 1");
+ Lizzie.frame.playerIsBlack = !Lizzie.board.getData().blackToPlay;
+ Lizzie.frame.isPlayingAgainstLeelaz = true;
+ Lizzie.leelaz.genmove((Lizzie.board.getData().blackToPlay ? "B" : "W"));
+ }
+ break;
+
+ case VK_DELETE:
+ case VK_BACK_SPACE:
+ if (e.isShiftDown()) {
+ deleteBranch();
+ } else {
+ deleteMove();
+ }
+ break;
- private void previousBranch() {
- if (Lizzie.frame.isPlayingAgainstLeelaz) {
- Lizzie.frame.isPlayingAgainstLeelaz = false;
+ case VK_Z:
+ if (e.isShiftDown()) {
+ toggleHints();
+ } else {
+ startRawBoard();
}
- Lizzie.board.previousBranch();
- }
+ break;
- private void moveBranchUp() {
- Lizzie.board.moveBranchUp();
- }
+ case VK_A:
+ shouldDisableAnalysis = false;
+ Lizzie.board.toggleAnalysis();
+ break;
- private void moveBranchDown() {
- Lizzie.board.moveBranchDown();
+ case VK_PERIOD:
+ if (Lizzie.board.getHistory().getNext() == null) {
+ Lizzie.board.setScoreMode(!Lizzie.board.inScoreMode());
+ }
+ break;
+
+ case VK_D:
+ 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;
}
- private void deleteMove() { Lizzie.board.deleteMove(); }
-
- private void deleteBranch() { Lizzie.board.deleteBranch(); }
+ if (shouldDisableAnalysis && Lizzie.board.inAnalysisMode()) Lizzie.board.toggleAnalysis();
- private boolean controlIsPressed(KeyEvent e) {
- boolean mac = System.getProperty("os.name", "").toUpperCase().startsWith("MAC");
- return e.isControlDown() || (mac && e.isMetaDown());
- }
+ Lizzie.frame.repaint();
+ }
- @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;
-
- switch (e.getKeyCode()) {
- case VK_RIGHT:
- if (e.isShiftDown()) {
- moveBranchDown();
- } else {
- nextBranch();
- }
- break;
-
- case VK_LEFT:
- if (e.isShiftDown()) {
- moveBranchUp();
- } else {
- previousBranch();
- }
- break;
-
- case VK_UP:
- if (e.isShiftDown()) {
- undoToChildOfPreviousWithVariation();
- } else if (controlIsPressed(e)) {
- undo(10);
- } else {
- undo();
- }
- break;
-
- case VK_PAGE_DOWN:
- redo(10);
- break;
-
- case VK_DOWN:
- if (controlIsPressed(e)) {
- redo(10);
- } else {
- redo();
- }
- break;
-
- case VK_N:
- // stop the ponder
- if (Lizzie.leelaz.isPondering())
- Lizzie.leelaz.togglePonder();
- LizzieFrame.startNewGame();
- break;
- case VK_SPACE:
- 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();
- break;
-
- case VK_P:
- Lizzie.board.pass();
- break;
-
- case VK_COMMA:
- if (!Lizzie.frame.playCurrentVariation())
- Lizzie.frame.playBestMove();
- break;
-
- case VK_M:
- Lizzie.config.toggleShowMoveNumber();
- break;
-
- case VK_F:
- Lizzie.config.toggleShowNextMoves();
- break;
-
- case VK_H:
- Lizzie.config.toggleHandicapInsteadOfWinrate();
- break;
-
- case VK_PAGE_UP:
- undo(10);
- break;
-
- case VK_I:
- // stop the ponder
- if (Lizzie.leelaz.isPondering())
- Lizzie.leelaz.togglePonder();
- Lizzie.frame.editGameInfo();
- break;
- case VK_S:
- // stop the ponder
- if (Lizzie.leelaz.isPondering())
- Lizzie.leelaz.togglePonder();
- LizzieFrame.saveSgf();
- break;
-
- case VK_O:
- if (Lizzie.leelaz.isPondering())
- Lizzie.leelaz.togglePonder();
- LizzieFrame.openSgf();
- break;
-
- case VK_V:
- if (controlIsPressed(e)) {
- Lizzie.frame.pasteSgf();
- } else {
- Lizzie.config.toggleShowBranch();
- }
- break;
-
- case VK_HOME:
- while (Lizzie.board.previousMove()) ;
- break;
-
- case VK_END:
- while (Lizzie.board.nextMove()) ;
- break;
-
- case VK_X:
- if (!Lizzie.frame.showControls) {
- if (Lizzie.leelaz.isPondering()) {
- wasPonderingWhenControlsShown = true;
- Lizzie.leelaz.togglePonder();
- } else {
- wasPonderingWhenControlsShown = false;
- }
- Lizzie.frame.drawControls();
- }
- Lizzie.frame.showControls = true;
- break;
-
- case VK_W:
- Lizzie.config.toggleShowWinrate();
- break;
-
- case VK_G:
- Lizzie.config.toggleShowVariationGraph();
- break;
-
- case VK_C:
- if (controlIsPressed(e)) {
- Lizzie.frame.copySgf();
- } else {
- Lizzie.frame.toggleCoordinates();
- }
- break;
-
- case VK_ENTER:
- if (!Lizzie.leelaz.isThinking) {
- Lizzie.leelaz.sendCommand("time_settings 0 " + Lizzie.config.config.getJSONObject("leelaz").getInt("max-game-thinking-time-seconds") + " 1");
- Lizzie.frame.playerIsBlack = !Lizzie.board.getData().blackToPlay;
- Lizzie.frame.isPlayingAgainstLeelaz = true;
- Lizzie.leelaz.genmove((Lizzie.board.getData().blackToPlay ? "B" : "W"));
- }
- break;
-
- case VK_DELETE:
- case VK_BACK_SPACE:
- if (e.isShiftDown()) {
- deleteBranch();
- } else {
- deleteMove();
- }
- break;
-
- case VK_Z:
- if (e.isShiftDown()) {
- toggleHints();
- } else {
- startRawBoard();
- }
- break;
-
- case VK_A:
- shouldDisableAnalysis = false;
- Lizzie.board.toggleAnalysis();
- break;
-
- case VK_PERIOD:
- if (Lizzie.board.getHistory().getNext() == null)
- {
- Lizzie.board.setScoreMode(!Lizzie.board.inScoreMode());
- }
- break;
-
- default:
- shouldDisableAnalysis = false;
- }
+ private boolean wasPonderingWhenControlsShown = false;
- if (shouldDisableAnalysis && Lizzie.board.inAnalysisMode())
- Lizzie.board.toggleAnalysis();
+ @Override
+ public void keyReleased(KeyEvent e) {
+ switch (e.getKeyCode()) {
+ case VK_X:
+ if (wasPonderingWhenControlsShown) Lizzie.leelaz.togglePonder();
+ Lizzie.frame.showControls = false;
+ Lizzie.frame.repaint();
+ break;
+ case VK_Z:
+ stopRawBoard();
Lizzie.frame.repaint();
- }
+ break;
- private boolean wasPonderingWhenControlsShown = false;
- @Override
- public void keyReleased(KeyEvent e) {
- PluginManager.onKeyReleased(e);
- switch (e.getKeyCode()) {
- case VK_X:
- if (wasPonderingWhenControlsShown)
- Lizzie.leelaz.togglePonder();
- Lizzie.frame.showControls = false;
- Lizzie.frame.repaint();
- break;
-
- case VK_Z:
- stopRawBoard();
- Lizzie.frame.repaint();
- break;
-
- default:
- }
+ default:
}
+ }
- @Override
- public void mouseWheelMoved(MouseWheelEvent e) {
- if (Lizzie.board.inAnalysisMode())
- Lizzie.board.toggleAnalysis();
- if (e.getWheelRotation() > 0) {
- redo();
- } else if (e.getWheelRotation() < 0) {
- undo();
- }
- Lizzie.frame.repaint();
+ @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();
}
+ Lizzie.frame.repaint();
+ }
}
diff --git a/src/main/java/featurecat/lizzie/gui/LizzieFrame.java b/src/main/java/featurecat/lizzie/gui/LizzieFrame.java
index fa45ff9de..1893a6414 100644
--- a/src/main/java/featurecat/lizzie/gui/LizzieFrame.java
+++ b/src/main/java/featurecat/lizzie/gui/LizzieFrame.java
@@ -1,15 +1,5 @@
package featurecat.lizzie.gui;
-import java.awt.BasicStroke;
-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.RenderingHints;
-
import com.jhlabs.image.GaussianFilter;
import featurecat.lizzie.Lizzie;
import featurecat.lizzie.Util;
@@ -19,881 +9,1139 @@
import featurecat.lizzie.rules.BoardData;
import featurecat.lizzie.rules.GIBParser;
import featurecat.lizzie.rules.SGFParser;
-import org.json.JSONObject;
-
-import javax.swing.*;
-import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
+import java.awt.BasicStroke;
+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.RenderingHints;
import java.awt.datatransfer.Clipboard;
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;
import java.awt.image.BufferedImage;
import java.io.*;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.ResourceBundle;
+import javax.swing.*;
+import javax.swing.filechooser.FileNameExtensionFilter;
+import org.json.JSONArray;
+import org.json.JSONObject;
-/**
- * The window used to display the game.
- */
+/** The window used to display the game. */
public class LizzieFrame extends JFrame {
- 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.keyG"),
- resourceBundle.getString("LizzieFrame.commands.keyHome"),
- resourceBundle.getString("LizzieFrame.commands.keyEnd"),
- resourceBundle.getString("LizzieFrame.commands.keyControl"),
- };
- private static final String DEFAULT_TITLE = "Lizzie - Leela Zero Interface";
- private static BoardRenderer boardRenderer;
- private static BoardRenderer subBoardRenderer;
- private static VariationTree variationTree;
- private static WinrateGraph winrateGraph;
-
- public static Font OpenSansRegularBase;
- public static Font OpenSansSemiboldBase;
-
- private final BufferStrategy bs;
-
- public int[] mouseHoverCoordinate;
- public boolean showControls = false;
- public boolean showCoordinates = false;
- public boolean isPlayingAgainstLeelaz = false;
- public boolean playerIsBlack = true;
- public int winRateGridLines = 3;
-
- // Get the font name in current system locale
- private String systemDefaultFontName = new JLabel().getFont().getFontName();
-
- static {
- // load fonts
- try {
- OpenSansRegularBase = Font.createFont(Font.TRUETYPE_FONT, Thread.currentThread().getContextClassLoader().getResourceAsStream("fonts/OpenSans-Regular.ttf"));
- OpenSansSemiboldBase = Font.createFont(Font.TRUETYPE_FONT, Thread.currentThread().getContextClassLoader().getResourceAsStream("fonts/OpenSans-Semibold.ttf"));
- } catch (IOException | FontFormatException e) {
- e.printStackTrace();
- }
+ 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.keyG"),
+ resourceBundle.getString("LizzieFrame.commands.keyT"),
+ resourceBundle.getString("LizzieFrame.commands.keyHome"),
+ resourceBundle.getString("LizzieFrame.commands.keyEnd"),
+ resourceBundle.getString("LizzieFrame.commands.keyControl"),
+ };
+ private static final String DEFAULT_TITLE = "Lizzie - Leela Zero Interface";
+ private static BoardRenderer boardRenderer;
+ private static BoardRenderer subBoardRenderer;
+ private static VariationTree variationTree;
+ private static WinrateGraph winrateGraph;
+
+ public static Font OpenSansRegularBase;
+ public static Font OpenSansSemiboldBase;
+
+ private final BufferStrategy bs;
+
+ public int[] mouseOverCoordinate;
+ public boolean showControls = false;
+ public boolean showCoordinates = false;
+ public boolean isPlayingAgainstLeelaz = false;
+ public boolean playerIsBlack = true;
+ public int winRateGridLines = 3;
+
+ // Get the font name in current system locale
+ private String systemDefaultFontName = new JLabel().getFont().getFontName();
+
+ 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 {
+ OpenSansRegularBase =
+ Font.createFont(
+ Font.TRUETYPE_FONT,
+ Thread.currentThread()
+ .getContextClassLoader()
+ .getResourceAsStream("fonts/OpenSans-Regular.ttf"));
+ OpenSansSemiboldBase =
+ Font.createFont(
+ Font.TRUETYPE_FONT,
+ Thread.currentThread()
+ .getContextClassLoader()
+ .getResourceAsStream("fonts/OpenSans-Semibold.ttf"));
+ } catch (IOException | FontFormatException e) {
+ e.printStackTrace();
}
+ }
- /**
- * Creates a window
- */
- public LizzieFrame() {
- super(DEFAULT_TITLE);
+ /** Creates a window */
+ public LizzieFrame() {
+ super(DEFAULT_TITLE);
- boardRenderer = new BoardRenderer(true);
- subBoardRenderer = new BoardRenderer(false);
- variationTree = new VariationTree();
- winrateGraph = new WinrateGraph();
+ boardRenderer = new BoardRenderer(true);
+ subBoardRenderer = new BoardRenderer(false);
+ variationTree = new VariationTree();
+ winrateGraph = new WinrateGraph();
- // on 1080p screens in Windows, this is a good width/height. removing a default size causes problems in Linux
- setSize(657, 687);
- setMinimumSize( new Dimension(640,480) );
- setLocationRelativeTo(null); // start centered
- setExtendedState(Frame.MAXIMIZED_BOTH); // start maximized
+ setMinimumSize(new Dimension(640, 480));
+ setLocationRelativeTo(null); // start centered
+ JSONArray windowSize = Lizzie.config.uiConfig.getJSONArray("window-size");
+ setSize(windowSize.getInt(0), windowSize.getInt(1)); // use config file window size
- setVisible(true);
-
- createBufferStrategy(2);
- bs = getBufferStrategy();
-
- Input input = new Input();
-
- this.addMouseListener(input);
- this.addKeyListener(input);
- this.addMouseWheelListener(input);
- this.addMouseMotionListener(input);
-
- // necessary for Windows users - otherwise Lizzie shows a blank white screen on startup until updates occur.
- repaint();
+ if (Lizzie.config.startMaximized) {
+ setExtendedState(Frame.MAXIMIZED_BOTH); // start maximized
+ }
- // when the window is closed: save the SGF file, then run shutdown()
- this.addWindowListener(new WindowAdapter() {
- public void windowClosing(WindowEvent e) {
- Lizzie.shutdown();
- }
+ // 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);
+ bs = getBufferStrategy();
+
+ Input input = new Input();
+
+ this.addMouseListener(input);
+ this.addKeyListener(input);
+ this.addMouseWheelListener(input);
+ this.addMouseMotionListener(input);
+
+ // necessary for Windows users - otherwise Lizzie shows a blank white screen on startup until
+ // updates occur.
+ repaint();
+
+ // when the window is closed: save the SGF file, then run shutdown()
+ this.addWindowListener(
+ new WindowAdapter() {
+ public void windowClosing(WindowEvent e) {
+ Lizzie.shutdown();
+ }
});
-
+ }
+
+ public static void startNewGame() {
+ 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;
+
+ 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.frame.playerIsBlack = playerIsBlack;
+ 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");
}
-
- public static void startNewGame() {
- 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;
-
- 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.frame.playerIsBlack = playerIsBlack;
- 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");
+ }
+
+ public static void editGameInfo() {
+ GameInfo gameInfo = Lizzie.board.getHistory().getGameInfo();
+
+ GameInfoDialog gameInfoDialog = new GameInfoDialog();
+ gameInfoDialog.setGameInfo(gameInfo);
+ gameInfoDialog.setVisible(true);
+
+ gameInfoDialog.dispose();
+ }
+
+ public static 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 static void editGameInfo() {
- GameInfo gameInfo = Lizzie.board.getHistory().getGameInfo();
-
- GameInfoDialog gameInfoDialog = new GameInfoDialog();
- gameInfoDialog.setGameInfo(gameInfo);
- gameInfoDialog.setVisible(true);
-
- gameInfoDialog.dispose();
+ }
+
+ public static 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 static 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);
+ }
+ }
+
+ private BufferedImage cachedImage = null;
+
+ private BufferedImage cachedBackground = null;
+ private int cachedBackgroundWidth = 0, cachedBackgroundHeight = 0;
+ private boolean cachedBackgroundShowControls = false;
+ private boolean cachedShowWinrate = true;
+ private boolean cachedShowVariationGraph = true;
+ private boolean redrawBackgroundAnyway = false;
+
+ /**
+ * Draws the game board and interface
+ *
+ * @param g0 not used
+ */
+ public void paint(Graphics g0) {
+ autosaveMaybe();
+ if (bs == null) return;
+
+ Graphics2D backgroundG;
+ if (cachedBackgroundWidth != getWidth()
+ || cachedBackgroundHeight != getHeight()
+ || cachedBackgroundShowControls != showControls
+ || cachedShowWinrate != Lizzie.config.showWinrate
+ || cachedShowVariationGraph != Lizzie.config.showVariationGraph
+ || redrawBackgroundAnyway) backgroundG = createBackground();
+ else backgroundG = null;
+
+ if (!showControls) {
+ // layout parameters
+
+ int topInset = this.getInsets().top;
+
+ // board
+ int maxSize = (int) (Math.min(getWidth(), getHeight() - topInset) * 0.98);
+ maxSize = Math.max(maxSize, Board.BOARD_SIZE + 5); // don't let maxWidth become too small
+ int boardX = (getWidth() - maxSize) / 2;
+ int boardY = topInset + (getHeight() - topInset - maxSize) / 2 + 3;
+
+ int panelMargin = (int) (maxSize * 0.05);
+
+ // move statistics (winrate bar)
+ // boardX equals width of space on each side
+ int statx = 0;
+ int staty = boardY + maxSize / 8;
+ int statw = boardX - statx - panelMargin;
+ int stath = maxSize / 10;
+
+ // winrate graph
+ int grx = statx;
+ int gry = staty + stath;
+ int grw = statw;
+ int grh = statw;
+
+ // graph container
+ int contx = statx;
+ int conty = staty;
+ int contw = statw;
+ int conth = stath;
+
+ // captured stones
+ int capx = 0;
+ int capy = this.getInsets().top;
+ int capw = boardX - (int) (maxSize * 0.05);
+ int caph = boardY + maxSize / 8 - this.getInsets().top;
+
+ // variation tree container
+ int vx = boardX + maxSize + panelMargin;
+ int vy = 0;
+ int vw = getWidth() - vx;
+ int vh = getHeight();
+
+ // variation tree
+ int treex = vx;
+ int treey = vy;
+ int treew = vw + 1;
+ int treeh = vh;
+
+ // pondering message
+ int ponderingX = this.getInsets().left;
+ int ponderingY = boardY + (int) (maxSize * 0.93);
+ double ponderingSize = .02;
+
+ // dynamic komi
+ int dynamicKomiLabelX = this.getInsets().left;
+ int dynamicKomiLabelY = boardY + (int) (maxSize * 0.86);
+
+ int dynamicKomiX = this.getInsets().left;
+ int dynamicKomiY = boardY + (int) (maxSize * 0.89);
+ double dynamicKomiSize = .02;
+
+ // loading message
+ int loadingX = ponderingX;
+ int loadingY = ponderingY;
+ double loadingSize = 0.03;
+
+ // subboard
+ int subBoardX = 0;
+ int subBoardY = gry + grh;
+ int subBoardWidth = grw;
+ int subBoardHeight = ponderingY - subBoardY;
+ int subBoardLength = Math.min(subBoardWidth, subBoardHeight);
+
+ if (Lizzie.config.showLargeSubBoard()) {
+ boardX = getWidth() - maxSize - panelMargin;
+ int spaceW = boardX - panelMargin;
+ int spaceH = getHeight() - topInset;
+ int panelW = spaceW / 2;
+ int panelH = spaceH / 4;
+ capx = 0;
+ capy = topInset;
+ capw = panelW;
+ caph = (int) (panelH * 0.2);
+ statx = 0;
+ staty = capy + caph;
+ statw = panelW;
+ stath = (int) (panelH * 0.4);
+ grx = statx;
+ gry = staty + stath;
+ grw = statw;
+ grh = panelH - caph - stath;
+ contx = statx;
+ conty = staty;
+ contw = statw;
+ conth = stath + grh;
+ vx = panelW;
+ vy = 0;
+ vw = panelW;
+ vh = topInset + panelH;
+ treex = vx;
+ treey = vy;
+ treew = vw + 1;
+ treeh = vh;
+ subBoardX = 0;
+ subBoardY = topInset + panelH;
+ subBoardWidth = spaceW;
+ subBoardHeight = ponderingY - subBoardY;
+ subBoardLength = Math.min(subBoardWidth, subBoardHeight);
+ }
+
+ // initialize
+
+ cachedImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
+ Graphics2D g = (Graphics2D) cachedImage.getGraphics();
+
+ if (Lizzie.config.showStatus) drawCommandString(g);
+
+ boardRenderer.setLocation(boardX, boardY);
+ boardRenderer.setBoardLength(maxSize);
+ boardRenderer.draw(g);
+
+ if (Lizzie.leelaz != null && Lizzie.leelaz.isLoaded()) {
+ if (Lizzie.config.showStatus) {
+ 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);
+ }
- public static void saveSgf() {
- 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.failedToSaveSgf"), "Error", JOptionPane.ERROR);
- }
+ 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);
}
- }
- public static void openSgf() {
- 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) {
- File file = chooser.getSelectedFile();
- 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.failedToOpenSgf"), "Error", JOptionPane.ERROR);
- }
+ // Todo: Make board move over when there is no space beside the board
+ if (Lizzie.config.showWinrate) {
+ drawWinrateGraphContainer(backgroundG, contx, conty, contw, conth);
+ drawMoveStatistics(g, statx, staty, statw, stath);
+ winrateGraph.draw(g, grx, gry, grw, grh);
}
- }
- private BufferedImage cachedImage = null;
-
- private BufferedImage cachedBackground = null;
- private int cachedBackgroundWidth = 0, cachedBackgroundHeight = 0;
- private boolean cachedBackgroundShowControls = false;
- private boolean cachedShowWinrate = true;
- private boolean cachedShowVariationGraph = true;
- private boolean redrawBackgroundAnyway = false;
-
- /**
- * Draws the game board and interface
- *
- * @param g0 not used
- */
- public void paint(Graphics g0) {
- if (bs == null)
- return;
-
- Graphics2D backgroundG;
- if (cachedBackgroundWidth != getWidth() || cachedBackgroundHeight != getHeight() || cachedBackgroundShowControls != showControls || cachedShowWinrate != Lizzie.config.showWinrate || cachedShowVariationGraph != Lizzie.config.showVariationGraph || redrawBackgroundAnyway)
- backgroundG = createBackground();
- else
- backgroundG = null;
-
- if (!showControls) {
- // layout parameters
-
- int topInset = this.getInsets().top;
-
- // board
- int maxSize = (int) (Math.min(getWidth(), getHeight() - topInset) * 0.98);
- maxSize = Math.max(maxSize, Board.BOARD_SIZE + 5); // don't let maxWidth become too small
- int boardX = (getWidth() - maxSize) / 2;
- int boardY = topInset + (getHeight() - topInset - maxSize) / 2 + 3;
-
- int panelMargin = (int) (maxSize * 0.05);
-
- // move statistics (winrate bar)
- // boardX equals width of space on each side
- int statx = 0;
- int staty = boardY + maxSize / 8;
- int statw = boardX - statx - panelMargin;
- int stath = maxSize / 10;
-
- // winrate graph
- int grx = statx;
- int gry = staty + stath;
- int grw = statw;
- int grh = statw;
-
- // graph container
- int contx = statx;
- int conty = staty;
- int contw = statw;
- int conth = stath;
-
- // captured stones
- int capx = 0;
- int capy = this.getInsets().top;
- int capw = boardX - (int)(maxSize*0.05);
- int caph = boardY+ maxSize/8 - this.getInsets().top;
-
- // variation tree container
- int vx = boardX + maxSize + panelMargin;
- int vy = 0;
- int vw = getWidth() - vx;
- int vh = getHeight();
-
- // variation tree
- int treex = vx;
- int treey = vy;
- int treew = vw + 1;
- int treeh = vh;
-
- // pondering message
- int ponderingX = this.getInsets().left;
- int ponderingY = boardY + (int) (maxSize*0.93);
- double ponderingSize = .02;
-
- // loading message
- int loadingX = ponderingX;
- int loadingY = ponderingY;
- double loadingSize = 0.03;
-
- // subboard
- int subBoardX = 0;
- int subBoardY = gry + grh;
- int subBoardWidth = grw;
- int subBoardHeight = ponderingY - subBoardY;
- int subBoardLength = Math.min(subBoardWidth, subBoardHeight);
-
- if (Lizzie.config.showLargeSubBoard()) {
- boardX = getWidth() - maxSize - panelMargin;
- int spaceW = boardX - panelMargin;
- int spaceH = getHeight() - topInset;
- int panelW = spaceW / 2;
- int panelH = spaceH / 4;
- capx = 0;
- capy = topInset;
- capw = panelW;
- caph = (int) (panelH * 0.2);
- statx = 0;
- staty = capy + caph;
- statw = panelW;
- stath = (int) (panelH * 0.4);
- grx = statx;
- gry = staty + stath;
- grw = statw;
- grh = panelH - caph - stath;
- contx = statx;
- conty = staty;
- contw = statw;
- conth = stath + grh;
- vx = panelW;
- vy = 0;
- vw = panelW;
- vh = topInset + panelH;
- treex = vx;
- treey = vy;
- treew = vw + 1;
- treeh = vh;
- subBoardX = 0;
- subBoardY = topInset + panelH;
- subBoardWidth = spaceW;
- subBoardHeight = ponderingY - subBoardY;
- subBoardLength = Math.min(subBoardWidth, subBoardHeight);
- }
-
- // initialize
-
- cachedImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
- Graphics2D g = (Graphics2D) cachedImage.getGraphics();
-
- drawCommandString(g);
-
- boardRenderer.setLocation(boardX, boardY);
- boardRenderer.setBoardLength(maxSize);
- boardRenderer.draw(g);
-
- if (Lizzie.leelaz != null && Lizzie.leelaz.isLoaded()) {
- drawPonderingState(g, resourceBundle.getString("LizzieFrame.display.pondering") +
- (Lizzie.leelaz.isPondering()?resourceBundle.getString("LizzieFrame.display.on"):resourceBundle.getString("LizzieFrame.display.off")),
- ponderingX, ponderingY, ponderingSize);
-
- // Todo: Make board move over when there is no space beside the board
- if (Lizzie.config.showWinrate) {
- drawWinrateGraphContainer(backgroundG, contx, conty, contw, conth);
- drawMoveStatistics(g, statx, staty, statw, stath);
- winrateGraph.draw(g, grx, gry, grw, grh);
- }
-
- if (Lizzie.config.showVariationGraph) {
- drawVariationTreeContainer(backgroundG, vx, vy, vw, vh);
- variationTree.draw(g, treex, treey, treew, treeh);
- }
- if (Lizzie.config.showSubBoard) {
- try {
- subBoardRenderer.setLocation(subBoardX, subBoardY);
- subBoardRenderer.setBoardLength(subBoardLength);
- subBoardRenderer.draw(g);
- } catch (Exception e) {
- // This can happen when no space is left for subboard.
- }
- }
- } else {
- drawPonderingState(g, resourceBundle.getString("LizzieFrame.display.loading"), loadingX, loadingY, loadingSize);
- }
-
- drawCaptured(g, capx, capy, capw, caph);
-
- // cleanup
- g.dispose();
+ if (Lizzie.config.showVariationGraph) {
+ drawVariationTreeContainer(backgroundG, vx, vy, vw, vh);
+ 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);
+ }
}
- // draw the image
- Graphics2D bsGraphics = (Graphics2D) bs.getDrawGraphics();
- bsGraphics.drawImage(cachedBackground, 0, 0, null);
- bsGraphics.drawImage(cachedImage, 0, 0, null);
+ if (Lizzie.config.showSubBoard) {
+ try {
+ subBoardRenderer.setLocation(subBoardX, subBoardY);
+ subBoardRenderer.setBoardLength(subBoardLength);
+ subBoardRenderer.draw(g);
+ } catch (Exception e) {
+ // This can happen when no space is left for subboard.
+ }
+ }
+ } else if (Lizzie.config.showStatus) {
+ String loadingText = resourceBundle.getString("LizzieFrame.display.loading");
+ drawPonderingState(g, loadingText, loadingX, loadingY, loadingSize);
+ }
- // cleanup
- bsGraphics.dispose();
- bs.show();
- }
+ if (Lizzie.config.showCaptured) drawCaptured(g, capx, capy, capw, caph);
- /**
- * 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;
+ // cleanup
+ g.dispose();
}
- private Graphics2D createBackground() {
- cachedBackground = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
- cachedBackgroundWidth = cachedBackground.getWidth();
- cachedBackgroundHeight = cachedBackground.getHeight();
- cachedBackgroundShowControls = showControls;
- cachedShowWinrate = Lizzie.config.showWinrate;
- cachedShowVariationGraph = Lizzie.config.showVariationGraph;
-
- redrawBackgroundAnyway = false;
-
- Graphics2D g = cachedBackground.createGraphics();
-
- BufferedImage background = boardRenderer.theme.getBackground();
- int drawWidth = Math.max(background.getWidth(), getWidth());
- int drawHeight = Math.max(background.getHeight(), getHeight());
-
- g.drawImage(background, 0, 0, drawWidth, drawHeight, null);
-
- return g;
+ // draw the image
+ Graphics2D bsGraphics = (Graphics2D) bs.getDrawGraphics();
+ bsGraphics.drawImage(cachedBackground, 0, 0, null);
+ bsGraphics.drawImage(cachedImage, 0, 0, null);
+
+ // cleanup
+ bsGraphics.dispose();
+ bs.show();
+ }
+
+ /**
+ * 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;
+ }
+
+ private Graphics2D createBackground() {
+ cachedBackground = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
+ cachedBackgroundWidth = cachedBackground.getWidth();
+ cachedBackgroundHeight = cachedBackground.getHeight();
+ cachedBackgroundShowControls = showControls;
+ cachedShowWinrate = Lizzie.config.showWinrate;
+ cachedShowVariationGraph = Lizzie.config.showVariationGraph;
+
+ redrawBackgroundAnyway = false;
+
+ Graphics2D g = cachedBackground.createGraphics();
+
+ BufferedImage wallpaper = boardRenderer.getWallpaper();
+ int drawWidth = Math.max(wallpaper.getWidth(), getWidth());
+ int drawHeight = Math.max(wallpaper.getHeight(), getHeight());
+ // Support seamless texture
+ boardRenderer.drawTextureImage(g, wallpaper, 0, 0, drawWidth, drawHeight);
+
+ return g;
+ }
+
+ private void drawVariationTreeContainer(Graphics2D g, int vx, int vy, int vw, int vh) {
+ vw = cachedBackground.getWidth() - vx;
+
+ if (g == null || vw <= 0 || vh <= 0) return;
+
+ BufferedImage result = new BufferedImage(vw, vh, BufferedImage.TYPE_INT_ARGB);
+ filter20.filter(cachedBackground.getSubimage(vx, vy, vw, vh), result);
+ g.drawImage(result, vx, vy, null);
+ }
+
+ private void drawPonderingState(Graphics2D g, String text, int x, int y, double 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);
+ }
}
-
- private void drawVariationTreeContainer(Graphics2D g, int vx, int vy, int vw, int vh) {
- vw = cachedBackground.getWidth() - vx;
-
- if (g == null || vw <= 0 || vh <= 0)
- return;
-
- BufferedImage result = new BufferedImage(vw, vh, BufferedImage.TYPE_INT_ARGB);
- filter20.filter(cachedBackground.getSubimage(vx, vy, vw, vh), result);
- g.drawImage(result, vx, vy, null);
+ int stringHeight = fm.getAscent() - fm.getDescent();
+ int width = stringWidth;
+ int height = (int) (stringHeight * 1.2);
+
+ BufferedImage result = new BufferedImage(width, height, BufferedImage.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 void drawWinrateGraphContainer(Graphics g, int statx, int staty, int statw, int stath) {
+ if (g == null || statw <= 0 || stath <= 0) return;
+
+ BufferedImage result = new BufferedImage(statw, stath + statw, BufferedImage.TYPE_INT_ARGB);
+ filter20.filter(
+ cachedBackground.getSubimage(statx, staty, result.getWidth(), result.getHeight()), result);
+ g.drawImage(result, statx, staty, null);
+ }
+
+ private GaussianFilter filter20 = new GaussianFilter(20);
+ private GaussianFilter filter10 = new GaussianFilter(10);
+
+ /** Display the controls */
+ void drawControls() {
+ userAlreadyKnowsAboutCommandString = true;
+
+ cachedImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
+
+ // redraw background
+ createBackground();
+
+ List commandsToShow = new ArrayList<>(Arrays.asList(commands));
+ if (Lizzie.leelaz.getDynamicKomi() != null) {
+ commandsToShow.add(resourceBundle.getString("LizzieFrame.commands.keyD"));
}
- 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));
- FontMetrics fm = g.getFontMetrics(font);
- int stringWidth = fm.stringWidth(text);
- int stringHeight = fm.getAscent() - fm.getDescent();
- int width = stringWidth;
- int height = (int)(stringHeight * 1.2);
-
- BufferedImage result = new BufferedImage(width, height, BufferedImage.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);
+ Graphics2D g = cachedImage.createGraphics();
+
+ 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 maxCmdWidth = commandsToShow.stream().mapToInt(c -> metrics.stringWidth(c)).max().orElse(0);
+ int lineHeight = (int) (font.getSize() * 1.15);
+
+ 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());
+ int commandsY = Util.clamp(getHeight() / 2 - boxHeight / 2, 0, getHeight());
+
+ BufferedImage result = new BufferedImage(boxWidth, boxHeight, BufferedImage.TYPE_INT_ARGB);
+ filter10.filter(
+ 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 = 2;
+ g.setStroke(new BasicStroke(2 * strokeRadius));
+ 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;
}
- private void drawWinrateGraphContainer(Graphics g, int statx, int staty, int statw, int stath) {
- if (g == null || statw <= 0 || stath <= 0)
- return;
-
- BufferedImage result = new BufferedImage(statw, stath + statw, BufferedImage.TYPE_INT_ARGB);
- filter20.filter(cachedBackground.getSubimage(statx, staty, result.getWidth(), result.getHeight()), result);
- g.drawImage(result, statx, staty, null);
+ refreshBackground();
+ }
+
+ private boolean userAlreadyKnowsAboutCommandString = false;
+
+ private void drawCommandString(Graphics2D g) {
+ if (userAlreadyKnowsAboutCommandString) return;
+
+ int maxSize = (int) (Math.min(getWidth(), getHeight()) * 0.98);
+
+ Font font = new Font(systemDefaultFontName, Font.PLAIN, (int) (maxSize * 0.03));
+ String commandString = resourceBundle.getString("LizzieFrame.prompt.showControlsHint");
+ int strokeRadius = 2;
+
+ 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);
+ 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());
+ }
+
+ 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
+ BoardData lastNode = Lizzie.board.getHistory().getPrevious();
+ if (lastNode != null && lastNode.playouts > 0) {
+ lastWR = lastNode.winrate;
+ validLastWinrate = true;
}
- private GaussianFilter filter20 = new GaussianFilter(20);
- private GaussianFilter filter10 = new GaussianFilter(10);
-
- /**
- * Display the controls
- */
- void drawControls() {
- userAlreadyKnowsAboutCommandString = 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 (isPlayingAgainstLeelaz && playerIsBlack == !Lizzie.board.getHistory().getData().blackToPlay)
+ validWinrate = false;
- cachedImage = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
-
- // redraw background
- createBackground();
-
- Graphics2D g = cachedImage.createGraphics();
-
- 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 = Arrays.asList(commands).stream().reduce(0, (Integer i, String command) -> Math.max(i, metrics.stringWidth(command)), (Integer a, Integer b) -> Math.max(a, b));
- int lineHeight = (int) (font.getSize() * 1.15);
-
- int boxWidth = Util.clamp((int) (maxCommandWidth * 1.4), 0, getWidth());
- int boxHeight = Util.clamp(commands.length * lineHeight, 0, getHeight());
-
- int commandsX = Util.clamp(getWidth() / 2 - boxWidth / 2, 0, getWidth());
- int commandsY = Util.clamp(getHeight() / 2 - boxHeight / 2, 0, getHeight());
-
- BufferedImage result = new BufferedImage(boxWidth, boxHeight, BufferedImage.TYPE_INT_ARGB);
- filter10.filter(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 = 2;
- g.setStroke(new BasicStroke(2 * strokeRadius));
- 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);
- for (int i = 0; i < commands.length; i++) {
- String[] split = commands[i].split("\\|");
- g.drawString(split[0], verticalLineX - metrics.stringWidth(split[0]) - strokeRadius * 4, font.getSize() + (commandsY + i * lineHeight));
- g.drawString(split[1], verticalLineX + strokeRadius * 4, font.getSize() + (commandsY + i * lineHeight));
- }
-
- refreshBackground();
+ if (!validWinrate) {
+ curWR = 100 - lastWR; // display last move's winrate for now (with color difference)
}
-
- private boolean userAlreadyKnowsAboutCommandString = false;
-
- private void drawCommandString(Graphics2D g) {
- if (userAlreadyKnowsAboutCommandString)
- return;
-
- int maxSize = (int) (Math.min(getWidth(), getHeight()) * 0.98);
-
- Font font = new Font(systemDefaultFontName, Font.PLAIN, (int) (maxSize * 0.03));
- String commandString = resourceBundle.getString("LizzieFrame.prompt.showControlsHint");
- int strokeRadius = 2;
-
- 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);
- 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());
+ double whiteWR, blackWR;
+ if (Lizzie.board.getData().blackToPlay) {
+ blackWR = curWR;
+ } else {
+ blackWR = 100 - curWR;
}
- 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
- BoardData lastNode = Lizzie.board.getHistory().getPrevious();
- if (lastNode != null && lastNode.playouts > 0) {
- lastWR = lastNode.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 (isPlayingAgainstLeelaz && 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 = 3;
- g.setStroke(new BasicStroke(2 * strokeRadius));
- 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);
-
- // 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) (Math.min(width, height) * 0.2));
-
- // Last move
- if (validLastWinrate && validWinrate) {
- String text;
- if(Lizzie.config.handicapInsteadOfWinrate) {
- text=String.format(": %.2f", Lizzie.leelaz.winrateToHandicap(100-curWR) - Lizzie.leelaz.winrateToHandicap(lastWR));
- } else {
- text=String.format(": %.1f%%", 100 - lastWR - curWR);
- }
-
- g.drawString(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);
- }
+ 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 = 3;
+ g.setStroke(new BasicStroke(2 * strokeRadius));
+ 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);
+
+ // 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) (Math.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(
+ 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);
}
- 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 = 3;
- g.setStroke(new BasicStroke(2 * strokeRadius));
- 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.inScoreMode()) {
- // do nothing
- } else if (Lizzie.board.getHistory().isBlacksTurn()) {
- wdiam = 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) (width * 0.06));
- 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);
+ 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 = OpenSansRegularBase.deriveFont(Font.PLAIN, size);
- g.setFont(font);
+ }
+
+ 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 = 3;
+ g.setStroke(new BasicStroke(2 * strokeRadius));
+ 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.inScoreMode()) {
+ // do nothing
+ } else if (Lizzie.board.getHistory().isBlacksTurn()) {
+ wdiam = smallDiam;
+ } else {
+ bdiam = smallDiam;
}
-
- /**
- * 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
- int[] boardCoordinates = boardRenderer.convertScreenToCoordinates(x, y);
- int moveNumber = winrateGraph.moveNumber(x, y);
-
- if (boardCoordinates != null) {
- if (Lizzie.board.inAnalysisMode())
- Lizzie.board.toggleAnalysis();
- if (!isPlayingAgainstLeelaz || (playerIsBlack == Lizzie.board.getData().blackToPlay))
- Lizzie.board.place(boardCoordinates[0], boardCoordinates[1]);
- }
- if (Lizzie.config.showWinrate && moveNumber >= 0) {
- isPlayingAgainstLeelaz = false;
- Lizzie.board.goToMoveNumberBeyondBranch(moveNumber);
- }
- if (Lizzie.config.showSubBoard && subBoardRenderer.isInside(x, y)) {
- Lizzie.config.toggleLargeSubBoard();
- }
- repaint();
+ 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) (width * 0.06));
+ 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);
}
- public boolean playCurrentVariation() {
- List variation = boardRenderer.variation;
- boolean onVariation = (variation != null);
- if (onVariation) {
- for (int i = 0; i < variation.size(); i++) {
- int[] boardCoordinates = Board.convertNameToCoordinates(variation.get(i));
- if (boardCoordinates != null)
- Lizzie.board.place(boardCoordinates[0], boardCoordinates[1]);
- }
- }
- return onVariation;
+ 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);
+ }
+
+ private void setPanelFont(Graphics2D g, float size) {
+ Font font = OpenSansRegularBase.deriveFont(Font.PLAIN, 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) {
+ // check for board click
+ int[] boardCoordinates = boardRenderer.convertScreenToCoordinates(x, y);
+ int moveNumber = winrateGraph.moveNumber(x, y);
+
+ if (boardCoordinates != null) {
+ if (Lizzie.board.inAnalysisMode()) Lizzie.board.toggleAnalysis();
+ if (!isPlayingAgainstLeelaz || (playerIsBlack == Lizzie.board.getData().blackToPlay))
+ Lizzie.board.place(boardCoordinates[0], boardCoordinates[1]);
}
-
- public void playBestMove() {
- String bestCoordinateName = boardRenderer.bestMoveCoordinateName();
- if (bestCoordinateName == null)
- return;
- int[] boardCoordinates = Board.convertNameToCoordinates(bestCoordinateName);
- if (boardCoordinates != null) {
- Lizzie.board.place(boardCoordinates[0], boardCoordinates[1]);
- }
+ if (Lizzie.config.showWinrate && moveNumber >= 0) {
+ isPlayingAgainstLeelaz = false;
+ Lizzie.board.goToMoveNumberBeyondBranch(moveNumber);
}
-
- 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;
- repaint();
- } else {
- mouseHoverCoordinate = newMouseHoverCoordinate;
- }
+ if (Lizzie.config.showSubBoard && subBoardRenderer.isInside(x, y)) {
+ Lizzie.config.toggleLargeSubBoard();
}
-
- 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();
- }
- }
+ repaint();
+ }
+
+ public boolean playCurrentVariation() {
+ List variation = boardRenderer.variation;
+ boolean onVariation = (variation != null);
+ if (onVariation) {
+ for (int i = 0; i < variation.size(); i++) {
+ int[] boardCoordinates = Board.convertNameToCoordinates(variation.get(i));
+ if (boardCoordinates != null) Lizzie.board.place(boardCoordinates[0], boardCoordinates[1]);
+ }
}
-
- public void toggleCoordinates() {
- showCoordinates = !showCoordinates;
+ return onVariation;
+ }
+
+ public void playBestMove() {
+ String bestCoordinateName = boardRenderer.bestMoveCoordinateName();
+ if (bestCoordinateName == null) return;
+ int[] boardCoordinates = Board.convertNameToCoordinates(bestCoordinateName);
+ if (boardCoordinates != null) {
+ Lizzie.board.place(boardCoordinates[0], boardCoordinates[1]);
}
+ }
- public void setPlayers(String whitePlayer, String blackPlayer) {
- setTitle(String.format("%s (%s [W] vs %s [B])", DEFAULT_TITLE,
- whitePlayer, blackPlayer));
+ public void onMouseMoved(int x, int y) {
+ int[] c = boardRenderer.convertScreenToCoordinates(x, y);
+ if (c != null && !isMouseOver(c[0], c[1])) {
+ repaint();
}
-
- private void setDisplayedBranchLength(int n) {
- boardRenderer.setDisplayedBranchLength(n);
+ 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) {
+ int moveNumber = winrateGraph.moveNumber(x, y);
+ if (Lizzie.config.showWinrate && moveNumber >= 0) {
+ if (Lizzie.board.goToMoveNumberWithinBranch(moveNumber)) {
+ repaint();
+ }
}
-
- public void startRawBoard() {
- boolean onBranch = boardRenderer.isShowingBranch();
- int n = (onBranch ? 1 : BoardRenderer.SHOW_RAW_BOARD);
- boardRenderer.setDisplayedBranchLength(n);
+ }
+
+ /**
+ * 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;
}
-
- public void stopRawBoard() {
- boardRenderer.setDisplayedBranchLength(BoardRenderer.SHOW_NORMAL_BOARD);
+ }
+
+ /**
+ * 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();
}
-
- public boolean incrementDisplayedBranchLength(int n) {
- return boardRenderer.incrementDisplayedBranchLength(n);
+ }
+
+ 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;
}
-
- public void resetTitle() {
- setTitle(DEFAULT_TITLE);
+ }
+
+ public void toggleCoordinates() {
+ showCoordinates = !showCoordinates;
+ }
+
+ public void setPlayers(String whitePlayer, String 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) {
+ 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 resetTitle() {
+ this.playerTitle = null;
+ this.updateTitle();
+ }
+
+ 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 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() {
+ try {
+ String sgfContent = null;
+ // Get string from clipboard
+ Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
+ Transferable clipboardContents = clipboard.getContents(null);
+ if (clipboardContents != null) {
+ if (clipboardContents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
+ sgfContent = (String) clipboardContents.getTransferData(DataFlavor.stringFlavor);
}
+ }
+
+ // load game contents from sgf string
+ if (sgfContent != null && !sgfContent.isEmpty()) {
+ SGFParser.loadFromString(sgfContent);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
}
-
- public void pasteSgf() {
- try {
- String sgfContent = null;
- // Get string from clipboard
- Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
- Transferable clipboardContents = clipboard.getContents(null);
- if (clipboardContents != null) {
- if (clipboardContents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
- sgfContent = (String) clipboardContents.getTransferData(DataFlavor.stringFlavor);
- }
- }
-
- // load game contents from sgf string
- if (sgfContent != null && !sgfContent.isEmpty()) {
- SGFParser.loadFromString(sgfContent);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
+ }
+
+ 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 1d3a8389a..668c2a6ed 100644
--- a/src/main/java/featurecat/lizzie/gui/NewGameDialog.java
+++ b/src/main/java/featurecat/lizzie/gui/NewGameDialog.java
@@ -5,185 +5,185 @@
package featurecat.lizzie.gui;
import featurecat.lizzie.analysis.GameInfo;
-
-import javax.swing.*;
-import javax.swing.border.EmptyBorder;
import java.awt.*;
import java.text.DecimalFormat;
import java.text.ParseException;
+import javax.swing.*;
+import javax.swing.border.EmptyBorder;
-/**
- * @author unknown
- */
+/** @author unknown */
public class NewGameDialog extends JDialog {
- // 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);
+ // 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);
+ }
+
+ private JPanel dialogPane = new JPanel();
+ private JPanel contentPanel = new JPanel();
+ private JPanel buttonBar = new JPanel();
+ private JButton okButton = new JButton();
+
+ private JCheckBox checkBoxPlayerIsBlack;
+ private JTextField textFieldBlack;
+ private JTextField textFieldWhite;
+ private JTextField textFieldKomi;
+ private JTextField textFieldHandicap;
+
+ private boolean cancelled = true;
+ private GameInfo gameInfo;
+
+ public NewGameDialog() {
+ initComponents();
+ }
+
+ private void initComponents() {
+ setMinimumSize(new Dimension(100, 100));
+ setResizable(false);
+ setTitle("New Game");
+ setModal(true);
+
+ Container contentPane = getContentPane();
+ contentPane.setLayout(new BorderLayout());
+
+ initDialogPane(contentPane);
+
+ pack();
+ setLocationRelativeTo(getOwner());
+ }
+
+ private void initDialogPane(Container contentPane) {
+ dialogPane.setBorder(new EmptyBorder(12, 12, 12, 12));
+ dialogPane.setLayout(new BorderLayout());
+
+ initContentPanel();
+ initButtonBar();
+
+ contentPane.add(dialogPane, BorderLayout.CENTER);
+ }
+
+ private void initContentPanel() {
+ GridLayout gridLayout = new GridLayout(5, 2, 4, 4);
+ contentPanel.setLayout(gridLayout);
+
+ checkBoxPlayerIsBlack = new JCheckBox("Play black?", true);
+ checkBoxPlayerIsBlack.addChangeListener(evt -> togglePlayerIsBlack());
+ textFieldWhite = new JTextField();
+ textFieldBlack = new JTextField();
+ textFieldKomi = new JFormattedTextField(FORMAT_KOMI);
+ textFieldHandicap = new JFormattedTextField(FORMAT_HANDICAP);
+ textFieldHandicap.addPropertyChangeListener(evt -> modifyHandicap());
+
+ contentPanel.add(checkBoxPlayerIsBlack);
+ contentPanel.add(PLACEHOLDER);
+ contentPanel.add(new JLabel("Black"));
+ contentPanel.add(textFieldBlack);
+ contentPanel.add(new JLabel("White"));
+ contentPanel.add(textFieldWhite);
+ contentPanel.add(new JLabel("Komi"));
+ contentPanel.add(textFieldKomi);
+ contentPanel.add(new JLabel("Handicap"));
+ contentPanel.add(textFieldHandicap);
+
+ textFieldKomi.setEnabled(false);
+
+ dialogPane.add(contentPanel, BorderLayout.CENTER);
+ }
+
+ private void togglePlayerIsBlack() {
+ JTextField humanTextField = playerIsBlack() ? textFieldBlack : textFieldWhite;
+ JTextField computerTextField = playerIsBlack() ? textFieldWhite : textFieldBlack;
+
+ humanTextField.setEnabled(true);
+ humanTextField.setText(GameInfo.DEFAULT_NAME_HUMAN_PLAYER);
+ computerTextField.setEnabled(false);
+ computerTextField.setText(GameInfo.DEFAULT_NAME_CPU_PLAYER);
+ }
+
+ private void modifyHandicap() {
+ try {
+ int handicap = FORMAT_HANDICAP.parse(textFieldHandicap.getText()).intValue();
+ if (handicap < 0) throw new IllegalArgumentException();
+
+ textFieldKomi.setText(FORMAT_KOMI.format(GameInfo.DEFAULT_KOMI));
+ } catch (ParseException | RuntimeException e) {
+ // do not correct user mistakes
}
-
- private JPanel dialogPane = new JPanel();
- private JPanel contentPanel = new JPanel();
- private JPanel buttonBar = new JPanel();
- private JButton okButton = new JButton();
-
- private JCheckBox checkBoxPlayerIsBlack;
- private JTextField textFieldBlack;
- private JTextField textFieldWhite;
- private JTextField textFieldKomi;
- private JTextField textFieldHandicap;
-
- private boolean cancelled = true;
- private GameInfo gameInfo;
-
- public NewGameDialog() {
- initComponents();
+ }
+
+ private void initButtonBar() {
+ buttonBar.setBorder(new EmptyBorder(12, 0, 0, 0));
+ buttonBar.setLayout(new GridBagLayout());
+ ((GridBagLayout) buttonBar.getLayout()).columnWidths = new int[] {0, 80};
+ ((GridBagLayout) buttonBar.getLayout()).columnWeights = new double[] {1.0, 0.0};
+
+ // ---- okButton ----
+ 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, center, both, new Insets(0, 0, 0, 0), 0, 0));
+
+ dialogPane.add(buttonBar, BorderLayout.SOUTH);
+ }
+
+ public void apply() {
+ try {
+ // validate data
+ String playerBlack = textFieldBlack.getText();
+ String playerWhite = textFieldWhite.getText();
+ double komi = FORMAT_KOMI.parse(textFieldKomi.getText()).doubleValue();
+ int handicap = FORMAT_HANDICAP.parse(textFieldHandicap.getText()).intValue();
+
+ // apply new values
+ gameInfo.setPlayerBlack(playerBlack);
+ gameInfo.setPlayerWhite(playerWhite);
+ gameInfo.setKomi(komi);
+ gameInfo.setHandicap(handicap);
+
+ // close window
+ cancelled = false;
+ setVisible(false);
+ } catch (ParseException e) {
+ // hide input mistakes.
}
-
- private void initComponents() {
- setMinimumSize(new Dimension(100, 100));
- setResizable(false);
- setTitle("New Game");
- setModal(true);
-
- Container contentPane = getContentPane();
- contentPane.setLayout(new BorderLayout());
-
- initDialogPane(contentPane);
-
- pack();
- setLocationRelativeTo(getOwner());
- }
-
- private void initDialogPane(Container contentPane) {
- dialogPane.setBorder(new EmptyBorder(12, 12, 12, 12));
- dialogPane.setLayout(new BorderLayout());
-
- initContentPanel();
- initButtonBar();
-
- contentPane.add(dialogPane, BorderLayout.CENTER);
- }
-
- private void initContentPanel() {
- GridLayout gridLayout = new GridLayout(5, 2, 4, 4);
- contentPanel.setLayout(gridLayout);
-
- checkBoxPlayerIsBlack = new JCheckBox("Play black?", true);
- checkBoxPlayerIsBlack.addChangeListener(evt -> togglePlayerIsBlack());
- textFieldWhite = new JTextField();
- textFieldBlack = new JTextField();
- textFieldKomi = new JFormattedTextField(FORMAT_KOMI);
- textFieldHandicap = new JFormattedTextField(FORMAT_HANDICAP);
- textFieldHandicap.addPropertyChangeListener(evt -> modifyHandicap());
-
- contentPanel.add(checkBoxPlayerIsBlack);
- contentPanel.add(PLACEHOLDER);
- contentPanel.add(new JLabel("Black"));
- contentPanel.add(textFieldBlack);
- contentPanel.add(new JLabel("White"));
- contentPanel.add(textFieldWhite);
- contentPanel.add(new JLabel("Komi"));
- contentPanel.add(textFieldKomi);
- contentPanel.add(new JLabel("Handicap"));
- contentPanel.add(textFieldHandicap);
-
- textFieldKomi.setEnabled(false);
-
- dialogPane.add(contentPanel, BorderLayout.CENTER);
- }
-
- private void togglePlayerIsBlack() {
- JTextField humanTextField = playerIsBlack() ? textFieldBlack : textFieldWhite;
- JTextField computerTextField = playerIsBlack() ? textFieldWhite : textFieldBlack;
-
- humanTextField.setEnabled(true);
- humanTextField.setText(GameInfo.DEFAULT_NAME_HUMAN_PLAYER);
- computerTextField.setEnabled(false);
- computerTextField.setText(GameInfo.DEFAULT_NAME_CPU_PLAYER);
- }
-
- private void modifyHandicap() {
- try {
- int handicap = FORMAT_HANDICAP.parse(textFieldHandicap.getText()).intValue();
- if (handicap < 0) throw new IllegalArgumentException();
-
- textFieldKomi.setText(FORMAT_KOMI.format(GameInfo.DEFAULT_KOMI));
- } catch (ParseException | RuntimeException e) {
- // do not correct user mistakes
- }
- }
-
- private void initButtonBar() {
- buttonBar.setBorder(new EmptyBorder(12, 0, 0, 0));
- buttonBar.setLayout(new GridBagLayout());
- ((GridBagLayout) buttonBar.getLayout()).columnWidths = new int[]{0, 80};
- ((GridBagLayout) buttonBar.getLayout()).columnWeights = new double[]{1.0, 0.0};
-
- //---- okButton ----
- okButton.setText("OK");
- okButton.addActionListener(e -> apply());
-
- 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));
-
- dialogPane.add(buttonBar, BorderLayout.SOUTH);
- }
-
- public void apply() {
- try {
- // validate data
- String playerBlack = textFieldBlack.getText();
- String playerWhite = textFieldWhite.getText();
- double komi = FORMAT_KOMI.parse(textFieldKomi.getText()).doubleValue();
- int handicap = FORMAT_HANDICAP.parse(textFieldHandicap.getText()).intValue();
-
- // apply new values
- gameInfo.setPlayerBlack(playerBlack);
- gameInfo.setPlayerWhite(playerWhite);
- gameInfo.setKomi(komi);
- gameInfo.setHandicap(handicap);
-
- // close window
- cancelled = false;
- setVisible(false);
- } catch (ParseException e) {
- // hide input mistakes.
- }
- }
-
- public void setGameInfo(GameInfo gameInfo) {
- this.gameInfo = gameInfo;
-
- textFieldBlack.setText(gameInfo.getPlayerBlack());
- textFieldWhite.setText(gameInfo.getPlayerWhite());
- textFieldHandicap.setText(FORMAT_HANDICAP.format(gameInfo.getHandicap()));
- textFieldKomi.setText(FORMAT_KOMI.format(gameInfo.getKomi()));
-
- // update player names
- togglePlayerIsBlack();
- }
-
- public boolean playerIsBlack() {
- return checkBoxPlayerIsBlack.isSelected();
- }
-
- 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 void setGameInfo(GameInfo gameInfo) {
+ this.gameInfo = gameInfo;
+
+ textFieldBlack.setText(gameInfo.getPlayerBlack());
+ textFieldWhite.setText(gameInfo.getPlayerWhite());
+ textFieldHandicap.setText(FORMAT_HANDICAP.format(gameInfo.getHandicap()));
+ textFieldKomi.setText(FORMAT_KOMI.format(gameInfo.getKomi()));
+
+ // update player names
+ togglePlayerIsBlack();
+ }
+
+ public boolean playerIsBlack() {
+ return checkBoxPlayerIsBlack.isSelected();
+ }
+
+ 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();
+ }
});
- }
+ }
}
diff --git a/src/main/java/featurecat/lizzie/gui/VariationTree.java b/src/main/java/featurecat/lizzie/gui/VariationTree.java
index 6c873dc0e..aef810e2b 100644
--- a/src/main/java/featurecat/lizzie/gui/VariationTree.java
+++ b/src/main/java/featurecat/lizzie/gui/VariationTree.java
@@ -3,143 +3,166 @@
import featurecat.lizzie.Lizzie;
import featurecat.lizzie.rules.BoardHistoryList;
import featurecat.lizzie.rules.BoardHistoryNode;
-
import java.awt.*;
import java.util.ArrayList;
public class VariationTree {
- private int YSPACING;
- private int XSPACING;
- private int DOT_DIAM = 11; // Should be odd number
-
- private ArrayList laneUsageList;
- private BoardHistoryNode curMove;
+ private int YSPACING;
+ private int XSPACING;
+ private int DOT_DIAM = 11; // Should be odd number
+
+ private ArrayList laneUsageList;
+ private BoardHistoryNode curMove;
+
+ public VariationTree() {
+ laneUsageList = new ArrayList();
+ }
+
+ public void drawTree(
+ Graphics2D g,
+ int posx,
+ int posy,
+ int startLane,
+ int maxposy,
+ BoardHistoryNode startNode,
+ int variationNumber,
+ boolean isMain) {
+ if (isMain) g.setColor(Color.white);
+ else g.setColor(Color.gray.brighter());
+
+ // Finds depth on leftmost variation of this tree
+ int depth = BoardHistoryList.getDepth(startNode) + 1;
+ int lane = startLane;
+ // Figures out how far out too the right (which lane) we have to go not to collide with other
+ // variations
+ while (lane < laneUsageList.size()
+ && laneUsageList.get(lane) <= startNode.getData().moveNumber + depth) {
+ // laneUsageList keeps a list of how far down it is to a variation in the different "lanes"
+ laneUsageList.set(lane, startNode.getData().moveNumber - 1);
+ lane++;
+ }
+ if (lane >= laneUsageList.size()) {
+ laneUsageList.add(0);
+ }
+ if (variationNumber > 1) laneUsageList.set(lane - 1, startNode.getData().moveNumber - 1);
+ laneUsageList.set(lane, startNode.getData().moveNumber);
+
+ // At this point, lane contains the lane we should use (the main branch is in lane 0)
+
+ BoardHistoryNode cur = startNode;
+ int curposx = posx + lane * XSPACING;
+ int dotoffset = DOT_DIAM / 2;
+
+ // Draw line back to main branch
+ if (lane > 0) {
+ if (lane - startLane > 0 || variationNumber > 1) {
+ // Need a horizontal and an angled line
+ g.drawLine(
+ curposx + dotoffset,
+ posy + dotoffset,
+ curposx + dotoffset - XSPACING,
+ posy + dotoffset - YSPACING);
+ g.drawLine(
+ posx + (startLane - variationNumber) * XSPACING + 2 * dotoffset,
+ posy - YSPACING + dotoffset,
+ curposx + dotoffset - XSPACING,
+ posy + dotoffset - YSPACING);
+ } else {
+ // Just an angled line
+ g.drawLine(
+ curposx + dotoffset,
+ posy + dotoffset,
+ curposx + 2 * dotoffset - XSPACING,
+ posy + 2 * dotoffset - YSPACING);
+ }
+ }
- public VariationTree()
- {
- laneUsageList = new ArrayList();
+ // Draw all the nodes and lines in this lane (not variations)
+ Color curcolor = g.getColor();
+ if (startNode == curMove) {
+ g.setColor(Color.green.brighter().brighter());
+ }
+ if (startNode.previous() != null) {
+ g.fillOval(curposx, posy, DOT_DIAM, DOT_DIAM);
+ g.setColor(Color.BLACK);
+ g.drawOval(curposx, posy, DOT_DIAM, DOT_DIAM);
+ } else {
+ g.fillRect(curposx, posy, DOT_DIAM, DOT_DIAM);
+ g.setColor(Color.BLACK);
+ g.drawRect(curposx, posy, DOT_DIAM, DOT_DIAM);
+ }
+ g.setColor(curcolor);
+
+ // Draw main line
+ while (cur.next() != null && posy + YSPACING < maxposy) {
+ posy += YSPACING;
+ cur = cur.next();
+ if (cur == curMove) {
+ g.setColor(Color.green.brighter().brighter());
+ }
+ g.fillOval(curposx, posy, DOT_DIAM, DOT_DIAM);
+ g.setColor(Color.BLACK);
+ g.drawOval(curposx, posy, DOT_DIAM, DOT_DIAM);
+ g.setColor(curcolor);
+ g.drawLine(
+ curposx + dotoffset, posy - 1, curposx + dotoffset, posy - YSPACING + 2 * dotoffset + 2);
+ }
+ // Now we have drawn all the nodes in this variation, and has reached the bottom of this
+ // variation
+ // Move back up, and for each, draw any variations we find
+ while (cur.previous() != null && cur != startNode) {
+ cur = cur.previous();
+ int curwidth = lane;
+ // Draw each variation, uses recursion
+ for (int i = 1; i < cur.numberOfChildren(); i++) {
+ curwidth++;
+ // Recursion, depth of recursion will normally not be very deep (one recursion level for
+ // every variation that has a variation (sort of))
+ drawTree(g, posx, posy, curwidth, maxposy, cur.getVariation(i), i, false);
+ }
+ posy -= YSPACING;
}
+ }
- public void drawTree(Graphics2D g, int posx, int posy, int startLane, int maxposy, BoardHistoryNode startNode, int variationNumber, boolean isMain)
- {
- if (isMain) g.setColor(Color.white);
- else g.setColor(Color.gray.brighter());
-
-
- // Finds depth on leftmost variation of this tree
- int depth = BoardHistoryList.getDepth(startNode) + 1;
- int lane = startLane;
- // Figures out how far out too the right (which lane) we have to go not to collide with other variations
- while (lane < laneUsageList.size() && laneUsageList.get(lane) <= startNode.getData().moveNumber + depth) {
- // laneUsageList keeps a list of how far down it is to a variation in the different "lanes"
- laneUsageList.set(lane, startNode.getData().moveNumber - 1);
- lane++;
- }
- if (lane >= laneUsageList.size())
- {
- laneUsageList.add(0);
- }
- if (variationNumber > 1)
- laneUsageList.set(lane - 1, startNode.getData().moveNumber - 1);
- laneUsageList.set(lane, startNode.getData().moveNumber);
-
- // At this point, lane contains the lane we should use (the main branch is in lane 0)
-
- BoardHistoryNode cur = startNode;
- int curposx = posx + lane*XSPACING;
- int dotoffset = DOT_DIAM/2;
-
- // Draw line back to main branch
- if (lane > 0) {
- if (lane - startLane > 0 || variationNumber > 1) {
- // Need a horizontal and an angled line
- g.drawLine(curposx + dotoffset, posy + dotoffset, curposx + dotoffset - XSPACING, posy + dotoffset - YSPACING);
- g.drawLine(posx + (startLane - variationNumber )*XSPACING + 2*dotoffset, posy - YSPACING + dotoffset, curposx + dotoffset - XSPACING, posy + dotoffset - YSPACING);
- } else {
- // Just an angled line
- g.drawLine(curposx + dotoffset, posy + dotoffset, curposx + 2*dotoffset - XSPACING, posy + 2*dotoffset - YSPACING);
- }
- }
-
- // Draw all the nodes and lines in this lane (not variations)
- Color curcolor = g.getColor();
- if (startNode == curMove) {
- g.setColor(Color.green.brighter().brighter());
- }
- if (startNode.previous() != null) {
- g.fillOval(curposx, posy, DOT_DIAM, DOT_DIAM);
- g.setColor(Color.BLACK);
- g.drawOval(curposx, posy, DOT_DIAM, DOT_DIAM);
- } else {
- g.fillRect(curposx, posy, DOT_DIAM, DOT_DIAM);
- g.setColor(Color.BLACK);
- g.drawRect(curposx, posy, DOT_DIAM, DOT_DIAM);
- }
- g.setColor(curcolor);
-
- // Draw main line
- while (cur.next() != null && posy + YSPACING < maxposy) {
- posy += YSPACING;
- cur = cur.next();
- if (cur == curMove) {
- g.setColor(Color.green.brighter().brighter());
- }
- g.fillOval(curposx , posy, DOT_DIAM, DOT_DIAM);
- g.setColor(Color.BLACK);
- g.drawOval(curposx, posy, DOT_DIAM, DOT_DIAM);
- g.setColor(curcolor);
- g.drawLine(curposx + dotoffset, posy-1, curposx + dotoffset , posy - YSPACING + 2*dotoffset+2);
- }
- // Now we have drawn all the nodes in this variation, and has reached the bottom of this variation
- // Move back up, and for each, draw any variations we find
- while (cur.previous() != null && cur != startNode) {
- cur = cur.previous();
- int curwidth = lane;
- // Draw each variation, uses recursion
- for (int i = 1; i < cur.numberOfChildren(); i++) {
- curwidth++;
- // Recursion, depth of recursion will normally not be very deep (one recursion level for every variation that has a variation (sort of))
- drawTree(g, posx, posy, curwidth, maxposy, cur.getVariation(i), i,false);
- }
- posy -= YSPACING;
- }
+ 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
}
- 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
-
- // Use dense tree for saving space if large-subboard
- YSPACING = (Lizzie.config.showLargeSubBoard() ? 20 : 30);
- XSPACING = YSPACING;
-
- // Draw background
- g.setColor(new Color(0, 0, 0, 60));
- g.fillRect(posx, posy, width, height);
-
- // draw edge of panel
- int strokeRadius = 2;
- g.setStroke(new BasicStroke(2 * strokeRadius));
- g.drawLine(posx+strokeRadius, posy+strokeRadius, posx+strokeRadius, posy-strokeRadius+height);
- g.setStroke(new BasicStroke(1));
-
-
- int middleY = posy + height/2;
- int xoffset = 30;
- laneUsageList.clear();
-
- curMove = Lizzie.board.getHistory().getCurrentHistoryNode();
-
- // Is current move a variation? If so, find top of variation
- BoardHistoryNode top = BoardHistoryList.findTop(curMove);
- int curposy = middleY - YSPACING*(curMove.getData().moveNumber - top.getData().moveNumber);
- // Go to very top of tree (visible in assigned area)
- BoardHistoryNode node = top;
- while (curposy > posy + YSPACING && node.previous() != null) {
- node = node.previous();
- curposy -= YSPACING;
- }
- drawTree(g, posx + xoffset, curposy, 0, posy + height, node, 0,true);
+ // Use dense tree for saving space if large-subboard
+ YSPACING = (Lizzie.config.showLargeSubBoard() ? 20 : 30);
+ XSPACING = YSPACING;
+
+ // Draw background
+ g.setColor(new Color(0, 0, 0, 60));
+ g.fillRect(posx, posy, width, height);
+
+ // draw edge of panel
+ int strokeRadius = 2;
+ g.setStroke(new BasicStroke(2 * strokeRadius));
+ g.drawLine(
+ posx + strokeRadius,
+ posy + strokeRadius,
+ posx + strokeRadius,
+ posy - strokeRadius + height);
+ g.setStroke(new BasicStroke(1));
+
+ int middleY = posy + height / 2;
+ int xoffset = 30;
+ laneUsageList.clear();
+
+ curMove = Lizzie.board.getHistory().getCurrentHistoryNode();
+
+ // Is current move a variation? If so, find top of variation
+ BoardHistoryNode top = BoardHistoryList.findTop(curMove);
+ int curposy = middleY - YSPACING * (curMove.getData().moveNumber - top.getData().moveNumber);
+ // Go to very top of tree (visible in assigned area)
+ BoardHistoryNode node = top;
+ while (curposy > posy + YSPACING && node.previous() != null) {
+ node = node.previous();
+ curposy -= YSPACING;
}
+ drawTree(g, posx + xoffset, curposy, 0, posy + height, node, 0, true);
+ }
}
diff --git a/src/main/java/featurecat/lizzie/gui/WinrateGraph.java b/src/main/java/featurecat/lizzie/gui/WinrateGraph.java
index 8f6074a56..3b4915c96 100644
--- a/src/main/java/featurecat/lizzie/gui/WinrateGraph.java
+++ b/src/main/java/featurecat/lizzie/gui/WinrateGraph.java
@@ -4,223 +4,225 @@
import featurecat.lizzie.analysis.Leelaz;
import featurecat.lizzie.rules.BoardHistoryList;
import featurecat.lizzie.rules.BoardHistoryNode;
-
import java.awt.*;
import java.awt.geom.Point2D;
public class WinrateGraph {
- private int DOT_RADIUS = 6;
- private int[] origParams = {0, 0, 0, 0};
- private int[] params = {0, 0, 0, 0, 0};
-
- public void draw(Graphics2D g, int posx, int posy, int width, int height)
- {
- BoardHistoryNode curMove = Lizzie.board.getHistory().getCurrentHistoryNode();
- BoardHistoryNode node = curMove;
-
- // draw background rectangle
- final Paint gradient = new GradientPaint(new Point2D.Float(posx, posy), new Color(0, 0, 0, 150), new Point2D.Float(posx, posy+height), new Color(255, 255, 255, 150));
- final Paint borderGradient = new GradientPaint(new Point2D.Float(posx, posy), new Color(0, 0, 0, 150), new Point2D.Float(posx, posy+height), new Color(255, 255, 255, 150));
-
- Paint original = g.getPaint();
- g.setPaint(gradient);
-
- g.fillRect(posx, posy, width, height);
-
- // draw border
- int strokeRadius = 3;
- g.setStroke(new BasicStroke(2 * strokeRadius));
- g.setPaint(borderGradient);
- g.drawRect(posx+ strokeRadius, posy + strokeRadius, width - 2 * strokeRadius, height- 2 * strokeRadius);
-
- g.setPaint(original);
+ private int DOT_RADIUS = 6;
+ private int[] origParams = {0, 0, 0, 0};
+ private int[] params = {0, 0, 0, 0, 0};
+
+ public void draw(Graphics2D g, int posx, int posy, int width, int height) {
+ BoardHistoryNode curMove = Lizzie.board.getHistory().getCurrentHistoryNode();
+ BoardHistoryNode node = curMove;
+
+ // draw background rectangle
+ final Paint gradient =
+ new GradientPaint(
+ new Point2D.Float(posx, posy),
+ new Color(0, 0, 0, 150),
+ new Point2D.Float(posx, posy + height),
+ new Color(255, 255, 255, 150));
+ final Paint borderGradient =
+ new GradientPaint(
+ new Point2D.Float(posx, posy),
+ new Color(0, 0, 0, 150),
+ new Point2D.Float(posx, posy + height),
+ new Color(255, 255, 255, 150));
+
+ Paint original = g.getPaint();
+ g.setPaint(gradient);
+
+ g.fillRect(posx, posy, width, height);
+
+ // draw border
+ int strokeRadius = 3;
+ g.setStroke(new BasicStroke(2 * strokeRadius));
+ g.setPaint(borderGradient);
+ g.drawRect(
+ posx + strokeRadius,
+ posy + strokeRadius,
+ width - 2 * strokeRadius,
+ height - 2 * strokeRadius);
+
+ g.setPaint(original);
+
+ // record parameters (before resizing) for calculating moveNumber
+ origParams[0] = posx;
+ origParams[1] = posy;
+ origParams[2] = width;
+ origParams[3] = height;
+
+ // resize the box now so it's inside the border
+ posx += 2 * strokeRadius;
+ posy += 2 * strokeRadius;
+ width -= 4 * strokeRadius;
+ height -= 4 * strokeRadius;
+
+ // draw lines marking 50% 60% 70% etc.
+ Stroke dashed =
+ new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0, new float[] {4}, 0);
+ g.setStroke(dashed);
+
+ g.setColor(Color.white);
+ int winRateGridLines = Lizzie.frame.winRateGridLines;
+ for (int i = 1; i <= winRateGridLines; i++) {
+ double percent = i * 100.0 / (winRateGridLines + 1);
+ int y = posy + height - (int) (height * convertWinrate(percent) / 100);
+ g.drawLine(posx, y, posx + width, y);
+ }
- // record parameters (before resizing) for calculating moveNumber
- origParams[0] = posx;
- origParams[1] = posy;
- origParams[2] = width;
- origParams[3] = height;
+ g.setColor(Color.green);
+ g.setStroke(new BasicStroke(3));
+
+ BoardHistoryNode topOfVariation = null;
+ int numMoves = 0;
+ if (!BoardHistoryList.isMainTrunk(curMove)) {
+ // We're in a variation, need to draw both main trunk and variation
+ // Find top of variation
+ topOfVariation = BoardHistoryList.findTop(curMove);
+ // Find depth of main trunk, need this for plot scaling
+ numMoves =
+ BoardHistoryList.getDepth(topOfVariation) + topOfVariation.getData().moveNumber - 1;
+ g.setStroke(dashed);
+ }
- // resize the box now so it's inside the border
- posx += 2*strokeRadius;
- posy += 2*strokeRadius;
- width -= 4*strokeRadius;
- height -= 4*strokeRadius;
+ // Go to end of variation and work our way backwards to the root
+ while (node.next() != null) node = node.next();
+ if (numMoves < node.getData().moveNumber - 1) {
+ numMoves = node.getData().moveNumber - 1;
+ }
- // draw lines marking 50% 60% 70% etc.
- Stroke dashed = new BasicStroke(1, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 0,
- new float[]{4}, 0);
+ if (numMoves < 1) return;
+
+ // Plot
+ width = (int) (width * 0.95); // Leave some space after last move
+ double lastWr = 50;
+ boolean lastNodeOk = false;
+ boolean inFirstPath = true;
+ int movenum = node.getData().moveNumber - 1;
+ int lastOkMove = -1;
+
+ while (node.previous() != null) {
+ double wr = node.getData().winrate;
+ int playouts = node.getData().playouts;
+ if (node == curMove) {
+ Leelaz.WinrateStats stats = Lizzie.leelaz.getWinrateStats();
+ double bwr = stats.maxWinrate;
+ if (bwr >= 0 && stats.totalPlayouts > playouts) {
+ 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);
- int winRateGridLines = Lizzie.frame.winRateGridLines;
- for (int i = 1; i <= winRateGridLines; i++) {
- double percent = i * 100.0 / (winRateGridLines + 1);
- int y = posy + height - (int) (height * convertWinrate(percent) / 100);
- g.drawLine(posx, y, posx + width, y);
+ 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) {
+ wr = 100 - lastWr;
+ } else if (!node.getData().blackToPlay) {
+ wr = 100 - wr;
}
-
- g.setColor(Color.green);
- g.setStroke(new BasicStroke(3));
-
- BoardHistoryNode topOfVariation = null;
- int numMoves = 0;
- if (!BoardHistoryList.isMainTrunk(curMove))
- {
- // We're in a variation, need to draw both main trunk and variation
- // Find top of variation
- topOfVariation = BoardHistoryList.findTop(curMove);
- // Find depth of main trunk, need this for plot scaling
- numMoves = BoardHistoryList.getDepth(topOfVariation) + topOfVariation.getData().moveNumber - 1;
- g.setStroke(dashed);
+ if (Lizzie.frame.isPlayingAgainstLeelaz
+ && Lizzie.frame.playerIsBlack == !node.getData().blackToPlay) {
+ wr = lastWr;
}
- // Go to end of variation and work our way backwards to the root
- while (node.next() != null) node = node.next();
- if (numMoves < node.getData().moveNumber-1) {
- numMoves = node.getData().moveNumber - 1;
- }
+ if (lastNodeOk) g.setColor(Color.green);
+ else g.setColor(Color.blue.darker());
- if (numMoves < 1) return;
-
- // Plot
- width = (int)(width*0.95); // Leave some space after last move
- double lastWr = 50;
- boolean lastNodeOk = false;
- boolean inFirstPath = true;
- int movenum = node.getData().moveNumber - 1;
- int lastOkMove = -1;
-
- while (node.previous() != null)
- {
- double wr = node.getData().winrate;
- int playouts = node.getData().playouts;
- if (node == curMove)
- {
- Leelaz.WinrateStats stats = Lizzie.leelaz.getWinrateStats();
- double bwr = stats.maxWinrate;
- if (bwr >= 0 && stats.totalPlayouts > playouts) {
- 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);
- }
- }
- if (playouts > 0) {
- if (wr < 0)
- {
- wr = 100 - lastWr;
- }
- else if (!node.getData().blackToPlay)
- {
- wr = 100 - wr;
- }
- if (Lizzie.frame.isPlayingAgainstLeelaz && Lizzie.frame.playerIsBlack == !node.getData().blackToPlay) {
- wr = lastWr;
- }
-
- if (lastNodeOk)
- g.setColor(Color.green);
- else
- g.setColor(Color.blue.darker());
-
- if (lastOkMove > 0) {
- g.drawLine(posx + (lastOkMove * width / numMoves),
- posy + height - (int) (convertWinrate(lastWr) * height / 100),
- posx + (movenum * width / numMoves),
- posy + height - (int) (convertWinrate(wr) * height / 100));
- }
-
- if (node == curMove)
- {
- g.setColor(Color.green);
- g.fillOval(posx + (movenum*width/numMoves) - DOT_RADIUS,
- posy + height - (int)(convertWinrate(wr)*height/100) - DOT_RADIUS,
- DOT_RADIUS*2,
- DOT_RADIUS*2);
- }
- lastWr = wr;
- lastNodeOk = true;
- // Check if we were in a variation and has reached the main trunk
- if (node == topOfVariation) {
- // Reached top of variation, go to end of main trunk before continuing
- while (node.next() != null) node = node.next();
- movenum = node.getData().moveNumber - 1;
- lastWr = node.getData().winrate;
- if (!node.getData().blackToPlay)
- lastWr = 100 - lastWr;
- g.setStroke(new BasicStroke(3));
- topOfVariation = null;
- if (node.getData().playouts == 0) {
- lastNodeOk = false;
- }
- inFirstPath = false;
- }
- lastOkMove = lastNodeOk ? movenum : -1;
- } else {
- lastNodeOk = false;
- }
-
- node = node.previous();
- movenum--;
+ if (lastOkMove > 0) {
+ g.drawLine(
+ posx + (lastOkMove * width / numMoves),
+ posy + height - (int) (convertWinrate(lastWr) * height / 100),
+ posx + (movenum * width / numMoves),
+ posy + height - (int) (convertWinrate(wr) * height / 100));
}
- g.setStroke(new BasicStroke(1));
+ if (node == curMove) {
+ g.setColor(Color.green);
+ g.fillOval(
+ posx + (movenum * width / numMoves) - DOT_RADIUS,
+ posy + height - (int) (convertWinrate(wr) * height / 100) - DOT_RADIUS,
+ DOT_RADIUS * 2,
+ DOT_RADIUS * 2);
+ }
+ lastWr = wr;
+ lastNodeOk = true;
+ // Check if we were in a variation and has reached the main trunk
+ if (node == topOfVariation) {
+ // Reached top of variation, go to end of main trunk before continuing
+ while (node.next() != null) node = node.next();
+ movenum = node.getData().moveNumber - 1;
+ lastWr = node.getData().winrate;
+ if (!node.getData().blackToPlay) lastWr = 100 - lastWr;
+ g.setStroke(new BasicStroke(3));
+ topOfVariation = null;
+ if (node.getData().playouts == 0) {
+ lastNodeOk = false;
+ }
+ inFirstPath = false;
+ }
+ lastOkMove = lastNodeOk ? movenum : -1;
+ } else {
+ lastNodeOk = false;
+ }
- // record parameters for calculating moveNumber
- params[0] = posx;
- params[1] = posy;
- params[2] = width;
- params[3] = height;
- params[4] = numMoves;
+ node = node.previous();
+ movenum--;
}
- private double convertWinrate(double winrate) {
- double maxHandicap = 10;
- if (Lizzie.config.handicapInsteadOfWinrate) {
- double handicap = Lizzie.leelaz.winrateToHandicap(winrate);
- // handicap == + maxHandicap => r == 1.0
- // handicap == - maxHandicap => r == 0.0
- double r = 0.5 + handicap / (2 * maxHandicap);
- return Math.max(0, Math.min(r, 1)) * 100;
- } else {
- return winrate;
- }
+ g.setStroke(new BasicStroke(1));
+
+ // record parameters for calculating moveNumber
+ params[0] = posx;
+ params[1] = posy;
+ params[2] = width;
+ params[3] = height;
+ params[4] = numMoves;
+ }
+
+ private double convertWinrate(double winrate) {
+ double maxHandicap = 10;
+ if (Lizzie.config.handicapInsteadOfWinrate) {
+ double handicap = Lizzie.leelaz.winrateToHandicap(winrate);
+ // handicap == + maxHandicap => r == 1.0
+ // handicap == - maxHandicap => r == 0.0
+ double r = 0.5 + handicap / (2 * maxHandicap);
+ return Math.max(0, Math.min(r, 1)) * 100;
+ } else {
+ return winrate;
}
-
- public int moveNumber(int x, int y)
- {
- int origPosx = origParams[0];
- int origPosy = origParams[1];
- int origWidth = origParams[2];
- int origHeight = origParams[3];
- int posx = params[0];
- int posy = params[1];
- int width = params[2];
- int height = params[3];
- int numMoves = params[4];
- if (origPosx <= x && x < origPosx + origWidth &&
- origPosy <= y && y < origPosy + origHeight) {
- // x == posx + (movenum * width / numMoves) ==> movenum = ...
- int movenum = Math.round((x - posx) * numMoves / (float) width);
- // movenum == moveNumber - 1 ==> moveNumber = ...
- return movenum + 1;
- } else {
- return -1;
- }
+ }
+
+ public int moveNumber(int x, int y) {
+ int origPosx = origParams[0];
+ int origPosy = origParams[1];
+ int origWidth = origParams[2];
+ int origHeight = origParams[3];
+ int posx = params[0];
+ int posy = params[1];
+ int width = params[2];
+ int height = params[3];
+ int numMoves = params[4];
+ if (origPosx <= x && x < origPosx + origWidth && origPosy <= y && y < origPosy + origHeight) {
+ // x == posx + (movenum * width / numMoves) ==> movenum = ...
+ int movenum = Math.round((x - posx) * numMoves / (float) width);
+ // movenum == moveNumber - 1 ==> moveNumber = ...
+ return movenum + 1;
+ } else {
+ return -1;
}
+ }
}
diff --git a/src/main/java/featurecat/lizzie/plugin/IPlugin.java b/src/main/java/featurecat/lizzie/plugin/IPlugin.java
deleted file mode 100644
index 190d4b54d..000000000
--- a/src/main/java/featurecat/lizzie/plugin/IPlugin.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package featurecat.lizzie.plugin;
-
-
-import java.awt.Graphics2D;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseEvent;
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLClassLoader;
-
-public interface IPlugin {
-
- public static IPlugin load(String uri) throws Exception {
- URLClassLoader loader = new URLClassLoader(new URL[] {new File(uri).toURI().toURL()});
- IPlugin plugin = (IPlugin) loader.loadClass("plugin.Plugin").newInstance();
- plugin.onInit();
-
- loader.close();
-
- return plugin;
- }
-
- public default void onInit() throws IOException {
-
- }
-
- public default void onMousePressed(MouseEvent e) {
-
- }
-
- public default void onMouseReleased(MouseEvent e) {
-
- }
-
- public default void onMouseMoved(MouseEvent e) {
-
- }
-
- public default void onKeyPressed(KeyEvent e) {
-
- }
-
- public default void onKeyReleased(KeyEvent e) {
-
- }
-
- public default boolean onDraw(Graphics2D g) {
- return false;
- }
-
- public default void onShutdown() throws IOException {
-
- }
-
- public default void onSgfLoaded() {
-
- }
-
- public String getName();
- public String getVersion();
-}
diff --git a/src/main/java/featurecat/lizzie/plugin/PluginManager.java b/src/main/java/featurecat/lizzie/plugin/PluginManager.java
deleted file mode 100644
index 426a36a77..000000000
--- a/src/main/java/featurecat/lizzie/plugin/PluginManager.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package featurecat.lizzie.plugin;
-
-import java.awt.Graphics2D;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseEvent;
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.util.HashSet;
-import java.io.IOException;
-
-import featurecat.lizzie.Lizzie;
-
-public class PluginManager {
- public static HashSet plugins;
-
- public static void loadPlugins() throws IOException {
- if (plugins != null) {
- for (IPlugin plugin : plugins) {
- plugin.onShutdown();
- }
- }
- plugins = new HashSet();
- File path = new File("./plugin/");
- path.mkdirs();
-
- for (File jarFile : path.listFiles()) {
- if (jarFile.isDirectory()) {
- continue;
- }
- try {
- plugins.add(IPlugin.load(jarFile.getPath()));
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
-
- public static void onMousePressed(MouseEvent e) {
- for (IPlugin plugin : plugins) {
- plugin.onMousePressed(e);
- }
- }
-
- public static void onMouseReleased(MouseEvent e) {
- for (IPlugin plugin : plugins) {
- plugin.onMousePressed(e);
- }
- }
-
- public static void onMouseMoved(MouseEvent e) {
- for (IPlugin plugin : plugins) {
- plugin.onMousePressed(e);
- }
- }
-
- public static void onKeyPressed(KeyEvent e) {
- for (IPlugin plugin : plugins) {
- plugin.onKeyPressed(e);
- }
- }
-
- public static void onKeyReleased(KeyEvent e) {
- for (IPlugin plugin : plugins) {
- plugin.onKeyReleased(e);
- }
- }
-
- public static void onShutdown(){
-
- for (IPlugin plugin : plugins) {
- try {plugin.onShutdown();
- } catch(IOException e) {
- e.printStackTrace();
- }
- }
- }
-
- public static void onSgfLoaded() {
- for (IPlugin plugin : plugins) {
- plugin.onSgfLoaded();
- }
- }
-
- public static void onDraw(Graphics2D g0) {
- int width = Lizzie.frame.getWidth();
- int height = Lizzie.frame.getHeight();
- BufferedImage cachedImageParent = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- Graphics2D g = cachedImageParent.createGraphics();
- for (IPlugin plugin : plugins) {
- BufferedImage cachedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
- Graphics2D graphics = cachedImage.createGraphics();
- if (plugin.onDraw(graphics)) {
- g.drawImage(cachedImage, 0, 0, null);
- }
- graphics.dispose();
- }
- g0.drawImage(cachedImageParent, 0, 0, null);
- g.dispose();
- }
-}
diff --git a/src/main/java/featurecat/lizzie/rules/Board.java b/src/main/java/featurecat/lizzie/rules/Board.java
index fcac07cc7..2de079d57 100644
--- a/src/main/java/featurecat/lizzie/rules/Board.java
+++ b/src/main/java/featurecat/lizzie/rules/Board.java
@@ -4,1049 +4,1204 @@
import featurecat.lizzie.analysis.Leelaz;
import featurecat.lizzie.analysis.LeelazListener;
import featurecat.lizzie.analysis.MoveData;
-
-import javax.swing.*;
+import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
-import java.util.Queue;
import java.util.List;
+import java.util.Queue;
+import javax.swing.*;
+import org.json.JSONException;
public class Board implements LeelazListener {
- public static final int BOARD_SIZE = Lizzie.config.config.getJSONObject("ui").optInt("board-size", 19);
- private final static String alphabet = "ABCDEFGHJKLMNOPQRST";
-
- private BoardHistoryList history;
- private Stone[] capturedStones;
-
- private boolean scoreMode;
-
- private boolean analysisMode = false;
- private int playoutsAnalysis = 100;
-
+ public static final int BOARD_SIZE =
+ Lizzie.config.config.getJSONObject("ui").optInt("board-size", 19);
+ private static final String alphabet = "ABCDEFGHJKLMNOPQRST";
- public Board() {
- initialize();
- }
+ private BoardHistoryList history;
+ private Stone[] capturedStones;
- /**
- * 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;
+ private boolean scoreMode;
- boolean blackToPlay = true;
- int[] lastMove = null;
+ private boolean analysisMode = false;
+ private int playoutsAnalysis = 100;
- capturedStones = null;
- scoreMode = false;
+ // Save the node for restore move when in the branch
+ private BoardHistoryNode saveNode = null;
- history = new BoardHistoryList(new BoardData(stones, lastMove, Stone.EMPTY, blackToPlay,
- new Zobrist(), 0, new int[BOARD_SIZE * BOARD_SIZE], 0, 0, 50, 0));
- }
+ public Board() {
+ initialize();
+ }
- /**
- * Calculates the array index of a stone stored at (x, y)
- *
- * @param x the x coordinate
- * @param y the y coordinate
- * @return the array index
- */
- public static int getIndex(int x, int y) {
- return x * Board.BOARD_SIZE + y;
+ /** 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;
}
- /**
- * Converts a named coordinate eg C16, T5, K10, etc to an x and y coordinate
- *
- * @param namedCoordinate a capitalized version of the named coordinate. Must be a valid 19x19 Go coordinate, without I
- * @return an array containing x, followed by y
- */
- public static int[] convertNameToCoordinates(String namedCoordinate) {
- namedCoordinate = namedCoordinate.trim();
- if (namedCoordinate.equalsIgnoreCase("pass")) {
- return null;
- }
- // coordinates take the form C16 A19 Q5 K10 etc. I is not used.
- int x = alphabet.indexOf(namedCoordinate.charAt(0));
- int y = Integer.parseInt(namedCoordinate.substring(1)) - 1;
- return new int[]{x, y};
+ capturedStones = null;
+ scoreMode = false;
+
+ 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);
+ }
+
+ /**
+ * Calculates the array index of a stone stored at (x, y)
+ *
+ * @param x the x coordinate
+ * @param y the y coordinate
+ * @return the array index
+ */
+ public static int getIndex(int x, int y) {
+ return x * Board.BOARD_SIZE + y;
+ }
+
+ /**
+ * Converts a named coordinate eg C16, T5, K10, etc to an x and y coordinate
+ *
+ * @param namedCoordinate a capitalized version of the named coordinate. Must be a valid 19x19 Go
+ * coordinate, without I
+ * @return an array containing x, followed by y
+ */
+ public static int[] convertNameToCoordinates(String namedCoordinate) {
+ namedCoordinate = namedCoordinate.trim();
+ if (namedCoordinate.equalsIgnoreCase("pass")) {
+ return null;
}
-
- /**
- * Converts a x and y coordinate to a named coordinate eg C16, T5, K10, etc
- *
- * @param x x coordinate -- must be valid
- * @param y y coordinate -- must be valid
- * @return a string representing the coordinate
- */
- public static String convertCoordinatesToName(int x, int y) {
- // coordinates take the form C16 A19 Q5 K10 etc. I is not used.
- return alphabet.charAt(x) + "" + (y + 1);
+ // coordinates take the form C16 A19 Q5 K10 etc. I is not used.
+ int x = alphabet.indexOf(namedCoordinate.charAt(0));
+ int y = BOARD_SIZE - Integer.parseInt(namedCoordinate.substring(1));
+ return new int[] {x, y};
+ }
+
+ /**
+ * Converts a x and y coordinate to a named coordinate eg C16, T5, K10, etc
+ *
+ * @param x x coordinate -- must be valid
+ * @param y y coordinate -- must be valid
+ * @return a string representing the coordinate
+ */
+ public static String convertCoordinatesToName(int x, int y) {
+ // coordinates take the form C16 A19 Q5 K10 etc. I is not used.
+ return alphabet.charAt(x) + "" + (BOARD_SIZE - y);
+ }
+
+ /**
+ * Checks if a coordinate is valid
+ *
+ * @param x x coordinate
+ * @param y y coordinate
+ * @return whether or not this coordinate is part of the board
+ */
+ public static boolean isValid(int x, int y) {
+ return x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE;
+ }
+
+ /**
+ * The comment. Thread safe
+ *
+ * @param comment the comment of stone
+ */
+ public void comment(String comment) {
+ synchronized (this) {
+ if (history.getData() != null) {
+ history.getData().comment = comment;
+ }
}
-
- /**
- * Checks if a coordinate is valid
- *
- * @param x x coordinate
- * @param y y coordinate
- * @return whether or not this coordinate is part of the board
- */
- public static boolean isValid(int x, int y) {
- return x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE;
+ }
+
+ /**
+ * The pass. Thread safe
+ *
+ * @param color the type of pass
+ */
+ public void pass(Stone color) {
+ synchronized (this) {
+
+ // check to see if this move is being replayed in history
+ BoardData next = history.getNext();
+ if (next != null && next.lastMove == null) {
+ // this is the next move in history. Just increment history so that we don't erase the
+ // redo's
+ history.next();
+ Lizzie.leelaz.playMove(color, "pass");
+ if (Lizzie.frame.isPlayingAgainstLeelaz)
+ Lizzie.leelaz.genmove((history.isBlacksTurn() ? "B" : "W"));
+
+ return;
+ }
+
+ Stone[] stones = history.getStones().clone();
+ Zobrist zobrist = history.getZobrist();
+ int moveNumber = history.getMoveNumber() + 1;
+ int[] moveNumberList = history.getMoveNumberList().clone();
+
+ // build the new game state
+ BoardData newState =
+ new BoardData(
+ stones,
+ null,
+ color,
+ color.equals(Stone.WHITE),
+ zobrist,
+ moveNumber,
+ moveNumberList,
+ history.getData().blackCaptures,
+ history.getData().whiteCaptures,
+ 0,
+ 0);
+
+ // update leelaz with pass
+ Lizzie.leelaz.playMove(color, "pass");
+ if (Lizzie.frame.isPlayingAgainstLeelaz)
+ Lizzie.leelaz.genmove((history.isBlacksTurn() ? "W" : "B"));
+
+ // update history with pass
+ history.addOrGoto(newState);
+
+ Lizzie.frame.repaint();
}
-
- /**
- * The pass. Thread safe
- *
- * @param color the type of pass
- */
- public void pass(Stone color) {
- synchronized (this) {
-
- // check to see if this move is being replayed in history
- BoardData next = history.getNext();
- if (next != null && next.lastMove == null) {
- // this is the next move in history. Just increment history so that we don't erase the redo's
- history.next();
- Lizzie.leelaz.playMove(color, "pass");
- if (Lizzie.frame.isPlayingAgainstLeelaz)
- Lizzie.leelaz.genmove((history.isBlacksTurn()? "B" : "W"));
-
- return;
- }
-
- Stone[] stones = history.getStones().clone();
- Zobrist zobrist = history.getZobrist();
- int moveNumber = history.getMoveNumber() + 1;
- int[] moveNumberList = history.getMoveNumberList().clone();
-
- // build the new game state
- BoardData newState = new BoardData(stones, null, color, color.equals(Stone.WHITE),
- zobrist, moveNumber, moveNumberList, history.getData().blackCaptures, history.getData().whiteCaptures, 0, 0);
-
- // update leelaz with pass
- Lizzie.leelaz.playMove(color, "pass");
- if (Lizzie.frame.isPlayingAgainstLeelaz)
- Lizzie.leelaz.genmove((history.isBlacksTurn()? "W" : "B"));
-
- // update history with pass
- history.addOrGoto(newState);
-
- Lizzie.frame.repaint();
+ }
+
+ /** overloaded method for pass(), chooses color in an alternating pattern */
+ public void pass() {
+ pass(history.isBlacksTurn() ? Stone.BLACK : Stone.WHITE);
+ }
+
+ /**
+ * Places a stone onto the board representation. Thread safe
+ *
+ * @param x x coordinate
+ * @param y y coordinate
+ * @param color the type of stone to place
+ */
+ public void place(int x, int y, Stone color) {
+ synchronized (this) {
+ if (scoreMode) {
+ // Mark clicked stone as dead
+ Stone[] stones = history.getStones();
+ toggleLiveStatus(capturedStones, x, y);
+ return;
+ }
+
+ if (!isValid(x, y) || history.getStones()[getIndex(x, y)] != Stone.EMPTY) return;
+
+ // Update winrate
+ Leelaz.WinrateStats stats = Lizzie.leelaz.getWinrateStats();
+
+ if (stats.maxWinrate >= 0 && stats.totalPlayouts > history.getData().playouts) {
+ history.getData().winrate = stats.maxWinrate;
+ history.getData().playouts = stats.totalPlayouts;
+ }
+ double nextWinrate = -100;
+ if (history.getData().winrate >= 0) nextWinrate = 100 - history.getData().winrate;
+
+ // check to see if this coordinate is being replayed in history
+ BoardData next = history.getNext();
+ if (next != null && next.lastMove != null && next.lastMove[0] == x && next.lastMove[1] == y) {
+ // this is the next coordinate in history. Just increment history so that we don't erase the
+ // redo's
+ history.next();
+ // should be opposite from the bottom case
+ if (Lizzie.frame.isPlayingAgainstLeelaz
+ && Lizzie.frame.playerIsBlack != getData().blackToPlay) {
+ Lizzie.leelaz.playMove(color, convertCoordinatesToName(x, y));
+ Lizzie.leelaz.genmove((Lizzie.board.getData().blackToPlay ? "W" : "B"));
+ } else if (!Lizzie.frame.isPlayingAgainstLeelaz) {
+ Lizzie.leelaz.playMove(color, convertCoordinatesToName(x, y));
}
- }
-
- /**
- * overloaded method for pass(), chooses color in an alternating pattern
- */
- public void pass() {
- pass(history.isBlacksTurn() ? Stone.BLACK : Stone.WHITE);
- }
-
- /**
- * Places a stone onto the board representation. Thread safe
- *
- * @param x x coordinate
- * @param y y coordinate
- * @param color the type of stone to place
- */
- public void place(int x, int y, Stone color) {
- synchronized (this) {
- if (scoreMode) {
- // Mark clicked stone as dead
- Stone[] stones = history.getStones();
- toggleLiveStatus(capturedStones, x, y);
- return;
- }
-
- if (!isValid(x, y) || history.getStones()[getIndex(x, y)] != Stone.EMPTY)
- return;
-
- // Update winrate
- Leelaz.WinrateStats stats = Lizzie.leelaz.getWinrateStats();
-
- if (stats.maxWinrate >= 0 && stats.totalPlayouts > history.getData().playouts)
- {
- history.getData().winrate = stats.maxWinrate;
- history.getData().playouts = stats.totalPlayouts;
- }
- double nextWinrate = -100;
- if (history.getData().winrate >= 0) nextWinrate = 100 - history.getData().winrate;
-
- // check to see if this coordinate is being replayed in history
- BoardData next = history.getNext();
- if (next != null && next.lastMove != null && next.lastMove[0] == x && next.lastMove[1] == y) {
- // this is the next coordinate in history. Just increment history so that we don't erase the redo's
- history.next();
- // should be opposite from the bottom case
- if (Lizzie.frame.isPlayingAgainstLeelaz && Lizzie.frame.playerIsBlack != getData().blackToPlay) {
- Lizzie.leelaz.playMove(color, convertCoordinatesToName(x, y));
- Lizzie.leelaz.genmove((Lizzie.board.getData().blackToPlay ? "W" : "B"));
- } else if (!Lizzie.frame.isPlayingAgainstLeelaz) {
- Lizzie.leelaz.playMove(color, convertCoordinatesToName(x, y));
- }
- return;
- }
-
- // load a copy of the data at the current node of history
- Stone[] stones = history.getStones().clone();
- Zobrist zobrist = history.getZobrist();
- int[] lastMove = new int[]{x, y}; // keep track of the last played stone
- int moveNumber = history.getMoveNumber() + 1;
- int[] moveNumberList = history.getMoveNumberList().clone();
-
- moveNumberList[Board.getIndex(x, y)] = moveNumber;
-
- // set the stone at (x, y) to color
- stones[getIndex(x, y)] = color;
- zobrist.toggleStone(x, y, color);
-
- // remove enemy stones
- int capturedStones = 0;
- capturedStones += removeDeadChain(x + 1, y, color.opposite(), stones, zobrist);
- capturedStones += removeDeadChain(x, y + 1, color.opposite(), stones, zobrist);
- capturedStones += removeDeadChain(x - 1, y, color.opposite(), stones, zobrist);
- capturedStones += removeDeadChain(x, y - 1, color.opposite(), stones, zobrist);
-
- // check to see if the player made a suicidal coordinate
- int isSuicidal = removeDeadChain(x, y, color, stones, zobrist);
-
- for (int i = 0; i < BOARD_SIZE * BOARD_SIZE; i++) {
- if (stones[i].equals(Stone.EMPTY)) {
- moveNumberList[i] = 0;
- }
- }
-
- int bc = history.getData().blackCaptures;
- int wc = history.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);
-
- // don't make this coordinate if it is suicidal or violates superko
- if (isSuicidal > 0|| history.violatesSuperko(newState))
- return;
-
- // update leelaz with board position
- if (Lizzie.frame.isPlayingAgainstLeelaz && Lizzie.frame.playerIsBlack == getData().blackToPlay) {
- Lizzie.leelaz.playMove(color, convertCoordinatesToName(x, y));
- Lizzie.leelaz.genmove((Lizzie.board.getData().blackToPlay ? "W" : "B"));
- } else if (!Lizzie.frame.isPlayingAgainstLeelaz) {
- Lizzie.leelaz.playMove(color, convertCoordinatesToName(x, y));
- }
-
- // update history with this coordinate
- history.addOrGoto(newState);
-
- Lizzie.frame.repaint();
+ return;
+ }
+
+ // load a copy of the data at the current node of history
+ Stone[] stones = history.getStones().clone();
+ Zobrist zobrist = history.getZobrist();
+ int[] lastMove = new int[] {x, y}; // keep track of the last played stone
+ int moveNumber = history.getMoveNumber() + 1;
+ int[] moveNumberList = history.getMoveNumberList().clone();
+
+ moveNumberList[Board.getIndex(x, y)] = moveNumber;
+
+ // set the stone at (x, y) to color
+ stones[getIndex(x, y)] = color;
+ zobrist.toggleStone(x, y, color);
+
+ // remove enemy stones
+ int capturedStones = 0;
+ capturedStones += removeDeadChain(x + 1, y, color.opposite(), stones, zobrist);
+ capturedStones += removeDeadChain(x, y + 1, color.opposite(), stones, zobrist);
+ capturedStones += removeDeadChain(x - 1, y, color.opposite(), stones, zobrist);
+ capturedStones += removeDeadChain(x, y - 1, color.opposite(), stones, zobrist);
+
+ // check to see if the player made a suicidal coordinate
+ int isSuicidal = removeDeadChain(x, y, color, stones, zobrist);
+
+ for (int i = 0; i < BOARD_SIZE * BOARD_SIZE; i++) {
+ if (stones[i].equals(Stone.EMPTY)) {
+ moveNumberList[i] = 0;
}
+ }
+
+ int bc = history.getData().blackCaptures;
+ int wc = history.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);
+
+ // don't make this coordinate if it is suicidal or violates superko
+ if (isSuicidal > 0 || history.violatesSuperko(newState)) return;
+
+ // update leelaz with board position
+ if (Lizzie.frame.isPlayingAgainstLeelaz
+ && Lizzie.frame.playerIsBlack == getData().blackToPlay) {
+ Lizzie.leelaz.playMove(color, convertCoordinatesToName(x, y));
+ Lizzie.leelaz.genmove((Lizzie.board.getData().blackToPlay ? "W" : "B"));
+ } else if (!Lizzie.frame.isPlayingAgainstLeelaz) {
+ Lizzie.leelaz.playMove(color, convertCoordinatesToName(x, y));
+ }
+
+ // update history with this coordinate
+ history.addOrGoto(newState);
+
+ Lizzie.frame.repaint();
}
-
- /**
- * overloaded method for place(), chooses color in an alternating pattern
- *
- * @param x x coordinate
- * @param y y coordinate
- */
- public void place(int x, int y) {
- place(x, y, history.isBlacksTurn() ? Stone.BLACK : Stone.WHITE);
+ }
+
+ /**
+ * overloaded method for place(), chooses color in an alternating pattern
+ *
+ * @param x x coordinate
+ * @param y y coordinate
+ */
+ public void place(int x, int y) {
+ place(x, y, history.isBlacksTurn() ? Stone.BLACK : Stone.WHITE);
+ }
+
+ /**
+ * overloaded method for place. To be used by the LeelaZ engine. Color is then assumed to be
+ * alternating
+ *
+ * @param namedCoordinate the coordinate to place a stone,
+ */
+ public void place(String namedCoordinate) {
+ if (namedCoordinate.contains("pass")) {
+ pass(history.isBlacksTurn() ? Stone.BLACK : Stone.WHITE);
+ return;
+ } else if (namedCoordinate.contains("resign")) {
+ pass(history.isBlacksTurn() ? Stone.BLACK : Stone.WHITE);
+ return;
}
- /**
- * overloaded method for place. To be used by the LeelaZ engine. Color is then assumed to be alternating
- *
- * @param namedCoordinate the coordinate to place a stone,
- */
- public void place(String namedCoordinate) {
- if (namedCoordinate.contains("pass")) {
- pass(history.isBlacksTurn() ? Stone.BLACK : Stone.WHITE);
- return;
- } else if (namedCoordinate.contains("resign")) {
- pass(history.isBlacksTurn() ? Stone.BLACK : Stone.WHITE);
- return;
- }
-
- int[] coordinates = convertNameToCoordinates(namedCoordinate);
-
- place(coordinates[0], coordinates[1]);
+ int[] coordinates = convertNameToCoordinates(namedCoordinate);
+
+ place(coordinates[0], coordinates[1]);
+ }
+
+ /** for handicap */
+ public void flatten() {
+ Stone[] stones = history.getStones();
+ boolean blackToPlay = history.isBlacksTurn();
+ Zobrist zobrist = history.getZobrist().clone();
+ BoardHistoryList oldHistory = history;
+ history =
+ new BoardHistoryList(
+ new BoardData(
+ stones,
+ null,
+ Stone.EMPTY,
+ blackToPlay,
+ zobrist,
+ 0,
+ new int[BOARD_SIZE * BOARD_SIZE],
+ 0,
+ 0,
+ 0.0,
+ 0));
+ history.setGameInfo(oldHistory.getGameInfo());
+ }
+
+ /**
+ * Removes a chain if it has no liberties
+ *
+ * @param x x coordinate -- needn't be valid
+ * @param y y coordinate -- needn't be valid
+ * @param color the color of the chain to remove
+ * @param stones the stones array to modify
+ * @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) {
+ if (!isValid(x, y) || stones[getIndex(x, y)] != color) return 0;
+
+ boolean hasLiberties = hasLibertiesHelper(x, y, color, stones);
+
+ // either remove stones or reset what hasLibertiesHelper does to the board
+ return cleanupHasLibertiesHelper(x, y, color.recursed(), stones, zobrist, !hasLiberties);
+ }
+
+ /**
+ * Recursively determines if a chain has liberties. Alters the state of stones, so it must be
+ * counteracted
+ *
+ * @param x x coordinate -- needn't be valid
+ * @param y y coordinate -- needn't be valid
+ * @param color the color of the chain to be investigated
+ * @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) {
+ if (!isValid(x, y)) return false;
+
+ if (stones[getIndex(x, y)] == Stone.EMPTY) return true; // a liberty was found
+ else if (stones[getIndex(x, y)] != color)
+ return false; // we are either neighboring an enemy stone, or one we've already recursed on
+
+ // set this index to be the recursed color to keep track of where we've already searched
+ stones[getIndex(x, y)] = color.recursed();
+
+ // set removeDeadChain to true if any recursive calls return true. Recurse in all 4 directions
+ boolean hasLiberties =
+ hasLibertiesHelper(x + 1, y, color, stones)
+ || hasLibertiesHelper(x, y + 1, color, stones)
+ || hasLibertiesHelper(x - 1, y, color, stones)
+ || hasLibertiesHelper(x, y - 1, color, stones);
+
+ return hasLiberties;
+ }
+
+ /**
+ * cleans up what hasLibertyHelper does to the board state
+ *
+ * @param x x coordinate -- needn't be valid
+ * @param y y coordinate -- needn't be valid
+ * @param color color to clean up. Must be a recursed stone type
+ * @param stones the stones array to modify
+ * @param zobrist the zobrist object to modify
+ * @param removeStones if true, we will remove all these stones. otherwise, we will set them to
+ * their unrecursed version
+ * @return number of removed stones
+ */
+ private 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;
+
+ stones[getIndex(x, y)] = removeStones ? Stone.EMPTY : color.unrecursed();
+ if (removeStones) {
+ zobrist.toggleStone(x, y, color.unrecursed());
+ removed = 1;
}
- /**
- * for handicap
- */
- public void flatten() {
- Stone[] stones = history.getStones();
- boolean blackToPlay = history.isBlacksTurn();
- Zobrist zobrist = history.getZobrist().clone();
- history = new BoardHistoryList(new BoardData(stones, null, Stone.EMPTY, blackToPlay, zobrist,
- 0, new int[BOARD_SIZE * BOARD_SIZE], 0, 0, 0.0, 0));
- }
-
- /**
- * Removes a chain if it has no liberties
- *
- * @param x x coordinate -- needn't be valid
- * @param y y coordinate -- needn't be valid
- * @param color the color of the chain to remove
- * @param stones the stones array to modify
- * @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) {
- if (!isValid(x, y) || stones[getIndex(x, y)] != color)
- return 0;
-
- boolean hasLiberties = hasLibertiesHelper(x, y, color, stones);
-
- // either remove stones or reset what hasLibertiesHelper does to the board
- return cleanupHasLibertiesHelper(x, y, color.recursed(), stones, zobrist, !hasLiberties);
+ // use the flood fill algorithm to replace all adjacent recursed stones
+ removed += cleanupHasLibertiesHelper(x + 1, y, color, stones, zobrist, removeStones);
+ removed += cleanupHasLibertiesHelper(x, y + 1, color, stones, zobrist, removeStones);
+ removed += cleanupHasLibertiesHelper(x - 1, y, color, stones, zobrist, removeStones);
+ removed += cleanupHasLibertiesHelper(x, y - 1, color, stones, zobrist, removeStones);
+ return removed;
+ }
+
+ /**
+ * get current board state
+ *
+ * @return the stones array corresponding to the current board state
+ */
+ public Stone[] getStones() {
+ return history.getStones();
+ }
+
+ /**
+ * shows where to mark the last coordinate
+ *
+ * @return the last played stone
+ */
+ public int[] getLastMove() {
+ return history.getLastMove();
+ }
+
+ /**
+ * get the move played in this position
+ *
+ * @return the next move, if any
+ */
+ public int[] getNextMove() {
+ return history.getNextMove();
+ }
+
+ /**
+ * get current board move number
+ *
+ * @return the int array corresponding to the current board move number
+ */
+ public int[] getMoveNumberList() {
+ return history.getMoveNumberList();
+ }
+
+ /** Goes to the next coordinate, thread safe */
+ public boolean nextMove() {
+ 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;
+ }
+ if (history.next() != null) {
+ // update leelaz board position, before updating to next node
+ if (history.getData().lastMove == null) {
+ Lizzie.leelaz.playMove(history.getLastMoveColor(), "pass");
+ } else {
+ Lizzie.leelaz.playMove(
+ history.getLastMoveColor(),
+ convertCoordinatesToName(history.getLastMove()[0], history.getLastMove()[1]));
+ }
+ Lizzie.frame.repaint();
+ return true;
+ }
+ return false;
}
-
- /**
- * Recursively determines if a chain has liberties. Alters the state of stones, so it must be counteracted
- *
- * @param x x coordinate -- needn't be valid
- * @param y y coordinate -- needn't be valid
- * @param color the color of the chain to be investigated
- * @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) {
- if (!isValid(x, y))
- return false;
-
- if (stones[getIndex(x, y)] == Stone.EMPTY)
- return true; // a liberty was found
- else if (stones[getIndex(x, y)] != color)
- return false; // we are either neighboring an enemy stone, or one we've already recursed on
-
- // set this index to be the recursed color to keep track of where we've already searched
- stones[getIndex(x, y)] = color.recursed();
-
- // set removeDeadChain to true if any recursive calls return true. Recurse in all 4 directions
- boolean hasLiberties = hasLibertiesHelper(x + 1, y, color, stones) ||
- hasLibertiesHelper(x, y + 1, color, stones) ||
- hasLibertiesHelper(x - 1, y, color, stones) ||
- hasLibertiesHelper(x, y - 1, color, stones);
-
- return hasLiberties;
+ }
+
+ /**
+ * 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);
}
-
- /**
- * cleans up what hasLibertyHelper does to the board state
- *
- * @param x x coordinate -- needn't be valid
- * @param y y coordinate -- needn't be valid
- * @param color color to clean up. Must be a recursed stone type
- * @param stones the stones array to modify
- * @param zobrist the zobrist object to modify
- * @param removeStones if true, we will remove all these stones. otherwise, we will set them to their unrecursed version
- * @return number of removed stones
- */
- private 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;
-
- stones[getIndex(x, y)] = removeStones ? Stone.EMPTY : color.unrecursed();
- if (removeStones) {
- zobrist.toggleStone(x, y, color.unrecursed());
- removed = 1;
- }
-
- // use the flood fill algorithm to replace all adjacent recursed stones
- removed += cleanupHasLibertiesHelper(x + 1, y, color, stones, zobrist, removeStones);
- removed += cleanupHasLibertiesHelper(x, y + 1, color, stones, zobrist, removeStones);
- removed += cleanupHasLibertiesHelper(x - 1, y, color, stones, zobrist, removeStones);
- removed += cleanupHasLibertiesHelper(x, y - 1, color, stones, zobrist, removeStones);
- return removed;
+ }
+
+ /** 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);
}
-
- /**
- * get current board state
- *
- * @return the stones array corresponding to the current board state
- */
- public Stone[] getStones() {
- return history.getStones();
+ 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());
}
+ }
- /**
- * shows where to mark the last coordinate
- *
- * @return the last played stone
- */
- public int[] getLastMove() {
- return history.getLastMove();
- }
+ /** Restore move number by saved node */
+ public void restoreMoveNumber() {
+ restoreMoveNumber(saveNode);
+ }
- /**
- * get the move played in this position
- *
- * @return the next move, if any
- */
- public int[] getNextMove() {
- return history.getNextMove();
+ /** Restore move number by node */
+ public void restoreMoveNumber(BoardHistoryNode node) {
+ if (node == null) {
+ return;
}
-
- /**
- * get current board move number
- *
- * @return the int array corresponding to the current board move number
- */
- public int[] getMoveNumberList() {
- return history.getMoveNumberList();
+ 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);
+ }
}
-
- /**
- * Goes to the next coordinate, thread safe
- */
- public boolean nextMove() {
- 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;
- }
- if (history.next() != null) {
- // update leelaz board position, before updating to next node
- if (history.getData().lastMove == null) {
- Lizzie.leelaz.playMove(history.getLastMoveColor(), "pass");
- } else {
- Lizzie.leelaz.playMove(history.getLastMoveColor(), convertCoordinatesToName(history.getLastMove()[0], history.getLastMove()[1]));
- }
- Lizzie.frame.repaint();
- return true;
- }
- return false;
+ }
+
+ /** 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);
- }
+ public boolean goToMoveNumber(int moveNumber) {
+ return goToMoveNumberHelper(moveNumber, false);
+ }
- public boolean goToMoveNumberWithinBranch(int moveNumber) {
- return goToMoveNumberHelper(moveNumber, true);
- }
+ public boolean goToMoveNumberWithinBranch(int moveNumber) {
+ return goToMoveNumberHelper(moveNumber, true);
+ }
- public boolean goToMoveNumberBeyondBranch(int moveNumber) {
- // Go to main trunk if current branch is shorter than moveNumber.
- if (moveNumber > history.currentBranchLength() &&
- moveNumber <= history.mainTrunkLength()) {
- goToMoveNumber(0);
+ public boolean goToMoveNumberBeyondBranch(int moveNumber) {
+ // Go to main trunk if current branch is shorter than moveNumber.
+ if (moveNumber > history.currentBranchLength() && moveNumber <= history.mainTrunkLength()) {
+ goToMoveNumber(0);
+ }
+ return goToMoveNumber(moveNumber);
+ }
+
+ public boolean goToMoveNumberHelper(int moveNumber, boolean withinBranch) {
+ int delta = moveNumber - history.getMoveNumber();
+ boolean moved = false;
+ for (int i = 0; i < Math.abs(delta); i++) {
+ if (withinBranch && delta < 0) {
+ BoardHistoryNode curNode = history.getCurrentHistoryNode();
+ if (!curNode.isFirstChild()) {
+ break;
}
- return goToMoveNumber(moveNumber);
+ }
+ if (!(delta > 0 ? nextMove() : previousMove())) {
+ break;
+ }
+ moved = true;
}
-
- public boolean goToMoveNumberHelper(int moveNumber, boolean withinBranch) {
- int delta = moveNumber - history.getMoveNumber();
- boolean moved = false;
- for (int i = 0; i < Math.abs(delta); i++) {
- if (withinBranch && delta < 0) {
- BoardHistoryNode curNode = history.getCurrentHistoryNode();
- if (!curNode.isFirstChild()) {
- break;
- }
- }
- if (!(delta > 0 ? nextMove() : previousMove())) {
- break;
- }
- moved = true;
+ return moved;
+ }
+
+ /** Goes to the next variation, thread safe */
+ public boolean nextVariation(int idx) {
+ synchronized (this) {
+ // Don't update winrate here as this is usually called when jumping between variations
+ if (history.nextVariation(idx) != null) {
+ // update leelaz board position, before updating to next node
+ if (history.getData().lastMove == null) {
+ Lizzie.leelaz.playMove(history.getLastMoveColor(), "pass");
+ } else {
+ Lizzie.leelaz.playMove(
+ history.getLastMoveColor(),
+ convertCoordinatesToName(history.getLastMove()[0], history.getLastMove()[1]));
}
- return moved;
+ Lizzie.frame.repaint();
+ return true;
+ }
+ return false;
}
-
- /**
- * Goes to the next variation, thread safe
- */
- public boolean nextVariation(int idx) {
- synchronized (this) {
- // Don't update winrate here as this is usually called when jumping between variations
- if (history.nextVariation(idx) != null) {
- // update leelaz board position, before updating to next node
- if (history.getData().lastMove == null) {
- Lizzie.leelaz.playMove(history.getLastMoveColor(), "pass");
- } else {
- Lizzie.leelaz.playMove(history.getLastMoveColor(), convertCoordinatesToName(history.getLastMove()[0], history.getLastMove()[1]));
- }
- Lizzie.frame.repaint();
- return true;
+ }
+
+ /*
+ * Moves to next variation (variation to the right) if possible
+ * To move to another variation, the variation must have a move with the same move number as the current move in it.
+ * Note: Will only look within variations that start at the same move on the main trunk/branch, and if on trunk
+ * only in the ones closest
+ */
+ public boolean nextBranch() {
+ synchronized (this) {
+ BoardHistoryNode curNode = history.getCurrentHistoryNode();
+ int curMoveNum = curNode.getData().moveNumber;
+ // First check if there is a branch to move to, if not, stay in same place
+ // Requirement: variaton need to have a move number same as current
+ if (BoardHistoryList.isMainTrunk(curNode)) {
+ // Check if there is a variation tree to the right that is deep enough
+ BoardHistoryNode startVarNode = BoardHistoryList.findChildOfPreviousWithVariation(curNode);
+ if (startVarNode == null) return false;
+ startVarNode = startVarNode.previous();
+ boolean isDeepEnough = false;
+ for (int i = 1; i < startVarNode.numberOfChildren(); i++) {
+ if (BoardHistoryList.hasDepth(
+ startVarNode.getVariation(i), curMoveNum - startVarNode.getData().moveNumber - 1)) {
+ isDeepEnough = true;
+ break;
+ }
+ }
+ if (!isDeepEnough) return false;
+ } else {
+ // We are in a variation, is there some variation to the right?
+ BoardHistoryNode tmpNode = curNode;
+ while (tmpNode != null) {
+ // Try to move to the right
+ BoardHistoryNode prevBranch = BoardHistoryList.findChildOfPreviousWithVariation(tmpNode);
+ int idx = BoardHistoryList.findIndexOfNode(prevBranch.previous(), prevBranch);
+ // Check if there are branches to the right, that are deep enough
+ boolean isDeepEnough = false;
+ for (int i = idx + 1; i < prevBranch.previous().numberOfChildren(); i++) {
+ if (BoardHistoryList.hasDepth(
+ prevBranch.previous().getVariation(i),
+ curMoveNum - prevBranch.previous().getData().moveNumber - 1)) {
+ isDeepEnough = true;
+ break;
}
+ }
+ if (isDeepEnough) break;
+ // Did not find a deep enough branch, move up unless we reached main trunk
+ if (BoardHistoryList.isMainTrunk(prevBranch.previous())) {
+ // No right hand side branch to move too
return false;
+ }
+ tmpNode = prevBranch.previous();
}
- }
-
- /*
- * Moves to next variation (variation to the right) if possible
- * To move to another variation, the variation must have a move with the same move number as the current move in it.
- * Note: Will only look within variations that start at the same move on the main trunk/branch, and if on trunk
- * only in the ones closest
- */
- public boolean nextBranch() {
- synchronized (this) {
- BoardHistoryNode curNode = history.getCurrentHistoryNode();
- int curMoveNum = curNode.getData().moveNumber;
- // First check if there is a branch to move to, if not, stay in same place
- // Requirement: variaton need to have a move number same as current
- if (BoardHistoryList.isMainTrunk(curNode)) {
- // Check if there is a variation tree to the right that is deep enough
- BoardHistoryNode startVarNode = BoardHistoryList.findChildOfPreviousWithVariation(curNode);
- if (startVarNode == null) return false;
- startVarNode = startVarNode.previous();
- boolean isDeepEnough = false;
- for (int i = 1; i < startVarNode.numberOfChildren(); i++)
- {
- if (BoardHistoryList.hasDepth(startVarNode.getVariation(i), curMoveNum - startVarNode.getData().moveNumber - 1)) {
- isDeepEnough = true;
- break;
- }
- }
- if (!isDeepEnough) return false;
- } else {
- // We are in a variation, is there some variation to the right?
- BoardHistoryNode tmpNode = curNode;
- while (tmpNode != null) {
- // Try to move to the right
- BoardHistoryNode prevBranch = BoardHistoryList.findChildOfPreviousWithVariation(tmpNode);
- int idx = BoardHistoryList.findIndexOfNode(prevBranch.previous(), prevBranch);
- // Check if there are branches to the right, that are deep enough
- boolean isDeepEnough = false;
- for (int i = idx + 1; i < prevBranch.previous().numberOfChildren(); i++) {
- if (BoardHistoryList.hasDepth(prevBranch.previous().getVariation(i),
- curMoveNum - prevBranch.previous().getData().moveNumber - 1)) {
- isDeepEnough = true;
- break;
- }
- }
- if (isDeepEnough)
- break;
- // Did not find a deep enough branch, move up unless we reached main trunk
- if (BoardHistoryList.isMainTrunk(prevBranch.previous())) {
- // No right hand side branch to move too
- return false;
- }
- tmpNode = prevBranch.previous();
-
- }
+ }
+
+ // At this point, we know there is somewhere to move to... Move there, one step at the time
+ // (because of Leelaz)
+ // Start moving up the tree until we reach a moves with variations that are deep enough
+ BoardHistoryNode prevNode;
+ int startIdx = 0;
+ while (curNode.previous() != null) {
+ prevNode = curNode;
+ previousMove();
+ curNode = history.getCurrentHistoryNode();
+ startIdx = BoardHistoryList.findIndexOfNode(curNode, prevNode) + 1;
+ if (curNode.numberOfChildren() > 1 && startIdx <= curNode.numberOfChildren()) {
+ // Find the variation that is deep enough
+ boolean isDeepEnough = false;
+ for (int i = startIdx; i < curNode.numberOfChildren(); i++) {
+ if (BoardHistoryList.hasDepth(
+ curNode.getVariation(i), curMoveNum - curNode.getData().moveNumber - 1)) {
+ isDeepEnough = true;
+ break;
}
-
- // At this point, we know there is somewhere to move to... Move there, one step at the time (because of Leelaz)
- // Start moving up the tree until we reach a moves with variations that are deep enough
- BoardHistoryNode prevNode;
- int startIdx = 0;
- while (curNode.previous() != null) {
- prevNode = curNode;
- previousMove();
- curNode = history.getCurrentHistoryNode();
- startIdx = BoardHistoryList.findIndexOfNode(curNode, prevNode) + 1;
- if (curNode.numberOfChildren() > 1 && startIdx <= curNode.numberOfChildren()) {
- // Find the variation that is deep enough
- boolean isDeepEnough = false;
- for (int i = startIdx; i < curNode.numberOfChildren(); i++) {
- if (BoardHistoryList.hasDepth(curNode.getVariation(i), curMoveNum - curNode.getData().moveNumber - 1))
- {
- isDeepEnough = true;
- break;
- }
- }
- if (isDeepEnough)
- break;
- }
- }
- // Now move forward in new branch
- while (curNode.getData().moveNumber < curMoveNum) {
- if (curNode.numberOfChildren() == 1) {
- // One-way street, just move to next
- if (!nextVariation(0)) {
- // Not supposed to happen, fail-safe
- break;
- }
- } else {
- // Has several variations, need to find the closest one that is deep enough
- for (int i = startIdx; i < curNode.numberOfChildren(); i++)
- {
- if (BoardHistoryList.hasDepth(curNode.getVariation(i), curMoveNum - curNode.getData().moveNumber - 1)) {
- nextVariation(i);
- break;
- }
- }
- }
- startIdx = 0;
- curNode = history.getCurrentHistoryNode();
+ }
+ if (isDeepEnough) break;
+ }
+ }
+ // Now move forward in new branch
+ while (curNode.getData().moveNumber < curMoveNum) {
+ if (curNode.numberOfChildren() == 1) {
+ // One-way street, just move to next
+ if (!nextVariation(0)) {
+ // Not supposed to happen, fail-safe
+ break;
+ }
+ } else {
+ // Has several variations, need to find the closest one that is deep enough
+ for (int i = startIdx; i < curNode.numberOfChildren(); i++) {
+ if (BoardHistoryList.hasDepth(
+ curNode.getVariation(i), curMoveNum - curNode.getData().moveNumber - 1)) {
+ nextVariation(i);
+ break;
}
- return true;
+ }
}
+ startIdx = 0;
+ curNode = history.getCurrentHistoryNode();
+ }
+ return true;
}
-
- /*
- * Moves to previous variation (variation to the left) if possible, or back to main trunk
- * To move to another variation, the variation must have the same number of moves in it.
- * If no variation with sufficient moves exist, move back to main trunk.
- * Note: Will always move back to main trunk, even if variation has more moves than main trunk (if that
- * is the case it will move to the last move in the trunk)
- */
- public boolean previousBranch() {
- synchronized (this) {
- BoardHistoryNode curNode = history.getCurrentHistoryNode();
- BoardHistoryNode prevNode;
- int curMoveNum = curNode.getData().moveNumber;
-
- if (BoardHistoryList.isMainTrunk(curNode)) {
- // Not possible to move further to the left, so just return
- return false;
- }
- // We already know we can move back (back to main trunk if necessary), so just start moving
- // Move backwards first
- int depth = 0;
- int startIdx = 0;
- boolean foundBranch = false;
- while (!BoardHistoryList.isMainTrunk(curNode)) {
- prevNode = curNode;
- // Move back
- previousMove();
- curNode = history.getCurrentHistoryNode();
- depth++;
- startIdx = BoardHistoryList.findIndexOfNode(curNode, prevNode);
- // If current move has children, check if any of those are deep enough (starting at the one closest)
- if (curNode.numberOfChildren() > 1 && startIdx != 0) {
- foundBranch = false;
- for (int i = startIdx - 1; i >= 0; i--) {
- if (BoardHistoryList.hasDepth(curNode.getVariation(i), depth-1)) {
- foundBranch = true;
- startIdx = i;
- break;
- }
- }
- if (foundBranch) break; // Found a variation (or main trunk) and it is deep enough
- }
+ }
+
+ /*
+ * Moves to previous variation (variation to the left) if possible, or back to main trunk
+ * To move to another variation, the variation must have the same number of moves in it.
+ * If no variation with sufficient moves exist, move back to main trunk.
+ * Note: Will always move back to main trunk, even if variation has more moves than main trunk (if that
+ * is the case it will move to the last move in the trunk)
+ */
+ public boolean previousBranch() {
+ synchronized (this) {
+ BoardHistoryNode curNode = history.getCurrentHistoryNode();
+ BoardHistoryNode prevNode;
+ int curMoveNum = curNode.getData().moveNumber;
+
+ if (BoardHistoryList.isMainTrunk(curNode)) {
+ // Not possible to move further to the left, so just return
+ return false;
+ }
+ // We already know we can move back (back to main trunk if necessary), so just start moving
+ // Move backwards first
+ int depth = 0;
+ int startIdx = 0;
+ boolean foundBranch = false;
+ while (!BoardHistoryList.isMainTrunk(curNode)) {
+ prevNode = curNode;
+ // Move back
+ previousMove();
+ curNode = history.getCurrentHistoryNode();
+ depth++;
+ startIdx = BoardHistoryList.findIndexOfNode(curNode, prevNode);
+ // If current move has children, check if any of those are deep enough (starting at the one
+ // closest)
+ if (curNode.numberOfChildren() > 1 && startIdx != 0) {
+ foundBranch = false;
+ for (int i = startIdx - 1; i >= 0; i--) {
+ if (BoardHistoryList.hasDepth(curNode.getVariation(i), depth - 1)) {
+ foundBranch = true;
+ startIdx = i;
+ break;
}
+ }
+ if (foundBranch) break; // Found a variation (or main trunk) and it is deep enough
+ }
+ }
- if (!foundBranch)
- {
- // Back at main trunk, and it is not long enough, move forward until we reach the end..
- while (nextVariation(0)) {
-
- };
- return true;
- }
+ if (!foundBranch) {
+ // Back at main trunk, and it is not long enough, move forward until we reach the end..
+ while (nextVariation(0)) {}
- // At this point, we are either back at the main trunk, or on top of variation we know is long enough
- // Move forward
- while (curNode.getData().moveNumber < curMoveNum && curNode.next() != null) {
- if (curNode.numberOfChildren() == 1) {
- // One-way street, just move to next
- if (!nextVariation(0)) {
- // Not supposed to happen...
- break;
- }
- } else {
- foundBranch = false;
- // Several variations to choose between, make sure we select the one closest that is deep enough (if any)
- for (int i = startIdx; i >= 0; i--)
- {
- if (BoardHistoryList.hasDepth(curNode.getVariation(i), curMoveNum - curNode.getData().moveNumber - 1)) {
- nextVariation(i);
- foundBranch = true;
- break;
- }
- }
- if (!foundBranch) {
- // Not supposed to happen, fail-safe
- nextVariation(0);
- }
- }
- // We have now moved one step further down
- curNode = history.getCurrentHistoryNode();
- startIdx = curNode.numberOfChildren() - 1;
+ ;
+ return true;
+ }
+
+ // At this point, we are either back at the main trunk, or on top of variation we know is long
+ // enough
+ // Move forward
+ while (curNode.getData().moveNumber < curMoveNum && curNode.next() != null) {
+ if (curNode.numberOfChildren() == 1) {
+ // One-way street, just move to next
+ if (!nextVariation(0)) {
+ // Not supposed to happen...
+ break;
+ }
+ } else {
+ foundBranch = false;
+ // Several variations to choose between, make sure we select the one closest that is deep
+ // enough (if any)
+ for (int i = startIdx; i >= 0; i--) {
+ if (BoardHistoryList.hasDepth(
+ curNode.getVariation(i), curMoveNum - curNode.getData().moveNumber - 1)) {
+ nextVariation(i);
+ foundBranch = true;
+ break;
}
- return true;
+ }
+ if (!foundBranch) {
+ // Not supposed to happen, fail-safe
+ nextVariation(0);
+ }
}
+ // We have now moved one step further down
+ curNode = history.getCurrentHistoryNode();
+ startIdx = curNode.numberOfChildren() - 1;
+ }
+ return true;
}
+ }
- public void moveBranchUp() {
- synchronized (this) {
- history.getCurrentHistoryNode().topOfBranch().moveUp();
- }
- }
-
- public void moveBranchDown() {
- synchronized (this) {
- history.getCurrentHistoryNode().topOfBranch().moveDown();
- }
+ public void moveBranchUp() {
+ synchronized (this) {
+ history.getCurrentHistoryNode().topOfBranch().moveUp();
}
+ }
- public void deleteMove() {
- synchronized (this) {
- BoardHistoryNode curNode = history.getCurrentHistoryNode();
- if (curNode.next() != null) {
- // Will delete more than one move, ask for confirmation
- int ret = JOptionPane.showConfirmDialog(null, "This will delete all moves and branches after this move", "Delete", JOptionPane.OK_CANCEL_OPTION);
- if (ret != JOptionPane.OK_OPTION) {
- return;
- }
-
- }
- // Clear the board if we're at the top
- if (curNode.previous() == null) {
- clear();
- return;
- }
- previousMove();
- int idx = BoardHistoryList.findIndexOfNode(curNode.previous(), curNode);
- curNode.previous().deleteChild(idx);
- }
+ public void moveBranchDown() {
+ synchronized (this) {
+ history.getCurrentHistoryNode().topOfBranch().moveDown();
}
-
- public void deleteBranch() {
- int originalMoveNumber = history.getMoveNumber();
- undoToChildOfPreviousWithVariation();
- int moveNumberBeforeOperation = history.getMoveNumber();
- deleteMove();
- boolean canceled = (history.getMoveNumber() == moveNumberBeforeOperation);
- if (canceled) {
- goToMoveNumber(originalMoveNumber);
+ }
+
+ public void deleteMove() {
+ synchronized (this) {
+ BoardHistoryNode curNode = history.getCurrentHistoryNode();
+ if (curNode.next() != null) {
+ // Will delete more than one move, ask for confirmation
+ int ret =
+ JOptionPane.showConfirmDialog(
+ null,
+ "This will delete all moves and branches after this move",
+ "Delete",
+ JOptionPane.OK_CANCEL_OPTION);
+ if (ret != JOptionPane.OK_OPTION) {
+ return;
}
+ }
+ // Clear the board if we're at the top
+ if (curNode.previous() == null) {
+ clear();
+ return;
+ }
+ previousMove();
+ int idx = BoardHistoryList.findIndexOfNode(curNode.previous(), curNode);
+ curNode.previous().deleteChild(idx);
}
-
- public BoardData getData() {
- return history.getData();
+ }
+
+ public void deleteBranch() {
+ int originalMoveNumber = history.getMoveNumber();
+ undoToChildOfPreviousWithVariation();
+ int moveNumberBeforeOperation = history.getMoveNumber();
+ deleteMove();
+ boolean canceled = (history.getMoveNumber() == moveNumberBeforeOperation);
+ if (canceled) {
+ goToMoveNumber(originalMoveNumber);
}
-
- public BoardHistoryList getHistory() {
- return history;
+ }
+
+ public BoardData getData() {
+ return history.getData();
+ }
+
+ public BoardHistoryList getHistory() {
+ return history;
+ }
+
+ /** Clears all history and starts over from empty board. */
+ public void clear() {
+ Lizzie.leelaz.sendCommand("clear_board");
+ Lizzie.frame.resetTitle();
+ initialize();
+ }
+
+ /** Goes to the previous coordinate, thread safe */
+ public boolean previousMove() {
+ synchronized (this) {
+ if (inScoreMode()) setScoreMode(false);
+ // 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;
+ }
+ if (history.previous() != null) {
+ Lizzie.leelaz.undo();
+ Lizzie.frame.repaint();
+ return true;
+ }
+ return false;
}
-
- /**
- * Clears all history and starts over from empty board.
- */
- public void clear() {
- Lizzie.leelaz.sendCommand("clear_board");
- Lizzie.frame.resetTitle();
- initialize();
+ }
+
+ public boolean undoToChildOfPreviousWithVariation() {
+ BoardHistoryNode start = history.getCurrentHistoryNode();
+ BoardHistoryNode goal = history.findChildOfPreviousWithVariation(start);
+ if (start == goal) return false;
+ while ((history.getCurrentHistoryNode() != goal) && previousMove()) ;
+ return true;
+ }
+
+ public void setScoreMode(boolean on) {
+ if (on) {
+ // load a copy of the data at the current node of history
+ capturedStones = history.getStones().clone();
+ } else {
+ capturedStones = null;
}
-
- /**
- * Goes to the previous coordinate, thread safe
- */
- public boolean previousMove() {
- synchronized (this) {
- if (inScoreMode()) setScoreMode(false);
- // 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;
- }
- if (history.previous() != null) {
- Lizzie.leelaz.undo();
- Lizzie.frame.repaint();
- return true;
- }
- return false;
- }
+ scoreMode = on;
+ }
+
+ /*
+ * Starting at position stonex, stoney, remove all stones with same color within an area bordered by stones
+ * of opposite color (AKA captured stones)
+ */
+ private void toggleLiveStatus(Stone[] stones, int stonex, int stoney) {
+ Stone[] shdwstones = stones.clone();
+ Stone toggle = stones[getIndex(stonex, stoney)];
+ Stone toggleToo;
+ switch (toggle) {
+ case BLACK:
+ toggleToo = Stone.BLACK_CAPTURED;
+ break;
+ case BLACK_CAPTURED:
+ toggleToo = Stone.BLACK;
+ break;
+ case WHITE:
+ toggleToo = Stone.WHITE_CAPTURED;
+ break;
+ case WHITE_CAPTURED:
+ toggleToo = Stone.WHITE;
+ break;
+ default:
+ return;
}
-
- public boolean undoToChildOfPreviousWithVariation() {
- BoardHistoryNode start = history.getCurrentHistoryNode();
- BoardHistoryNode goal = history.findChildOfPreviousWithVariation(start);
- if (start == goal)
- return false;
- while ((history.getCurrentHistoryNode() != goal) && previousMove()) ;
- return true;
- }
-
- public void setScoreMode(boolean on) {
- if (on) {
- // load a copy of the data at the current node of history
- capturedStones = history.getStones().clone();
+ boolean lastup, lastdown;
+ // This is using a flood fill algorithm that uses a Q instead of being recursive
+ Queue visitQ = new ArrayDeque<>();
+ visitQ.add(new int[] {stonex, stoney});
+ while (!visitQ.isEmpty()) {
+ int[] curpos = visitQ.remove();
+ int x = curpos[0];
+ int y = curpos[1];
+
+ // Move all the way left
+ while (x > 0
+ && (stones[getIndex(x - 1, y)] == Stone.EMPTY || stones[getIndex(x - 1, y)] == toggle)) {
+ x--;
+ }
+
+ lastup = lastdown = false;
+ // Find all stones within empty area line by line (new lines added to Q)
+ while (x < BOARD_SIZE) {
+ if (shdwstones[getIndex(x, y)] == Stone.EMPTY) {
+ shdwstones[getIndex(x, y)] = Stone.DAME; // Too mark that it has been visited
+ } else if (stones[getIndex(x, y)] == toggle) {
+ stones[getIndex(x, y)] = toggleToo;
} else {
- capturedStones = null;
+ break;
}
- scoreMode = on;
- }
-
- /*
- * Starting at position stonex, stoney, remove all stones with same color within an area bordered by stones
- * of opposite color (AKA captured stones)
- */
- private void toggleLiveStatus(Stone[] stones, int stonex, int stoney)
- {
- Stone[] shdwstones = stones.clone();
- Stone toggle = stones[getIndex(stonex, stoney)];
- Stone toggleToo;
- switch (toggle) {
- case BLACK:
- toggleToo = Stone.BLACK_CAPTURED;
- break;
- case BLACK_CAPTURED:
- toggleToo = Stone.BLACK;
- break;
- case WHITE:
- toggleToo = Stone.WHITE_CAPTURED;
- break;
- case WHITE_CAPTURED:
- toggleToo = Stone.WHITE;
- break;
- default:
- return;
+ // Check above
+ if (y - 1 >= 0
+ && (shdwstones[getIndex(x, y - 1)] == Stone.EMPTY
+ || stones[getIndex(x, y - 1)] == toggle)) {
+ if (!lastup) visitQ.add(new int[] {x, y - 1});
+ lastup = true;
+ } else {
+ lastup = false;
}
- boolean lastup, lastdown;
- // This is using a flood fill algorithm that uses a Q instead of being recursive
- Queue visitQ = new ArrayDeque<>();
- visitQ.add(new int[]{stonex, stoney});
- while (!visitQ.isEmpty())
- {
- int[] curpos = visitQ.remove();
- int x = curpos[0];
- int y = curpos[1];
-
- // Move all the way left
- while (x > 0 && (stones[getIndex(x-1, y)] == Stone.EMPTY || stones[getIndex(x-1, y)] == toggle))
- {
- x--;
- }
-
- lastup = lastdown = false;
- // Find all stones within empty area line by line (new lines added to Q)
- while (x < BOARD_SIZE ) {
- if (shdwstones[getIndex(x, y)] == Stone.EMPTY) {
- shdwstones[getIndex(x, y)] = Stone.DAME; // Too mark that it has been visited
- } else if (stones[getIndex(x, y)] == toggle) {
- stones[getIndex(x, y)] = toggleToo;
- } else {
- break;
- }
- // Check above
- if (y - 1 >= 0 && (shdwstones[getIndex(x, y - 1)] == Stone.EMPTY || stones[getIndex(x, y - 1)] == toggle)) {
- if (!lastup)
- visitQ.add(new int[]{x, y - 1});
- lastup = true;
- } else {
- lastup = false;
- }
- // Check below
- if (y + 1 < BOARD_SIZE && (shdwstones[getIndex(x, y + 1)] == Stone.EMPTY || stones[getIndex(x, y + 1)] == toggle)) {
- if (!lastdown)
- visitQ.add(new int[]{x, y + 1});
- lastdown = true;
- } else {
- lastdown = false;
- }
- x++;
- }
+ // Check below
+ if (y + 1 < BOARD_SIZE
+ && (shdwstones[getIndex(x, y + 1)] == Stone.EMPTY
+ || stones[getIndex(x, y + 1)] == toggle)) {
+ if (!lastdown) visitQ.add(new int[] {x, y + 1});
+ lastdown = true;
+ } else {
+ lastdown = false;
}
- Lizzie.frame.repaint();
+ x++;
+ }
}
-
- /*
- * Check if a point on the board is empty or contains a captured stone
- */
- private boolean emptyOrCaptured(Stone[] stones, int x, int y) {
- int curidx = getIndex(x, y);
- if (stones[curidx] == Stone.EMPTY || stones[curidx] == Stone.BLACK_CAPTURED || stones[curidx] == Stone.WHITE_CAPTURED)
- return true;
- return false;
- }
-
- /*
- * Starting from startx, starty, mark all empty points within area as either white, black or dame.
- * If two stones of opposite color (neither marked as captured) is encountered, the area is dame.
- *
- * @return A stone with color white, black or dame
- */
- private Stone markEmptyArea(Stone[] stones, int startx, int starty) {
- Stone[] shdwstones = stones.clone();
- Stone found = Stone.EMPTY; // Found will either be black or white, or dame if both are found in area
- boolean lastup, lastdown;
- Queue visitQ = new ArrayDeque<>();
- visitQ.add(new int[]{startx, starty});
- Deque allPoints = new ArrayDeque<>();
- // Check one line at the time, new lines added to visitQ
- while (!visitQ.isEmpty())
- {
- int[] curpos = visitQ.remove();
- int x = curpos[0];
- int y = curpos[1];
- if (!emptyOrCaptured(shdwstones, x, y)) {
- continue;
+ Lizzie.frame.repaint();
+ }
+
+ /*
+ * Check if a point on the board is empty or contains a captured stone
+ */
+ private boolean emptyOrCaptured(Stone[] stones, int x, int y) {
+ int curidx = getIndex(x, y);
+ if (stones[curidx] == Stone.EMPTY
+ || stones[curidx] == Stone.BLACK_CAPTURED
+ || stones[curidx] == Stone.WHITE_CAPTURED) return true;
+ return false;
+ }
+
+ /*
+ * Starting from startx, starty, mark all empty points within area as either white, black or dame.
+ * If two stones of opposite color (neither marked as captured) is encountered, the area is dame.
+ *
+ * @return A stone with color white, black or dame
+ */
+ private Stone markEmptyArea(Stone[] stones, int startx, int starty) {
+ Stone[] shdwstones = stones.clone();
+ Stone found =
+ Stone.EMPTY; // Found will either be black or white, or dame if both are found in area
+ boolean lastup, lastdown;
+ Queue visitQ = new ArrayDeque<>();
+ visitQ.add(new int[] {startx, starty});
+ Deque allPoints = new ArrayDeque<>();
+ // Check one line at the time, new lines added to visitQ
+ while (!visitQ.isEmpty()) {
+ int[] curpos = visitQ.remove();
+ int x = curpos[0];
+ int y = curpos[1];
+ if (!emptyOrCaptured(shdwstones, x, y)) {
+ continue;
+ }
+ // Move all the way left
+ while (x > 0 && emptyOrCaptured(shdwstones, x - 1, y)) {
+ x--;
+ }
+ // Are we on the border, or do we have a stone to the left?
+ if (x > 0 && shdwstones[getIndex(x - 1, y)] != found) {
+ if (found == Stone.EMPTY) found = shdwstones[getIndex(x - 1, y)];
+ else found = Stone.DAME;
+ }
+
+ lastup = lastdown = false;
+ while (x < BOARD_SIZE && emptyOrCaptured(shdwstones, x, y)) {
+ // Check above
+ if (y - 1 >= 0 && shdwstones[getIndex(x, y - 1)] != Stone.DAME) {
+ if (emptyOrCaptured(shdwstones, x, y - 1)) {
+ if (!lastup) visitQ.add(new int[] {x, y - 1});
+ lastup = true;
+ } else {
+ lastup = false;
+ if (found != shdwstones[getIndex(x, y - 1)]) {
+ if (found == Stone.EMPTY) {
+ found = shdwstones[getIndex(x, y - 1)];
+ } else {
+ found = Stone.DAME;
+ }
}
- // Move all the way left
- while (x > 0 && emptyOrCaptured(shdwstones, x - 1, y))
- {
- x--;
- }
- // Are we on the border, or do we have a stone to the left?
- if (x > 0 && shdwstones[getIndex(x - 1, y)] != found) {
- if (found == Stone.EMPTY) found = shdwstones[getIndex(x - 1, y)];
- else found = Stone.DAME;
- }
-
- lastup = lastdown = false;
- while (x < BOARD_SIZE && emptyOrCaptured(shdwstones, x, y)) {
- // Check above
- if (y - 1 >= 0 && shdwstones[getIndex(x, y - 1)] != Stone.DAME) {
- if (emptyOrCaptured(shdwstones, x, y - 1)) {
- if (!lastup)
- visitQ.add(new int[]{x, y - 1});
- lastup = true;
- } else {
- lastup = false;
- if (found != shdwstones[getIndex(x, y - 1)]) {
- if (found == Stone.EMPTY) {
- found = shdwstones[getIndex(x, y - 1)];
- }
- else {
- found = Stone.DAME;
- }
- }
- }
- }
- // Check below
- if (y + 1 < BOARD_SIZE && shdwstones[getIndex(x, y + 1)] != Stone.DAME) {
- if (emptyOrCaptured(shdwstones, x, y + 1)) {
- if (!lastdown) {
- visitQ.add(new int[]{x, y + 1});
- }
- lastdown = true;
- } else {
- lastdown = false;
- if (found != shdwstones[getIndex(x, y + 1)]) {
- if (found == Stone.EMPTY) {
- found = shdwstones[getIndex(x, y + 1)];
- }
- else {
- found = Stone.DAME;
- }
- }
- }
- }
- // Add current stone to empty area and mark as visited
- if (shdwstones[getIndex(x,y)] == Stone.EMPTY)
- allPoints.add(getIndex(x,y));
-
- // Use dame stone to mark as visited
- shdwstones[getIndex(x, y)] = Stone.DAME;
- x++;
+ }
+ }
+ // Check below
+ if (y + 1 < BOARD_SIZE && shdwstones[getIndex(x, y + 1)] != Stone.DAME) {
+ if (emptyOrCaptured(shdwstones, x, y + 1)) {
+ if (!lastdown) {
+ visitQ.add(new int[] {x, y + 1});
}
- // At this point x is at the edge of the board or on a stone
- if (x < BOARD_SIZE && shdwstones[getIndex(x, y)] != found) {
- if (found == Stone.EMPTY) found = shdwstones[getIndex(x, y )];
- else found = Stone.DAME;
+ lastdown = true;
+ } else {
+ lastdown = false;
+ if (found != shdwstones[getIndex(x, y + 1)]) {
+ if (found == Stone.EMPTY) {
+ found = shdwstones[getIndex(x, y + 1)];
+ } else {
+ found = Stone.DAME;
+ }
}
+ }
}
- // Finally mark all points as black or white captured if they were surronded by white or black
- if (found == Stone.WHITE) found = Stone.WHITE_POINT;
- else if (found == Stone.BLACK) found = Stone.BLACK_POINT;
- // else found == DAME and will be set as this.
- while (!allPoints.isEmpty()) {
- int idx = allPoints.remove();
- stones[idx] = found;
- }
- return found;
+ // Add current stone to empty area and mark as visited
+ if (shdwstones[getIndex(x, y)] == Stone.EMPTY) allPoints.add(getIndex(x, y));
+
+ // Use dame stone to mark as visited
+ shdwstones[getIndex(x, y)] = Stone.DAME;
+ x++;
+ }
+ // At this point x is at the edge of the board or on a stone
+ if (x < BOARD_SIZE && shdwstones[getIndex(x, y)] != found) {
+ if (found == Stone.EMPTY) found = shdwstones[getIndex(x, y)];
+ else found = Stone.DAME;
+ }
+ }
+ // Finally mark all points as black or white captured if they were surronded by white or black
+ if (found == Stone.WHITE) found = Stone.WHITE_POINT;
+ else if (found == Stone.BLACK) found = Stone.BLACK_POINT;
+ // else found == DAME and will be set as this.
+ while (!allPoints.isEmpty()) {
+ int idx = allPoints.remove();
+ stones[idx] = found;
}
+ return found;
+ }
- /*
- * Mark all empty points on board as black point, white point or dame
- */
- public Stone[] scoreStones() {
+ /*
+ * Mark all empty points on board as black point, white point or dame
+ */
+ public Stone[] scoreStones() {
- Stone[] scoreStones = capturedStones.clone();
+ Stone[] scoreStones = capturedStones.clone();
- for (int i = 0; i < BOARD_SIZE; i++) {
- for (int j = 0; j < BOARD_SIZE; j++) {
- if (scoreStones[getIndex(i, j)] == Stone.EMPTY) {
- markEmptyArea(scoreStones, i, j);
- }
- }
+ for (int i = 0; i < BOARD_SIZE; i++) {
+ for (int j = 0; j < BOARD_SIZE; j++) {
+ if (scoreStones[getIndex(i, j)] == Stone.EMPTY) {
+ markEmptyArea(scoreStones, i, j);
}
- return scoreStones;
+ }
}
-
- /*
- * Count score for whole board, including komi and captured stones
- */
- public double[] getScore(Stone[] scoreStones) {
- double score[] = new double[] {getData().blackCaptures, getData().whiteCaptures + getHistory().getGameInfo().getKomi()};
- for (int i = 0; i < BOARD_SIZE; i++) {
- for (int j = 0; j < BOARD_SIZE; j++) {
- switch (scoreStones[getIndex(i, j)]) {
- case BLACK_POINT:
- score[0]++;
- break;
- case BLACK_CAPTURED:
- score[1] += 2;
- break;
-
- case WHITE_POINT:
- score[1]++;
- break;
- case WHITE_CAPTURED:
- score[0] += 2;
- break;
-
- }
- }
+ return scoreStones;
+ }
+
+ /*
+ * Count score for whole board, including komi and captured stones
+ */
+ public double[] getScore(Stone[] scoreStones) {
+ double score[] =
+ new double[] {
+ getData().blackCaptures, getData().whiteCaptures + getHistory().getGameInfo().getKomi()
+ };
+ for (int i = 0; i < BOARD_SIZE; i++) {
+ for (int j = 0; j < BOARD_SIZE; j++) {
+ switch (scoreStones[getIndex(i, j)]) {
+ case BLACK_POINT:
+ score[0]++;
+ break;
+ case BLACK_CAPTURED:
+ score[1] += 2;
+ break;
+
+ case WHITE_POINT:
+ score[1]++;
+ break;
+ case WHITE_CAPTURED:
+ score[0] += 2;
+ break;
}
- return score;
+ }
}
-
- public boolean inAnalysisMode() {
- return analysisMode;
- }
-
- public boolean inScoreMode() {
- return scoreMode;
+ return score;
+ }
+
+ public boolean inAnalysisMode() {
+ return analysisMode;
+ }
+
+ public boolean inScoreMode() {
+ return scoreMode;
+ }
+
+ public void toggleAnalysis() {
+ if (analysisMode) {
+ Lizzie.leelaz.removeListener(this);
+ analysisMode = false;
+ } else {
+ if (getNextMove() == null) return;
+ String answer =
+ JOptionPane.showInputDialog(
+ "# playouts for analysis (e.g. 100 (fast) or 50000 (slow)): ");
+ if (answer == null) return;
+ try {
+ playoutsAnalysis = Integer.parseInt(answer);
+ } catch (NumberFormatException err) {
+ System.out.println("Not a valid number");
+ return;
+ }
+ Lizzie.leelaz.addListener(this);
+ analysisMode = true;
+ if (!Lizzie.leelaz.isPondering()) Lizzie.leelaz.togglePonder();
}
-
- public void toggleAnalysis() {
- if (analysisMode) {
- Lizzie.leelaz.removeListener(this);
- analysisMode = false;
- } else {
- if (getNextMove() == null) return;
- String answer = JOptionPane.showInputDialog("# playouts for analysis (e.g. 100 (fast) or 50000 (slow)): ");
- if (answer == null) return;
- try {
- playoutsAnalysis = Integer.parseInt(answer);
- } catch (NumberFormatException err) {
- System.out.println("Not a valid number");
- return;
- }
- Lizzie.leelaz.addListener(this);
- analysisMode = true;
- if (!Lizzie.leelaz.isPondering()) Lizzie.leelaz.togglePonder();
+ }
+
+ public void bestMoveNotification(List bestMoves) {
+ if (analysisMode) {
+ boolean isSuccessivePass =
+ (history.getPrevious() != null
+ && history.getPrevious().lastMove == null
+ && getLastMove() == null);
+ // Note: We cannot replace this history.getNext() with getNextMove()
+ // because the latter returns null if the next move is "pass".
+ if (history.getNext() == null || isSuccessivePass) {
+ // Reached the end...
+ toggleAnalysis();
+ } else if (bestMoves == null || bestMoves.size() == 0) {
+ // If we get empty list, something strange happened, ignore notification
+ } else {
+ // sum the playouts to proceed like leelaz's --visits option.
+ int sum = 0;
+ for (MoveData move : bestMoves) {
+ sum += move.playouts;
}
+ if (sum >= playoutsAnalysis) {
+ nextMove();
+ }
+ }
+ }
+ }
+
+ public void autosave() {
+ if (autosaveToMemory()) {
+ try {
+ Lizzie.config.persist();
+ } catch (IOException err) {
+ }
}
+ }
- public void bestMoveNotification(List bestMoves) {
- if (analysisMode) {
- boolean isSuccessivePass = (history.getPrevious() != null &&
- history.getPrevious().lastMove == null &&
- getLastMove() == null);
- // Note: We cannot replace this history.getNext() with getNextMove()
- // because the latter returns null if the next move is "pass".
- if (history.getNext() == null || isSuccessivePass) {
- // Reached the end...
- toggleAnalysis();
- } else if (bestMoves == null || bestMoves.size() == 0) {
- // If we get empty list, something strange happened, ignore notification
- } else {
- // sum the playouts to proceed like leelaz's --visits option.
- int sum = 0;
- for (MoveData move : bestMoves) {
- sum += move.playouts;
- }
- if (sum >= playoutsAnalysis) {
- nextMove();
- }
- }
- }
+ public boolean autosaveToMemory() {
+ try {
+ String sgf = SGFParser.saveToString();
+ if (sgf.equals(Lizzie.config.persisted.getString("autosave"))) {
+ return false;
+ }
+ Lizzie.config.persisted.put("autosave", sgf);
+ } catch (Exception err) { // IOException or JSONException
+ return false;
+ }
+ return true;
+ }
+
+ public void resumePreviousGame() {
+ try {
+ SGFParser.loadFromString(Lizzie.config.persisted.getString("autosave"));
+ while (nextMove()) ;
+ } catch (JSONException err) {
}
+ }
}
diff --git a/src/main/java/featurecat/lizzie/rules/BoardData.java b/src/main/java/featurecat/lizzie/rules/BoardData.java
index 1fce99366..9b266ceef 100644
--- a/src/main/java/featurecat/lizzie/rules/BoardData.java
+++ b/src/main/java/featurecat/lizzie/rules/BoardData.java
@@ -1,37 +1,49 @@
package featurecat.lizzie.rules;
public class BoardData {
- public int moveNumber;
- public int[] lastMove;
- public int[] moveNumberList;
- public boolean blackToPlay;
-
- public Stone lastMoveColor;
- public Stone[] stones;
- public Zobrist zobrist;
-
- public boolean verify;
-
- public double winrate;
- public int playouts;
-
- public int blackCaptures;
- public int whiteCaptures;
-
- public BoardData(Stone[] stones, int[] lastMove, Stone lastMoveColor, boolean blackToPlay, Zobrist zobrist, int moveNumber, int[] moveNumberList, int blackCaptures, int whiteCaptures, double winrate, int playouts) {
- this.moveNumber = moveNumber;
- this.lastMove = lastMove;
- this.moveNumberList = moveNumberList;
- this.blackToPlay = blackToPlay;
-
- this.lastMoveColor = lastMoveColor;
- this.stones = stones;
- this.zobrist = zobrist;
- this.verify = false;
-
- this.winrate = winrate;
- this.playouts = playouts;
- this.blackCaptures = blackCaptures;
- this.whiteCaptures = whiteCaptures;
- }
+ public int moveNumber;
+ public int[] lastMove;
+ public int[] moveNumberList;
+ public boolean blackToPlay;
+
+ public Stone lastMoveColor;
+ public Stone[] stones;
+ public Zobrist zobrist;
+ public boolean verify;
+
+ public double winrate;
+ public int playouts;
+ public int blackCaptures;
+ public int whiteCaptures;
+
+ // Comment in the Sgf move
+ public String comment;
+
+ public BoardData(
+ Stone[] stones,
+ int[] lastMove,
+ Stone lastMoveColor,
+ boolean blackToPlay,
+ Zobrist zobrist,
+ int moveNumber,
+ int[] moveNumberList,
+ int blackCaptures,
+ int whiteCaptures,
+ double winrate,
+ int playouts) {
+ this.moveNumber = moveNumber;
+ this.lastMove = lastMove;
+ this.moveNumberList = moveNumberList;
+ this.blackToPlay = blackToPlay;
+
+ this.lastMoveColor = lastMoveColor;
+ this.stones = stones;
+ this.zobrist = zobrist;
+ this.verify = false;
+
+ this.winrate = winrate;
+ this.playouts = playouts;
+ this.blackCaptures = blackCaptures;
+ this.whiteCaptures = whiteCaptures;
+ }
}
diff --git a/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java b/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java
index aaf6764d5..3d4027a16 100644
--- a/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java
+++ b/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java
@@ -1,341 +1,319 @@
package featurecat.lizzie.rules;
-import java.util.List;
-
import featurecat.lizzie.analysis.GameInfo;
+import java.util.List;
-/**
- * Linked list data structure to store board history
- */
+/** Linked list data structure to store board history */
public class BoardHistoryList {
- private GameInfo gameInfo;
- private BoardHistoryNode head;
-
- /**
- * Initialize a new board history list, whose first node is data
- *
- * @param data the data to be stored for the first entry
- */
- public BoardHistoryList(BoardData data) {
- head = new BoardHistoryNode(data);
- gameInfo = new GameInfo();
- }
-
- public GameInfo getGameInfo() {
- return gameInfo;
- }
-
- /**
- * Clear history.
- */
- public void clear() {
- head.clear();
- }
-
- /**
- * Add new data after head. Overwrites any data that may have been stored after head.
- *
- * @param data the data to add
- */
- public void add(BoardData data) {
- head = head.add(new BoardHistoryNode(data));
- }
-
- public void addOrGoto(BoardData data) {
- head = head.addOrGoto(data);
- }
-
- /**
- * moves the pointer to the left, returns the data stored there
- *
- * @return data of previous node, null if there is no previous node
- */
- public BoardData previous() {
- if (head.previous() == null)
- return null;
- else
- head = head.previous();
-
- return head.getData();
- }
-
- public void toStart() {
- while (previous() != null);
- }
-
- /**
- * moves the pointer to the right, returns the data stored there
- *
- * @return the data of next node, null if there is no next node
- */
- public BoardData next() {
- if (head.next() == null)
- return null;
- else
- head = head.next();
-
- return head.getData();
- }
-
- /**
- * moves the pointer to the variation number idx, returns the data stored there
- *
- * @return the data of next node, null if there is no variaton with index
- */
- public BoardData nextVariation(int idx) {
- if (head.getVariation(idx) == null)
- return null;
- else
- head = head.getVariation(idx);
-
- return head.getData();
- }
-
- /**
- * Does not change the pointer position
- *
- * @return the data stored at the next index. null if not present
- */
- public BoardData getNext() {
- if (head.next() == null)
- return null;
- else
- return head.next().getData();
- }
-
- /**
- * @return nexts for display
- */
- public List getNexts() {
- return head.getNexts();
- }
-
- /**
- * Does not change the pointer position
- *
- * @return the data stored at the previous index. null if not present
- */
- public BoardData getPrevious() {
- if (head.previous() == null)
- return null;
- else
- return head.previous().getData();
- }
-
- /**
- * @return the data of the current node
- */
- public BoardData getData() {
- return head.getData();
- }
-
- public void setStone(int[] coordinates, Stone stone) {
- int index = Board.getIndex(coordinates[0], coordinates[1]);
- head.getData().stones[index] = stone;
- head.getData().zobrist.toggleStone(coordinates[0], coordinates[1], stone);
- }
-
- public Stone[] getStones() {
- return head.getData().stones;
- }
-
- public int[] getLastMove() {
- return head.getData().lastMove;
- }
-
- public int[] getNextMove() {
- BoardData next = getNext();
- if (next == null)
- return null;
- else
- return next.lastMove;
- }
-
- public Stone getLastMoveColor() {
- return head.getData().lastMoveColor;
- }
-
- public boolean isBlacksTurn() {
- return head.getData().blackToPlay;
- }
-
- public Zobrist getZobrist() {
- return head.getData().zobrist.clone();
- }
-
- public int getMoveNumber() {
- return head.getData().moveNumber;
- }
-
- public int[] getMoveNumberList() {
- return head.getData().moveNumberList;
- }
-
- public BoardHistoryNode getCurrentHistoryNode() {
- return head;
- }
-
- /**
- * @param data the board position to check against superko
- * @return whether or not the given position violates the superko rule at the head's state
- */
- public boolean violatesSuperko(BoardData data) {
- BoardHistoryNode head = this.head;
-
- // check to see if this position has occurred before
- while (head.previous() != null) {
- // if two zobrist hashes are equal, and it's the same player to coordinate, they are the same position
- if (data.zobrist.equals(head.getData().zobrist) && data.blackToPlay == head.getData().blackToPlay)
- return true;
-
- head = head.previous();
+ private GameInfo gameInfo;
+ private BoardHistoryNode head;
+
+ /**
+ * Initialize a new board history list, whose first node is data
+ *
+ * @param data the data to be stored for the first entry
+ */
+ public BoardHistoryList(BoardData data) {
+ head = new BoardHistoryNode(data);
+ gameInfo = new GameInfo();
+ }
+
+ public GameInfo getGameInfo() {
+ return gameInfo;
+ }
+
+ public void setGameInfo(GameInfo gameInfo) {
+ this.gameInfo = gameInfo;
+ }
+
+ public BoardHistoryList shallowCopy() {
+ BoardHistoryList copy = new BoardHistoryList(null);
+ copy.head = head;
+ copy.gameInfo = gameInfo;
+ return copy;
+ }
+
+ /** Clear history. */
+ public void clear() {
+ head.clear();
+ }
+
+ /**
+ * Add new data after head. Overwrites any data that may have been stored after head.
+ *
+ * @param data the data to add
+ */
+ public void add(BoardData data) {
+ head = head.add(new BoardHistoryNode(data));
+ }
+
+ public void addOrGoto(BoardData data) {
+ head = head.addOrGoto(data);
+ }
+
+ /**
+ * moves the pointer to the left, returns the data stored there
+ *
+ * @return data of previous node, null if there is no previous node
+ */
+ public BoardData previous() {
+ if (head.previous() == null) return null;
+ else head = head.previous();
+
+ return head.getData();
+ }
+
+ public void toStart() {
+ while (previous() != null) ;
+ }
+
+ /**
+ * moves the pointer to the right, returns the data stored there
+ *
+ * @return the data of next node, null if there is no next node
+ */
+ public BoardData next() {
+ if (head.next() == null) return null;
+ else head = head.next();
+
+ return head.getData();
+ }
+
+ /**
+ * moves the pointer to the variation number idx, returns the data stored there
+ *
+ * @return the data of next node, null if there is no variaton with index
+ */
+ public BoardData nextVariation(int idx) {
+ if (head.getVariation(idx) == null) return null;
+ else head = head.getVariation(idx);
+
+ return head.getData();
+ }
+
+ /**
+ * Does not change the pointer position
+ *
+ * @return the data stored at the next index. null if not present
+ */
+ public BoardData getNext() {
+ if (head.next() == null) return null;
+ else return head.next().getData();
+ }
+
+ /** @return nexts for display */
+ public List getNexts() {
+ return head.getNexts();
+ }
+
+ /**
+ * Does not change the pointer position
+ *
+ * @return the data stored at the previous index. null if not present
+ */
+ public BoardData getPrevious() {
+ if (head.previous() == null) return null;
+ else return head.previous().getData();
+ }
+
+ /** @return the data of the current node */
+ public BoardData getData() {
+ return head.getData();
+ }
+
+ public void setStone(int[] coordinates, Stone stone) {
+ int index = Board.getIndex(coordinates[0], coordinates[1]);
+ head.getData().stones[index] = stone;
+ head.getData().zobrist.toggleStone(coordinates[0], coordinates[1], stone);
+ }
+
+ public Stone[] getStones() {
+ return head.getData().stones;
+ }
+
+ public int[] getLastMove() {
+ return head.getData().lastMove;
+ }
+
+ public int[] getNextMove() {
+ BoardData next = getNext();
+ if (next == null) return null;
+ else return next.lastMove;
+ }
+
+ public Stone getLastMoveColor() {
+ return head.getData().lastMoveColor;
+ }
+
+ public boolean isBlacksTurn() {
+ return head.getData().blackToPlay;
+ }
+
+ public Zobrist getZobrist() {
+ return head.getData().zobrist.clone();
+ }
+
+ public int getMoveNumber() {
+ return head.getData().moveNumber;
+ }
+
+ public int[] getMoveNumberList() {
+ return head.getData().moveNumberList;
+ }
+
+ public BoardHistoryNode getCurrentHistoryNode() {
+ return head;
+ }
+
+ /**
+ * @param data the board position to check against superko
+ * @return whether or not the given position violates the superko rule at the head's state
+ */
+ public boolean violatesSuperko(BoardData data) {
+ BoardHistoryNode head = this.head;
+
+ // check to see if this position has occurred before
+ while (head.previous() != null) {
+ // if two zobrist hashes are equal, and it's the same player to coordinate, they are the same
+ // position
+ if (data.zobrist.equals(head.getData().zobrist)
+ && data.blackToPlay == head.getData().blackToPlay) return true;
+
+ head = head.previous();
+ }
+
+ // no position matched this position, so it's valid
+ return false;
+ }
+
+ /**
+ * Returns the root node
+ *
+ * @return root node
+ */
+ public BoardHistoryNode root() {
+ BoardHistoryNode top = head;
+ while (top.previous() != null) {
+ top = top.previous();
+ }
+ return top;
+ }
+
+ /**
+ * Returns the length of current branch
+ *
+ * @return length of current branch
+ */
+ public int currentBranchLength() {
+ return getMoveNumber() + BoardHistoryList.getDepth(head);
+ }
+
+ /**
+ * Returns the length of main trunk
+ *
+ * @return length of main trunk
+ */
+ public int mainTrunkLength() {
+ return BoardHistoryList.getDepth(root());
+ }
+
+ /*
+ * Static helper methods
+ */
+
+ /**
+ * Returns the number of moves in a tree (only the left-most (trunk) variation)
+ *
+ * @return number of moves in a tree
+ */
+ public static int getDepth(BoardHistoryNode node) {
+ int c = 0;
+ while (node.next() != null) {
+ c++;
+ node = node.next();
+ }
+ return c;
+ }
+
+ /**
+ * Check if there is a branch that is at least depth deep (at least depth moves)
+ *
+ * @return true if it is deep enough, false otherwise
+ */
+ public static boolean hasDepth(BoardHistoryNode node, int depth) {
+ int c = 0;
+ if (depth <= 0) return true;
+ while (node.next() != null) {
+ if (node.numberOfChildren() > 1) {
+ for (int i = 0; i < node.numberOfChildren(); i++) {
+ if (hasDepth(node.getVariation(i), depth - c - 1)) return true;
}
-
- // no position matched this position, so it's valid
return false;
- }
-
- /**
- * Returns the root node
- *
- * @return root node
- */
- public BoardHistoryNode root() {
- BoardHistoryNode top = head;
- while (top.previous() != null) {
- top = top.previous();
- }
- return top;
- }
-
- /**
- * Returns the length of current branch
- *
- * @return length of current branch
- */
- public int currentBranchLength() {
- return getMoveNumber() + BoardHistoryList.getDepth(head);
- }
-
- /**
- * Returns the length of main trunk
- *
- * @return length of main trunk
- */
- public int mainTrunkLength() {
- return BoardHistoryList.getDepth(root());
- }
-
- /*
- * Static helper methods
- */
-
- /**
- * Returns the number of moves in a tree (only the left-most (trunk) variation)
- *
- * @return number of moves in a tree
- */
- static public int getDepth(BoardHistoryNode node)
- {
- int c = 0;
- while (node.next() != null)
- {
- c++;
- node = node.next();
- }
- return c;
- }
-
- /**
- * Check if there is a branch that is at least depth deep (at least depth moves)
- *
- * @return true if it is deep enough, false otherwise
- */
- static public boolean hasDepth(BoardHistoryNode node, int depth) {
- int c = 0;
- if (depth <= 0) return true;
- while (node.next() != null) {
- if (node.numberOfChildren() > 1) {
- for (int i = 0; i < node.numberOfChildren(); i++) {
- if (hasDepth(node.getVariation(i), depth - c - 1))
- return true;
- }
- return false;
- } else {
- node = node.next();
- c++;
- if (c >= depth) return true;
- }
- }
+ } else {
+ node = node.next();
+ c++;
+ if (c >= depth) return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Find top of variation (the first move that is on the main trunk)
+ *
+ * @return top of variaton, if on main trunk, return start move
+ */
+ public static BoardHistoryNode findTop(BoardHistoryNode start) {
+ BoardHistoryNode top = start;
+ while (start.previous() != null) {
+ if (start.previous().next() != start) {
+ top = start.previous();
+ }
+ start = start.previous();
+ }
+ return top;
+ }
+
+ /**
+ * Find first move with variations in tree above node
+ *
+ * @return The child (in the current variation) of the first node with variations
+ */
+ public static BoardHistoryNode findChildOfPreviousWithVariation(BoardHistoryNode node) {
+ while (node.previous() != null) {
+ if (node.previous().numberOfChildren() > 1) {
+ return node;
+ }
+ node = node.previous();
+ }
+ return null;
+ }
+
+ /**
+ * Given a parent node and a child node, find the index of the child node
+ *
+ * @return index of child node, -1 if child node not a child of parent
+ */
+ public static int findIndexOfNode(BoardHistoryNode parentNode, BoardHistoryNode childNode) {
+ if (parentNode.next() == null) return -1;
+ for (int i = 0; i < parentNode.numberOfChildren(); i++) {
+ if (parentNode.getVariation(i) == childNode) return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Check if node is part of the main trunk (rightmost branch)
+ *
+ * @return true if node is part of main trunk, false otherwise
+ */
+ public static boolean isMainTrunk(BoardHistoryNode node) {
+ while (node.previous() != null) {
+ if (node.previous().next() != node) {
return false;
+ }
+ node = node.previous();
}
-
- /**
- * Find top of variation (the first move that is on the main trunk)
- *
- * @return top of variaton, if on main trunk, return start move
- */
- static public BoardHistoryNode findTop(BoardHistoryNode start)
- {
- BoardHistoryNode top = start;
- while (start.previous() != null)
- {
- if (start.previous().next() != start)
- {
- top = start.previous();
- }
- start = start.previous();
- }
- return top;
- }
-
- /**
- * Find first move with variations in tree above node
- *
- * @return The child (in the current variation) of the first node with variations
- */
- static public BoardHistoryNode findChildOfPreviousWithVariation(BoardHistoryNode node)
- {
- while (node.previous() != null)
- {
- if (node.previous().numberOfChildren() > 1)
- {
- return node;
- }
- node = node.previous();
- }
- return null;
- }
-
- /**
- * Given a parent node and a child node, find the index of the child node
- *
- * @return index of child node, -1 if child node not a child of parent
- */
- static public int findIndexOfNode(BoardHistoryNode parentNode, BoardHistoryNode childNode)
- {
- if (parentNode.next() == null) return -1;
- for (int i = 0; i < parentNode.numberOfChildren(); i++)
- {
- if (parentNode.getVariation(i) == childNode) return i;
- }
- return -1;
- }
-
- /**
- * Check if node is part of the main trunk (rightmost branch)
- *
- * @return true if node is part of main trunk, false otherwise
- */
- static public boolean isMainTrunk(BoardHistoryNode node)
- {
- while (node.previous() != null) {
- if (node.previous().next() != node) {
- return false;
- }
- node = node.previous();
- }
- return true;
- }
-
+ return true;
+ }
}
diff --git a/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java b/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java
index 95cf436f0..01ac257af 100644
--- a/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java
+++ b/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java
@@ -1,176 +1,183 @@
package featurecat.lizzie.rules;
+
import java.util.ArrayList;
import java.util.List;
-/**
- * Node structure for the board history / sgf tree
- */
+/** Node structure for the board history / sgf tree */
public class BoardHistoryNode {
- private BoardHistoryNode previous;
- private ArrayList nexts;
-
- private BoardData data;
-
- /**
- * Initializes a new list node
- */
- public BoardHistoryNode(BoardData data) {
- previous = null;
- nexts = new ArrayList();
- this.data = data;
- }
-
- /**
- * Remove all subsequent nodes.
- */
- public void clear() {
- nexts.clear();
- }
-
- /**
- * Sets up for a new node. Overwrites future history.
- *
- * @param node the node following this one
- * @return the node that was just set
- */
- public BoardHistoryNode add(BoardHistoryNode node) {
- nexts.clear();
- nexts.add(node);
- node.previous = this;
-
- return node;
- }
-
- /**
- * If we already have a next node with the same BoardData, move to it,
- * otherwise add it and move to it.
- *
- * @param data the node following this one
- * @return the node that was just set
- */
- public BoardHistoryNode addOrGoto(BoardData data) {
- // If you play a hand and immediately return it, it is most likely that you have made a mistake. Ask whether to delete the previous node.
-// if (!nexts.isEmpty() && !nexts.get(0).data.zobrist.equals(data.zobrist)) {
-// // You may just mark this hand, so it's not necessarily wrong. Answer when the first query is wrong or it will not ask whether the move is wrong.
-// if (!nexts.get(0).data.verify) {
-// int ret = JOptionPane.showConfirmDialog(null, "Do you want undo?", "Undo", JOptionPane.OK_CANCEL_OPTION);
-// if (ret == JOptionPane.OK_OPTION) {
-// nexts.remove(0);
-// } else {
-// nexts.get(0).data.verify = true;
-// }
-// }
-// }
- for (int i = 0; i < nexts.size(); i++) {
- if (nexts.get(i).data.zobrist.equals(data.zobrist)) {
-// if (i != 0) {
-// // Swap selected next to foremost
-// BoardHistoryNode currentNext = nexts.get(i);
-// nexts.set(i, nexts.get(0));
-// nexts.set(0, currentNext);
-// }
- return nexts.get(i);
- }
- }
- BoardHistoryNode node = new BoardHistoryNode(data);
- // Add node
- nexts.add(node);
- node.previous = this;
-
- return node;
- }
-
- /**
- * @return data stored on this node
- */
- public BoardData getData() {
- return data;
- }
-
- /**
- * @return nexts for display
- */
- public List getNexts() {
- return nexts;
- }
-
- public BoardHistoryNode previous() {
- return previous;
- }
-
- public BoardHistoryNode next() {
- if (nexts.size() == 0) {
- return null;
- } else {
- return nexts.get(0);
- }
- }
-
- public BoardHistoryNode topOfBranch() {
- BoardHistoryNode top = this;
- while (top.previous != null && top.previous.nexts.size() == 1) {
- top = top.previous;
- }
- return top;
- }
-
- public int numberOfChildren() {
- if (nexts == null) {
- return 0;
- } else {
- return nexts.size();
- }
- }
-
- public boolean isFirstChild() {
- return (previous != null) && previous.next() == this;
- }
-
- public BoardHistoryNode getVariation(int idx) {
- if (nexts.size() <= idx) {
- return null;
- } else {
- return nexts.get(idx);
- }
- }
-
- public void moveUp() {
- if (previous != null) {
- previous.moveChildUp(this);
- }
- }
-
- public void moveDown() {
- if (previous != null) {
- previous.moveChildDown(this);
- }
- }
-
- public void moveChildUp(BoardHistoryNode child) {
- for (int i = 1; i < nexts.size(); i++) {
- if (nexts.get(i).data.zobrist.equals(child.data.zobrist)) {
- BoardHistoryNode tmp = nexts.get(i-1);
- nexts.set(i-1, child);
- nexts.set(i, tmp);
- return;
- }
- }
- }
-
- public void moveChildDown(BoardHistoryNode child) {
- for (int i = 0; i < nexts.size() - 1; i++) {
- if (nexts.get(i).data.zobrist.equals(child.data.zobrist)) {
- BoardHistoryNode tmp = nexts.get(i+1);
- nexts.set(i+1, child);
- nexts.set(i, tmp);
- return;
- }
- }
- }
-
- public void deleteChild(int idx) {
- if (idx < numberOfChildren()) {
- nexts.remove(idx);
- }
- }
+ private BoardHistoryNode previous;
+ private ArrayList nexts;
+
+ private BoardData data;
+
+ // Save the children for restore to branch
+ private int fromBackChildren;
+
+ /** Initializes a new list node */
+ public BoardHistoryNode(BoardData data) {
+ previous = null;
+ nexts = new ArrayList();
+ this.data = data;
+ }
+
+ /** Remove all subsequent nodes. */
+ public void clear() {
+ nexts.clear();
+ }
+
+ /**
+ * Sets up for a new node. Overwrites future history.
+ *
+ * @param node the node following this one
+ * @return the node that was just set
+ */
+ public BoardHistoryNode add(BoardHistoryNode node) {
+ nexts.clear();
+ nexts.add(node);
+ node.previous = this;
+
+ return node;
+ }
+
+ /**
+ * If we already have a next node with the same BoardData, move to it, otherwise add it and move
+ * to it.
+ *
+ * @param data the node following this one
+ * @return the node that was just set
+ */
+ public BoardHistoryNode addOrGoto(BoardData data) {
+ // If you play a hand and immediately return it, it is most likely that you have made a mistake.
+ // Ask whether to delete the previous node.
+ // if (!nexts.isEmpty() && !nexts.get(0).data.zobrist.equals(data.zobrist)) {
+ // // You may just mark this hand, so it's not necessarily wrong. Answer when the
+ // first query is wrong or it will not ask whether the move is wrong.
+ // if (!nexts.get(0).data.verify) {
+ // int ret = JOptionPane.showConfirmDialog(null, "Do you want undo?", "Undo",
+ // JOptionPane.OK_CANCEL_OPTION);
+ // if (ret == JOptionPane.OK_OPTION) {
+ // nexts.remove(0);
+ // } else {
+ // nexts.get(0).data.verify = true;
+ // }
+ // }
+ // }
+ for (int i = 0; i < nexts.size(); i++) {
+ if (nexts.get(i).data.zobrist.equals(data.zobrist)) {
+ // if (i != 0) {
+ // // Swap selected next to foremost
+ // BoardHistoryNode currentNext = nexts.get(i);
+ // nexts.set(i, nexts.get(0));
+ // nexts.set(0, currentNext);
+ // }
+ return nexts.get(i);
+ }
+ }
+ BoardHistoryNode node = new BoardHistoryNode(data);
+ // Add node
+ nexts.add(node);
+ node.previous = this;
+
+ return node;
+ }
+
+ /** @return data stored on this node */
+ public BoardData getData() {
+ return data;
+ }
+
+ /** @return nexts for display */
+ public List getNexts() {
+ return nexts;
+ }
+
+ public BoardHistoryNode previous() {
+ return previous;
+ }
+
+ public BoardHistoryNode next() {
+ if (nexts.size() == 0) {
+ return null;
+ } else {
+ return nexts.get(0);
+ }
+ }
+
+ public BoardHistoryNode topOfBranch() {
+ BoardHistoryNode top = this;
+ while (top.previous != null && top.previous.nexts.size() == 1) {
+ top = top.previous;
+ }
+ return top;
+ }
+
+ public int numberOfChildren() {
+ if (nexts == null) {
+ return 0;
+ } else {
+ return nexts.size();
+ }
+ }
+
+ public boolean isFirstChild() {
+ return (previous != null) && previous.next() == this;
+ }
+
+ public BoardHistoryNode getVariation(int idx) {
+ if (nexts.size() <= idx) {
+ return null;
+ } else {
+ return nexts.get(idx);
+ }
+ }
+
+ public void moveUp() {
+ if (previous != null) {
+ previous.moveChildUp(this);
+ }
+ }
+
+ public void moveDown() {
+ if (previous != null) {
+ previous.moveChildDown(this);
+ }
+ }
+
+ public void moveChildUp(BoardHistoryNode child) {
+ for (int i = 1; i < nexts.size(); i++) {
+ if (nexts.get(i).data.zobrist.equals(child.data.zobrist)) {
+ BoardHistoryNode tmp = nexts.get(i - 1);
+ nexts.set(i - 1, child);
+ nexts.set(i, tmp);
+ return;
+ }
+ }
+ }
+
+ public void moveChildDown(BoardHistoryNode child) {
+ for (int i = 0; i < nexts.size() - 1; i++) {
+ if (nexts.get(i).data.zobrist.equals(child.data.zobrist)) {
+ BoardHistoryNode tmp = nexts.get(i + 1);
+ nexts.set(i + 1, child);
+ nexts.set(i, tmp);
+ return;
+ }
+ }
+ }
+
+ public void deleteChild(int idx) {
+ if (idx < numberOfChildren()) {
+ 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 7961a5230..3f5e826ff 100644
--- a/src/main/java/featurecat/lizzie/rules/GIBParser.java
+++ b/src/main/java/featurecat/lizzie/rules/GIBParser.java
@@ -1,109 +1,106 @@
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;
import java.io.InputStreamReader;
public class GIBParser {
- private static int[][] handicapPlacement = {{3, 15}, {15,3}, {15, 15}, {3, 3}, {3, 9},
- {15, 9}, {9, 3}, {9, 15}, {9, 9}};
-
- public static boolean load(String filename) throws IOException {
- // Clear the board
- Lizzie.board.clear();
+ private static int[][] handicapPlacement = {
+ {3, 15}, {15, 3}, {15, 15}, {3, 3}, {3, 9}, {15, 9}, {9, 3}, {9, 15}, {9, 9}
+ };
- File file = new File(filename);
- if (!file.exists() || !file.canRead()) {
- return false;
- }
+ public static boolean load(String filename) throws IOException {
+ // Clear the board
+ Lizzie.board.clear();
- FileInputStream fp = new FileInputStream(file);
- InputStreamReader reader = new InputStreamReader(fp);
- StringBuilder builder = new StringBuilder();
- while (reader.ready()) {
- builder.append((char) reader.read());
- }
- reader.close();
- fp.close();
- String value = builder.toString();
- if (value.isEmpty()) {
- return false;
- }
+ File file = new File(filename);
+ if (!file.exists() || !file.canRead()) {
+ return false;
+ }
- boolean returnValue = parse(value);
- PluginManager.onSgfLoaded();
- return returnValue;
+ FileInputStream fp = new FileInputStream(file);
+ InputStreamReader reader = new InputStreamReader(fp);
+ StringBuilder builder = new StringBuilder();
+ while (reader.ready()) {
+ builder.append((char) reader.read());
+ }
+ reader.close();
+ fp.close();
+ String value = builder.toString();
+ if (value.isEmpty()) {
+ return false;
}
- private static void placeHandicap(int handi) {
- if (handi > 9) {
- System.out.println("More than 9 in handicap not supported!");
- handi = 9;
- }
- if (handi == 5 || handi == 7) {
- Lizzie.board.place(9, 9, Stone.BLACK);
- handi--;
- }
- for (int i = 0; i < handi; i++) {
- Lizzie.board.place(handicapPlacement[i][0], handicapPlacement[i][1], Stone.BLACK);
- }
+ boolean returnValue = parse(value);
+ return returnValue;
+ }
+
+ private static void placeHandicap(int handi) {
+ if (handi > 9) {
+ System.out.println("More than 9 in handicap not supported!");
+ handi = 9;
+ }
+ if (handi == 5 || handi == 7) {
+ Lizzie.board.place(9, 9, Stone.BLACK);
+ handi--;
}
+ for (int i = 0; i < handi; i++) {
+ Lizzie.board.place(handicapPlacement[i][0], handicapPlacement[i][1], Stone.BLACK);
+ }
+ }
- private static boolean parse(String value) {
- String[] lines = value.trim().split("\n");
- String whitePlayer = "Player 1";
- String blackPlayer = "Player 2";
- double komi = 1.5;
- int handicap = 0;
+ private static boolean parse(String value) {
+ String[] lines = value.trim().split("\n");
+ String whitePlayer = "Player 1";
+ String blackPlayer = "Player 2";
+ double komi = 1.5;
+ int handicap = 0;
- for (String line: lines) {
- if (line.startsWith("\\[GAMEINFOMAIN=")) {
- // See if komi is included
- int sk = line.indexOf("GONGJE:") + 7;
- int ek = line.indexOf(',', sk);
+ for (String line : lines) {
+ if (line.startsWith("\\[GAMEINFOMAIN=")) {
+ // See if komi is included
+ int sk = line.indexOf("GONGJE:") + 7;
+ int ek = line.indexOf(',', sk);
- if (sk > 0) {
- komi = Integer.parseInt(line.substring(sk, ek))/10.0;
- }
- }
- // Players names
- if (line.startsWith("\\[GAMEBLACKNAME=")) {
- blackPlayer = line.substring(16, line.length() - 3);
- }
- if (line.startsWith("\\[GAMEWHITENAME=")) {
- whitePlayer = line.substring(16, line.length()-3);
- }
- // Handicap info
- if (line.startsWith("INI")) {
- String[] fields = line.split(" ");
- handicap = Integer.parseInt(fields[3]);
- if (handicap >= 2) {
- placeHandicap(handicap);
- }
- }
- // Actual moves
- if (line.startsWith("STO")) {
- String[] fields = line.split(" ");
- int x = Integer.parseInt(fields[4]);
- int y = Integer.parseInt(fields[5]);
- Stone s = fields[3].equals("1") ? Stone.BLACK : Stone.WHITE;
- Lizzie.board.place(x, y, s);
- }
- // Pass
- if (line.startsWith("SKI")) {
- Lizzie.board.pass();
- }
+ if (sk > 0) {
+ komi = Integer.parseInt(line.substring(sk, ek)) / 10.0;
}
- Lizzie.board.getHistory().getGameInfo().setKomi(komi);
- Lizzie.frame.setPlayers(whitePlayer, blackPlayer);
- // Rewind to game start
- while (Lizzie.board.previousMove()) ;
-
- return false;
+ }
+ // Players names
+ if (line.startsWith("\\[GAMEBLACKNAME=")) {
+ blackPlayer = line.substring(16, line.length() - 3);
+ }
+ if (line.startsWith("\\[GAMEWHITENAME=")) {
+ whitePlayer = line.substring(16, line.length() - 3);
+ }
+ // Handicap info
+ if (line.startsWith("INI")) {
+ String[] fields = line.split(" ");
+ handicap = Integer.parseInt(fields[3]);
+ if (handicap >= 2) {
+ placeHandicap(handicap);
+ }
+ }
+ // Actual moves
+ if (line.startsWith("STO")) {
+ String[] fields = line.split(" ");
+ int x = Integer.parseInt(fields[4]);
+ int y = Integer.parseInt(fields[5]);
+ Stone s = fields[3].equals("1") ? Stone.BLACK : Stone.WHITE;
+ Lizzie.board.place(x, y, s);
+ }
+ // Pass
+ if (line.startsWith("SKI")) {
+ Lizzie.board.pass();
+ }
}
+ Lizzie.board.getHistory().getGameInfo().setKomi(komi);
+ Lizzie.frame.setPlayers(whitePlayer, blackPlayer);
+ // Rewind to game start
+ while (Lizzie.board.previousMove()) ;
+ return false;
+ }
}
diff --git a/src/main/java/featurecat/lizzie/rules/SGFParser.java b/src/main/java/featurecat/lizzie/rules/SGFParser.java
index d496c23a3..bafb37173 100644
--- a/src/main/java/featurecat/lizzie/rules/SGFParser.java
+++ b/src/main/java/featurecat/lizzie/rules/SGFParser.java
@@ -1,307 +1,370 @@
package featurecat.lizzie.rules;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
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;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
public class SGFParser {
- private static final SimpleDateFormat SGF_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
-
- public static boolean load(String filename) throws IOException {
- // Clear the board
- Lizzie.board.clear();
+ private static final SimpleDateFormat SGF_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd");
- File file = new File(filename);
- if (!file.exists() || !file.canRead()) {
- return false;
- }
+ public static boolean load(String filename) throws IOException {
+ // Clear the board
+ Lizzie.board.clear();
- FileInputStream fp = new FileInputStream(file);
- InputStreamReader reader = new InputStreamReader(fp);
- StringBuilder builder = new StringBuilder();
- while (reader.ready()) {
- builder.append((char) reader.read());
- }
- reader.close();
- fp.close();
- String value = builder.toString();
- if (value.isEmpty()) {
- return false;
- }
+ File file = new File(filename);
+ if (!file.exists() || !file.canRead()) {
+ return false;
+ }
- boolean returnValue = parse(value);
- PluginManager.onSgfLoaded();
- return returnValue;
+ FileInputStream fp = new FileInputStream(file);
+ InputStreamReader reader = new InputStreamReader(fp);
+ StringBuilder builder = new StringBuilder();
+ while (reader.ready()) {
+ builder.append((char) reader.read());
+ }
+ reader.close();
+ fp.close();
+ String value = builder.toString();
+ if (value.isEmpty()) {
+ return false;
}
- public static boolean loadFromString(String sgfString) {
- // Clear the board
- Lizzie.board.clear();
+ boolean returnValue = parse(value);
+ return returnValue;
+ }
- return parse(sgfString);
- }
+ public static boolean loadFromString(String sgfString) {
+ // Clear the board
+ Lizzie.board.clear();
- public static int[] convertSgfPosToCoord(String pos) {
- if (pos.equals("tt") || pos.isEmpty())
- return null;
- int[] ret = new int[2];
- ret[0] = (int) pos.charAt(0) - 'a';
- ret[1] = (int) pos.charAt(1) - 'a';
- return ret;
- }
+ return parse(sgfString);
+ }
- private static boolean parse(String value) {
- // Drop anything outside "(;...)"
- final Pattern SGF_PATTERN = Pattern.compile("(?s).*?(\\(\\s*;.*\\)).*?");
- Matcher sgfMatcher = SGF_PATTERN.matcher(value);
- if (sgfMatcher.matches()) {
- value = sgfMatcher.group(1);
- } else {
- return false;
- }
- int subTreeDepth = 0;
- boolean inTag = false, isMultiGo = false, escaping = false;
- String tag = null;
- StringBuilder tagBuilder = new StringBuilder();
- StringBuilder tagContentBuilder = new StringBuilder();
- // MultiGo 's branch: (Main Branch (Main Branch) (Branch) )
- // Other 's branch: (Main Branch (Branch) Main Branch)
- if (value.charAt(value.length() - 2) == ')') {
- isMultiGo = true;
- }
+ public static int[] convertSgfPosToCoord(String pos) {
+ if (pos.equals("tt") || pos.isEmpty()) return null;
+ int[] ret = new int[2];
+ ret[0] = (int) pos.charAt(0) - 'a';
+ ret[1] = (int) pos.charAt(1) - 'a';
+ return ret;
+ }
- String blackPlayer = "", whitePlayer = "";
+ private static boolean parse(String value) {
+ // Drop anything outside "(;...)"
+ final Pattern SGF_PATTERN = Pattern.compile("(?s).*?(\\(\\s*;.*\\)).*?");
+ Matcher sgfMatcher = SGF_PATTERN.matcher(value);
+ if (sgfMatcher.matches()) {
+ value = sgfMatcher.group(1);
+ } else {
+ return false;
+ }
+ int subTreeDepth = 0;
+ // Save the variation step count
+ Map subTreeStepMap = new HashMap();
+ // Comment of the AW/AB (Add White/Add Black) stone
+ String awabComment = null;
+ // Previous Tag
+ String prevTag = null;
+ boolean inTag = false, isMultiGo = false, escaping = false;
+ String tag = null;
+ StringBuilder tagBuilder = new StringBuilder();
+ StringBuilder tagContentBuilder = new StringBuilder();
+ // MultiGo 's branch: (Main Branch (Main Branch) (Branch) )
+ // Other 's branch: (Main Branch (Branch) Main Branch)
+ if (value.matches("(?s).*\\)\\s*\\)")) {
+ isMultiGo = true;
+ }
- PARSE_LOOP:
- for (byte b : value.getBytes()) {
- // Check unicode charactors (UTF-8)
- char c = (char) b;
- if (((int) b & 0x80) != 0) {
- continue;
+ String blackPlayer = "", whitePlayer = "";
+
+ // Support unicode characters (UTF-8)
+ for (int i = 0; i < value.length(); i++) {
+ char c = value.charAt(i);
+ if (escaping) {
+ // Any char following "\" is inserted verbatim
+ // (ref) "3.2. Text" in https://www.red-bean.com/sgf/sgf4.html
+ tagContentBuilder.append(c);
+ escaping = false;
+ continue;
+ }
+ switch (c) {
+ case '(':
+ if (!inTag) {
+ subTreeDepth += 1;
+ // Initialize the step count
+ subTreeStepMap.put(subTreeDepth, 0);
+ } else {
+ if (i > 0) {
+ // Allow the comment tag includes '('
+ tagContentBuilder.append(c);
}
- if (escaping) {
- // Any char following "\" is inserted verbatim
- // (ref) "3.2. Text" in https://www.red-bean.com/sgf/sgf4.html
- tagContentBuilder.append(c);
- escaping = false;
- continue;
+ }
+ break;
+ case ')':
+ if (!inTag) {
+ if (isMultiGo) {
+ // Restore to the variation node
+ int varStep = subTreeStepMap.get(subTreeDepth);
+ for (int s = 0; s < varStep; s++) {
+ Lizzie.board.previousMove();
+ }
}
- switch (c) {
- case '(':
- if (!inTag) {
- subTreeDepth += 1;
- }
- break;
- case ')':
- if (!inTag) {
- subTreeDepth -= 1;
- if (isMultiGo) {
- break PARSE_LOOP;
- }
- }
- break;
- case '[':
- if (subTreeDepth > 1 && !isMultiGo) {
- break;
- }
- inTag = true;
- String tagTemp = tagBuilder.toString();
- if (!tagTemp.isEmpty()) {
- // Ignore small letters in tags for the long format Smart-Go file.
- // (ex) "PlayerBlack" ==> "PB"
- // It is the default format of mgt, an old SGF tool.
- // (Mgt is still supported in Debian and Ubuntu.)
- tag = tagTemp.replaceAll("[a-z]", "");
- }
- tagContentBuilder = new StringBuilder();
- break;
- case ']':
- if (subTreeDepth > 1 && !isMultiGo) {
- break;
- }
- inTag = false;
- tagBuilder = new StringBuilder();
- String tagContent = tagContentBuilder.toString();
- // We got tag, we can parse this tag now.
- if (tag.equals("B")) {
- int[] move = convertSgfPosToCoord(tagContent);
- if (move == null) {
- Lizzie.board.pass(Stone.BLACK);
- } else {
- Lizzie.board.place(move[0], move[1], Stone.BLACK);
- }
- } else if (tag.equals("W")) {
- int[] move = convertSgfPosToCoord(tagContent);
- if (move == null) {
- Lizzie.board.pass(Stone.WHITE);
- } else {
- Lizzie.board.place(move[0], move[1], Stone.WHITE);
- }
- } else if (tag.equals("AB")) {
- int[] move = convertSgfPosToCoord(tagContent);
- if (move == null) {
- Lizzie.board.pass(Stone.BLACK);
- } else {
- Lizzie.board.place(move[0], move[1], Stone.BLACK);
- }
- Lizzie.board.flatten();
- } else if (tag.equals("AW")) {
- int[] move = convertSgfPosToCoord(tagContent);
- if (move == null) {
- Lizzie.board.pass(Stone.WHITE);
- } else {
- Lizzie.board.place(move[0], move[1], Stone.WHITE);
- }
- Lizzie.board.flatten();
- } else if (tag.equals("PB")) {
- blackPlayer = tagContent;
- } else if (tag.equals("PW")) {
- whitePlayer = tagContent;
- } else if (tag.equals("KM")) {
- Lizzie.board.getHistory().getGameInfo().setKomi(Double.parseDouble(tagContent));
- }
- break;
- case ';':
- break;
- default:
- if (subTreeDepth > 1 && !isMultiGo) {
- break;
- }
- if (inTag) {
- if (c == '\\') {
- escaping = true;
- continue;
- }
- tagContentBuilder.append(c);
- } else {
- if (c != '\n' && c != '\r' && c != '\t' && c != ' ') {
- tagBuilder.append(c);
- }
- }
+ subTreeDepth -= 1;
+ } else {
+ // Allow the comment tag includes '('
+ tagContentBuilder.append(c);
+ }
+ break;
+ case '[':
+ if (subTreeDepth > 1 && !isMultiGo) {
+ break;
+ }
+ inTag = true;
+ String tagTemp = tagBuilder.toString();
+ if (!tagTemp.isEmpty()) {
+ // Ignore small letters in tags for the long format Smart-Go file.
+ // (ex) "PlayerBlack" ==> "PB"
+ // It is the default format of mgt, an old SGF tool.
+ // (Mgt is still supported in Debian and Ubuntu.)
+ tag = tagTemp.replaceAll("[a-z]", "");
+ }
+ tagContentBuilder = new StringBuilder();
+ break;
+ case ']':
+ if (subTreeDepth > 1 && !isMultiGo) {
+ break;
+ }
+ inTag = false;
+ tagBuilder = new StringBuilder();
+ String tagContent = tagContentBuilder.toString();
+ // We got tag, we can parse this tag now.
+ if (tag.equals("B")) {
+ int[] move = convertSgfPosToCoord(tagContent);
+ if (move == null) {
+ Lizzie.board.pass(Stone.BLACK);
+ } else {
+ // Save the step count
+ subTreeStepMap.put(subTreeDepth, subTreeStepMap.get(subTreeDepth) + 1);
+ Lizzie.board.place(move[0], move[1], Stone.BLACK);
}
- }
+ } else if (tag.equals("W")) {
+ int[] move = convertSgfPosToCoord(tagContent);
+ if (move == null) {
+ Lizzie.board.pass(Stone.WHITE);
+ } else {
+ // Save the step count
+ subTreeStepMap.put(subTreeDepth, subTreeStepMap.get(subTreeDepth) + 1);
+ Lizzie.board.place(move[0], move[1], Stone.WHITE);
+ }
+ } else if (tag.equals("C")) {
+ // Support comment
+ if ("AW".equals(prevTag) || "AB".equals(prevTag)) {
+ awabComment = tagContent;
+ } else {
+ Lizzie.board.comment(tagContent);
+ }
+ } else if (tag.equals("AB")) {
+ int[] move = convertSgfPosToCoord(tagContent);
+ if (move == null) {
+ Lizzie.board.pass(Stone.BLACK);
+ } else {
+ Lizzie.board.place(move[0], move[1], Stone.BLACK);
+ }
+ Lizzie.board.flatten();
+ } else if (tag.equals("AW")) {
+ int[] move = convertSgfPosToCoord(tagContent);
+ if (move == null) {
+ Lizzie.board.pass(Stone.WHITE);
+ } else {
+ Lizzie.board.place(move[0], move[1], Stone.WHITE);
+ }
+ Lizzie.board.flatten();
+ } else if (tag.equals("PB")) {
+ blackPlayer = tagContent;
+ } else if (tag.equals("PW")) {
+ whitePlayer = tagContent;
+ } else if (tag.equals("KM")) {
+ try {
+ if (tagContent.trim().isEmpty()) {
+ tagContent = "0.0";
+ }
+ Lizzie.board.getHistory().getGameInfo().setKomi(Double.parseDouble(tagContent));
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ }
+ prevTag = tag;
+ break;
+ case ';':
+ break;
+ default:
+ if (subTreeDepth > 1 && !isMultiGo) {
+ break;
+ }
+ if (inTag) {
+ if (c == '\\') {
+ escaping = true;
+ continue;
+ }
+ tagContentBuilder.append(c);
+ } else {
+ if (c != '\n' && c != '\r' && c != '\t' && c != ' ') {
+ tagBuilder.append(c);
+ }
+ }
+ }
+ }
- Lizzie.frame.setPlayers(whitePlayer, blackPlayer);
+ Lizzie.frame.setPlayers(whitePlayer, blackPlayer);
- // Rewind to game start
- while (Lizzie.board.previousMove()) ;
+ // Rewind to game start
+ while (Lizzie.board.previousMove()) ;
- return true;
+ // Set AW/AB Comment
+ if (awabComment != null) {
+ Lizzie.board.comment(awabComment);
}
- public static String saveToString() throws IOException {
- try (StringWriter writer = new StringWriter()) {
- saveToStream(Lizzie.board, writer);
- return writer.toString();
- }
+ return true;
+ }
+
+ public static String saveToString() throws IOException {
+ try (StringWriter writer = new StringWriter()) {
+ saveToStream(Lizzie.board, writer);
+ return writer.toString();
}
+ }
- public static void save(Board board, String filename) throws IOException {
- try (Writer writer = new OutputStreamWriter(new FileOutputStream(filename))) {
- saveToStream(board, writer);
- }
+ public static void save(Board board, String filename) throws IOException {
+ try (Writer writer = new OutputStreamWriter(new FileOutputStream(filename))) {
+ saveToStream(board, writer);
}
+ }
- private static void saveToStream(Board board, Writer writer) throws IOException {
- // collect game info
- BoardHistoryList history = board.getHistory();
- GameInfo gameInfo = history.getGameInfo();
- String playerBlack = gameInfo.getPlayerBlack();
- String playerWhite = gameInfo.getPlayerWhite();
- Double komi = gameInfo.getKomi();
- Integer handicap = gameInfo.getHandicap();
- String date = SGF_DATE_FORMAT.format(gameInfo.getDate());
-
- // add SGF header
- StringBuilder builder = new StringBuilder("(;");
- if (handicap != 0) builder.append(String.format("HA[%s]", handicap));
- builder.append(String.format("KM[%s]PW[%s]PB[%s]DT[%s]AP[Lizzie: %s]",
- komi, playerWhite, playerBlack, date, Lizzie.lizzieVersion));
-
- // move to the first move
- history.toStart();
-
- // add handicap stones to SGF
- if (handicap != 0) {
- builder.append("AB");
- Stone[] stones = history.getStones();
- for (int i = 0; i < stones.length; i++) {
- Stone stone = stones[i];
- if (stone.isBlack()) {
- // i = x * Board.BOARD_SIZE + y;
- int corY = i % Board.BOARD_SIZE;
- int corX = (i - corY) / Board.BOARD_SIZE;
-
- char x = (char) (corX + 'a');
- char y = (char) (corY + 'a');
- builder.append(String.format("[%c%c]", x, y));
- }
- }
+ private static void saveToStream(Board board, Writer writer) throws IOException {
+ // collect game info
+ BoardHistoryList history = board.getHistory().shallowCopy();
+ GameInfo gameInfo = history.getGameInfo();
+ String playerB = gameInfo.getPlayerBlack();
+ String playerW = gameInfo.getPlayerWhite();
+ Double komi = gameInfo.getKomi();
+ Integer handicap = gameInfo.getHandicap();
+ String date = SGF_DATE_FORMAT.format(gameInfo.getDate());
+
+ // add SGF header
+ StringBuilder builder = new StringBuilder("(;");
+ if (handicap != 0) builder.append(String.format("HA[%s]", handicap));
+ String header = "KM[%s]PW[%s]PB[%s]DT[%s]AP[Lizzie: %s]";
+ builder.append(String.format(header, komi, playerW, playerB, date, Lizzie.lizzieVersion));
+
+ // move to the first move
+ history.toStart();
+
+ // add handicap stones to SGF
+ if (handicap != 0) {
+ builder.append("AB");
+ Stone[] stones = history.getStones();
+ for (int i = 0; i < stones.length; i++) {
+ Stone stone = stones[i];
+ if (stone.isBlack()) {
+ // i = x * Board.BOARD_SIZE + y;
+ int corY = i % Board.BOARD_SIZE;
+ int corX = (i - corY) / Board.BOARD_SIZE;
+
+ char x = (char) (corX + 'a');
+ char y = (char) (corY + 'a');
+ builder.append(String.format("[%c%c]", x, y));
+ }
+ }
+ } else {
+ // Process the AW/AB stone
+ Stone[] stones = history.getStones();
+ StringBuilder abStone = new StringBuilder();
+ StringBuilder awStone = new StringBuilder();
+ for (int i = 0; i < stones.length; i++) {
+ Stone stone = stones[i];
+ if (stone.isBlack() || stone.isWhite()) {
+ // i = x * Board.BOARD_SIZE + y;
+ int corY = i % Board.BOARD_SIZE;
+ int corX = (i - corY) / Board.BOARD_SIZE;
+
+ char x = (char) (corX + 'a');
+ char y = (char) (corY + 'a');
+
+ if (stone.isBlack()) {
+ abStone.append(String.format("[%c%c]", x, y));
+ } else {
+ awStone.append(String.format("[%c%c]", x, y));
+ }
}
+ }
+ if (abStone.length() > 0) {
+ builder.append("AB").append(abStone);
+ }
+ if (awStone.length() > 0) {
+ builder.append("AW").append(awStone);
+ }
+ }
- // replay moves, and convert them to tags.
- // * format: ";B[xy]" or ";W[xy]"
- // * with 'xy' = coordinates ; or 'tt' for pass.
- BoardData data;
-
- // TODO: this code comes from cngoodboy's plugin PR #65. It looks like it might be useful for handling
- // AB/AW commands for sgfs in general -- we can extend it beyond just handicap. TODO integrate it
-// data = history.getData();
-//
-// // For handicap
-// ArrayList abList = new ArrayList();
-// ArrayList awList = new ArrayList();
-//
-// for (int i = 0; i < Board.BOARD_SIZE; i++) {
-// for (int j = 0; j < Board.BOARD_SIZE; j++) {
-// switch (data.stones[Board.getIndex(i, j)]) {
-// case BLACK:
-// abList.add(new int[]{i, j});
-// break;
-// case WHITE:
-// awList.add(new int[]{i, j});
-// break;
-// default:
-// break;
-// }
-// }
-// }
-//
-// if (!abList.isEmpty()) {
-// builder.append(";AB");
-// for (int i = 0; i < abList.size(); i++) {
-// builder.append(String.format("[%s]", convertCoordToSgfPos(abList.get(i))));
-// }
-// }
-//
-// if (!awList.isEmpty()) {
-// builder.append(";AW");
-// for (int i = 0; i < awList.size(); i++) {
-// builder.append(String.format("[%s]", convertCoordToSgfPos(awList.get(i))));
-// }
-// }
-
- while ((data = history.next()) != null) {
-
- String stone;
- if (Stone.BLACK.equals(data.lastMoveColor)) stone = "B";
- else if (Stone.WHITE.equals(data.lastMoveColor)) stone = "W";
- else continue;
-
- char x = data.lastMove == null ? 't' : (char) (data.lastMove[0] + 'a');
- char y = data.lastMove == null ? 't' : (char) (data.lastMove[1] + 'a');
-
- builder.append(String.format(";%s[%c%c]", stone, x, y));
+ // The AW/AB Comment
+ if (history.getData().comment != null) {
+ builder.append(String.format("C[%s]", history.getData().comment));
+ }
+
+ // replay moves, and convert them to tags.
+ // * format: ";B[xy]" or ";W[xy]"
+ // * with 'xy' = coordinates ; or 'tt' for pass.
+
+ // Write variation tree
+ builder.append(generateNode(board, history.getCurrentHistoryNode()));
+
+ // close file
+ builder.append(')');
+ writer.append(builder.toString());
+ }
+
+ /** Generate node with variations */
+ private static String generateNode(Board board, BoardHistoryNode node) throws IOException {
+ StringBuilder builder = new StringBuilder("");
+
+ if (node != null) {
+
+ BoardData data = node.getData();
+ String stone = "";
+ if (Stone.BLACK.equals(data.lastMoveColor) || Stone.WHITE.equals(data.lastMoveColor)) {
+
+ if (Stone.BLACK.equals(data.lastMoveColor)) stone = "B";
+ else if (Stone.WHITE.equals(data.lastMoveColor)) stone = "W";
+
+ char x = data.lastMove == null ? 't' : (char) (data.lastMove[0] + 'a');
+ char y = data.lastMove == null ? 't' : (char) (data.lastMove[1] + 'a');
+
+ builder.append(String.format(";%s[%c%c]", stone, x, y));
+
+ // Write the comment
+ if (data.comment != null) {
+ builder.append(String.format("C[%s]", data.comment));
}
+ }
- // close file
- builder.append(')');
- writer.append(builder.toString());
+ if (node.numberOfChildren() > 1) {
+ // Variation
+ for (BoardHistoryNode sub : node.getNexts()) {
+ builder.append("(");
+ builder.append(generateNode(board, sub));
+ builder.append(")");
+ }
+ } else if (node.numberOfChildren() == 1) {
+ builder.append(generateNode(board, node.next()));
+ } else {
+ return builder.toString();
+ }
}
+
+ return builder.toString();
+ }
}
diff --git a/src/main/java/featurecat/lizzie/rules/Stone.java b/src/main/java/featurecat/lizzie/rules/Stone.java
index 585fd6c10..46e49d7fe 100644
--- a/src/main/java/featurecat/lizzie/rules/Stone.java
+++ b/src/main/java/featurecat/lizzie/rules/Stone.java
@@ -1,80 +1,87 @@
package featurecat.lizzie.rules;
public enum Stone {
- BLACK, WHITE, EMPTY, BLACK_RECURSED, WHITE_RECURSED, BLACK_GHOST, WHITE_GHOST, DAME, BLACK_POINT, WHITE_POINT, BLACK_CAPTURED, WHITE_CAPTURED;
+ BLACK,
+ WHITE,
+ EMPTY,
+ BLACK_RECURSED,
+ WHITE_RECURSED,
+ BLACK_GHOST,
+ WHITE_GHOST,
+ DAME,
+ BLACK_POINT,
+ WHITE_POINT,
+ BLACK_CAPTURED,
+ WHITE_CAPTURED;
- /**
- * used to find the opposite color stone
- *
- * @return the opposite stone type
- */
- public Stone opposite() {
- switch (this) {
- case BLACK:
- return WHITE;
- case WHITE:
- return BLACK;
- default:
- return this;
- }
+ /**
+ * used to find the opposite color stone
+ *
+ * @return the opposite stone type
+ */
+ public Stone opposite() {
+ switch (this) {
+ case BLACK:
+ return WHITE;
+ case WHITE:
+ return BLACK;
+ default:
+ return this;
}
+ }
- /**
- * used to keep track of which stones were visited during removal of dead stones
- *
- * @return the recursed version of this stone color
- */
- public Stone recursed() {
- switch (this) {
- case BLACK:
- return BLACK_RECURSED;
- case WHITE:
- return WHITE_RECURSED;
- default:
- return this;
- }
+ /**
+ * used to keep track of which stones were visited during removal of dead stones
+ *
+ * @return the recursed version of this stone color
+ */
+ public Stone recursed() {
+ switch (this) {
+ case BLACK:
+ return BLACK_RECURSED;
+ case WHITE:
+ return WHITE_RECURSED;
+ default:
+ return this;
}
+ }
- /**
- * used to keep track of which stones were visited during removal of dead stones
- *
- * @return the unrecursed version of this stone color
- */
- public Stone unrecursed() {
- switch (this) {
- case BLACK_RECURSED:
- return BLACK;
- case WHITE_RECURSED:
- return WHITE;
- default:
- return this;
- }
+ /**
+ * used to keep track of which stones were visited during removal of dead stones
+ *
+ * @return the unrecursed version of this stone color
+ */
+ public Stone unrecursed() {
+ switch (this) {
+ case BLACK_RECURSED:
+ return BLACK;
+ case WHITE_RECURSED:
+ return WHITE;
+ default:
+ return this;
}
+ }
- /**
- * @return Whether or not this stone is of the black variants.
- */
- public boolean isBlack() {
- return this == BLACK || this == BLACK_RECURSED || this == BLACK_GHOST;
- }
+ /** @return Whether or not this stone is of the black variants. */
+ public boolean isBlack() {
+ return this == BLACK || this == BLACK_RECURSED || this == BLACK_GHOST;
+ }
- /**
- * @return Whether or not this stone is of the white variants.
- */
- public boolean isWhite() {
- return this != EMPTY && !this.isBlack();
- }
+ /** @return Whether or not this stone is of the white variants. */
+ public boolean isWhite() {
+ return this != EMPTY && !this.isBlack();
+ }
- public Stone unGhosted() {
- switch (this) {
- case BLACK:
- case BLACK_GHOST:
- return BLACK;
- case WHITE:
- case WHITE_GHOST:
- return WHITE;
- default:
- return EMPTY;
- }
+ public Stone unGhosted() {
+ switch (this) {
+ case BLACK:
+ case BLACK_GHOST:
+ return BLACK;
+ case WHITE:
+ case WHITE_GHOST:
+ return WHITE;
+ default:
+ return EMPTY;
}
+ }
}
diff --git a/src/main/java/featurecat/lizzie/rules/Zobrist.java b/src/main/java/featurecat/lizzie/rules/Zobrist.java
index 781df4ca5..508755847 100644
--- a/src/main/java/featurecat/lizzie/rules/Zobrist.java
+++ b/src/main/java/featurecat/lizzie/rules/Zobrist.java
@@ -2,73 +2,69 @@
import java.util.Random;
-/**
- * Used to maintain zobrist hashes for ko detection
- */
+/** Used to maintain zobrist hashes for ko detection */
public class Zobrist {
- private static final long[] blackZobrist, whiteZobrist;
+ private static final long[] blackZobrist, whiteZobrist;
- // initialize zobrist hashing
- static {
- Random random = new Random();
- blackZobrist = new long[Board.BOARD_SIZE * Board.BOARD_SIZE];
- whiteZobrist = new long[Board.BOARD_SIZE * Board.BOARD_SIZE];
+ // initialize zobrist hashing
+ static {
+ Random random = new Random();
+ blackZobrist = new long[Board.BOARD_SIZE * Board.BOARD_SIZE];
+ whiteZobrist = new long[Board.BOARD_SIZE * Board.BOARD_SIZE];
- for (int i = 0; i < blackZobrist.length; i++) {
- blackZobrist[i] = random.nextLong();
- whiteZobrist[i] = random.nextLong();
- }
+ for (int i = 0; i < blackZobrist.length; i++) {
+ blackZobrist[i] = random.nextLong();
+ whiteZobrist[i] = random.nextLong();
}
+ }
- // hash to be used to compare two board states
- private long zhash;
+ // hash to be used to compare two board states
+ private long zhash;
- public Zobrist() {
- zhash = 0;
- }
+ public Zobrist() {
+ zhash = 0;
+ }
- public Zobrist(long zhash) {
- this.zhash = zhash;
- }
+ public Zobrist(long zhash) {
+ this.zhash = zhash;
+ }
- /**
- * @return a copy of this zobrist
- */
- public Zobrist clone() {
- return new Zobrist(zhash);
- }
+ /** @return a copy of this zobrist */
+ public Zobrist clone() {
+ return new Zobrist(zhash);
+ }
- /**
- * Call this method to alter the current zobrist hash for this stone
- *
- * @param x x coordinate -- must be valid
- * @param y y coordinate -- must be valid
- * @param color color of the stone to alter (for adding or removing a stone color)
- */
- public void toggleStone(int x, int y, Stone color) {
- switch (color) {
- case BLACK:
- zhash ^= blackZobrist[Board.getIndex(x, y)];
- break;
- case WHITE:
- zhash ^= whiteZobrist[Board.getIndex(x, y)];
- break;
- default:
- }
+ /**
+ * Call this method to alter the current zobrist hash for this stone
+ *
+ * @param x x coordinate -- must be valid
+ * @param y y coordinate -- must be valid
+ * @param color color of the stone to alter (for adding or removing a stone color)
+ */
+ public void toggleStone(int x, int y, Stone color) {
+ switch (color) {
+ case BLACK:
+ zhash ^= blackZobrist[Board.getIndex(x, y)];
+ break;
+ case WHITE:
+ zhash ^= whiteZobrist[Board.getIndex(x, y)];
+ break;
+ default:
}
+ }
- @Override
- public boolean equals(Object o) {
- return o instanceof Zobrist && (((Zobrist) o).zhash == zhash);
- }
+ @Override
+ public boolean equals(Object o) {
+ return o instanceof Zobrist && (((Zobrist) o).zhash == zhash);
+ }
- @Override
- public int hashCode() {
- return (int) zhash;
- }
+ @Override
+ public int hashCode() {
+ return (int) zhash;
+ }
- @Override
- public String toString() {
- return "" + zhash;
- }
+ @Override
+ public String toString() {
+ return "" + zhash;
+ }
}
diff --git a/src/main/java/featurecat/lizzie/theme/DefaultTheme.java b/src/main/java/featurecat/lizzie/theme/DefaultTheme.java
deleted file mode 100644
index f70bc1fa7..000000000
--- a/src/main/java/featurecat/lizzie/theme/DefaultTheme.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package featurecat.lizzie.theme;
-
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.io.IOException;
-
-import javax.imageio.ImageIO;
-
-
-/**
- * DefaultTheme
- */
-public class DefaultTheme implements ITheme {
- BufferedImage blackStoneCached = null;
- BufferedImage whiteStoneCached = null;
- BufferedImage boardCached = null;
- BufferedImage backgroundCached = null;
-
- @Override
- public BufferedImage getBlackStone(int[] position) {
- if (blackStoneCached == null) {
- try {
- blackStoneCached = ImageIO.read(new File("assets/black0.png"));
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- return blackStoneCached;
- }
-
- @Override
- public BufferedImage getWhiteStone(int[] position) {
- if (whiteStoneCached == null) {
- try {
- whiteStoneCached = ImageIO.read(new File("assets/white0.png"));
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- return whiteStoneCached;
- }
-
- @Override
- public BufferedImage getBoard() {
- if (boardCached == null) {
- try {
- boardCached = ImageIO.read(new File("assets/board.png"));
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- return boardCached;
- }
-
- @Override
- public BufferedImage getBackground() {
- if (backgroundCached == null) {
- try {
- backgroundCached = ImageIO.read(new File("assets/background.jpg"));
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- return backgroundCached;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/featurecat/lizzie/theme/ITheme.java b/src/main/java/featurecat/lizzie/theme/ITheme.java
deleted file mode 100644
index e820a4e44..000000000
--- a/src/main/java/featurecat/lizzie/theme/ITheme.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package featurecat.lizzie.theme;
-
-import java.awt.image.BufferedImage;
-import java.io.File;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.ArrayList;
-
-/**
- * ITheme
- */
-public interface ITheme {
- static ITheme loadTheme(String name) {
- ITheme ret = _loadTheme(name);
- if (ret == null) {
- return new DefaultTheme();
- }
- return ret;
- }
-
- static ITheme _loadTheme(String name) {
- try {
- File themes = new File("theme");
- if (!themes.isDirectory()) {
- return null;
- }
- ArrayList jarFileList = new ArrayList();
- for (File file : themes.listFiles()) {
- if (file.canRead() && file.getName().endsWith(".jar")) {
- jarFileList.add(file.toURI().toURL());
- }
- }
- URLClassLoader loader = new URLClassLoader(jarFileList.toArray(new URL[jarFileList.size()]));
- Class> theme = loader.loadClass(name);
- loader.close();
- ITheme ret = (ITheme) theme.newInstance();
- return ret;
- } catch (Exception e) {
- return new DefaultTheme();
- }
- }
-
- // Considering that the theme may implement different pieces for each coordinate, you need to pass in the coordinates.
- BufferedImage getBlackStone(int[] position);
-
- BufferedImage getWhiteStone(int[] position);
-
- BufferedImage getBoard();
-
- BufferedImage getBackground();
-}
diff --git a/assets/background.jpg b/src/main/resources/assets/background.jpg
similarity index 100%
rename from assets/background.jpg
rename to src/main/resources/assets/background.jpg
diff --git a/assets/black0.png b/src/main/resources/assets/black0.png
similarity index 100%
rename from assets/black0.png
rename to src/main/resources/assets/black0.png
diff --git a/assets/board.png b/src/main/resources/assets/board.png
similarity index 100%
rename from assets/board.png
rename to src/main/resources/assets/board.png
diff --git a/assets/white0.png b/src/main/resources/assets/white0.png
similarity index 100%
rename from assets/white0.png
rename to src/main/resources/assets/white0.png
diff --git a/src/main/resources/l10n/DisplayStrings.properties b/src/main/resources/l10n/DisplayStrings.properties
index ca7686f4a..be3e5dce7 100644
--- a/src/main/resources/l10n/DisplayStrings.properties
+++ b/src/main/resources/l10n/DisplayStrings.properties
@@ -19,11 +19,13 @@ LizzieFrame.commands.keyAltC=ctrl-c|copy SGF to clipboard
LizzieFrame.commands.keyAltV=ctrl-v|paste SGF from clipboard
LizzieFrame.commands.keyC=c|toggle coordinates
LizzieFrame.commands.keyControl=ctrl|undo/redo 10 moves
+LizzieFrame.commands.keyD=d|show/hide dynamic komi
LizzieFrame.commands.keyDownArrow=down arrow|redo
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
@@ -39,13 +41,17 @@ LizzieFrame.commands.keyW=w|toggle winrate display
LizzieFrame.commands.keyPeriod=.|score game
LizzieFrame.commands.mouseWheelScroll=scrollwheel|undo/redo
LizzieFrame.commands.rightClick=right click|undo
-LizzieFrame.prompt.failedToOpenSgf=Failed to open the SGF file.
-LizzieFrame.prompt.failedToSaveSgf=Failed to save the SGF file.
+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
LizzieFrame.display.off=off
LizzieFrame.display.loading=Leela Zero is loading...
-LizzieFrame.display.download-latest-network-prompt=Download the latest network file? This may take some time.
\ No newline at end of file
+LizzieFrame.display.download-latest-network-prompt=Download the latest network file? This may take some time.
+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:
diff --git a/src/main/resources/l10n/DisplayStrings_RO.properties b/src/main/resources/l10n/DisplayStrings_RO.properties
new file mode 100644
index 000000000..14c54dce5
--- /dev/null
+++ b/src/main/resources/l10n/DisplayStrings_RO.properties
@@ -0,0 +1,53 @@
+# Note for localization
+#
+# The localization file set(resource bundle) consist of one base file(this file) and several translated property files.
+# Each localized string is assigned a unique key. All keys referenced in codes should be present in the base file.
+# If a key is present in the translated file matching the current system locale, the value in the translated
+# file will be used. Otherwise the value in the base file will be used instead.
+# Usually strings in the base file are written in English. So that if a string has no translations, it will be
+# displayed in English by default.
+#
+# In Java 8 or before, the encoding of the resource bundle files has no standards.
+# To avoid encoding inconsistencies, it is recommended to use "native2ascii" tool in JDK
+# to encode native translations in raw unicode codes, such as \u0001 \u0002 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.
+#
+# You can directly use the original display texts as the key, but it is recommended to name the key properly.
+
+LizzieFrame.commands.keyAltC=control-c|copiere SGF în memoria temporară
+LizzieFrame.commands.keyAltV=control-v|lipire SGF din memoria temporară
+LizzieFrame.commands.keyC=c|afișează/ascunde coordonate
+LizzieFrame.commands.keyControl=control|înainte/înapoi 10 mutări
+LizzieFrame.commands.keyDownArrow=săgeată jos|înainte
+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
+LizzieFrame.commands.keyM=m|afișează/ascunde numărul mutărilor
+LizzieFrame.commands.keyN=n|începe un joc împotriva lui Leela Zero
+LizzieFrame.commands.keyO=o|deschide SGF
+LizzieFrame.commands.keyP=p|pas
+LizzieFrame.commands.keyS=s|salvează SGF
+LizzieFrame.commands.keySpace=spațiu|pornește/oprește analiza
+LizzieFrame.commands.keyUpArrow=săgeată sus|înapoi
+LizzieFrame.commands.keyV=v|afișează/ascunde variante
+LizzieFrame.commands.keyW=w|afișează/ascunde probabilitate victorie
+LizzieFrame.commands.keyPeriod=.|scor
+LizzieFrame.commands.mouseWheelScroll=rotiță mouse|înainte/înapoi
+LizzieFrame.commands.rightClick=clic dreapta|înapoi
+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
+LizzieFrame.display.off=oprit
+LizzieFrame.display.loading=Leela Zero se încarcă...
+LizzieFrame.display.download-latest-network-prompt=Descărcați cel mai nou fișier rețea? Poate să dureze.
diff --git a/src/main/resources/l10n/DisplayStrings_zh_CN.properties b/src/main/resources/l10n/DisplayStrings_zh_CN.properties
index 3c26237c0..c7effab3e 100644
--- a/src/main/resources/l10n/DisplayStrings_zh_CN.properties
+++ b/src/main/resources/l10n/DisplayStrings_zh_CN.properties
@@ -6,12 +6,14 @@
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
+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.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
@@ -27,13 +29,17 @@ LizzieFrame.commands.keyW=w|\u663E\u793A/\u9690\u85CF\u80DC\u7387\u56FE
LizzieFrame.commands.keyPeriod=.(\u5C0F\u6570\u70B9)|\u70B9\u76EE
LizzieFrame.commands.mouseWheelScroll=\u9F20\u6807\u6EDA\u8F6E|\u5728\u68CB\u8C31\u4E2D\u5411\u524D/\u5411\u540E\u79FB\u52A8
LizzieFrame.commands.rightClick=\u9F20\u6807\u53F3\u952E|\u56DE\u4E0A\u4E00\u624B
-LizzieFrame.prompt.failedToOpenSgf=\u4E0D\u80FD\u6253\u5F00SGF\u6587\u4EF6.
-LizzieFrame.prompt.failedToSaveSgf=\u4E0D\u80FD\u4FDD\u5B58SGF\u6587\u4EF6.
+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
LizzieFrame.display.off=\u6682\u505C
LizzieFrame.display.loading=Leela Zero\u6B63\u5728\u8F7D\u5165\u4E2D...
LizzieFrame.display.download-latest-network-prompt=Download the latest network file? This may take some time.
+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:
diff --git a/src/test/java/common/Util.java b/src/test/java/common/Util.java
new file mode 100644
index 000000000..7fa429488
--- /dev/null
+++ b/src/test/java/common/Util.java
@@ -0,0 +1,136 @@
+package common;
+
+import featurecat.lizzie.Lizzie;
+import featurecat.lizzie.rules.Board;
+import featurecat.lizzie.rules.BoardData;
+import featurecat.lizzie.rules.BoardHistoryList;
+import featurecat.lizzie.rules.BoardHistoryNode;
+import featurecat.lizzie.rules.SGFParser;
+import featurecat.lizzie.rules.Stone;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class Util {
+
+ private static ArrayList laneUsageList = new ArrayList();
+
+ /**
+ * Get Variation Tree as String List The logic is same as the function VariationTree.drawTree
+ *
+ * @param startLane
+ * @param startNode
+ * @param variationNumber
+ * @param isMain
+ */
+ public static void getVariationTree(
+ List moveList,
+ int startLane,
+ BoardHistoryNode startNode,
+ int variationNumber,
+ boolean isMain) {
+ // Finds depth on leftmost variation of this tree
+ int depth = BoardHistoryList.getDepth(startNode) + 1;
+ int lane = startLane;
+ // Figures out how far out too the right (which lane) we have to go not to collide with other
+ // variations
+ while (lane < laneUsageList.size()
+ && laneUsageList.get(lane) <= startNode.getData().moveNumber + depth) {
+ // laneUsageList keeps a list of how far down it is to a variation in the different "lanes"
+ laneUsageList.set(lane, startNode.getData().moveNumber - 1);
+ lane++;
+ }
+ if (lane >= laneUsageList.size()) {
+ laneUsageList.add(0);
+ }
+ if (variationNumber > 1) laneUsageList.set(lane - 1, startNode.getData().moveNumber - 1);
+ laneUsageList.set(lane, startNode.getData().moveNumber);
+
+ // At this point, lane contains the lane we should use (the main branch is in lane 0)
+ BoardHistoryNode cur = startNode;
+
+ // Draw main line
+ StringBuilder sb = new StringBuilder();
+ sb.append(formatMove(cur.getData()));
+ while (cur.next() != null) {
+ cur = cur.next();
+ sb.append(formatMove(cur.getData()));
+ }
+ moveList.add(sb.toString());
+ // Now we have drawn all the nodes in this variation, and has reached the bottom of this
+ // variation
+ // Move back up, and for each, draw any variations we find
+ while (cur.previous() != null && cur != startNode) {
+ cur = cur.previous();
+ int curwidth = lane;
+ // Draw each variation, uses recursion
+ for (int i = 1; i < cur.numberOfChildren(); i++) {
+ curwidth++;
+ // Recursion, depth of recursion will normally not be very deep (one recursion level for
+ // every variation that has a variation (sort of))
+ getVariationTree(moveList, curwidth, cur.getVariation(i), i, false);
+ }
+ }
+ }
+
+ private static String formatMove(BoardData data) {
+ String stone = "";
+ if (Stone.BLACK.equals(data.lastMoveColor)) stone = "B";
+ else if (Stone.WHITE.equals(data.lastMoveColor)) stone = "W";
+ else return stone;
+
+ char x = data.lastMove == null ? 't' : (char) (data.lastMove[0] + 'a');
+ char y = data.lastMove == null ? 't' : (char) (data.lastMove[1] + 'a');
+
+ String comment = "";
+ if (data.comment != null && data.comment.trim().length() > 0) {
+ comment = String.format("C[%s]", data.comment);
+ }
+ return String.format(";%s[%c%c]%s", stone, x, y, comment);
+ }
+
+ public static String trimGameInfo(String sgf) {
+ String gameInfo = String.format("(?s).*AP\\[Lizzie: %s\\]", Lizzie.lizzieVersion);
+ return sgf.replaceFirst(gameInfo, "(");
+ }
+
+ public static String[] splitAwAbSgf(String sgf) {
+ String[] ret = new String[2];
+ String regex = "(A[BW]{1}(\\[[a-z]{2}\\])+)";
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(sgf);
+ StringBuilder sb = new StringBuilder();
+ while (matcher.find()) {
+ sb.append(matcher.group(0));
+ }
+ ret[0] = sb.toString();
+ ret[1] = sgf.replaceAll(regex, "");
+ return ret;
+ }
+
+ public static Stone[] convertStones(String awAb) {
+ Stone[] stones = new Stone[Board.BOARD_SIZE * Board.BOARD_SIZE];
+ for (int i = 0; i < stones.length; i++) {
+ stones[i] = Stone.EMPTY;
+ }
+ String regex = "(A[BW]{1})|(?<=\\[)([a-z]{2})(?=\\])";
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(awAb);
+ StringBuilder sb = new StringBuilder();
+ Stone stone = Stone.EMPTY;
+ while (matcher.find()) {
+ String str = matcher.group(0);
+ if ("AB".equals(str)) {
+ stone = Stone.BLACK;
+ } else if ("AW".equals(str)) {
+ stone = Stone.WHITE;
+ } else {
+ int[] move = SGFParser.convertSgfPosToCoord(str);
+ int index = Board.getIndex(move[0], move[1]);
+ stones[index] = stone;
+ }
+ }
+ return stones;
+ }
+}
diff --git a/src/test/java/featurecat/lizzie/analysis/MoveDataTest.java b/src/test/java/featurecat/lizzie/analysis/MoveDataTest.java
new file mode 100644
index 000000000..dbb5c5103
--- /dev/null
+++ b/src/test/java/featurecat/lizzie/analysis/MoveDataTest.java
@@ -0,0 +1,64 @@
+package featurecat.lizzie.analysis;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.List;
+import org.junit.Test;
+
+public class MoveDataTest {
+ @Test
+ public void testFromInfoLine() {
+ String info = "move R5 visits 38 winrate 5404 order 0 pv R5 Q5 R6 S4 Q10 C3 D3 C4 C6 C5 D5";
+ MoveData moveData = MoveData.fromInfo(info);
+
+ assertEquals(moveData.coordinate, "R5");
+ assertEquals(moveData.playouts, 38);
+ assertEquals(moveData.winrate, 54.04, 0.01);
+ assertEquals(
+ moveData.variation,
+ Arrays.asList("R5", "Q5", "R6", "S4", "Q10", "C3", "D3", "C4", "C6", "C5", "D5"));
+ }
+
+ private void testSummary(
+ String summary, String coordinate, int playouts, double winrate, List variation) {
+ MoveData moveData = MoveData.fromSummary(summary);
+ assertEquals(moveData.coordinate, coordinate);
+ assertEquals(moveData.playouts, playouts);
+ assertEquals(moveData.winrate, winrate, 0.01);
+ assertEquals(moveData.variation, variation);
+ }
+
+ @Test
+ public void summaryLine1() {
+ testSummary(
+ " P16 -> 4 (V: 50.94%) (N: 5.79%) PV: P16 N18 R5 Q5",
+ "P16", 4, 50.94, Arrays.asList("P16", "N18", "R5", "Q5"));
+ }
+
+ @Test
+ public void summaryLine2() {
+ testSummary(
+ " D9 -> 59 (V: 60.61%) (N: 52.59%) PV: D9 D12 E9 C13 C15 F17",
+ "D9", 59, 60.61, Arrays.asList("D9", "D12", "E9", "C13", "C15", "F17"));
+ }
+
+ @Test
+ public void summaryLine3() {
+ testSummary(
+ " B2 -> 1 (V: 46.52%) (N: 86.74%) PV: B2", "B2", 1, 46.52, Arrays.asList("B2"));
+ }
+
+ @Test
+ public void summaryLine4() {
+ testSummary(
+ " D16 -> 33 (V: 53.63%) (N: 27.64%) PV: D16 D4 Q16 O4 C3 C4",
+ "D16", 33, 53.63, Arrays.asList("D16", "D4", "Q16", "O4", "C3", "C4"));
+ }
+
+ @Test
+ public void summaryLine5() {
+ testSummary(
+ " Q16 -> 0 (V: 0.00%) (N: 0.52%) PV: Q16\n", "Q16", 0, 0.0, Arrays.asList("Q16"));
+ }
+}
diff --git a/src/test/java/featurecat/lizzie/rules/SGFParserTest.java b/src/test/java/featurecat/lizzie/rules/SGFParserTest.java
new file mode 100644
index 000000000..632e70c92
--- /dev/null
+++ b/src/test/java/featurecat/lizzie/rules/SGFParserTest.java
@@ -0,0 +1,125 @@
+package featurecat.lizzie.rules;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import common.Util;
+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;
+import org.junit.Test;
+
+public class SGFParserTest {
+
+ private Lizzie lizzie = null;
+
+ @Test
+ public void run() throws IOException {
+ lizzie = new Lizzie();
+ lizzie.config = new Config();
+ lizzie.board = new Board();
+ lizzie.frame = new LizzieFrame();
+ // new Thread( () -> {
+ lizzie.leelaz = new Leelaz();
+ // }).start();
+
+ testVariaionOnly1();
+ testFull1();
+ }
+
+ public void testVariaionOnly1() throws IOException {
+
+ String sgfString =
+ "(;B[pd];W[dp];B[pp];W[dd];B[fq]"
+ + "(;W[cn];B[cc];W[cd];B[dc];W[ed];B[fc];W[fd]"
+ + "(;B[gb]"
+ + "(;W[hc];B[nq])"
+ + "(;W[gc];B[ec];W[hc];B[hb];W[ic]))"
+ + "(;B[gc];W[ec];B[eb];W[fb];B[db];W[hc];B[gb];W[gd];B[hb]))"
+ + "(;W[nq];B[cn];W[fp];B[gp];W[fo];B[dq];W[cq];B[eq];W[cp];B[dm];W[fm]))";
+
+ int variationNum = 4;
+ String mainBranch =
+ ";B[pd];W[dp];B[pp];W[dd];B[fq];W[cn];B[cc];W[cd];B[dc];W[ed];B[fc];W[fd];B[gb];W[hc];B[nq]";
+ String variation1 = ";W[gc];B[ec];W[hc];B[hb];W[ic]";
+ String variation2 = ";B[gc];W[ec];B[eb];W[fb];B[db];W[hc];B[gb];W[gd];B[hb]";
+ String variation3 = ";W[nq];B[cn];W[fp];B[gp];W[fo];B[dq];W[cq];B[eq];W[cp];B[dm];W[fm]";
+
+ // Load correctly
+ boolean loaded = SGFParser.loadFromString(sgfString);
+ assertTrue(loaded);
+
+ // Variations
+ List moveList = new ArrayList();
+ Util.getVariationTree(moveList, 0, lizzie.board.getHistory().getCurrentHistoryNode(), 0, true);
+
+ assertTrue(moveList != null);
+ assertEquals(moveList.size(), variationNum);
+
+ assertEquals(moveList.get(0), mainBranch);
+ assertEquals(moveList.get(1), variation1);
+ assertEquals(moveList.get(2), variation2);
+ assertEquals(moveList.get(3), variation3);
+
+ // Save correctly
+ String saveSgf = SGFParser.saveToString();
+ assertTrue(saveSgf != null && saveSgf.trim().length() > 0);
+
+ assertEquals(sgfString, Util.trimGameInfo(saveSgf));
+ }
+
+ public void testFull1() throws IOException {
+
+ String sgfInfo = "(;CA[utf8]AP[MultiGo:4.4.4]SZ[19]";
+ String sgfAwAb =
+ "AB[pe][pq][oq][nq][mq][cp][dq][eq][fp]AB[qd]AW[dc][cf][oc][qo][op][np][mp][ep][fq]";
+ String sgfContent =
+ ";W[lp]C[25th question Overall view Black first Superior White 1 has a long hand. The first requirement in the layout phase is to have a big picture. What is the next black point in this situation?]"
+ + "(;B[qi]C[Correct Answer Limiting the thickness Black 1 is broken. The reason why Black is under the command of four hands is to win the first hand and occupy the black one. That is to say, on the lower side, the bigger one is the right side. Black 1 is both good and bad, and it limits the development of white and thick. It is good chess. Black 1 is appropriate, and it will not work if you go all the way or take a break.];W[lq];B[rp]C[1 Figure (turning head value?) After black 1 , white is like 2 songs, then it is not too late to fly black again. There is a saying that \"the head is worth a thousand dollars\" in the chessboard, but in the situation of this picture, the white song has no such value. Because after the next white A, black B, white must be on the lower side to be complete. It can be seen that for Black, the meaning of playing chess below is also not significant. The following is a gesture that has come to an end. Both sides have no need to rush to settle down here.])"
+ + "(;B[kq];W[pi]C[2 diagram (failure) Black 1 jump failed. The reason is not difficult to understand from the above analysis. If Black wants to jump out, he shouldn’t have four hands in the first place. By the white 2 on the right side of the hand, it immediately constitutes a strong appearance, black is not good. Although the black got some fixed ground below, but the position was too low, and it became a condensate, black is not worth the candle. ]))";
+ String sgfString = sgfInfo + sgfAwAb + sgfContent;
+
+ int variationNum = 2;
+ String mainBranch =
+ ";W[lp]C[25th question Overall view Black first Superior White 1 has a long hand. The first requirement in the layout phase is to have a big picture. What is the next black point in this situation?];B[qi]C[Correct Answer Limiting the thickness Black 1 is broken. The reason why Black is under the command of four hands is to win the first hand and occupy the black one. That is to say, on the lower side, the bigger one is the right side. Black 1 is both good and bad, and it limits the development of white and thick. It is good chess. Black 1 is appropriate, and it will not work if you go all the way or take a break.];W[lq];B[rp]C[1 Figure (turning head value?) After black 1 , white is like 2 songs, then it is not too late to fly black again. There is a saying that \"the head is worth a thousand dollars\" in the chessboard, but in the situation of this picture, the white song has no such value. Because after the next white A, black B, white must be on the lower side to be complete. It can be seen that for Black, the meaning of playing chess below is also not significant. The following is a gesture that has come to an end. Both sides have no need to rush to settle down here.]";
+ String variation1 =
+ ";B[kq];W[pi]C[2 diagram (failure) Black 1 jump failed. The reason is not difficult to understand from the above analysis. If Black wants to jump out, he shouldn’t have four hands in the first place. By the white 2 on the right side of the hand, it immediately constitutes a strong appearance, black is not good. Although the black got some fixed ground below, but the position was too low, and it became a condensate, black is not worth the candle. ]";
+
+ Stone[] expectStones = Util.convertStones(sgfAwAb);
+
+ // Load correctly
+ boolean loaded = SGFParser.loadFromString(sgfString);
+ assertTrue(loaded);
+
+ // Variations
+ List moveList = new ArrayList();
+ Util.getVariationTree(moveList, 0, lizzie.board.getHistory().getCurrentHistoryNode(), 0, true);
+
+ assertTrue(moveList != null);
+ assertEquals(moveList.size(), variationNum);
+ assertEquals(moveList.get(0), mainBranch);
+ assertEquals(moveList.get(1), variation1);
+
+ // AW/AB
+ assertArrayEquals(expectStones, Lizzie.board.getHistory().getStones());
+
+ // Save correctly
+ String saveSgf = SGFParser.saveToString();
+ assertTrue(saveSgf != null && saveSgf.trim().length() > 0);
+
+ String sgf = Util.trimGameInfo(saveSgf);
+ String[] ret = Util.splitAwAbSgf(sgf);
+ Stone[] actualStones = Util.convertStones(ret[0]);
+
+ // AW/AB
+ assertArrayEquals(expectStones, actualStones);
+
+ // Content
+ assertEquals("(" + sgfContent, ret[1]);
+ }
+}