From fb13568c33a045677d6ac62d3a3f8f10c19ed0ee Mon Sep 17 00:00:00 2001 From: Sander Land Date: Fri, 1 May 2020 13:31:19 +0200 Subject: [PATCH] tooltips --- ai.py | 7 ++++++- bots/ai2gtp.py | 7 +++++-- gui/kivyutils.py | 42 ++++++++++++++++++++++++++++++++++++++++-- katrain.kv | 40 ++++++++++++++++++++++++++++++---------- 4 files changed, 81 insertions(+), 15 deletions(-) diff --git a/ai.py b/ai.py index df2eb1c..a57325a 100644 --- a/ai.py +++ b/ai.py @@ -80,8 +80,13 @@ def ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, GameNode d_noise = dirichlet_noise(len(selected_policy_moves)) noisy_policy_moves = [(((1 - noise_str) * pol + noise_str * noise), mv) for ((pol, mv), noise) in zip(selected_policy_moves, d_noise)] new_top = heapq.nlargest(5, noisy_policy_moves) + ai_thoughts += f"Noisy policy strategy (strength={noise_str:.2f}) generated 5 moves {fmt_moves(new_top)} " aimove = new_top[0][1] - ai_thoughts += f"Noisy policy strategy (strength={noise_str:.2f}) generated 5 moves {fmt_moves(new_top)} so picked {aimove.gtp()} ({policy_grid[aimove.coords[1]][aimove.coords[0]]:.2%}). " + if new_top[0][0] < pass_policy: + ai_thoughts += f", but found pass ({pass_policy:.2%} to be higher rated than {aimove.gtp()} ({new_top[0][0]:.2%}) so will play top policy move instead." + aimove = top_policy_move + else: + ai_thoughts += f" so picked {aimove.gtp()} ({policy_grid[aimove.coords[1]][aimove.coords[0]]:.2%})." elif "p:" in ai_mode: legal_policy_moves = [(pol, mv) for pol, mv in policy_moves if not mv.is_pass if pol > 0] n_moves = int(ai_settings["pick_frac"] * len(legal_policy_moves) + ai_settings["pick_n"]) diff --git a/bots/ai2gtp.py b/bots/ai2gtp.py index d39fb9c..34ac11f 100644 --- a/bots/ai2gtp.py +++ b/bots/ai2gtp.py @@ -73,9 +73,12 @@ def malkovich_analysis(cn): logger.log(f"dscore {dscore} = {cn.analysis['root']['scoreLead']} {cn.parent.analysis['root']['scoreLead']} at {move}...", OUTPUT_ERROR) if abs(dscore) > REPORT_SCORE_THRESHOLD and (cn.player == "B" and dscore < 0 or cn.player == "W" and dscore > 0): # relevant mistakes favpl = "B" if dscore > 0 else "W" - msg = f"MALKOVICH:{cn.player} {cn.single_move.gtp()} caused a significant score change ({favpl} gained {abs(dscore):.1f} points) -> Win Rate {cn.format_win_rate()} Expected Score {cn.format_score()}" + msg = f"MALKOVICH:{cn.player} {cn.single_move.gtp()} caused a significant score change ({favpl} gained {abs(dscore):.1f} points)" if cn.ai_thoughts: - msg += f" AI Thoughts: {cn.ai_thoughts}" + msg += f" -> Win Rate {cn.format_win_rate()} Score {cn.format_score()} AI Thoughts: {cn.ai_thoughts}" + else: + comment = cn.comment(sgf=True).replace('\n',' ') + msg += f" -> Detailed move analysis: {comment}" print(msg, file=sys.stderr) sys.stderr.flush() diff --git a/gui/kivyutils.py b/gui/kivyutils.py index 0e46010..1ede8dc 100644 --- a/gui/kivyutils.py +++ b/gui/kivyutils.py @@ -5,6 +5,7 @@ import re from kivy.clock import Clock from kivy.core.text import Label as CoreLabel +from kivy.core.window import Window from kivy.graphics import * from kivy.properties import BooleanProperty, ListProperty, NumericProperty, ObjectProperty, StringProperty from kivy.uix.behaviors import ToggleButtonBehavior @@ -18,11 +19,48 @@ from kivy.uix.spinner import Spinner from kivy.uix.textinput import TextInput from kivy.uix.widget import Widget +class ToolTipLabel(Label): + pass + +class ToolTipBehavior(Widget): + tooltip_text = StringProperty('') + font_size = NumericProperty(10) + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.tooltip = ToolTipLabel() + self.open = False + + def on_touch_up(self, touch): + inside = self.collide_point(*self.to_widget(*touch.pos)) + if inside and touch.button == "right" and self.tooltip_text: + if not self.open: + self.display_tooltip(touch.pos) + Clock.schedule_once(lambda _: self.set_position(touch.pos), 0) + elif not inside and self.open: + self.close_tooltip() + return super().on_touch_up(touch) + + def close_tooltip(self): + self.open = False + Window.remove_widget(self.tooltip) + + def on_size(self,*args): + mid = (self.pos[0]+self.width/2,self.pos[1]+self.height/2) + self.tooltip.font_size = self.font_size + self.set_position(mid) + + def set_position(self,pos): + self.tooltip.pos = (pos[0]-self.tooltip.texture_size[0],pos[1]) + + def display_tooltip(self, pos): + self.open = True + self.tooltip.text = self.tooltip_text + self.tooltip.font_size = self.font_size + Window.add_widget(self.tooltip) class DarkLabel(Label): pass - class ScaledLightLabel(DarkLabel): pass @@ -44,7 +82,7 @@ class ScrollableLabel(ScrollView): border_color = ListProperty([0, 0, 0, 1]) -class StyledButton(Button): +class StyledButton(Button,ToolTipBehavior): button_color = ListProperty([]) button_color_down = ListProperty([]) radius = ListProperty((0,)) diff --git a/katrain.kv b/katrain.kv index 372578e..ad88d5a 100644 --- a/katrain.kv +++ b/katrain.kv @@ -4,6 +4,7 @@ #:set PLAYER_MODES ['Human', 'Teach','AI'] #:set PLAYER_MODE_VALUES ['human','human+undo','ai'] #:set BUTTON_COLOR [0.23, 0.30, 0.35, 1] +#:set TOOLTIP_COLOR [0.33, 0.40, 0.45, 1] #:set ICON_BUTTON_COLOR [0.71, 0.78, 0.81, 1] #:set BOARD_COLOR [0.85, 0.68, 0.40,1] #:set GRAPH_DOT_COLOR [0.85, 0.3, 0.3,1] @@ -12,6 +13,8 @@ #:set GREY [0.5,0.5,0.5,1] +# mixins + : canvas.before: Color: @@ -20,6 +23,25 @@ pos: self.pos size: self.size +: + size_hint: None, None + size: self.texture_size + padding: (2,2) + halign: 'center' + font_size: app.gui.board_controls.pass_btn.height*0.6 if app.gui else 15 + canvas.before: + Color: + rgba: TOOLTIP_COLOR + Rectangle: + size: self.size + pos: self.pos + Color: + rgba: BUTTON_COLOR + Line: + width: 1.1 + rectangle: (*self.pos,*self.size) + +# widgets : color: WHITE @@ -139,9 +161,6 @@ rectangle: [self.pos[0],self.pos[1]+1,self.width-1,self.height] width:1.5 - - - : font_size: 0.4*self.size[1] color: BLACK @@ -171,8 +190,6 @@ id: value bold: True - - : text: '' color: WHITE @@ -268,11 +285,12 @@ pos: root.right_edge - self.width-1, root.pos[1] text: 'W+' + str(int(root.y_scale)) +# UI components + : size: self.parent.height, self.parent.height katrain: app.gui - : katrain: app.gui black_prisoners: black_prisoners @@ -341,8 +359,9 @@ padding: 0.2 * self.size[1] StyledButton: text: 'AI Move' + tooltip_text: '[enter] Play an AI move\nusing the selected strategy.' + on_press: if self.last_touch.button=="left": root.katrain("ai-move",None) font_size: 0.6*self.size[1] - on_press: root.katrain("ai-move",None) ClickableLabel: size_hint: 0.025, 1 canvas: @@ -487,18 +506,19 @@ StyledButton: id: analyze_extra text: 'Extra' + tooltip_text: 'Analyze the current position more\nincreasing accuracy of top move predictions\nand score difference.' size_hint: 1, 1 - on_press: root.katrain("analyze-extra","extra") + on_press: if self.last_touch.button=="left": root.katrain("analyze-extra","extra") StyledButton: id: analyze_equalize text: 'Equalize' size_hint: 1, 1 - on_press: root.katrain("analyze-extra","equalize") + on_press: if self.last_touch.button=="left": root.katrain("analyze-extra","equalize") StyledButton: id: analyze_sweep text: 'Sweep' size_hint: 1, 1 - on_press: root.katrain("analyze-extra","sweep") + on_press: if self.last_touch.button=="left": root.katrain("analyze-extra","sweep") ScoreGraph: id: graph size_hint: 1,0.7