diff --git a/.gitignore b/.gitignore
index 79e484680..e1d4690cd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,9 @@ src/main/resources/META-INF
target
leelaz_opencl_tuning
lizzie.sh
+
+.classpath
+
+.project
+
+*.prefs
diff --git a/src/main/java/featurecat/lizzie/WrapString.java b/src/main/java/featurecat/lizzie/WrapString.java
new file mode 100644
index 000000000..7ddeab79a
--- /dev/null
+++ b/src/main/java/featurecat/lizzie/WrapString.java
@@ -0,0 +1,181 @@
+package featurecat.lizzie;
+
+import java.awt.FontMetrics;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Globally available utility classes, mostly for string manipulation.
+ *
+ * @author Jim Menard, jimm@io.com
+ */
+public class WrapString {
+ /**
+ * Returns an array of strings, one for each line in the string after it has
+ * been wrapped to fit lines of maxWidth. Lines end with any of cr,
+ * lf, or cr lf. A line ending at the end of the string will not output a
+ * further, empty string.
+ *
+ * This code assumes str is not null
.
+ *
+ * @param str
+ * the string to split
+ * @param fm
+ * needed for string width calculations
+ * @param maxWidth
+ * the max line width, in points
+ * @return a non-empty list of strings
+ */
+ public static List wrap(String str, FontMetrics fm, int maxWidth) {
+ List lines = splitIntoLines(str);
+ if (lines.size() == 0)
+ return lines;
+
+ ArrayList strings = new ArrayList();
+ for (Iterator iter = lines.iterator(); iter.hasNext();)
+ wrapLineInto((String) iter.next(), strings, fm, maxWidth);
+ return strings;
+ }
+
+ /**
+ * Given a line of text and font metrics information, wrap the line and add the
+ * new line(s) to list.
+ *
+ * @param line
+ * a line of text
+ * @param list
+ * an output list of strings
+ * @param fm
+ * font metrics
+ * @param maxWidth
+ * maximum width of the line(s)
+ */
+ public static void wrapLineInto(String line, List list, FontMetrics fm, int maxWidth) {
+ int len = line.length();
+ int width;
+ while (len > 0 && (width = fm.stringWidth(line)) > maxWidth) {
+ // Guess where to split the line. Look for the next space before
+ // or after the guess.
+ int guess = len * maxWidth / width;
+ String before = line.substring(0, guess).trim();
+
+ width = fm.stringWidth(before);
+ int pos = 0;
+ if (width > maxWidth) { // Too long
+ pos = findBreakBefore(line, guess);
+ // fix too long bug
+ if (pos <= 0 || (width = fm.stringWidth(line.substring(0, pos).trim())) > maxWidth) {
+ int diff = width - maxWidth;
+ int i = 0;
+ for (; (diff > 0 && i < 4); i++) {
+ diff = diff - fm.stringWidth(line.substring(guess - i - 1, guess - i));
+ }
+ pos = guess - i;
+ }
+ }
+ else { // Too short or possibly just right
+ pos = findBreakAfter(line, guess);
+ if (pos != -1) { // Make sure this doesn't make us too long
+ before = line.substring(0, pos).trim();
+ if (fm.stringWidth(before) > maxWidth)
+ pos = findBreakBefore(line, guess);
+ }
+ }
+ // fix the bug for '-'
+// if (pos == -1)
+ if (pos <= 0)
+ pos = guess; // Split in the middle of the word
+
+ list.add(line.substring(0, pos).trim());
+ line = line.substring(pos).trim();
+ len = line.length();
+ }
+ if (len > 0)
+ list.add(line);
+ }
+
+ /**
+ * Returns the index of the first whitespace character or '-' in line
+ * that is at or before start. Returns -1 if no such character is
+ * found.
+ *
+ * @param line
+ * a string
+ * @param start
+ * where to star looking
+ */
+ public static int findBreakBefore(String line, int start) {
+ for (int i = start; i >= 0; --i) {
+ char c = line.charAt(i);
+ if (Character.isWhitespace(c) || c == '-')
+ return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the index of the first whitespace character or '-' in line
+ * that is at or after start. Returns -1 if no such character is
+ * found.
+ *
+ * @param line
+ * a string
+ * @param start
+ * where to star looking
+ */
+ public static int findBreakAfter(String line, int start) {
+ int len = line.length();
+ for (int i = start; i < len; ++i) {
+ char c = line.charAt(i);
+ if (Character.isWhitespace(c) || c == '-')
+ return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns an array of strings, one for each line in the string. Lines end with
+ * any of cr, lf, or cr lf. A line ending at the end of the string will not
+ * output a further, empty string.
+ *
+ * This code assumes str is not null
.
+ *
+ * @param str
+ * the string to split
+ * @return a non-empty list of strings
+ */
+ public static List splitIntoLines(String str) {
+ ArrayList strings = new ArrayList();
+
+ int len = str.length();
+ if (len == 0) {
+ strings.add("");
+ return strings;
+ }
+
+ int lineStart = 0;
+
+ for (int i = 0; i < len; ++i) {
+ char c = str.charAt(i);
+ if (c == '\r') {
+ int newlineLength = 1;
+ if ((i + 1) < len && str.charAt(i + 1) == '\n')
+ newlineLength = 2;
+ strings.add(str.substring(lineStart, i));
+ lineStart = i + newlineLength;
+ if (newlineLength == 2) // skip \n next time through loop
+ ++i;
+ } else if (c == '\n') {
+ strings.add(str.substring(lineStart, i));
+ lineStart = i + 1;
+ }
+ }
+ if (lineStart < len)
+ strings.add(str.substring(lineStart));
+
+ return strings;
+ }
+
+}
diff --git a/src/main/java/featurecat/lizzie/gui/LizzieFrame.java b/src/main/java/featurecat/lizzie/gui/LizzieFrame.java
index 3bb067a5d..f52935854 100644
--- a/src/main/java/featurecat/lizzie/gui/LizzieFrame.java
+++ b/src/main/java/featurecat/lizzie/gui/LizzieFrame.java
@@ -13,6 +13,7 @@
import com.jhlabs.image.GaussianFilter;
import featurecat.lizzie.Lizzie;
import featurecat.lizzie.Util;
+import featurecat.lizzie.WrapString;
import featurecat.lizzie.analysis.GameInfo;
import featurecat.lizzie.analysis.Leelaz;
import featurecat.lizzie.rules.Board;
@@ -21,6 +22,7 @@
import featurecat.lizzie.rules.SGFParser;
import org.json.JSONObject;
import org.json.JSONArray;
+import org.json.JSONException;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
@@ -414,7 +416,12 @@ public void paint(Graphics g0) {
if (Lizzie.config.showVariationGraph) {
drawVariationTreeContainer(backgroundG, vx, vy, vw, vh);
- variationTree.draw(g, treex, treey, treew, treeh);
+ // Draw the Comment of the Sgf
+ int cHeight = drawCommnet(g, vx, vy, vw, vh, false);
+ variationTree.draw(g, treex, treey, treew, treeh - cHeight);
+ } else {
+ // Draw the Comment of the Sgf
+ int cHeight = drawCommnet(g, vx, boardY, vw, vh, true);
}
if (Lizzie.config.showSubBoard) {
try {
@@ -490,6 +497,16 @@ private void drawVariationTreeContainer(Graphics2D g, int vx, int vy, int vw, in
private void drawPonderingState(Graphics2D g, String text, int x, int y, double size) {
Font font = new Font(systemDefaultFontName, Font.PLAIN, (int)(Math.max(getWidth(), getHeight()) * size));
FontMetrics fm = g.getFontMetrics(font);
+ // for trim long text
+ if (Lizzie.leelaz.isLoaded()) {
+ int mainBoardX = (boardRenderer != null && boardRenderer.getLocation() != null) ? boardRenderer.getLocation().x : 0;
+ if (mainBoardX > x) {
+ ArrayList list = (ArrayList) WrapString.wrap(text, fm, mainBoardX - x);
+ if (list != null && list.size() > 0) {
+ text = list.get(0);
+ }
+ }
+ }
int stringWidth = fm.stringWidth(text);
int stringHeight = fm.getAscent() - fm.getDescent();
int width = stringWidth;
@@ -945,4 +962,71 @@ public void pasteSgf() {
public void increaseMaxAlpha(int k) {
boardRenderer.increaseMaxAlpha(k);
}
+
+ /**
+ * Draw the Comment of the Sgf file
+ *
+ * @param g
+ * @param x
+ * @param y
+ * @param w
+ * @param h
+ * @param full
+ * @return
+ */
+ private int drawCommnet(Graphics2D g, int x, int y, int w, int h, boolean full) {
+ int cHeight = 0;
+ String comment = (Lizzie.board.getHistory().getData() != null && Lizzie.board.getHistory().getData().comment != null) ? Lizzie.board.getHistory().getData().comment : "";
+ if (comment != null && comment.trim().length() > 0) {
+ double rate = full ? 1 : 0.1;
+ cHeight = (int)(h * rate);
+ // May be need to set up a Chinese Font for display a Chinese Text in the non-Chinese environment
+// String systemDefaultFontName = "宋体";
+ int fontSize = (int)(Math.min(getWidth(), getHeight()) * 0.98 * 0.03);
+ try {
+ fontSize = Lizzie.config.uiConfig.getInt("comment-font-size");
+ } catch (JSONException e) {
+ if (fontSize < 16) {
+ fontSize = 16;
+ } else if (fontSize < 16) {
+ fontSize = 16;
+ }
+ }
+ Font font = new Font(systemDefaultFontName, Font.PLAIN, fontSize);
+ FontMetrics fm = g.getFontMetrics(font);
+ int stringWidth = fm.stringWidth(comment);
+ int stringHeight = fm.getHeight(); //fm.getAscent() - fm.getDescent();
+ int width = stringWidth;
+ int height = stringHeight; //(int)(stringHeight * 1.2);
+
+ ArrayList list = (ArrayList) WrapString.wrap(comment, fm, (int)(w - height*0.9));
+ if (list != null && list.size() > 0) {
+ if (!full) {
+ if (list.size() * height > cHeight) {
+ cHeight = list.size() * height;
+ if (cHeight > (int)(h * 0.4)) {
+ cHeight = (int)(h * 0.4);
+ }
+ }
+ }
+ int ystart = full ? y : h - cHeight;
+ // Draw background
+ Color oriColor = g.getColor();
+ g.setColor(new Color(0, 0, 0, 150));
+ g.fillRect(x, ystart - height, w, cHeight + height * 2);
+ g.setColor(Color.white);
+ g.setFont(font);
+ int i = 0;
+ for (String s : list) {
+ g.drawString(s, x + (int)(height * 0.2), ystart + height * (full?(i+1):i));
+ i++;
+ }
+ g.setColor(oriColor);
+ cHeight = cHeight + height;
+ } else {
+ cHeight = 0;
+ }
+ }
+ return cHeight;
+ }
}
diff --git a/src/main/java/featurecat/lizzie/gui/VariationTree.java b/src/main/java/featurecat/lizzie/gui/VariationTree.java
index 6c873dc0e..ff05d30bc 100644
--- a/src/main/java/featurecat/lizzie/gui/VariationTree.java
+++ b/src/main/java/featurecat/lizzie/gui/VariationTree.java
@@ -79,7 +79,7 @@ public void drawTree(Graphics2D g, int posx, int posy, int startLane, int maxpos
g.setColor(curcolor);
// Draw main line
- while (cur.next() != null && posy + YSPACING < maxposy) {
+ while (cur.next() != null && ((posy + YSPACING + DOT_DIAM) < maxposy)) { // Fix oval cover issue sometimes
posy += YSPACING;
cur = cur.next();
if (cur == curMove) {
diff --git a/src/main/java/featurecat/lizzie/rules/Board.java b/src/main/java/featurecat/lizzie/rules/Board.java
index 81fe97825..6746128ad 100644
--- a/src/main/java/featurecat/lizzie/rules/Board.java
+++ b/src/main/java/featurecat/lizzie/rules/Board.java
@@ -99,6 +99,19 @@ public static String convertCoordinatesToName(int x, int y) {
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;
+ }
+ }
+ }
/**
* The pass. Thread safe
diff --git a/src/main/java/featurecat/lizzie/rules/BoardData.java b/src/main/java/featurecat/lizzie/rules/BoardData.java
index 1fce99366..167566089 100644
--- a/src/main/java/featurecat/lizzie/rules/BoardData.java
+++ b/src/main/java/featurecat/lizzie/rules/BoardData.java
@@ -17,6 +17,8 @@ public class BoardData {
public int blackCaptures;
public int whiteCaptures;
+
+ 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;
diff --git a/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java b/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java
index 30ba2b81c..45d301440 100644
--- a/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java
+++ b/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java
@@ -88,6 +88,20 @@ public BoardData next() {
return head.getData();
}
+ /**
+ * moves the pointer to the right, returns the node stored there
+ *
+ * @return the next node, null if there is no next node
+ */
+ public BoardHistoryNode nextNode() {
+ if (head.next() == null)
+ return null;
+ else
+ head = head.next();
+
+ return head;
+ }
+
/**
* moves the pointer to the variation number idx, returns the data stored there
*
@@ -185,6 +199,10 @@ public int[] getMoveNumberList() {
public BoardHistoryNode getCurrentHistoryNode() {
return head;
}
+
+ public void setCurrentHistoryNode(BoardHistoryNode head) {
+ this.head = head;
+ }
/**
* @param data the board position to check against superko
diff --git a/src/main/java/featurecat/lizzie/rules/SGFParser.java b/src/main/java/featurecat/lizzie/rules/SGFParser.java
old mode 100644
new mode 100755
index c5f274b12..cf1e52820
--- a/src/main/java/featurecat/lizzie/rules/SGFParser.java
+++ b/src/main/java/featurecat/lizzie/rules/SGFParser.java
@@ -1,10 +1,13 @@
package featurecat.lizzie.rules;
+import java.util.HashMap;
+import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import featurecat.lizzie.Lizzie;
import featurecat.lizzie.analysis.GameInfo;
+import featurecat.lizzie.analysis.Leelaz;
import featurecat.lizzie.plugin.PluginManager;
import java.io.*;
@@ -66,6 +69,9 @@ private static boolean parse(String value) {
return false;
}
int subTreeDepth = 0;
+ // the variation step count
+ Map subTreeStepMap = new HashMap();
+ String awabComment = null, prevTag = null;
boolean inTag = false, isMultiGo = false, escaping = false;
String tag = null;
StringBuilder tagBuilder = new StringBuilder();
@@ -79,12 +85,16 @@ private static boolean parse(String value) {
String blackPlayer = "", whitePlayer = "";
PARSE_LOOP:
- for (byte b : value.getBytes()) {
+ // for suppoert unicode char
+ // for (byte b : value.getBytes()) {
+ for (int i = 0; i < value.length(); i++) {
// Check unicode charactors (UTF-8)
- char c = (char) b;
- if (((int) b & 0x80) != 0) {
- continue;
- }
+ // for suppoert unicode char
+ // char c = (char) b;
+ char c = value.charAt(i);
+ // if (((int) b & 0x80) != 0) {
+ // continue;
+ // }
if (escaping) {
// Any char following "\" is inserted verbatim
// (ref) "3.2. Text" in https://www.red-bean.com/sgf/sgf4.html
@@ -96,14 +106,26 @@ private static boolean parse(String value) {
case '(':
if (!inTag) {
subTreeDepth += 1;
+ // init the step count
+ subTreeStepMap.put(Integer.valueOf(subTreeDepth), Integer.valueOf(0));
+ } else {
+ if (i > 0) {
+ tagContentBuilder.append(c);
+ }
}
break;
case ')':
if (!inTag) {
- subTreeDepth -= 1;
if (isMultiGo) {
- break PARSE_LOOP;
+ // restore the variation nodes
+ for (int s = 0; s < subTreeStepMap.get(Integer.valueOf(subTreeDepth)).intValue(); s++) {
+ Lizzie.board.previousMove();
+ }
+// break PARSE_LOOP;
}
+ subTreeDepth -= 1;
+ } else {
+ tagContentBuilder.append(c);
}
break;
case '[':
@@ -134,6 +156,7 @@ private static boolean parse(String value) {
if (move == null) {
Lizzie.board.pass(Stone.BLACK);
} else {
+ subTreeStepMap.put(Integer.valueOf(subTreeDepth), Integer.valueOf(subTreeStepMap.get(Integer.valueOf(subTreeDepth)).intValue() + 1));
Lizzie.board.place(move[0], move[1], Stone.BLACK);
}
} else if (tag.equals("W")) {
@@ -141,8 +164,16 @@ private static boolean parse(String value) {
if (move == null) {
Lizzie.board.pass(Stone.WHITE);
} else {
+ subTreeStepMap.put(Integer.valueOf(subTreeDepth), Integer.valueOf(subTreeStepMap.get(Integer.valueOf(subTreeDepth)).intValue() + 1));
Lizzie.board.place(move[0], move[1], Stone.WHITE);
}
+ } else if (tag.equals("C")) {
+ // for 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) {
@@ -173,6 +204,7 @@ private static boolean parse(String value) {
e.printStackTrace();
}
}
+ prevTag = tag;
break;
case ';':
break;
@@ -198,6 +230,11 @@ private static boolean parse(String value) {
// Rewind to game start
while (Lizzie.board.previousMove()) ;
+
+ // Set AW/AB Comment
+ if (awabComment != null) {
+ Lizzie.board.comment(awabComment);
+ }
return true;
}
@@ -231,6 +268,15 @@ private static void saveToStream(Board board, Writer writer) throws IOException
builder.append(String.format("KM[%s]PW[%s]PB[%s]DT[%s]AP[Lizzie: %s]",
komi, playerWhite, playerBlack, date, Lizzie.lizzieVersion));
+ // 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;
+ }
+
// move to the first move
history.toStart();
@@ -250,12 +296,45 @@ private static void saveToStream(Board board, Writer writer) throws IOException
builder.append(String.format("[%c%c]", x, y));
}
}
+ } else {
+ //has AW/AB?
+ 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);
+ }
}
+ // Start 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.
- BoardData data;
+// 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
@@ -294,21 +373,139 @@ private static void saveToStream(Board board, Writer writer) throws IOException
// }
// }
- while ((data = history.next()) != null) {
+ // Write variation tree
+// 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));
+// }
+ builder.append(generateNode(board, writer, history.nextNode()));
+
+ // close file
+ builder.append(')');
+ writer.append(builder.toString());
+ }
+
+
+ private static String generateNode(Board board, Writer writer, BoardHistoryNode node) throws IOException {
+ StringBuilder builder = new StringBuilder("");
+
+ if (node != null) {
- String stone;
- if (Stone.BLACK.equals(data.lastMoveColor)) stone = "B";
- else if (Stone.WHITE.equals(data.lastMoveColor)) stone = "W";
- else continue;
+ BoardData data = node.getData();
+ String stone = "";
+ if (Stone.BLACK.equals(data.lastMoveColor) || Stone.WHITE.equals(data.lastMoveColor)) {
- char x = data.lastMove == null ? 't' : (char) (data.lastMove[0] + 'a');
- char y = data.lastMove == null ? 't' : (char) (data.lastMove[1] + 'a');
+ 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 with win rate
+ String winrateComment = formatWinrate(node);
+ if (data.comment != null) {
+ String winratePattern = "\\([^\\(\\)/]*\\/[0-9\\.]*[kmKM]*\\)[0-9\\.\\-]+%*\\(*[0-9\\.\\-]+%*\\)*";
+ if (data.comment.matches("(?s).*" + winratePattern + "(?s).*")) {
+ winrateComment = data.comment.replaceAll(winratePattern, winrateComment);
+ } else {
+ winrateComment = String.format("%s %s", data.comment, winrateComment);
+ }
+ }
+ builder.append(String.format("C[%s]", winrateComment));
+
+ if (node.numberOfChildren() > 1) {
+ // Variation
+ for (BoardHistoryNode sub : node.getNexts()) {
+ builder.append("(");
+ builder.append(generateNode(board, writer, sub));
+ builder.append(")");
+ }
+ } else if (node.numberOfChildren() == 1) {
+ builder.append(generateNode(board, writer, node.next()));
+ } else {
+ return builder.toString();
+ }
+ }
+ }
+
+ return builder.toString();
+ }
+
+ /**
+ * Format Winrate
+ *
+ */
+ private static String formatWinrate(BoardHistoryNode node) {
+ if (node == null) {
+ return "";
+ }
+ BoardData data = node.getData();
+ String engine = "Leelaz"; //Lizzie.leelaz.currentWeight();
+ String playouts = "";
+ String curWinrate = "";
+ String lastMoveWinrate = "";
- builder.append(String.format(";%s[%c%c]", stone, x, y));
+ double lastWR = 50; // winrate the previous move
+ boolean validLastWinrate = false; // whether it was actually calculated
+ BoardData lastNode = node.previous().getData();
+ if (lastNode != null && lastNode.playouts > 0) {
+ lastWR = lastNode.winrate;
+ validLastWinrate = true;
}
+ double curWR = data.winrate; // winrate on this move
+ boolean validWinrate = (data.playouts > 0); // and whether it was actually calculated
+ if (!validWinrate) {
+ curWR = 100 - lastWR; // display last move's winrate for now (with color difference)
+ }
+
+ // Playouts
+ playouts = getPlayoutsString(data.playouts);
- // close file
- builder.append(')');
- writer.append(builder.toString());
+ // Winrate
+ if(Lizzie.config.handicapInsteadOfWinrate) {
+ curWinrate = String.format("%.2f", Lizzie.leelaz.winrateToHandicap(100-curWR));
+ } else {
+ curWinrate = String.format("%.1f%%", 100 - curWR);
+ }
+
+ // Last move
+ if (validLastWinrate && validWinrate) {
+ if(Lizzie.config.handicapInsteadOfWinrate) {
+ lastMoveWinrate = String.format("(%.2f)", Lizzie.leelaz.winrateToHandicap(100-curWR) - Lizzie.leelaz.winrateToHandicap(lastWR));
+ } else {
+ lastMoveWinrate = String.format("(%.1f%%)", 100 - lastWR - curWR);
+ }
+ }
+
+ return String.format("(%s/%s)%s%s", engine, playouts, curWinrate, lastMoveWinrate);
+ }
+
+ /**
+ * Temp TODO
+ * @return a shorter, rounded string version of playouts. e.g. 345 -> 345, 1265 -> 1.3k, 44556 -> 45k, 133523 -> 134k, 1234567 -> 1.2m
+ */
+ private static 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);
+ }
}
}