diff --git a/src/main/java/featurecat/lizzie/gui/Input.java b/src/main/java/featurecat/lizzie/gui/Input.java index f082fdddb..57c25d915 100644 --- a/src/main/java/featurecat/lizzie/gui/Input.java +++ b/src/main/java/featurecat/lizzie/gui/Input.java @@ -275,7 +275,11 @@ public void keyPressed(KeyEvent e) { break; case VK_W: - Lizzie.config.toggleShowWinrate(); + if (controlIsPressed(e)) { + Lizzie.config.toggleLargeWinrate(); + } else { + Lizzie.config.toggleShowWinrate(); + } break; case VK_G: diff --git a/src/main/java/featurecat/lizzie/gui/LizzieFrame.java b/src/main/java/featurecat/lizzie/gui/LizzieFrame.java index c98e07203..0944a63c7 100644 --- a/src/main/java/featurecat/lizzie/gui/LizzieFrame.java +++ b/src/main/java/featurecat/lizzie/gui/LizzieFrame.java @@ -137,7 +137,7 @@ public LizzieFrame() { variationTree = new VariationTree(); winrateGraph = new WinrateGraph(); - setMinimumSize(new Dimension(640, 480)); + setMinimumSize(new Dimension(640, 400)); JSONArray windowSize = Lizzie.config.uiConfig.getJSONArray("window-size"); setSize(windowSize.getInt(0), windowSize.getInt(1)); setLocationRelativeTo(null); // Start centered, needs to be called *after* setSize... @@ -191,6 +191,13 @@ public void windowClosing(WindowEvent e) { }); } + /** Clears related status from empty board. */ + public void clear() { + if (winrateGraph != null) { + winrateGraph.clear(); + } + } + public static void startNewGame() { GameInfo gameInfo = Lizzie.board.getHistory().getGameInfo(); @@ -308,7 +315,11 @@ public static void loadFile(File file) { private boolean cachedBackgroundShowControls = false; private boolean cachedShowWinrate = true; private boolean cachedShowVariationGraph = true; + private boolean cachedShowLargeSubBoard = true; + private boolean cachedLargeWinrate = true; + private boolean cachedShowComment = true; private boolean redrawBackgroundAnyway = false; + private boolean redrawContainerAnyway = false; /** * Draws the game board and interface @@ -318,13 +329,20 @@ public static void loadFile(File file) { public void paint(Graphics g0) { autosaveMaybe(); + int width = getWidth(); + int height = getHeight(); + Optional backgroundG; - if (cachedBackgroundWidth != getWidth() - || cachedBackgroundHeight != getHeight() + if (cachedBackgroundWidth != width + || cachedBackgroundHeight != height || cachedBackgroundShowControls != showControls || cachedShowWinrate != Lizzie.config.showWinrate || cachedShowVariationGraph != Lizzie.config.showVariationGraph - || redrawBackgroundAnyway) { + || cachedShowLargeSubBoard != Lizzie.config.showLargeSubBoard() + || cachedLargeWinrate != Lizzie.config.showLargeWinrate() + || cachedShowComment != Lizzie.config.showComment + || redrawBackgroundAnyway + || redrawContainerAnyway) { backgroundG = Optional.of(createBackground()); } else { backgroundG = Optional.empty(); @@ -334,117 +352,285 @@ public void paint(Graphics g0) { // layout parameters int topInset = this.getInsets().top; + int leftInset = this.getInsets().left; + int rightInset = this.getInsets().right; + int bottomInset = this.getInsets().bottom; + int maxBound = Math.max(width, height); // board - int maxSize = (int) (min(getWidth(), getHeight() - topInset)); + int maxSize = (int) (min(width, height - topInset)); maxSize = max(maxSize, Board.boardSize + 5); // don't let maxWidth become too small - int boardX = (getWidth() - maxSize) / 2; - int boardY = topInset + (getHeight() - topInset - maxSize) / 2 + 3; + int boardX = (width - maxSize) / 2; + int boardY = topInset + (height - topInset - maxSize) / 2 + 3; - int panelMargin = (int) (maxSize * 0.05); + int panelMargin = (int) (maxSize * 0.02); + + // captured stones + int capx = leftInset; + int capy = topInset; + int capw = boardX - panelMargin; + int caph = boardY + maxSize / 8 - topInset; // 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 statx = capx; + int staty = capy + caph; + int statw = capw; int stath = maxSize / 10; // winrate graph int grx = statx; int gry = staty + stath; int grw = statw; - int grh = 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; - + int grh = maxSize / 3; // 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; + int vw = width - vx; + int vh = height; // pondering message - int ponderingX = this.getInsets().left; - int ponderingY = boardY + (int) (maxSize * 0.93); double ponderingSize = .02; + int ponderingX = leftInset; + int ponderingY = + height - bottomInset - (int) (maxSize * 0.033) - (int) (maxBound * ponderingSize); // 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; + int dynamicKomiX = leftInset; + int dynamicKomiY = ponderingY - (int) (maxBound * dynamicKomiSize); + int dynamicKomiLabelX = leftInset; + int dynamicKomiLabelY = dynamicKomiY - (int) (maxBound * dynamicKomiSize); - // loading message - int loadingX = ponderingX; - int loadingY = ponderingY; + // loading message; double loadingSize = 0.03; + int loadingX = ponderingX; + int loadingY = ponderingY - (int) (maxBound * (loadingSize - ponderingSize)); // subboard - int subBoardX = 0; + int subBoardX = statx; int subBoardY = gry + grh; int subBoardWidth = grw; int subBoardHeight = ponderingY - subBoardY; int subBoardLength = 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 = min(subBoardWidth, subBoardHeight); + if (width >= height) { + // Landscape mode + if (Lizzie.config.showLargeSubBoard()) { + boardX = width - maxSize - panelMargin; + int spaceW = boardX - panelMargin; + int spaceH = height - topInset; + int panelW = spaceW / 2; + int panelH = spaceH / 4; + + // captured stones + capw = panelW; + caph = (int) (panelH * 0.2); + // move statistics (winrate bar) + staty = capy + caph; + statw = capw; + stath = (int) (panelH * 0.4); + // winrate graph + gry = staty + stath; + grw = statw; + grh = panelH - caph - stath; + // variation tree container + vx = panelW; + vw = panelW; + vh = topInset + panelH; + // subboard + subBoardY = gry + grh; + subBoardWidth = spaceW; + subBoardHeight = ponderingY - subBoardY; + subBoardLength = Math.min(subBoardWidth, subBoardHeight); + } else if (Lizzie.config.showLargeWinrate()) { + boardX = width - maxSize - panelMargin; + int spaceW = boardX - panelMargin; + int spaceH = height - topInset; + int panelW = spaceW / 2; + int panelH = spaceH / 4; + + // captured stones + capy = topInset + panelH + 1; + capw = spaceW; + caph = (int) ((ponderingY - topInset - panelH) * 0.15); + // move statistics (winrate bar) + staty = capy + caph; + statw = capw; + stath = caph; + // winrate graph + gry = staty + stath; + grw = statw; + grh = ponderingY - gry; + // variation tree container + vx = panelW; + vw = panelW; + vh = topInset + panelH; + // subboard + subBoardY = topInset; + subBoardWidth = panelW - leftInset; + subBoardHeight = panelH; + subBoardLength = Math.min(subBoardWidth, subBoardHeight); + } + } else { + // Portrait mode + if (Lizzie.config.showLargeSubBoard()) { + // board + maxSize = (int) (maxSize * 0.8); + boardY = height - maxSize - bottomInset; + int spaceW = width - leftInset - rightInset; + int spaceH = boardY - panelMargin; + int panelW = spaceW / 2; + int panelH = spaceH / 2; + boardX = (spaceW - maxSize) / 2 + leftInset; + + // captured stones + capw = panelW / 2; + caph = panelH / 2; + // move statistics (winrate bar) + staty = capy + caph; + statw = capw; + stath = caph; + // winrate graph + gry = staty + stath; + grw = statw; + grh = spaceH - caph - stath; + // variation tree container + vx = capx + capw; + vw = panelW / 2; + vh = topInset + spaceH; + // subboard + subBoardX = vx + vw; + subBoardY = capy; + subBoardWidth = spaceW; + subBoardHeight = boardY - topInset - 1; + subBoardLength = Math.min(subBoardWidth, subBoardHeight); + // pondering message + ponderingY = height; + } else if (Lizzie.config.showLargeWinrate()) { + // board + maxSize = (int) (maxSize * 0.8); + boardY = height - maxSize - bottomInset; + int spaceW = width - leftInset - rightInset; + int spaceH = boardY - panelMargin; + int panelW = spaceW / 2; + int panelH = spaceH / 2; + boardX = (spaceW - maxSize) / 2 + leftInset; + + // captured stones + capw = panelW / 2; + caph = panelH / 4; + // move statistics (winrate bar) + statx = capx + capw; + staty = capy; + statw = capw; + stath = caph; + // winrate graph + gry = staty + stath; + grw = spaceW; + grh = boardY - capy - caph - 1; + // variation tree container + vx = statx + statw; + vy = capy; + vw = panelW / 2; + vh = caph; + // subboard + subBoardX = vx + vw; + subBoardY = topInset / 2; + subBoardWidth = panelW / 2; + subBoardHeight = gry - 2; + subBoardLength = Math.min(subBoardWidth, subBoardHeight); + // pondering message + ponderingY = height; + } else { + // Normal + // board + boardY = (height - maxSize + topInset - bottomInset) / 2; + int spaceW = width - leftInset - rightInset; + int spaceH = boardY - panelMargin; + int panelW = spaceW / 2; + int panelH = spaceH / 2; + + // captured stones + capw = panelW * 3 / 4; + caph = panelH / 2; + // move statistics (winrate bar) + statx = capx + capw; + staty = capy; + statw = capw; + stath = caph; + // winrate graph + grx = capx; + gry = staty + stath; + grw = capw + statw; + grh = boardY - gry; + // subboard + subBoardX = grx + grw; + subBoardY = capy; + subBoardWidth = panelW / 2; + subBoardHeight = boardY - topInset; + subBoardLength = Math.min(subBoardWidth, subBoardHeight); + // variation tree container + vx = panelW; + vy = boardY + maxSize; + vw = panelW; + vh = height - vy; + } + } + + // graph container + int contx = statx; + int conty = staty; + int contw = statw; + int conth = stath + grh; + if (width < height && Lizzie.config.showLargeWinrate()) { + contx = grx; + conty = gry; + contw = grw; + conth = grh; + } + + // comment panel + int cx = 0, cy = 0, cw = 0, ch = 0; + if (Lizzie.config.showComment) { + if (width >= height) { + cx = vx; + if (Lizzie.config.showVariationGraph) { + vh = vh / 2; + cy = vy + vh; + ch = vh; + } else { + cy = topInset; + ch = vh - topInset; + } + } else { + cy = vy; + if (Lizzie.config.showVariationGraph) { + if (Lizzie.config.showLargeSubBoard()) { + vh = vh / 2; + cx = vx; + cy = vy + vh; + } else { + vw = vw / 2; + cx = vx + vw; + } + } else { + cx = vx; + } + ch = vh; + } + cw = vw; } + // variation tree + int treex = vx; + int treey = vy; + int treew = vw; + int treeh = vh; + // initialize - cachedImage = new BufferedImage(getWidth(), getHeight(), TYPE_INT_ARGB); + cachedImage = new BufferedImage(width, height, TYPE_INT_ARGB); Graphics2D g = (Graphics2D) cachedImage.getGraphics(); if (Lizzie.config.showStatus) drawCommandString(g); @@ -485,17 +671,12 @@ public void paint(Graphics g0) { if (backgroundG.isPresent()) { drawContainer(backgroundG.get(), 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); - } + variationTree.draw(g, treex, treey, treew, treeh); + } + + if (Lizzie.config.showComment) { + // Draw the Comment of the Sgf + drawComment(g, cx, cy, cw, ch); } if (Lizzie.config.showSubBoard) { @@ -544,8 +725,12 @@ private Graphics2D createBackground() { cachedBackgroundShowControls = showControls; cachedShowWinrate = Lizzie.config.showWinrate; cachedShowVariationGraph = Lizzie.config.showVariationGraph; + cachedShowLargeSubBoard = Lizzie.config.showLargeSubBoard(); + cachedLargeWinrate = Lizzie.config.showLargeWinrate(); + cachedShowComment = Lizzie.config.showComment; redrawBackgroundAnyway = false; + redrawContainerAnyway = true; Graphics2D g = cachedBackground.createGraphics(); @@ -559,13 +744,16 @@ private Graphics2D createBackground() { } private void drawContainer(Graphics g, int vx, int vy, int vw, int vh) { - if (vx < cachedBackground.getMinX() + if (vw <= 0 + || vh <= 0 + || vx < cachedBackground.getMinX() || vx + vw > cachedBackground.getMinX() + cachedBackground.getWidth() || vy < cachedBackground.getMinY() || vy + vh > cachedBackground.getMinY() + cachedBackground.getHeight()) { return; } + redrawContainerAnyway = false; BufferedImage result = new BufferedImage(vw, vh, TYPE_INT_ARGB); filter20.filter(cachedBackground.getSubimage(vx, vy, vw, vh), result); g.drawImage(result, vx, vy, null); @@ -579,11 +767,15 @@ private void drawPonderingState(Graphics2D g, String text, int x, int y, double // Truncate too long text when display switching prompt if (Lizzie.leelaz.isLoaded()) { int mainBoardX = boardRenderer.getLocation().x; - if ((mainBoardX > x) && stringWidth > (mainBoardX - x)) { + if (getWidth() > getHeight() && (mainBoardX > x) && stringWidth > (mainBoardX - x)) { text = truncateStringByWidth(text, fm, mainBoardX - x); stringWidth = fm.stringWidth(text); } } + // Do nothing when no text + if (stringWidth <= 0) { + return; + } int stringHeight = fm.getAscent() - fm.getDescent(); int width = max(stringWidth, 1); int height = max((int) (stringHeight * 1.2), 1); @@ -918,7 +1110,7 @@ private void drawCaptured(Graphics2D g, int posX, int posY, int width, int heigh // Draw captures String bval, wval; - setPanelFont(g, (float) (width * 0.06)); + setPanelFont(g, (float) (height * 0.18)); if (Lizzie.board.inScoreMode()) { double score[] = Lizzie.board.getScore(Lizzie.board.scoreStones()); bval = String.format("%.0f", score[0]); @@ -1011,7 +1203,7 @@ public void onMouseDragged(int x, int y) { public boolean processCommentMouseWheelMoved(MouseWheelEvent e) { if (Lizzie.config.showComment && commentRect.contains(e.getX(), e.getY())) { scrollPane.dispatchEvent(e); - createCommentImage(true, 0, 0); + createCommentImage(true, commentRect.width, commentRect.height); getGraphics() .drawImage( cachedCommentImage, @@ -1037,15 +1229,15 @@ public void createCommentImage(boolean forceRefresh, int w, int h) { if (forceRefresh || scrollPane.getWidth() != w || scrollPane.getHeight() != h) { if (w > 0 && h > 0) { scrollPane.setSize(w, h); + cachedCommentImage = + new BufferedImage(scrollPane.getWidth(), scrollPane.getHeight(), TYPE_INT_ARGB); + Graphics2D g2 = cachedCommentImage.createGraphics(); + scrollPane.doLayout(); + scrollPane.addNotify(); + scrollPane.validate(); + scrollPane.printAll(g2); + g2.dispose(); } - cachedCommentImage = - new BufferedImage(scrollPane.getWidth(), scrollPane.getHeight(), TYPE_INT_ARGB); - Graphics2D g2 = cachedCommentImage.createGraphics(); - scrollPane.doLayout(); - scrollPane.addNotify(); - scrollPane.validate(); - scrollPane.printAll(g2); - g2.dispose(); } } @@ -1148,12 +1340,9 @@ public void increaseMaxAlpha(int k) { * @param y * @param w * @param h - * @param full - * @return */ - private int drawComment(Graphics2D g, int x, int y, int w, int h, boolean full) { + private void drawComment(Graphics2D g, int x, int y, int w, int h) { String comment = Lizzie.board.getHistory().getData().comment; - int cHeight = full ? h : (int) (h * 0.5); int fontSize = (int) (min(getWidth(), getHeight()) * 0.0294); if (Lizzie.config.commentFontSize > 0) { fontSize = Lizzie.config.commentFontSize; @@ -1163,10 +1352,9 @@ private int drawComment(Graphics2D g, int x, int y, int w, int h, boolean full) Font font = new Font(Lizzie.config.fontName, Font.PLAIN, fontSize); commentPane.setFont(font); commentPane.setText(comment); - commentPane.setSize(w, cHeight); - createCommentImage(!comment.equals(this.cachedComment), w, cHeight); - commentRect = - new Rectangle(x, y + (h - cHeight), scrollPane.getWidth(), scrollPane.getHeight()); + commentPane.setSize(w, h); + createCommentImage(!comment.equals(this.cachedComment), w, h); + commentRect = new Rectangle(x, y, scrollPane.getWidth(), scrollPane.getHeight()); g.drawImage( cachedCommentImage, commentRect.x, @@ -1175,6 +1363,5 @@ private int drawComment(Graphics2D g, int x, int y, int w, int h, boolean full) commentRect.height, null); cachedComment = comment; - return cHeight; } } diff --git a/src/main/java/featurecat/lizzie/gui/WinrateGraph.java b/src/main/java/featurecat/lizzie/gui/WinrateGraph.java index 051f80bf2..a2804626c 100644 --- a/src/main/java/featurecat/lizzie/gui/WinrateGraph.java +++ b/src/main/java/featurecat/lizzie/gui/WinrateGraph.java @@ -12,6 +12,7 @@ public class WinrateGraph { private int DOT_RADIUS = 6; private int[] origParams = {0, 0, 0, 0}; private int[] params = {0, 0, 0, 0, 0}; + private int numMovesOfPlayed = 0; public void draw(Graphics2D g, int posx, int posy, int width, int height) { BoardHistoryNode curMove = Lizzie.board.getHistory().getCurrentHistoryNode(); @@ -67,9 +68,18 @@ public void draw(Graphics2D g, int posx, int posy, int width, int height) { g.setColor(Color.white); int winRateGridLines = Lizzie.frame.winRateGridLines; + int midline = 0; + int midy = 0; + if (Lizzie.config.showBlunderBar) { + midline = (int) Math.ceil(winRateGridLines / 2.0); + midy = posy + height / 2; + } for (int i = 1; i <= winRateGridLines; i++) { double percent = i * 100.0 / (winRateGridLines + 1); int y = posy + height - (int) (height * convertWinrate(percent) / 100); + if (Lizzie.config.showBlunderBar && i == midline) { + midy = y; + } g.drawLine(posx, y, posx + width, y); } @@ -105,12 +115,19 @@ public void draw(Graphics2D g, int posx, int posy, int width, int height) { boolean inFirstPath = true; int movenum = node.getData().moveNumber - 1; int lastOkMove = -1; + if (Lizzie.config.dynamicWinrateGraphWidth && this.numMovesOfPlayed > 0) { + numMoves = this.numMovesOfPlayed; + } while (node.previous().isPresent()) { - BoardHistoryNode previous = node.previous().get(); double wr = node.getData().winrate; int playouts = node.getData().playouts; if (node == curMove) { + if (Lizzie.config.dynamicWinrateGraphWidth + && node.getData().moveNumber - 1 > this.numMovesOfPlayed) { + this.numMovesOfPlayed = node.getData().moveNumber - 1; + numMoves = this.numMovesOfPlayed; + } Leelaz.WinrateStats stats = Lizzie.leelaz.getWinrateStats(); double bwr = stats.maxWinrate; if (bwr >= 0 && stats.totalPlayouts > playouts) { @@ -146,6 +163,30 @@ public void draw(Graphics2D g, int posx, int posy, int width, int height) { else g.setColor(Lizzie.config.winrateMissLineColor); if (lastOkMove > 0) { + if (Lizzie.config.showBlunderBar) { + Color lineColor = g.getColor(); + g.setColor(Lizzie.config.blunderBarColor); + double lastMoveRate = convertWinrate(lastWr) - convertWinrate(wr); + int lastHeight = 0; + if (Lizzie.config.weightedBlunderBarHeight) { + // Weighted display: <= 50% will use 75% of height, >= 50% will use 25% of height + if (Math.abs(lastMoveRate) <= 50) { + lastHeight = Math.abs((int) (lastMoveRate) * height * 3 / 400); + } else { + lastHeight = height / 4 + Math.abs((int) (Math.abs(lastMoveRate)) * height / 400); + } + } else { + lastHeight = Math.abs((int) (lastMoveRate) * height / 200); + } + int lastWidth = Math.abs((movenum - lastOkMove) * width / numMoves); + int rectWidth = Math.max(lastWidth / 10, Lizzie.config.minimumBlunderBarWidth); + g.fillRect( + posx + (movenum * width / numMoves) + (lastWidth - rectWidth) / 2, + midy + (!node.getData().blackToPlay && lastMoveRate > 0 ? lastHeight * -1 : 0), + rectWidth, + lastHeight); + g.setColor(lineColor); + } g.drawLine( posx + (lastOkMove * width / numMoves), posy + height - (int) (convertWinrate(lastWr) * height / 100), @@ -184,7 +225,7 @@ public void draw(Graphics2D g, int posx, int posy, int width, int height) { lastNodeOk = false; } - node = previous; + node = node.previous().get(); movenum--; } @@ -230,4 +271,9 @@ public int moveNumber(int x, int y) { return -1; } } + + /** Clears winrate status from empty board. */ + public void clear() { + this.numMovesOfPlayed = 0; + } } diff --git a/src/main/java/featurecat/lizzie/rules/Board.java b/src/main/java/featurecat/lizzie/rules/Board.java index b3ff4214a..8cafd5c8f 100644 --- a/src/main/java/featurecat/lizzie/rules/Board.java +++ b/src/main/java/featurecat/lizzie/rules/Board.java @@ -937,6 +937,7 @@ public BoardHistoryList getHistory() { public void clear() { Lizzie.leelaz.sendCommand("clear_board"); Lizzie.frame.resetTitle(); + Lizzie.frame.clear(); initialize(); }