ui improvements, color improvements

This commit is contained in:
Sander Land committed 2020-04-22 22:34:10 +02:00
1 parent 751c66a902
commit 2c4c2a2693
11 files changed
+122 -87

No files matched your search

+8 -5
View File
@@ -2,17 +2,20 @@
[x Non square boards
[x] remove fast -> in settings?
[x] Polish graph
[] Multi engine (detect all req. analysis in analysis[engine] fn)
[] Graph is awkward when reviewing. Should we store nodes rather than values? & populate on new game.
[x] BoxLayout padding/spacing use instead of fiddling?
[] Score instead of game end
[] Self-play tournaments?
[] List edit settings/object edit settings?
[] README
[] Release notes
[] more teaching / groups in danger?
[] more AI modes (noisy policy? heavy balance? jigo?)
[] split feedback in box vs top moves
[] box to label ? split in status and comment?
[] BoxLayout padding/spacing use instead of fiddling?
wont do
[] Self-play tournaments?
[] Multi engine (detect all req. analysis in analysis[engine] fn)
+1 -2
View File
@@ -36,8 +36,7 @@
"ghost_alpha": 0.5,
"top_move_x_alpha": 0.3,
"_child_scale": 0.95,
"eval_colors": [[0.537, 0.129, 0.42], [1, 0, 0], [1,0.5,0], [0.88, 0.70, 0.30], [0.67, 0.95, 0.18], [0.117, 0.588, 0]],
"board_color": [0.85, 0.68, 0.40],
"eval_colors": [[0.447, 0.129, 0.42], [0.8, 0, 0], [0.9,0.4,0.1], [0.85, 0.89, 0.30], [0.67, 0.9, 0.18], [0.117, 0.588, 0]],
"line_color": [0,0,0],
"policy_color": [0,0.2,0.8]
},
+2 -2
View File
@@ -281,10 +281,10 @@ class Game:
elif mode == "sweep":
board_size_x, board_size_y = self.board_size
analyze_moves = [Move(coords=(x, y), player=cn.next_player) for x in range(board_size_x) for y in range(board_size_y) if (x, y) not in stones]
visits = self.engine.config["visits"] * self.config["sweep_visits_frac"]
visits = int(self.engine.config["visits"] * self.config["sweep_visits_frac"] + 0.5)
self.katrain.controls.set_status(f"Refining analysis of entire board to {visits} visits")
priority = -1_000_000_000
else: # mode=='refine':
else: # mode=='equalize':
analyze_moves = [Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items()]
visits = max(d["visits"] for d in cn.analysis["moves"].values())
self.katrain.controls.set_status(f"Equalizing analysis of candidate moves to {visits} visits")
+16 -8
View File
@@ -74,18 +74,21 @@ class GameNode(SGFNode):
text += f"Score: {self.format_score(score)}\n"
if self.parent and self.parent.analysis_ready:
previous_top_move = self.parent.candidate_moves[0]
if sgf or hints and previous_top_move["move"] != single_move.gtp(): # TODO: when to include?
text += f"Predicted top move was {previous_top_move['move']} ({self.format_score(previous_top_move['scoreLead'])})\n"
points_lost = self.points_lost
if sgf and points_lost > 0.5:
text += f"Estimated point loss: {points_lost:.1f}\n"
if sgf or hints:
if previous_top_move["move"] != single_move.gtp():
text += f"Predicted top move was {previous_top_move['move']} ({self.format_score(previous_top_move['scoreLead'])}).\n"
points_lost = self.points_lost
if sgf and points_lost > 0.5:
text += f"Estimated point loss: {points_lost:.1f}\n"
else:
text += f"Move was predicted best move.\n"
if sgf or hints:
policy_ranking = self.parent.policy_ranking
policy_ix = [ix + 1 for (m, p), ix in zip(policy_ranking, range(len(policy_ranking))) if m == single_move]
if not policy_ix or policy_ix[0] != 1:
text += f"Top policy move was {policy_ranking[0][0].gtp()}\n"
if policy_ix:
text += f"Your move was #{policy_ix} according to NN policy\n"
text += f"Move was #{policy_ix[0]} according to policy.\n"
if not policy_ix or policy_ix[0] != 1:
text += f"Top policy move was {policy_ranking[0][0].gtp()}.\n"
if self.auto_undo:
text += "Move was automatically undone."
else:
@@ -113,6 +116,11 @@ class GameNode(SGFNode):
def candidate_moves(self) -> List[Dict]:
if not self.analysis_ready:
return []
if not self.analysis["moves"]:
polmoves = self.policy_ranking
top_polmove = polmoves[0][0] if polmoves else Move(None) # if no info at all, pass
return [{**self.analysis["root"], "pointsLost": 0, "order": 0, "move": top_polmove.gtp()}] # single visit -> go by policy/root
return sorted(
[{"pointsLost": self.player_sign(self.next_player) * (self.analysis["root"]["scoreLead"] - d["scoreLead"]), **d} for d in self.analysis["moves"].values()],
key=lambda d: (d["order"], d["pointsLost"]),
+25 -17
View File
@@ -6,7 +6,7 @@ from kivy.properties import ListProperty
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget
from constants import OUTPUT_DEBUG
from constants import OUTPUT_DEBUG, OUTPUT_EXTRA_DEBUG
from gui.kivyutils import draw_circle, draw_text
from game import Move
@@ -79,9 +79,6 @@ class BadukPanWidget(Widget):
if evalcol:
evalsize = self.stone_size * evalscale * self.ui_config["eval_dot_max_size"]
draw_circle((self.gridpos_x[x], self.gridpos_y[y]), evalsize, evalcol)
# highlight_col = [ ((1-c)*0.33+e)/1.33 for c,e in zip(col,evalcol) ]
# Color(*highlight_col[:3],0.5)
# Line(circle=(self.gridpos[x], self.gridpos[y], evalsize))
if innercol:
Color(*innercol)
@@ -103,19 +100,19 @@ class BadukPanWidget(Widget):
max_board_size = max(board_size_x, board_size_y)
self.canvas.before.clear()
with self.canvas.before:
# board
board_px_size = min(self.width, self.height)
self.board_color = self.ui_config["board_color"]
Rectangle(pos=self.pos, size=(self.width, self.height))
# grid lines
margin = 1.5
margin_x = margin + (max_board_size - board_size_x) / 2
margin_y = margin - 0.125 + (max_board_size - board_size_y) / 2
self.grid_size = board_px_size / (max_board_size - 1 + 1.5 * margin)
# set up margins and grid lines
grid_spaces_margin_x = [1.5, 0.75] # left, right
grid_spaces_margin_y = [1.125, 0.75] # bottom, top
x_grid_spaces = board_size_x - 1 + sum(grid_spaces_margin_x)
y_grid_spaces = board_size_y - 1 + sum(grid_spaces_margin_y)
self.grid_size = min(self.width / x_grid_spaces, self.height / y_grid_spaces)
board_width_with_margins = x_grid_spaces * self.grid_size
board_height_with_margins = y_grid_spaces * self.grid_size
extra_px_margin_x = (self.width - board_width_with_margins) / 2
extra_px_margin_y = (self.height - board_height_with_margins) / 2
self.stone_size = self.grid_size * self.ui_config["stone_size"]
self.gridpos_x = [self.pos[0] + math.floor((margin_x + i) * self.grid_size + 0.5) for i in range(board_size_x)] #
self.gridpos_y = [self.pos[1] + math.floor((margin_y + i) * self.grid_size + 0.5) for i in range(board_size_y)]
self.gridpos_x = [self.pos[0] + extra_px_margin_x + math.floor((grid_spaces_margin_x[0] + i) * self.grid_size + 0.5) for i in range(board_size_x)] #
self.gridpos_y = [self.pos[1] + extra_px_margin_y + math.floor((grid_spaces_margin_y[0] + i) * self.grid_size + 0.5) for i in range(board_size_y)]
line_color = self.ui_config["line_color"]
Color(*line_color)
@@ -138,7 +135,7 @@ class BadukPanWidget(Widget):
# coordinates
Color(0.25, 0.25, 0.25)
coord_offset = self.grid_size * margin / 2
coord_offset = self.grid_size * 1.5 / 2
for i in range(board_size_x):
draw_text(pos=(self.gridpos_x[i], self.gridpos_y[0] - coord_offset), text=Move.GTP_COORD[i], font_size=self.grid_size / 1.5)
for i in range(board_size_y):
@@ -178,6 +175,17 @@ class BadukPanWidget(Widget):
drawn_stone[m.coords] = m.player
self.draw_stone(m.coords[0], m.coords[1], stone_color[m.player], outline_color[m.player], inner, evalcol, evalsize)
if katrain.game.current_node.is_root and katrain.config("debug/level") >= OUTPUT_EXTRA_DEBUG:
for s in range(0, 19):
c = s
evalcol = self.eval_color(s)
evalsize = 1
self.draw_stone(5, c, stone_color["B"], outline_color["B"], None, evalcol, evalsize)
self.draw_stone(6, c, stone_color["B"], outline_color["B"], stone_color["W"], evalcol, evalsize)
self.draw_stone(7, c, stone_color["W"], outline_color["W"], None, evalcol, evalsize)
self.draw_stone(8, c, stone_color["W"], outline_color["W"], stone_color["B"], evalcol, evalsize)
self.draw_stone(9, c, [*evalcol, 0.5], scale=0.8)
# ownership - allow one move out of date for smooth animation
ownership = current_node.ownership or (current_node.parent and current_node.parent.ownership)
if katrain.controls.ownership.active and ownership:
+2 -3
View File
@@ -11,7 +11,7 @@ class Controls(BoxLayout):
def set_status(self, msg, at_node=None):
self.status = msg
self.status_node = at_node or self.katrain.game and self.parent.game.current_node
self.status_node = at_node or self.katrain.game and self.katrain.game.current_node
self.info.text = msg
self.update_evaluation()
@@ -74,7 +74,6 @@ class Controls(BoxLayout):
if current_player_is_human_or_both_robots:
self.show_evaluation_stats(current_node)
if current_node.score:
self.graph.update_value(current_node)
self.graph.update_value(current_node)
self.info.text = info
+10 -8
View File
@@ -24,10 +24,8 @@ class DarkLabel(Label):
class StyledButton(Button):
text_color = ListProperty([0.95, 0.95, 0.95, 1]) # TODO defaults as in ..
button_color = ListProperty([0.21, 0.28, 0.31, 1])
button_color_down = ListProperty([0.105, 0.14, 0.155, 1])
margin = ListProperty([1, 1, 1, 1]) # margin left bottom right top
button_color = ListProperty([])
button_color_down = ListProperty([])
radius = ListProperty((0,))
@@ -183,11 +181,12 @@ class ScoreGraph(Label):
def on_size(self, *args):
nodes = self.nodes
if nodes:
values = [n.score if n and n.score else 0 for n in nodes]
val_range = min(values or [0]), max(values or [0])
values = [n.score if n and n.score else math.nan for n in nodes]
nn_values = [n.score for n in nodes if n and n.score]
val_range = min(nn_values or [0]), max(nn_values or [0])
scale = math.ceil(max(self.min_scale, max(-val_range[0], val_range[1]) * 1.05))
xscale = self.width * 0.9 / max(len(values), 20)
xscale = self.width * 0.9 / max(len(values) - 1, 15)
available_height = self.height * (1 - 2 * self.marginy)
line_points = [[self.pos[0] + self.marginx * self.width + i * xscale, self.pos[1] + available_height / 2 * (1 + val / scale)] for i, val in enumerate(values)]
self.line_points = sum(line_points, [])
@@ -196,7 +195,10 @@ class ScoreGraph(Label):
if self.highlighted_index is not None:
self.highlighted_index = min(self.highlighted_index, len(values) - 1)
self.dot_pos = [c - self.highlight_size / 2 for c in line_points[self.highlighted_index]]
dot_point = line_points[self.highlighted_index]
if math.isnan(dot_point[1]):
dot_point[1] = self.pos[1] + available_height / 2 * (1 + (nn_values or [0])[-1] / scale)
self.dot_pos = [c - self.highlight_size / 2 for c in dot_point]
def update_value(self, node):
index = node.depth
+1 -3
View File
@@ -153,7 +153,5 @@ class ConfigPopup(QuickConfigGui):
self.katrain.engine = KataGoEngine(self.katrain, self.config[cat])
self.katrain.game.engine = self.katrain.engine
old_engine.shutdown(finish=True)
engine_restart = True
if engine_restart:
self.katrain.update_state(redraw_board=True)
self.katrain.update_state(redraw_board=True)
+42 -29
View File
@@ -4,24 +4,24 @@
#:set AI_MODES ['Default','Balance','Policy']
#:set PLAYER_MODES ['Human', 'Teach','AI:']
#:set BUTTON_COLOR [0.21, 0.28, 0.31, 1]
#:set BUTTON_COLOR [0.23, 0.30, 0.35, 1]
#:set ICON_BUTTON_COLOR [0.71, 0.78, 0.81, 1]
#:set BLACK [0.05,0.05,0.05,1]
#:set WHITE [0.95,0.95,0.95,1]
#:set GREY [0.5,0.5,0.5,1]
<StyledButton>:
color: WHITE
font_size: self.size[1] * 0.45
radius: (self.size[1]/7,)
# dont set these
disabled_mask: (0.5,0.5,0.5,1) if self.disabled else (1,1,1,1)
color: self.disabled_mask if self.disabled else self.text_color
button_color: BUTTON_COLOR
button_color_down: [0.105, 0.14, 0.155, 1]
button_color_highlight: [(a+b)/2 for a,b in zip(self.button_color,self.button_color_down)]
background_color: 0,0,0,0
background_normal: ''
face_color: [c*m for c,m in zip(self.button_color if root.state=='normal' else self.button_color_down,self.disabled_mask)]
canvas.before:
Color:
rgba: root.face_color or [0,0,0,0]
rgba: self.button_color if root.state=='normal' else self.button_color_down
RoundedRectangle:
size: self.size[0],self.size[1]
pos: (self.pos[0],self.pos[1])
@@ -31,7 +31,7 @@
font_size: self.size[1] * 0.45
background_color: BUTTON_COLOR
background_normal: ''
color: (1,1,1,1)
color: WHITE
<StyledSpinner>:
text: self.values[0] if self.values else ''
@@ -55,8 +55,7 @@
button_color: ICON_BUTTON_COLOR
icon_margin: 0.15 * self.size[0]
icon: ''
icon_size: min(self.size[0]-self.margin[0]-self.margin[2]-2*self.icon_margin,self.size[1]-self.margin[1]-self.margin[3]-2*self.icon_margin)
#EffectWidget: effects: [ew.FXAAEffect()]
icon_size: min(self.size[0]-2*self.icon_margin,self.size[1]-2*self.icon_margin)
Image:
size: (root.icon_size or 0,root.icon_size or 0) # square
pos: [root.pos[i] + (root.size[i] - root.icon_size)/2 for i in [0,1]] if root.icon_size else [0,0]
@@ -66,8 +65,7 @@
<TransparentIconButton@Button>:
background_normal: ''
background_color: (0,0,0,0)
margin: (1,1,1,1)
icon_size: min(self.size[0]-self.margin[0]-self.margin[2],self.size[1]-self.margin[1]-self.margin[3])
icon_size: min(self.size[0],self.size[1])
icon: ''
Image:
size: (root.icon_size or 0,root.icon_size or 0) # square
@@ -123,6 +121,7 @@
DarkLabel:
size_hint: 0.4,None
text: root.text
height: desc.height
font_size: desc.font_size
id: value
bold: True
@@ -169,7 +168,7 @@
Color:
rgba: GREY
Line:
points: [root.pos[0]+self.width*root.marginx,root.pos[1]+self.height*0.5,root.pos[0]+self.width*0.95,root.pos[1]+self.height*0.5]
points: [root.pos[0]+self.width*root.marginx,root.pos[1]+self.height*0.5*(1 - 2 * root.marginy),root.pos[0]+self.width*0.95,root.pos[1]+self.height*0.5]
width: 1.0
dashes: [2,2]
Line:
@@ -179,7 +178,7 @@
rgba: BLACK
Line:
points: root.line_points
width: 1.0
width: 1.1
Color:
rgba: BUTTON_COLOR
Ellipse:
@@ -207,11 +206,11 @@
<BadukPanWidget>:
size: self.parent.height, self.parent.height
katrain: self.parent.parent
katrain: app.gui
<BadukPanControls>:
katrain: self.parent.parent
katrain: app.gui
black_prisoners: black_prisoners
white_prisoners: white_prisoners
mid_circles_container: mid_circles_container
@@ -295,14 +294,17 @@
ownership: ownership
policy: policy
eval: eval
analyze_extra: analyze_extra
analyze_equalize: analyze_equalize
analyze_sweep: analyze_sweep
player_mode_groups: {'B':B_player_mode,'W':W_player_mode}
ai_mode_groups: {'B':B_AI_mode,'W':W_AI_mode}
graph: graph
katrain: self.parent.parent
katrain: app.gui
BoxLayout:
size_hint: 1,0.05
orientation: 'horizontal'
padding: 5,1,5,0
padding: 3,1,3,0
spacing: 2
StyledTabButton:
text: 'Play'
@@ -372,15 +374,24 @@
size_hint: 1,0.2
DarkLabel:
text: 'Analysis'
size_hint: 1, 1
multiline: False
font_size: 0.45*self.size[1]
StyledButton:
id: analyze_extra
text: 'Extra'
size_hint: 1, 1
on_press: root.katrain("analyze-extra","extra")
StyledButton:
id: analyze_equalize
text: 'Equalize'
size_hint: 1, 1
on_press: root.katrain("analyze-extra","equalize")
StyledButton:
id: analyze_sweep
text: 'Sweep'
size_hint: 1, 1
on_press: root.katrain("analyze-extra","sweep")
ScoreGraph:
id: graph
size_hint: 1,0.7
@@ -443,7 +454,7 @@
size_hint: 1,0.3
CensorableLabel:
id: points_lost
size_hint: 1, 0.03
size_hint: 1, 0.0225
label: 'Point loss'
text: ''
TextInput:
@@ -474,6 +485,7 @@
<KaTrainGui>:
board_gui: board_gui
controls_box: controls_box
controls: controls
board_controls: board_controls
board_aspect_ratio: 1.075
@@ -486,17 +498,19 @@
pos: self.pos
size: self.size
orientation: 'vertical'
size_hint: 1 - controls_box.size_hint[0], 1
BadukPanWidget:
id: board_gui
size_hint: 1,1
BadukPanControls:
id: board_controls
size_hint: (1,root.board_aspect_ratio-1)
BoxLayout:
orientation: 'vertical'
size_hint_y: None
height: self.width / 13.5
BoxLayout: # avoids a weird syntax error in builder .. somehow
size_hint_x: None
size_hint_y: 1
hidden: False
width: 0 if self.hidden else self.height * 0.4
opacity: 0 if self.hidden else 1
id: controls_box
size_hint: max(0.25,(root.width - root.height/root.board_aspect_ratio)/root.width), 1
canvas.before:
Color:
rgba: 0.95, 0.95, 0.95, 1
@@ -505,10 +519,6 @@
size: self.size
Controls:
id: controls
size_hint: 1, 1 - filler.size_hint[1]
Label:
id: filler
size_hint: 1, max(0.0001, ((controls_box.height / controls_box.width) - 3)/(controls_box.height / controls_box.width)) # limits aspect ratio
<LoadSGFPopup>:
fast: fast
@@ -586,9 +596,12 @@
halign: 'center'
valign: 'center'
multiline: True
text_size: self.size
text_size: self.width-6, None
id: info
StyledButton:
text: 'Start\nGame'
font_size: 0.3 * self.size[1]
on_press: root.new_game()
#EffectWidget: effects: [ew.FXAAEffect()]
+12 -7
View File
@@ -203,12 +203,12 @@ class KaTrainGui(BoxLayout):
"q": self.controls.show_children,
"w": self.controls.eval,
"e": self.controls.hints,
"r": self.controls.policy,
"t": self.controls.ownership,
"a": ("ai-move",),
"s": ("analyze-extra", "extra"),
"f": ("analyze-extra", "refine"),
"d": ("analyze-extra", "sweep"),
"r": self.controls.ownership,
"t": self.controls.policy,
"enter": ("ai-move",),
"a": self.controls.analyze_extra,
"s": self.controls.analyze_equalize,
"d": self.controls.analyze_sweep,
"right": ("switch-branch", 1),
"left": ("switch-branch", -1),
}
@@ -220,6 +220,8 @@ class KaTrainGui(BoxLayout):
self(*shortcut)
elif keycode[1] == "tab":
self.controls.switch_mode()
elif keycode[1] in ["`", "~", "p"]:
self.controls_box.hidden = not self.controls_box.hidden
elif keycode[1] in ["up", "z"]:
self("undo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999)
elif keycode[1] in ["down", "x"]:
@@ -241,8 +243,9 @@ class KaTrainGui(BoxLayout):
try:
move_tree = KaTrainSGF.parse(clipboard)
except Exception as e:
self.controls.set_status(f"Failed to imported game from clipboard: {e}")
self.controls.set_status(f"Failed to imported game from clipboard: {e}\nClipboard contents: {clipboard[:50]}...")
return
move_tree.nodes_from_root[-1].analyze(self.engine) # speed up result for looking at end of game
self._do_new_game(move_tree=move_tree)
self("redo", 999)
self.log("Imported game from clipboard.", OUTPUT_INFO)
@@ -250,6 +253,8 @@ class KaTrainGui(BoxLayout):
class KaTrainApp(App):
gui = ObjectProperty(None)
def build(self):
self.icon = "./img/icon.png"
self.gui = KaTrainGui()
+3 -3
View File
@@ -78,9 +78,9 @@ class SGFNode:
return copy.deepcopy(self.properties)
def sgf(self) -> str:
import sys # TODO
import sys
sys.setrecursionlimit(max(sys.getrecursionlimit(), 3 * 29 * 29))
sys.setrecursionlimit(max(sys.getrecursionlimit(), 3 * 29 * 29)) # thanks to lightvector for causing stack overflows
sgf_str = "".join([prop + "".join(f"[{v}]" for v in values) for prop, values in self.sgf_properties.items() if values])
if self.children:
children = [c.sgf() for c in self.children]
@@ -259,5 +259,5 @@ class SGF:
values = re.split(r"\]\s*\[", value)
current_move.add_property(property, values)
if self.ix < len(self.contents):
raise ParseError(f"Parse Error: unexpected character at {self.contents[self.ix - 25:self.ix]}>{self.contents[self.ix]}<{self.contents[self.ix + 1:self.ix + 25]}")
raise ParseError(f"Parse Error: unexpected character at {self.contents[self.ix:self.ix+25]}")
raise ParseError("Parse Error: expected ')' at end of input.")