diff --git a/src/main/java/featurecat/lizzie/Config.java b/src/main/java/featurecat/lizzie/Config.java index 64d8c2513..02d2ae9ab 100644 --- a/src/main/java/featurecat/lizzie/Config.java +++ b/src/main/java/featurecat/lizzie/Config.java @@ -14,6 +14,7 @@ public class Config { public boolean showMoveNumber = false; + public boolean newMoveNumberInBranch = true; public boolean showWinrate = true; public boolean largeWinrate = false; public boolean showBlunderBar = true; @@ -140,6 +141,7 @@ public Config() throws IOException { theme = new Theme(uiConfig); showMoveNumber = uiConfig.getBoolean("show-move-number"); + newMoveNumberInBranch = uiConfig.optBoolean("new-move-number-in-branch", true); showStatus = uiConfig.getBoolean("show-status"); showBranch = uiConfig.getBoolean("show-leelaz-variation"); showWinrate = uiConfig.getBoolean("show-winrate"); diff --git a/src/main/java/featurecat/lizzie/gui/VariationTree.java b/src/main/java/featurecat/lizzie/gui/VariationTree.java index 68e24d02a..a63fc0bb5 100644 --- a/src/main/java/featurecat/lizzie/gui/VariationTree.java +++ b/src/main/java/featurecat/lizzie/gui/VariationTree.java @@ -25,6 +25,7 @@ public void drawTree( int posy, int startLane, int maxposy, + int minposx, BoardHistoryNode startNode, int variationNumber, boolean isMain) { @@ -36,17 +37,17 @@ public void drawTree( 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) { + int moveNumber = startNode.getData().moveNumber; + while (lane < laneUsageList.size() && laneUsageList.get(lane) <= 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); + laneUsageList.set(lane, 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); + if (variationNumber > 1) laneUsageList.set(lane - 1, moveNumber - 1); + laneUsageList.set(lane, moveNumber); // At this point, lane contains the lane we should use (the main branch is in lane 0) @@ -58,39 +59,47 @@ public void drawTree( if (lane > 0) { if (lane - startLane > 0 || variationNumber > 1) { // Need a horizontal and an angled line - g.drawLine( + drawLine( + g, curposx + dotoffset, posy + dotoffset, curposx + dotoffset - XSPACING, - posy + dotoffset - YSPACING); - g.drawLine( + posy + dotoffset - YSPACING, + minposx); + drawLine( + g, posx + (startLane - variationNumber) * XSPACING + 2 * dotoffset, posy - YSPACING + dotoffset, curposx + dotoffset - XSPACING, - posy + dotoffset - YSPACING); + posy + dotoffset - YSPACING, + minposx); } else { // Just an angled line - g.drawLine( + drawLine( + g, curposx + dotoffset, posy + dotoffset, curposx + 2 * dotoffset - XSPACING, - posy + 2 * dotoffset - YSPACING); + posy + 2 * dotoffset - YSPACING, + minposx); } } // 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().isPresent()) { - 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); + if (curposx > minposx && posy > 0) { + if (startNode == curMove) { + g.setColor(Color.green.brighter().brighter()); + } + if (startNode.previous().isPresent()) { + 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); @@ -98,20 +107,25 @@ public void drawTree( while (cur.next().isPresent() && posy + YSPACING < maxposy) { posy += YSPACING; cur = cur.next().get(); - if (cur == curMove) { - g.setColor(Color.green.brighter().brighter()); + if (curposx > minposx && posy > 0) { + 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); } - 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().isPresent() && cur != startNode) { + while (cur.previous().isPresent() && (isMain || cur != startNode)) { cur = cur.previous().get(); int curwidth = lane; // Draw each variation, uses recursion @@ -121,7 +135,7 @@ public void drawTree( // every variation that has a variation (sort of)) Optional variation = cur.getVariation(i); if (variation.isPresent()) { - drawTree(g, posx, posy, curwidth, maxposy, variation.get(), i, false); + drawTree(g, posx, posy, curwidth, maxposy, minposx, variation.get(), i, false); } } posy -= YSPACING; @@ -166,6 +180,59 @@ public void draw(Graphics2D g, int posx, int posy, int width, int height) { node = node.previous().get(); curposy -= YSPACING; } - drawTree(g, posx + xoffset, curposy, 0, posy + height, node, 0, true); + int lane = getCurLane(node, curMove, curposy, posy + height, 0, true); + int startx = posx + xoffset; + if (((lane + 1) * XSPACING + xoffset + DOT_DIAM + strokeRadius - width) > 0) { + startx = startx - ((lane + 1) * XSPACING + xoffset + DOT_DIAM + strokeRadius - width); + } + drawTree(g, startx, curposy, 0, posy + height, posx + strokeRadius, node, 0, true); + } + + private void drawLine(Graphics g, int x1, int y1, int x2, int y2, int minx) { + if (x1 <= minx && x2 <= minx) { + return; + } + int nx1 = x1, ny1 = y1, nx2 = x2, ny2 = y2; + if (x1 > minx && x2 <= minx) { + ny2 = y2 - (x1 - minx) / (x1 - x2) * (y2 - y1); + nx2 = minx; + } else if (x2 > minx && x1 <= minx) { + ny1 = y1 - (x2 - minx) / (x2 - x1) * (y1 - y2); + nx1 = minx; + } + g.drawLine(nx1, ny1, nx2, ny2); + } + + private int getCurLane( + BoardHistoryNode start, + BoardHistoryNode curMove, + int curposy, + int maxy, + int laneCount, + boolean isMain) { + BoardHistoryNode next = start; + int nexty = curposy; + while (next.next().isPresent() && nexty + YSPACING < maxy) { + nexty += YSPACING; + next = next.next().get(); + } + while (next.previous().isPresent() && (isMain || next != start)) { + next = next.previous().get(); + for (int i = 1; i < next.numberOfChildren(); i++) { + laneCount++; + if (next.findIndexOfNode(curMove, true) == i) { + return laneCount; + } + Optional variation = next.getVariation(i); + if (variation.isPresent()) { + int subLane = getCurLane(variation.get(), curMove, nexty, maxy, laneCount, false); + if (subLane > 0) { + return subLane; + } + } + } + nexty -= YSPACING; + } + return 0; } } diff --git a/src/main/java/featurecat/lizzie/rules/Board.java b/src/main/java/featurecat/lizzie/rules/Board.java index 7488bb563..5a83fb2be 100644 --- a/src/main/java/featurecat/lizzie/rules/Board.java +++ b/src/main/java/featurecat/lizzie/rules/Board.java @@ -146,7 +146,6 @@ public void comment(String comment) { public void moveNumber(int moveNumber) { synchronized (this) { BoardData data = history.getData(); - data.moveNumber = moveNumber; if (data.lastMove.isPresent()) { int[] moveNumberList = history.getMoveNumberList(); moveNumberList[Board.getIndex(data.lastMove.get()[0], data.lastMove.get()[1])] = moveNumber; @@ -205,7 +204,6 @@ public void removeStone(int x, int y, Stone color) { Stone oriColor = stones[getIndex(x, y)]; stones[getIndex(x, y)] = Stone.EMPTY; zobrist.toggleStone(x, y, oriColor); - data.moveNumber = 0; data.moveNumberList[Board.getIndex(x, y)] = 0; Lizzie.frame.repaint(); @@ -221,6 +219,9 @@ public void removeStone(int x, int y, Stone color) { public void addNodeProperty(String key, String value) { synchronized (this) { history.getData().addProperty(key, value); + if ("MN".equals(key)) { + moveNumber(Integer.parseInt(value)); + } } } @@ -241,10 +242,20 @@ public void addNodeProperties(Map properties) { * @param color the type of pass */ public void pass(Stone color) { + pass(color, false, false); + } + + /** + * The pass. Thread safe + * + * @param color the type of pass + * @param newBranch add a new branch + */ + public void pass(Stone color, boolean newBranch, boolean dummy) { synchronized (this) { // check to see if this move is being replayed in history - if (history.getNext().map(n -> !n.lastMove.isPresent()).orElse(false)) { + if (history.getNext().map(n -> !n.lastMove.isPresent()).orElse(false) && !newBranch) { // this is the next move in history. Just increment history so that we don't erase the // redo's history.next(); @@ -258,7 +269,10 @@ public void pass(Stone color) { Stone[] stones = history.getStones().clone(); Zobrist zobrist = history.getZobrist(); int moveNumber = history.getMoveNumber() + 1; - int[] moveNumberList = history.getMoveNumberList().clone(); + int[] moveNumberList = + newBranch && history.getNext().isPresent() + ? new int[Board.boardSize * Board.boardSize] + : history.getMoveNumberList().clone(); // build the new game state BoardData newState = @@ -274,6 +288,7 @@ public void pass(Stone color) { history.getData().whiteCaptures, 0, 0); + newState.dummy = dummy; // update leelaz with pass Lizzie.leelaz.playMove(color, "pass"); @@ -281,7 +296,7 @@ public void pass(Stone color) { Lizzie.leelaz.genmove((history.isBlacksTurn() ? "W" : "B")); // update history with pass - history.addOrGoto(newState); + history.addOrGoto(newState, newBranch); Lizzie.frame.repaint(); } @@ -329,7 +344,7 @@ public void place(int x, int y, Stone color, boolean newBranch) { // check to see if this coordinate is being replayed in history Optional nextLast = history.getNext().flatMap(n -> n.lastMove); - if (nextLast.isPresent() && nextLast.get()[0] == x && nextLast.get()[1] == y) { + if (nextLast.isPresent() && nextLast.get()[0] == x && nextLast.get()[1] == y && !newBranch) { // this is the next coordinate in history. Just increment history so that we don't erase the // redo's history.next(); @@ -349,9 +364,14 @@ public void place(int x, int y, Stone color, boolean newBranch) { Zobrist zobrist = history.getZobrist(); Optional lastMove = Optional.of(new int[] {x, y}); int moveNumber = history.getMoveNumber() + 1; - int[] moveNumberList = history.getMoveNumberList().clone(); + int moveMNNumber = + history.getMoveMNNumber() > -1 && !newBranch ? history.getMoveMNNumber() + 1 : -1; + int[] moveNumberList = + newBranch && history.getNext().isPresent() + ? new int[Board.boardSize * Board.boardSize] + : history.getMoveNumberList().clone(); - moveNumberList[Board.getIndex(x, y)] = moveNumber; + moveNumberList[Board.getIndex(x, y)] = moveMNNumber > -1 ? moveMNNumber : moveNumber; // set the stone at (x, y) to color stones[getIndex(x, y)] = color; @@ -390,6 +410,7 @@ public void place(int x, int y, Stone color, boolean newBranch) { wc, nextWinrate, 0); + newState.moveMNNumber = moveMNNumber; // don't make this coordinate if it is suicidal or violates superko if (isSuicidal > 0 || history.violatesKoRule(newState)) return; @@ -890,7 +911,7 @@ public void deleteMove() { if (currentNode.previous().isPresent()) { BoardHistoryNode pre = currentNode.previous().get(); previousMove(); - int idx = pre.findIndexOfNode(currentNode); + int idx = pre.indexOfNode(currentNode); pre.deleteChild(idx); } else { clear(); // Clear the board if we're at the top diff --git a/src/main/java/featurecat/lizzie/rules/BoardData.java b/src/main/java/featurecat/lizzie/rules/BoardData.java index 8458b0f86..b5148df80 100644 --- a/src/main/java/featurecat/lizzie/rules/BoardData.java +++ b/src/main/java/featurecat/lizzie/rules/BoardData.java @@ -6,9 +6,11 @@ public class BoardData { public int moveNumber; + public int moveMNNumber; public Optional lastMove; public int[] moveNumberList; public boolean blackToPlay; + public boolean dummy; public Stone lastMoveColor; public Stone[] stones; @@ -37,10 +39,12 @@ public BoardData( int whiteCaptures, double winrate, int playouts) { + this.moveMNNumber = -1; this.moveNumber = moveNumber; this.lastMove = lastMove; this.moveNumberList = moveNumberList; this.blackToPlay = blackToPlay; + this.dummy = false; this.lastMoveColor = lastMoveColor; this.stones = stones; @@ -74,6 +78,8 @@ public void addProperty(String key, String value) { SGFParser.addProperty(properties, key, value); if ("N".equals(key) && comment.isEmpty()) { comment = value; + } else if ("MN".equals(key)) { + moveMNNumber = Integer.parseInt(getOrDefault("MN", "-1")); } } diff --git a/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java b/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java index 0cc0a4089..b3d52b047 100644 --- a/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java +++ b/src/main/java/featurecat/lizzie/rules/BoardHistoryList.java @@ -156,6 +156,10 @@ public int getMoveNumber() { return head.getData().moveNumber; } + public int getMoveMNNumber() { + return head.getData().moveMNNumber; + } + public int[] getMoveNumberList() { return head.getData().moveNumberList; } diff --git a/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java b/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java index be2df08dd..67829991f 100644 --- a/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java +++ b/src/main/java/featurecat/lizzie/rules/BoardHistoryNode.java @@ -1,5 +1,6 @@ package featurecat.lizzie.rules; +import featurecat.lizzie.Lizzie; import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -63,7 +64,7 @@ public BoardHistoryNode addOrGoto(BoardData data, boolean newBranch) { // 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 (!variations.isEmpty() && !variations.get(0).data.zobrist.equals(data.zobrist)) { - // // You may just mark this hand, so it's not necessarily wrong. Answer when the + // // You may just mark this hand, so its not necessarily wrong. Answer when the // first query is wrong or it will not ask whether the move is wrong. // if (!variations.get(0).data.verify) { // int ret = JOptionPane.showConfirmDialog(null, "Do you want undo?", "Undo", @@ -88,6 +89,20 @@ public BoardHistoryNode addOrGoto(BoardData data, boolean newBranch) { } } } + if (!this.previous.isPresent()) { + data.moveMNNumber = 1; + } + if (Lizzie.config.newMoveNumberInBranch && !variations.isEmpty()) { + if (!newBranch) { + data.moveNumberList = new int[Board.boardSize * Board.boardSize]; + data.moveMNNumber = -1; + } + if (data.moveMNNumber == -1) { + data.moveMNNumber = data.dummy ? 0 : 1; + } + data.lastMove.ifPresent( + m -> data.moveNumberList[Board.getIndex(m[0], m[1])] = data.moveMNNumber); + } BoardHistoryNode node = new BoardHistoryNode(data); // Add node variations.add(node); @@ -156,6 +171,9 @@ public void moveChildUp(BoardHistoryNode child) { BoardHistoryNode tmp = variations.get(i - 1); variations.set(i - 1, child); variations.set(i, tmp); + if ((i - 1) == 0) { + child.swapMoveNumberList(tmp); + } return; } } @@ -167,11 +185,51 @@ public void moveChildDown(BoardHistoryNode child) { BoardHistoryNode tmp = variations.get(i + 1); variations.set(i + 1, child); variations.set(i, tmp); + if (i == 0) { + tmp.swapMoveNumberList(child); + } return; } } } + public void swapMoveNumberList(BoardHistoryNode child) { + int childStart = child.getData().moveMNNumber; + child.resetMoveNumberListBranch(this.getData().moveMNNumber); + this.resetMoveNumberListBranch(childStart); + } + + public void resetMoveNumberListBranch(int start) { + this.resetMoveNumberList(start); + BoardHistoryNode node = this; + while (node.next().isPresent()) { + node = node.next().get(); + start++; + node.resetMoveNumberList(start); + } + } + + public void resetMoveNumberList(int start) { + BoardData data = this.getData(); + int[] moveNumberList = data.moveNumberList; + data.moveMNNumber = start; + if (data.lastMove.isPresent() && !data.dummy) { + int[] move = data.lastMove.get(); + moveNumberList[Board.getIndex(move[0], move[1])] = start; + } + Optional node = this.previous(); + int moveNumber = start; + while (node.isPresent()) { + BoardData nodeData = node.get().getData(); + if (nodeData.lastMove.isPresent()) { + int[] move = nodeData.lastMove.get(); + moveNumber = (moveNumber > 1) ? moveNumber - 1 : 0; + moveNumberList[Board.getIndex(move[0], move[1])] = moveNumber; + } + node = node.get().previous(); + } + } + public void deleteChild(int idx) { if (idx < numberOfChildren()) { variations.remove(idx); @@ -290,11 +348,11 @@ public Optional findChildOfPreviousWithVariation() { } /** - * Given a child node, find the index of that child node in it's parent + * Given a child node, find the index of that child node in its parent * * @return index of child node, -1 if child node not a child of parent */ - public int findIndexOfNode(BoardHistoryNode childNode) { + public int indexOfNode(BoardHistoryNode childNode) { if (!next().isPresent()) { return -1; } @@ -306,6 +364,69 @@ public int findIndexOfNode(BoardHistoryNode childNode) { return -1; } + /** + * Given a child node, find the index of that child node in its parent + * + * @return index of child node, -1 if child node not a child of parent + */ + public int findIndexOfNode(BoardHistoryNode childNode, boolean allSub) { + if (!next().isPresent()) { + return -1; + } + for (int i = 0; i < numberOfChildren(); i++) { + Optional node = getVariation(i); + while (node.isPresent()) { + if (node.map(n -> n == childNode).orElse(false)) { + return i; + } + node = node.get().next(); + } + } + return -1; + } + + /** + * Given a child node, find the depth of that child node in its parent + * + * @return depth of child node, 0 if child node not a child of parent + */ + public int depthOfNode(BoardHistoryNode childNode) { + if (!next().isPresent()) { + return 0; + } + for (int i = 0; i < numberOfChildren(); i++) { + Optional node = getVariation(i); + int move = 1; + while (node.isPresent()) { + if (node.map(n -> n == childNode).orElse(false)) { + return move; + } + move++; + node = node.get().next(); + } + } + return 0; + } + + /** + * The move number of that node in its branch + * + * @return move number of node, 0 if node not a child of branch + */ + public int moveNumberInBranch() { + Optional top = firstParentWithVariations(); + return top.isPresent() ? top.get().moveNumberOfNode() + top.get().depthOfNode(this) : 0; + } + + /** + * The move number of that node + * + * @return move number of node + */ + public int moveNumberOfNode() { + return isMainTrunk() ? getData().moveNumber : moveNumberInBranch(); + } + /** * Check if node is part of the main trunk (rightmost branch) * diff --git a/src/main/java/featurecat/lizzie/rules/SGFParser.java b/src/main/java/featurecat/lizzie/rules/SGFParser.java index d1c38136a..96d59b5cf 100644 --- a/src/main/java/featurecat/lizzie/rules/SGFParser.java +++ b/src/main/java/featurecat/lizzie/rules/SGFParser.java @@ -83,15 +83,17 @@ private static boolean parse(String value) { int subTreeDepth = 0; // Save the variation step count Map subTreeStepMap = new HashMap(); - // Comment of the AW/AB (Add White/Add Black) stone - String awabComment = ""; + // Comment of the game head + String headComment = ""; // Game properties Map gameProperties = new HashMap(); + Map pendingProps = new HashMap(); boolean inTag = false, isMultiGo = false, escaping = false, moveStart = false, - addPassForAwAb = true; + addPassForMove = true; + boolean inProp = false; String tag = ""; StringBuilder tagBuilder = new StringBuilder(); StringBuilder tagContentBuilder = new StringBuilder(); @@ -119,6 +121,8 @@ private static boolean parse(String value) { subTreeDepth += 1; // Initialize the step count subTreeStepMap.put(subTreeDepth, 0); + addPassForMove = true; + pendingProps = new HashMap(); } else { if (i > 0) { // Allow the comment tag includes '(' @@ -142,45 +146,54 @@ private static boolean parse(String value) { } 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]", ""); + if (!inProp) { + inProp = true; + 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(); + } else { + tagContentBuilder.append(c); } - tagContentBuilder = new StringBuilder(); break; case ']': if (subTreeDepth > 1 && !isMultiGo) { break; } inTag = false; + inProp = false; tagBuilder = new StringBuilder(); String tagContent = tagContentBuilder.toString(); // We got tag, we can parse this tag now. if (tag.equals("B") || tag.equals("W")) { moveStart = true; - addPassForAwAb = true; + addPassForMove = true; int[] move = convertSgfPosToCoord(tagContent); // Save the step count subTreeStepMap.put(subTreeDepth, subTreeStepMap.get(subTreeDepth) + 1); Stone color = tag.equals("B") ? Stone.BLACK : Stone.WHITE; + boolean newBranch = (subTreeStepMap.get(subTreeDepth) == 1); if (move == null) { - Lizzie.board.pass(color); + Lizzie.board.pass(color, newBranch, false); } else { - boolean newBranch = (subTreeStepMap.get(subTreeDepth) == 1); Lizzie.board.place(move[0], move[1], color, newBranch); } + if (newBranch) { + processPendingPros(pendingProps); + } } else if (tag.equals("C")) { // Support comment if (!moveStart) { - awabComment = tagContent; + headComment = tagContent; } else { Lizzie.board.comment(tagContent); } @@ -190,10 +203,17 @@ private static boolean parse(String value) { if (moveStart) { // add to node properties Lizzie.board.addNodeProperty(tag, tagContent); - if (addPassForAwAb) { - Lizzie.board.pass(color); - addPassForAwAb = false; + if (addPassForMove) { + // Save the step count + subTreeStepMap.put(subTreeDepth, subTreeStepMap.get(subTreeDepth) + 1); + boolean newBranch = (subTreeStepMap.get(subTreeDepth) == 1); + Lizzie.board.pass(color, newBranch, true); + if (newBranch) { + processPendingPros(pendingProps); + } + addPassForMove = false; } + Lizzie.board.addNodeProperty(tag, tagContent); if (move != null) { Lizzie.board.addStone(move[0], move[1], color); } @@ -221,20 +241,35 @@ private static boolean parse(String value) { } else { if (moveStart) { // Other SGF node properties - Lizzie.board.addNodeProperty(tag, tagContent); - if ("MN".equals(tag)) { - Lizzie.board.moveNumber(Integer.parseInt(tagContent)); - } else if ("AE".equals(tag)) { + if ("AE".equals(tag)) { // remove a stone - if (addPassForAwAb) { - Lizzie.board.pass(tag.equals("AB") ? Stone.BLACK : Stone.WHITE); - addPassForAwAb = false; + if (addPassForMove) { + // Save the step count + subTreeStepMap.put(subTreeDepth, subTreeStepMap.get(subTreeDepth) + 1); + Stone color = + Lizzie.board.getHistory().getLastMoveColor() == Stone.WHITE + ? Stone.BLACK + : Stone.WHITE; + boolean newBranch = (subTreeStepMap.get(subTreeDepth) == 1); + Lizzie.board.pass(color, newBranch, true); + if (newBranch) { + processPendingPros(pendingProps); + } + addPassForMove = false; } + Lizzie.board.addNodeProperty(tag, tagContent); int[] move = convertSgfPosToCoord(tagContent); if (move != null) { Lizzie.board.removeStone( move[0], move[1], tag.equals("AB") ? Stone.BLACK : Stone.WHITE); } + } else { + boolean firstProp = (subTreeStepMap.get(subTreeDepth) == 0); + if (firstProp) { + addProperty(pendingProps, tag, tagContent); + } else { + Lizzie.board.addNodeProperty(tag, tagContent); + } } } else { addProperty(gameProperties, tag, tagContent); @@ -267,8 +302,8 @@ private static boolean parse(String value) { while (Lizzie.board.previousMove()) ; // Set AW/AB Comment - if (!awabComment.isEmpty()) { - Lizzie.board.comment(awabComment); + if (!headComment.isEmpty()) { + Lizzie.board.comment(headComment); } if (gameProperties.size() > 0) { Lizzie.board.addNodeProperties(gameProperties); @@ -397,10 +432,12 @@ private static String generateNode(Board board, BoardHistoryNode node) throws IO if (Stone.BLACK.equals(data.lastMoveColor)) stone = "B"; else if (Stone.WHITE.equals(data.lastMoveColor)) stone = "W"; - char x = data.lastMove.isPresent() ? (char) (data.lastMove.get()[0] + 'a') : 't'; - char y = data.lastMove.isPresent() ? (char) (data.lastMove.get()[1] + 'a') : 't'; - - builder.append(String.format(";%s[%c%c]", stone, x, y)); + builder.append(";"); + if (!data.dummy) { + char x = data.lastMove.isPresent() ? (char) (data.lastMove.get()[0] + 'a') : 't'; + char y = data.lastMove.isPresent() ? (char) (data.lastMove.get()[1] + 'a') : 't'; + builder.append(String.format("%s[%c%c]", stone, x, y)); + } // Node properties builder.append(data.propertiesString()); @@ -623,4 +660,9 @@ public static String nodeString(String key, String value) { } return sb.toString(); } + + private static void processPendingPros(Map props) { + props.forEach((key, value) -> Lizzie.board.addNodeProperty(key, value)); + props = new HashMap(); + } }