Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use different color to indicate move blunder in variation window #389

Closed
wants to merge 11 commits into from
76 changes: 62 additions & 14 deletions src/main/java/featurecat/lizzie/gui/LizzieFrame.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import featurecat.lizzie.analysis.Leelaz;
import featurecat.lizzie.rules.Board;
import featurecat.lizzie.rules.BoardData;
import featurecat.lizzie.rules.BoardHistoryNode;
import featurecat.lizzie.rules.GIBParser;
import featurecat.lizzie.rules.SGFParser;
import java.awt.*;
Expand Down Expand Up @@ -165,8 +166,8 @@ public LizzieFrame() {
this.addMouseWheelListener(input);
this.addMouseMotionListener(input);

// necessary for Windows users - otherwise Lizzie shows a blank white screen on startup until
// updates occur.
// 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()
Expand Down Expand Up @@ -569,9 +570,9 @@ private void drawPonderingState(Graphics2D g, String text, int x, int y, double
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(),
// 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);

Expand Down Expand Up @@ -739,14 +740,17 @@ private void drawMoveStatistics(Graphics2D g, int posX, int posY, int width, int
int strokeRadius = 3;
g.setStroke(new BasicStroke(2 * strokeRadius));
g.drawLine(
posX + strokeRadius, posY + strokeRadius,
posX - strokeRadius + width, posY + strokeRadius);
posX + strokeRadius, posY + strokeRadius, posX - strokeRadius + width, posY + strokeRadius);
g.drawLine(
posX + strokeRadius, posY + 3 * strokeRadius,
posX + strokeRadius, posY - strokeRadius + height);
posX + strokeRadius,
posY + 3 * strokeRadius,
posX + strokeRadius,
posY - strokeRadius + height);
g.drawLine(
posX - strokeRadius + width, posY + 3 * strokeRadius,
posX - strokeRadius + width, posY - strokeRadius + height);
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;
Expand Down Expand Up @@ -778,8 +782,9 @@ private void drawMoveStatistics(Graphics2D g, int posX, int posY, int width, int
} 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);
// g.drawString(resourceBundle.getString("LizzieFrame.display.lastMove") + ":
// ?%",
// posX + 2 * strokeRadius, posY + height - 2 * strokeRadius);
}

if (validWinrate || validLastWinrate) {
Expand Down Expand Up @@ -909,7 +914,6 @@ 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))
Expand All @@ -922,6 +926,11 @@ public void onClicked(int x, int y) {
if (Lizzie.config.showSubBoard && subBoardRenderer.isInside(x, y)) {
Lizzie.config.toggleLargeSubBoard();
}

if (Lizzie.config.showVariationGraph) {
Lizzie.frame.variationTree.jumpVariationTree(x, y);
}

repaint();
}

Expand Down Expand Up @@ -1146,4 +1155,43 @@ private int drawComment(Graphics2D g, int x, int y, int w, int h, boolean full)
cachedComment = comment;
return cHeight;
}

public double deltaWinrate(BoardHistoryNode boardNode) {
double lastWR = -1; // winrate of the previous move
double curWR = -1; // winrate of current one
boolean validLastWinrate = false; // whether it was actually calculated
boolean validWinrate = false;
BoardData lastNode = null, currentNode = null;
final double InValidWR = 1000; // return Invalid Winrate if can't find proper one

if (boardNode != null) currentNode = boardNode.getData();

if (boardNode.previous() != null) lastNode = boardNode.previous().getData();

if (lastNode != null && lastNode.playouts > 0) {
lastWR = lastNode.winrate;
validLastWinrate = true;
} else return InValidWR;

if (boardNode == Lizzie.board.getHistory().getCurrentHistoryNode()) {
Leelaz.WinrateStats stats = Lizzie.leelaz.getWinrateStats();
curWR = stats.maxWinrate; // winrate on this move
validWinrate = (stats.totalPlayouts > 0); // and whether it was actually calculated
if (isPlayingAgainstLeelaz
&& playerIsBlack == !Lizzie.board.getHistory().getData().blackToPlay)
validWinrate = false;
}

if (!validWinrate)
if (currentNode != null && currentNode.playouts > 0) {
curWR = currentNode.winrate;
validWinrate = true;
}

// Last move
if (validWinrate && validLastWinrate) {
return 100 - curWR - lastWR;
}
return InValidWR;
}
}
182 changes: 161 additions & 21 deletions src/main/java/featurecat/lizzie/gui/VariationTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ public class VariationTree {
private int XSPACING;
private int DOT_DIAM = 11; // Should be odd number

private ArrayList<Integer> laneUsageList;
private BoardHistoryNode curMove;
private ArrayList<Integer> laneUsageList,
laneUsageList1; // laneUsageList1 is lane used to cacalate (x,y)
private BoardHistoryNode curMove, curMove1;
private int posx1, posy1, width1, height1; // Reserve a copy of varaition window axis

public VariationTree() {
laneUsageList = new ArrayList<Integer>();
laneUsageList1 = new ArrayList<Integer>(); // for mouse click event
}

public void drawTree(
Expand All @@ -34,11 +37,12 @@ public void drawTree(
// 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
// 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 keeps a list of how far down it is to a variation in the
// different "lanes"
laneUsageList.set(lane, startNode.getData().moveNumber - 1);
lane++;
}
Expand All @@ -48,7 +52,8 @@ public void drawTree(
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)
// 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;
Expand Down Expand Up @@ -80,45 +85,56 @@ public void drawTree(

// Draw all the nodes and lines in this lane (not variations)
Color curcolor = g.getColor();
g.setColor(getWinrateColor(startNode, curcolor));
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);
if (cur.getData().comment != null) // Does this node have coments?
g.drawOval(curposx - 3, posy - 3, DOT_DIAM + 6, DOT_DIAM + 6);
} else {
g.fillOval(curposx, posy, DOT_DIAM, DOT_DIAM);
g.setColor(Color.BLACK);
g.drawOval(curposx, posy, DOT_DIAM, DOT_DIAM);
if (cur.getData().comment != null) // Does this node have coments?
g.drawOval(curposx - 3, posy - 3, DOT_DIAM + 6, DOT_DIAM + 6);
}
g.setColor(curcolor);

// Draw main line
while (cur.next() != null && posy + YSPACING < maxposy) {
posy += YSPACING;
cur = cur.next();
// Choose color for move which winrate rise/fall dramatically
g.setColor(getWinrateColor(cur, curcolor));
if (cur == curMove) {
g.setColor(Color.green.brighter().brighter());
g.fillRect(curposx, posy, DOT_DIAM, DOT_DIAM);
g.setColor(Color.BLACK);
g.drawRect(curposx, posy, DOT_DIAM, DOT_DIAM);
if (cur.getData().comment != null) // Does this node have coments?
g.drawOval(curposx - 3, posy - 3, DOT_DIAM + 6, DOT_DIAM + 6);
} else {
g.fillOval(curposx, posy, DOT_DIAM, DOT_DIAM);
g.setColor(Color.BLACK);
g.drawOval(curposx, posy, DOT_DIAM, DOT_DIAM);
if (cur.getData().comment != null) // Does this node have coments?
g.drawOval(curposx - 3, posy - 3, DOT_DIAM + 6, DOT_DIAM + 6);
}
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
// 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))
// 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;
Expand All @@ -134,6 +150,11 @@ public void draw(Graphics2D g, int posx, int posy, int width, int height) {
YSPACING = (Lizzie.config.showLargeSubBoard() ? 20 : 30);
XSPACING = YSPACING;

posx1 = posx;
posy1 = posy;
width1 = width;
height1 = height; // backup for mouse click event

// Draw background
g.setColor(new Color(0, 0, 0, 60));
g.fillRect(posx, posy, width, height);
Expand All @@ -152,6 +173,10 @@ public void draw(Graphics2D g, int posx, int posy, int width, int height) {
int xoffset = 30;
laneUsageList.clear();

// Draw mark of current node
g.setColor(new Color(0, 255, 0, 255));
g.fillOval(posx + 10, middleY, DOT_DIAM, DOT_DIAM);

curMove = Lizzie.board.getHistory().getCurrentHistoryNode();

// Is current move a variation? If so, find top of variation
Expand All @@ -165,4 +190,119 @@ public void draw(Graphics2D g, int posx, int posy, int width, int height) {
}
drawTree(g, posx + xoffset, curposy, 0, posy + height, node, 0, true);
}

// Clone from drawTree() method but draw nothing, just caculate (x,y)->BoardNode
public BoardHistoryNode inSubtree(
int posx,
int posy,
int mouseX,
int mouseY,
int startLane,
int maxposy,
BoardHistoryNode startNode,
int variationNumber) {
// 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 < laneUsageList1.size()
&& laneUsageList1.get(lane) <= startNode.getData().moveNumber + depth) {
// laneUsageList keeps a list of how far down it is to a variation in the
// different "lanes"
laneUsageList1.set(lane, startNode.getData().moveNumber - 1);
lane++;
}
if (lane >= laneUsageList1.size()) {
laneUsageList1.add(0);
}
if (variationNumber > 1) laneUsageList1.set(lane - 1, startNode.getData().moveNumber - 1);
laneUsageList1.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;

// Check first node
if (Math.abs(mouseX - curposx - dotoffset) < XSPACING / 2
&& Math.abs(mouseY - posy - dotoffset) < YSPACING / 2) return startNode;

// Draw main line
while (cur.next() != null && posy + YSPACING < maxposy) {
posy += YSPACING;
cur = cur.next();
if (Math.abs(mouseX - curposx - dotoffset) < XSPACING / 2
&& Math.abs(mouseY - posy - dotoffset) < YSPACING / 2) return cur;
}
// 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;
BoardHistoryNode ret;
// 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))
if ((ret = inSubtree(posx, posy, mouseX, mouseY, curwidth, maxposy, cur.getVariation(i), i))
!= null) return ret;
}
posy -= YSPACING;
}
return null;
}

// Clone from draw() method but draw nothing, just caculate (x,y)->BoardNode
public BoardHistoryNode inVariationTree(int mouseX, int mouseY) {

// check if (x,y) is in the variation window
if (mouseX < posx1 || mouseX > posx1 + width1 || mouseY < posy1 || mouseY > posy1 + height1)
return null;

// Use dense tree for saving space if large-subboard
YSPACING = (Lizzie.config.showLargeSubBoard() ? 20 : 30);
XSPACING = YSPACING;

int middleY = posy1 + height1 / 2;
int xoffset = 30;
laneUsageList1.clear();

curMove1 = Lizzie.board.getHistory().getCurrentHistoryNode();

// Is current move a variation? If so, find top of variation
BoardHistoryNode top = BoardHistoryList.findTop(curMove1);
int curposy = middleY - YSPACING * (curMove1.getData().moveNumber - top.getData().moveNumber);
// Go to very top of tree (visible in assigned area)
BoardHistoryNode node = top;
while (curposy > posy1 + YSPACING && node.previous() != null) {
node = node.previous();
curposy -= YSPACING;
}
return inSubtree(posx1 + xoffset, curposy, mouseX, mouseY, 0, posy1 + height1, node, 0);
}

public int jumpVariationTree(int mouseX, int mouseY) {
BoardHistoryNode node;
node = inVariationTree(mouseX, mouseY); // check if (x,y) hit proper branch node
if (node == null) return -1; // check if any error occur
Lizzie.board.jumpToAnyPosition(node);
return 0;
}

private Color getWinrateColor(BoardHistoryNode Node, Color current) {
double delta = Lizzie.frame.deltaWinrate(Node);
if (delta < -20) return Color.BLACK;
else if (delta < -10 && delta >= -20) return Color.RED;
else if (delta < -5 && delta >= -10) return Color.MAGENTA;
else if (delta < -2 && delta >= -5) return Color.YELLOW;
else if (delta < 5 && delta >= -2) return current; // Do nothing if it is normal move
else if (delta >= 5 && delta <= 20) return Color.BLUE;
else if (delta >= 20 && delta < 100) return Color.BLUE.brighter().brighter();
else return current;
}
}
Loading