From 71b1dc2d700b803b876f4a83c71cb3e4f4ec2fab Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 21 Jun 2020 00:00:27 +0200 Subject: [PATCH 01/77] slider --- katrain/__main__.py | 4 +- katrain/core/constants.py | 22 ++-- katrain/gui/popups.py | 41 ++++++-- katrain/gui/widgets/selection_slider.py | 132 ++++++++++++++++++++++++ katrain/popups.kv | 14 ++- 5 files changed, 193 insertions(+), 20 deletions(-) create mode 100644 katrain/gui/widgets/selection_slider.py diff --git a/katrain/__main__.py b/katrain/__main__.py index 29b05fd..9bfa191 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -65,7 +65,7 @@ from katrain.core.engine import KataGoEngine from katrain.core.game import Game, IllegalMoveException, KaTrainSGF from katrain.core.sgf_parser import Move, ParseError from katrain.gui.kivyutils import * -from katrain.gui.popups import ConfigPopup, LoadSGFPopup, NewGamePopup, AIPopup +from katrain.gui.popups import ConfigPopup, LoadSGFPopup, NewGamePopup, ConfigAIPopup from katrain.gui.style import ENGINE_BUSY_COL, ENGINE_DOWN_COL, ENGINE_READY_COL, LIGHTGREY from katrain.gui.widgets.graph import ScoreGraph from katrain.gui.widgets.movetree import MoveTree @@ -311,7 +311,7 @@ class KaTrainGui(Screen, KaTrainBase): self.controls.timer.paused = True if not self.ai_settings_popup: self.ai_settings_popup = I18NPopup( - title_key="ai settings", size=[dp(600), dp(600)], content=AIPopup(self) + title_key="ai settings", size=[dp(600), dp(600)], content=ConfigAIPopup(self) ).__self__ self.ai_settings_popup.content.popup = self.ai_settings_popup self.ai_settings_popup.open() diff --git a/katrain/core/constants.py b/katrain/core/constants.py index 7eaf22e..868688f 100644 --- a/katrain/core/constants.py +++ b/katrain/core/constants.py @@ -1,6 +1,12 @@ -VERSION = "1.2.1" +VERSION = "1.3.0" HOMEPAGE = "https://github.com/sanderland/katrain" -CONFIG_MIN_VERSION = "1.2.0" +CONFIG_MIN_VERSION = "1.2.0" # keep config files from this version + +OUTPUT_ERROR = -1 +OUTPUT_KATAGO_STDERR = -0.5 +OUTPUT_INFO = 0 +OUTPUT_DEBUG = 1 +OUTPUT_EXTRA_DEBUG = 2 PLAYER_HUMAN, PLAYER_AI = "player:human", "player:ai" PLAYER_TYPES = [PLAYER_HUMAN, PLAYER_AI] @@ -43,7 +49,7 @@ AI_STRATEGIES_RECOMMENDED_ORDER = [ ] -AI_STRENGTH = { +AI_STRENGTH = { # not used AI_DEFAULT: "9d", AI_POLICY: "4d", AI_JIGO: "?d", @@ -57,9 +63,7 @@ AI_STRENGTH = { AI_RANK: "15k - 3d", } - -OUTPUT_ERROR = -1 -OUTPUT_KATAGO_STDERR = -0.5 -OUTPUT_INFO = 0 -OUTPUT_DEBUG = 1 -OUTPUT_EXTRA_DEBUG = 2 +AI_OPTION_VALUES = { + "kyu_rank": [(k, f"{k}[strength:kyu]") for k in range(15, 0, -1)] + + [(k, f"{1-k}[strength:dan]") for k in range(0, -3, -1)] +} diff --git a/katrain/gui/popups.py b/katrain/gui/popups.py index 48c6f46..89d21da 100644 --- a/katrain/gui/popups.py +++ b/katrain/gui/popups.py @@ -4,7 +4,7 @@ import re from typing import Any, Dict, List, Tuple, Union from kivy.clock import Clock -from kivy.properties import BooleanProperty, NumericProperty, StringProperty +from kivy.properties import BooleanProperty, NumericProperty, StringProperty, ListProperty from kivy.uix.anchorlayout import AnchorLayout from kivy.uix.boxlayout import BoxLayout from kivy.uix.label import Label @@ -21,12 +21,14 @@ from katrain.core.constants import ( OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_INFO, + AI_OPTION_VALUES, ) from katrain.core.engine import KataGoEngine from katrain.core.lang import i18n from katrain.core.utils import find_package_resource from katrain.gui.kivyutils import BackgroundMixin, I18NSpinner from katrain.gui.style import DEFAULT_FONT, EVAL_COLORS +from katrain.gui.widgets.selection_slider import SelectionSlider class I18NPopup(Popup): @@ -129,6 +131,21 @@ class LabelledIntInput(LabelledTextInput): return int(self.text or "0") +class LabelledSelectionSlider(BoxLayout): + input_property = StringProperty("") + values = ListProperty([(0, "")]) # (value:numeric,label:string) pairs + + def set_value(self, v): + self.slider.set_value(v) + self.textbox.text = str(v) + + @property + def input_value(self): + if self.textbox.text: + return float(self.textbox.text) + return self.slider.values[self.silder.index][0] + + class InputParseError(Exception): pass @@ -183,12 +200,14 @@ class QuickConfigGui(MDBoxLayout): return self._set_properties_subtree(self) def _set_properties_subtree(self, widget): - if isinstance(widget, (LabelledTextInput, LabelledSpinner, LabelledCheckBox)) and getattr( - widget, "input_property", None - ): + if isinstance( + widget, (LabelledTextInput, LabelledSpinner, LabelledCheckBox, LabelledSelectionSlider) + ) and getattr(widget, "input_property", None): value = self.get_setting(widget.input_property)[0] if isinstance(widget, LabelledCheckBox): widget.active = value is True + elif isinstance(widget, LabelledSelectionSlider): + widget.set_value(value) elif isinstance(widget, LabelledSpinner): selected = 0 try: @@ -281,7 +300,7 @@ class DescriptionLabel(Label): pass -class AIPopup(QuickConfigGui): +class ConfigAIPopup(QuickConfigGui): max_options = NumericProperty(6) def __init__(self, katrain): @@ -300,9 +319,15 @@ class AIPopup(QuickConfigGui): self.help_label.text = i18n._(strategy.replace("ai:", "aihelp:")) for k, v in sorted(mode_settings.items(), key=lambda kv: kv[0]): self.options_grid.add_widget(DescriptionLabel(text=k)) - self.options_grid.add_widget( - wrap_anchor(LabelledFloatInput(text=str(v), input_property=f"ai/{strategy}/{k}")) - ) + if k in AI_OPTION_VALUES: + values = [(v, re.sub(r"\[(.*?)\]", lambda m: i18n._(m[1]), l)) for v, l in AI_OPTION_VALUES[k]] + widget = LabelledSelectionSlider(values=values, input_property=f"ai/{strategy}/{k}") + widget.set_value(v) + self.options_grid.add_widget(wrap_anchor(widget)) + else: + self.options_grid.add_widget( + wrap_anchor(LabelledFloatInput(text=str(v), input_property=f"ai/{strategy}/{k}")) + ) for _ in range((self.max_options - len(mode_settings)) * 2): self.options_grid.add_widget(Label()) diff --git a/katrain/gui/widgets/selection_slider.py b/katrain/gui/widgets/selection_slider.py new file mode 100644 index 0000000..13e3866 --- /dev/null +++ b/katrain/gui/widgets/selection_slider.py @@ -0,0 +1,132 @@ +from kivy.lang import Builder +from kivy.uix.widget import Widget +from kivy.properties import ( + NumericProperty, + AliasProperty, + ListProperty, + BooleanProperty, +) +from kivymd.uix.label import MDLabel +from kivymd.uix.selectioncontrol import Thumb +from katrain.gui.style import LIGHTER_BACKGROUND_COLOR + + +class SelectionSlider(Widget): + __events__ = ["on_select"] + active = BooleanProperty(False) + hint = BooleanProperty(True) + + index = NumericProperty(0) # selected index + values = ListProperty([(0, "")]) # (value:numeric,label:string) pairs + normalized_pos = NumericProperty(0) # slider relative pos from 0-1 + px_pos = NumericProperty(0) # actual px pos + padding = NumericProperty("16sp") + + track_color = ListProperty([1, 1, 1, 0.3]) + thumb_color = LIGHTER_BACKGROUND_COLOR + + def __init__(self,**kwargs): + super().__init__(**kwargs) + self.bind(size=self.set_index_and_positions,pos=self.set_index_and_positions,values=self.set_index_and_positions,index=self.set_index_and_positions) + + def set_index_and_positions(self, *_args): + self.index = max(0, min(self.index, len(self.values) - 1)) + self.normalized_pos = self.index / (len(self.values)-1) + self.px_pos = self.x + self.padding + self.normalized_pos * (self.width - 2 * self.padding) + + @property + def value(self): + return self.values[self.index][0] + + def set_value(self, set_value): # set to closest value + eq_value = sorted([(abs(v - set_value), i) for i, (v, l) in enumerate(self.values)]) + self.index = eq_value[0][1] + + def set_from_pos(self,pos): + norm_value = (pos[0] - self.x - self.padding) / (self.width - 2 * self.padding) + self.index = round(norm_value * (len(self.values) - 1)) + + def on_touch_down(self, touch): + if self.disabled or not self.collide_point(*touch.pos): + return + if touch.is_mouse_scrolling: + if "down" in touch.button or "left" in touch.button: + self.index += 1 + if "up" in touch.button or "right" in touch.button: + self.index -= 1 + else: + touch.grab(self) + self.active = True + self.set_from_pos(touch.pos) + return True + + def on_touch_move(self, touch): + if touch.grab_current == self: + self.set_from_pos(touch.pos) + return True + + def on_touch_up(self, touch): + if touch.grab_current == self: + self.set_from_pos(touch.pos) + self.active = False + self.dispatch("on_select", self.value) + return True + + def on_select(self,value): + pass + + +KV = """ +: + id: slider + canvas: + Clear + Color: + rgba: + self.track_color + Rectangle: + size: + (self.width - self.padding*2, dp(4)) + pos: + (self.x + self.padding, self.center_y - dp(4)) + Color: + rgba: + self.thumb_color + Rectangle: + size: + ((self.width-self.padding*2)*self.normalized_pos, sp(4)) + pos: + (self.x + self.padding, self.center_y - dp(4)) + + Thumb: + id: thumb + size_hint: None, None + size: + ((dp(24), dp(24)) if root.active else (dp(16), dp(16))) + pos: + (slider.px_pos - dp(8), slider.center_y - thumb.height/2 - dp(2)) + color: + root.thumb_color + elevation: + 4 if root.active else 2 + + MDCard: + id: hint_box + size_hint: None, None + md_bg_color: [1, 1, 1, 1] if root.active else [0, 0, 0, 0] + elevation: 4 if root.active else 0 + size: + (dp(28), dp(28) if root.active else (dp(20), dp(20))) + pos: + (slider.px_pos - dp(9), slider.center_y - hint_box.height / 2 + dp(30)) + + MDLabel: + text: slider.values[slider.index][1] + font_style: "Caption" + halign: "center" + color: root.thumb_color if root.active else [0, 0, 0, 0] + + +""" + +Builder.load_string(KV) diff --git a/katrain/popups.kv b/katrain/popups.kv index ef4cd68..91a37da 100644 --- a/katrain/popups.kv +++ b/katrain/popups.kv @@ -366,8 +366,20 @@ on_press: root.update_config(True) +: + slider: slider + textbox: textbox + SelectionSlider: + id: slider + size_hint: 3,1 + values: root.values + on_select: textbox.text = str(slider.value) + LabelledFloatInput: + id: textbox - + + +: options_grid: options_grid help_label: help_label ai_select: ai_select From ea1a8f78f89a713a2c5ce252aa793596e6288aa1 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 21 Jun 2020 11:19:49 +0200 Subject: [PATCH 02/77] ai options --- katrain/core/constants.py | 8 ++++++-- katrain/gui/popups.py | 14 +++++++++----- katrain/gui/widgets/selection_slider.py | 16 ++++++++-------- katrain/popups.kv | 12 +++++++----- 4 files changed, 30 insertions(+), 20 deletions(-) diff --git a/katrain/core/constants.py b/katrain/core/constants.py index 868688f..c009cb8 100644 --- a/katrain/core/constants.py +++ b/katrain/core/constants.py @@ -64,6 +64,10 @@ AI_STRENGTH = { # not used } AI_OPTION_VALUES = { - "kyu_rank": [(k, f"{k}[strength:kyu]") for k in range(15, 0, -1)] - + [(k, f"{1-k}[strength:dan]") for k in range(0, -3, -1)] + "kyu_rank": [(k, f"{k}[strength:kyu]") for k in range(15, 0, -1)] + [(k, f"{1-k}[strength:dan]") for k in range(0, -3, -1)], + "strength": [0.25,0.5,1,2,4], + "opening_moves": range(0,51), + "pick_override": [0,0.5,0.6,0.7,0.8,0.9,0.95,1], + "lower_bound": [(v,f"{v:.2%}") for v in [0,0.0001,0.0005,0.001,0.005,0.01,0.05] ], + "weaken_fac": [-2,-1,0.5,1,1.25,1.5,2,4] } diff --git a/katrain/gui/popups.py b/katrain/gui/popups.py index 89d21da..b04da06 100644 --- a/katrain/gui/popups.py +++ b/katrain/gui/popups.py @@ -158,7 +158,7 @@ class QuickConfigGui(MDBoxLayout): Clock.schedule_once(self.build_and_set_properties, 0) def collect_properties(self, widget) -> Dict: - if isinstance(widget, (LabelledTextInput, LabelledSpinner, LabelledCheckBox)) and getattr( + if isinstance(widget, (LabelledTextInput, LabelledSpinner, LabelledCheckBox,LabelledSelectionSlider)) and getattr( widget, "input_property", None ): try: @@ -318,10 +318,14 @@ class ConfigAIPopup(QuickConfigGui): self.options_grid.clear_widgets() self.help_label.text = i18n._(strategy.replace("ai:", "aihelp:")) for k, v in sorted(mode_settings.items(), key=lambda kv: kv[0]): - self.options_grid.add_widget(DescriptionLabel(text=k)) + self.options_grid.add_widget(DescriptionLabel(text=k,size_hint_x=0.25)) if k in AI_OPTION_VALUES: - values = [(v, re.sub(r"\[(.*?)\]", lambda m: i18n._(m[1]), l)) for v, l in AI_OPTION_VALUES[k]] - widget = LabelledSelectionSlider(values=values, input_property=f"ai/{strategy}/{k}") + values = AI_OPTION_VALUES[k] + if isinstance(values[0],Tuple): # with descriptions, possibly language-specific + fixed_values = [(v, re.sub(r"\[(.*?)\]", lambda m: i18n._(m[1]), l)) for v, l in values] + else: # just numbers + fixed_values = [(v, str(v)) for v in values] + widget = LabelledSelectionSlider(values=fixed_values, input_property=f"ai/{strategy}/{k}") widget.set_value(v) self.options_grid.add_widget(wrap_anchor(widget)) else: @@ -329,7 +333,7 @@ class ConfigAIPopup(QuickConfigGui): wrap_anchor(LabelledFloatInput(text=str(v), input_property=f"ai/{strategy}/{k}")) ) for _ in range((self.max_options - len(mode_settings)) * 2): - self.options_grid.add_widget(Label()) + self.options_grid.add_widget(Label(size_hint_x=None)) class ConfigPopup(QuickConfigGui): diff --git a/katrain/gui/widgets/selection_slider.py b/katrain/gui/widgets/selection_slider.py index 13e3866..2ddc952 100644 --- a/katrain/gui/widgets/selection_slider.py +++ b/katrain/gui/widgets/selection_slider.py @@ -2,13 +2,12 @@ from kivy.lang import Builder from kivy.uix.widget import Widget from kivy.properties import ( NumericProperty, - AliasProperty, ListProperty, BooleanProperty, ) from kivymd.uix.label import MDLabel from kivymd.uix.selectioncontrol import Thumb -from katrain.gui.style import LIGHTER_BACKGROUND_COLOR + class SelectionSlider(Widget): @@ -23,7 +22,7 @@ class SelectionSlider(Widget): padding = NumericProperty("16sp") track_color = ListProperty([1, 1, 1, 0.3]) - thumb_color = LIGHTER_BACKGROUND_COLOR + thumb_color = ListProperty([0.5, 0.5, 0.5, 1]) def __init__(self,**kwargs): super().__init__(**kwargs) @@ -77,6 +76,7 @@ class SelectionSlider(Widget): KV = """ +#:import i18n katrain.core.lang.i18n : id: slider canvas: @@ -116,17 +116,17 @@ KV = """ md_bg_color: [1, 1, 1, 1] if root.active else [0, 0, 0, 0] elevation: 4 if root.active else 0 size: - (dp(28), dp(28) if root.active else (dp(20), dp(20))) + (max(dp(28), label.texture_size[0]+4) , dp(28)) pos: (slider.px_pos - dp(9), slider.center_y - hint_box.height / 2 + dp(30)) - MDLabel: + Label: + id: label text: slider.values[slider.index][1] - font_style: "Caption" + font_size: dp(12) + lang_change_tracking: i18n._('') # for font halign: "center" color: root.thumb_color if root.active else [0, 0, 0, 0] - - """ Builder.load_string(KV) diff --git a/katrain/popups.kv b/katrain/popups.kv index 91a37da..4bdda23 100644 --- a/katrain/popups.kv +++ b/katrain/popups.kv @@ -13,6 +13,8 @@ #:set LIGHTGREY [0.7,0.7,0.7,1] #:import BACKGROUND_COLOR katrain.gui.style.BACKGROUND_COLOR +#:import LIGHTER_BACKGROUND_COLOR katrain.gui.style.LIGHTER_BACKGROUND_COLOR +#:import LIGHTGREY katrain.gui.style.LIGHTGREY #:import TEXT_COLOR katrain.gui.style.TEXT_COLOR @@ -371,14 +373,14 @@ textbox: textbox SelectionSlider: id: slider - size_hint: 3,1 + size_hint: 2,1 values: root.values on_select: textbox.text = str(slider.value) + track_color: LIGHTGREY + thumb_color: LIGHTER_BACKGROUND_COLOR LabelledFloatInput: id: textbox - - : options_grid: options_grid help_label: help_label @@ -397,12 +399,12 @@ size_hint: 1,1.25 text: i18n._('') GridLayout: - size_hint: 1,4 + size_hint: 1, 4 id: options_grid rows: root.max_options cols: 2 AnchorLayout: - size_hint: 1,1.5 + size_hint: 1, 1.5 AutoSizedRoundedRectangleButton: padding_x: 15 size_hint: None,0.5 From 41c2addbdfa0ded37f1324a6dda2be2541e6ce23 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 21 Jun 2020 11:53:15 +0200 Subject: [PATCH 03/77] slider based options done --- katrain/config.json | 6 +++--- katrain/core/constants.py | 19 +++++++++++++------ katrain/gui/popups.py | 12 ++++++------ katrain/gui/widgets/selection_slider.py | 18 +++++++++++------- katrain/popups.kv | 3 +-- 5 files changed, 34 insertions(+), 24 deletions(-) diff --git a/katrain/config.json b/katrain/config.json index d5e3957..813f170 100644 --- a/katrain/config.json +++ b/katrain/config.json @@ -79,14 +79,14 @@ "opening_moves": 22.0 }, "ai:p:weighted": { + "weaken_fac": 1.25, "pick_override": 1.0, - "lower_bound": 0.001, - "weaken_fac": 1.25 + "lower_bound": 0.001 }, "ai:p:pick": { "pick_override": 0.95, "pick_n": 5, - "pick_frac": 0.33 + "pick_frac": 0.35 }, "ai:p:local": { "pick_override": 0.95, diff --git a/katrain/core/constants.py b/katrain/core/constants.py index c009cb8..1e5c0fe 100644 --- a/katrain/core/constants.py +++ b/katrain/core/constants.py @@ -64,10 +64,17 @@ AI_STRENGTH = { # not used } AI_OPTION_VALUES = { - "kyu_rank": [(k, f"{k}[strength:kyu]") for k in range(15, 0, -1)] + [(k, f"{1-k}[strength:dan]") for k in range(0, -3, -1)], - "strength": [0.25,0.5,1,2,4], - "opening_moves": range(0,51), - "pick_override": [0,0.5,0.6,0.7,0.8,0.9,0.95,1], - "lower_bound": [(v,f"{v:.2%}") for v in [0,0.0001,0.0005,0.001,0.005,0.01,0.05] ], - "weaken_fac": [-2,-1,0.5,1,1.25,1.5,2,4] + "kyu_rank": [(k, f"{k}[strength:kyu]") for k in range(15, 0, -1)] + + [(k, f"{1-k}[strength:dan]") for k in range(0, -3, -1)], + "strength": [0.25, 0.5, 1, 2, 4], + "opening_moves": range(0, 51), + "pick_override": [0, 0.5, 0.6, 0.7, 0.8, 0.85, 0.9, 0.95, 0.99, 1], + "lower_bound": [(v, f"{v:.2%}") for v in [0, 0.0001, 0.0005, 0.001, 0.005, 0.01, 0.05]], + "weaken_fac": [0.25, 0.5, 0.75, 1, 1.25, 1.5, 2, 4], + "endgame": [x / 100 for x in range(10, 80, 5)], + "pick_frac": [x / 100 for x in range(0, 101, 5)], + "pick_n": range(0, 26), + "stddev": [x / 2 for x in range(21)], + "line_weight": range(0, 11), + "threshold": [2, 2.5, 3, 3.5, 4, 4.5], } diff --git a/katrain/gui/popups.py b/katrain/gui/popups.py index b04da06..1ca2b9b 100644 --- a/katrain/gui/popups.py +++ b/katrain/gui/popups.py @@ -158,9 +158,9 @@ class QuickConfigGui(MDBoxLayout): Clock.schedule_once(self.build_and_set_properties, 0) def collect_properties(self, widget) -> Dict: - if isinstance(widget, (LabelledTextInput, LabelledSpinner, LabelledCheckBox,LabelledSelectionSlider)) and getattr( - widget, "input_property", None - ): + if isinstance( + widget, (LabelledTextInput, LabelledSpinner, LabelledCheckBox, LabelledSelectionSlider) + ) and getattr(widget, "input_property", None): try: ret = {widget.input_property: widget.input_value} except Exception as e: # TODO : on widget? @@ -318,12 +318,12 @@ class ConfigAIPopup(QuickConfigGui): self.options_grid.clear_widgets() self.help_label.text = i18n._(strategy.replace("ai:", "aihelp:")) for k, v in sorted(mode_settings.items(), key=lambda kv: kv[0]): - self.options_grid.add_widget(DescriptionLabel(text=k,size_hint_x=0.25)) + self.options_grid.add_widget(DescriptionLabel(text=k, size_hint_x=0.25)) if k in AI_OPTION_VALUES: values = AI_OPTION_VALUES[k] - if isinstance(values[0],Tuple): # with descriptions, possibly language-specific + if isinstance(values[0], Tuple): # with descriptions, possibly language-specific fixed_values = [(v, re.sub(r"\[(.*?)\]", lambda m: i18n._(m[1]), l)) for v, l in values] - else: # just numbers + else: # just numbers fixed_values = [(v, str(v)) for v in values] widget = LabelledSelectionSlider(values=fixed_values, input_property=f"ai/{strategy}/{k}") widget.set_value(v) diff --git a/katrain/gui/widgets/selection_slider.py b/katrain/gui/widgets/selection_slider.py index 2ddc952..1791118 100644 --- a/katrain/gui/widgets/selection_slider.py +++ b/katrain/gui/widgets/selection_slider.py @@ -9,7 +9,6 @@ from kivymd.uix.label import MDLabel from kivymd.uix.selectioncontrol import Thumb - class SelectionSlider(Widget): __events__ = ["on_select"] active = BooleanProperty(False) @@ -18,19 +17,24 @@ class SelectionSlider(Widget): index = NumericProperty(0) # selected index values = ListProperty([(0, "")]) # (value:numeric,label:string) pairs normalized_pos = NumericProperty(0) # slider relative pos from 0-1 - px_pos = NumericProperty(0) # actual px pos + px_pos = NumericProperty(0) # actual px pos padding = NumericProperty("16sp") track_color = ListProperty([1, 1, 1, 0.3]) thumb_color = ListProperty([0.5, 0.5, 0.5, 1]) - def __init__(self,**kwargs): + def __init__(self, **kwargs): super().__init__(**kwargs) - self.bind(size=self.set_index_and_positions,pos=self.set_index_and_positions,values=self.set_index_and_positions,index=self.set_index_and_positions) + self.bind( + size=self.set_index_and_positions, + pos=self.set_index_and_positions, + values=self.set_index_and_positions, + index=self.set_index_and_positions, + ) def set_index_and_positions(self, *_args): self.index = max(0, min(self.index, len(self.values) - 1)) - self.normalized_pos = self.index / (len(self.values)-1) + self.normalized_pos = self.index / (len(self.values) - 1) self.px_pos = self.x + self.padding + self.normalized_pos * (self.width - 2 * self.padding) @property @@ -41,7 +45,7 @@ class SelectionSlider(Widget): eq_value = sorted([(abs(v - set_value), i) for i, (v, l) in enumerate(self.values)]) self.index = eq_value[0][1] - def set_from_pos(self,pos): + def set_from_pos(self, pos): norm_value = (pos[0] - self.x - self.padding) / (self.width - 2 * self.padding) self.index = round(norm_value * (len(self.values) - 1)) @@ -71,7 +75,7 @@ class SelectionSlider(Widget): self.dispatch("on_select", self.value) return True - def on_select(self,value): + def on_select(self, value): pass diff --git a/katrain/popups.kv b/katrain/popups.kv index 4bdda23..94a4365 100644 --- a/katrain/popups.kv +++ b/katrain/popups.kv @@ -13,7 +13,6 @@ #:set LIGHTGREY [0.7,0.7,0.7,1] #:import BACKGROUND_COLOR katrain.gui.style.BACKGROUND_COLOR -#:import LIGHTER_BACKGROUND_COLOR katrain.gui.style.LIGHTER_BACKGROUND_COLOR #:import LIGHTGREY katrain.gui.style.LIGHTGREY #:import TEXT_COLOR katrain.gui.style.TEXT_COLOR @@ -377,7 +376,7 @@ values: root.values on_select: textbox.text = str(slider.value) track_color: LIGHTGREY - thumb_color: LIGHTER_BACKGROUND_COLOR + thumb_color: 0.5, 0.6, 0.8, 1 LabelledFloatInput: id: textbox From 05349fd832e2a22586824d07d1cd3944a6eac0c8 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 21 Jun 2020 17:32:00 +0200 Subject: [PATCH 04/77] download models --- katrain/gui/badukpan.py | 1 + katrain/gui/popups.py | 79 ++++++++----- katrain/gui/widgets/progress_loader.py | 108 ++++++++++++++++++ .../i18n/locales/en/LC_MESSAGES/katrain.po | 3 + katrain/popups.kv | 49 +++++--- 5 files changed, 198 insertions(+), 42 deletions(-) create mode 100644 katrain/gui/widgets/progress_loader.py diff --git a/katrain/gui/badukpan.py b/katrain/gui/badukpan.py index 671f9f5..5d89b22 100644 --- a/katrain/gui/badukpan.py +++ b/katrain/gui/badukpan.py @@ -333,6 +333,7 @@ class BadukPanWidget(Widget): if passed: if game_ended: text = katrain.game.manual_score or i18n._("board-game-end") + katrain.controls.timer.paused = True else: text = i18n._("board-pass") Color(0.45, 0.05, 0.45, 0.7) diff --git a/katrain/gui/popups.py b/katrain/gui/popups.py index 1ca2b9b..91b428e 100644 --- a/katrain/gui/popups.py +++ b/katrain/gui/popups.py @@ -25,9 +25,10 @@ from katrain.core.constants import ( ) from katrain.core.engine import KataGoEngine from katrain.core.lang import i18n -from katrain.core.utils import find_package_resource +from katrain.core.utils import find_package_resource, PATHS from katrain.gui.kivyutils import BackgroundMixin, I18NSpinner from katrain.gui.style import DEFAULT_FONT, EVAL_COLORS +from katrain.gui.widgets.progress_loader import ProgressLoader from katrain.gui.widgets.selection_slider import SelectionSlider @@ -339,36 +340,58 @@ class ConfigAIPopup(QuickConfigGui): class ConfigPopup(QuickConfigGui): def build_and_set_properties(self, *_args): super().build_and_set_properties() - # self.check_models() def check_models(self, *args): # WIP - try: - model = self.collect_properties(self)["engine/model"] - except InputParseError: - self.model_files.values = [] - return + paths = [self.model_path.text, self.katrain.config("engine/model"), "katrain/models", "~/.katrain"] + done = set() + model_files = [] + for path in paths: + path = path.rstrip("/\\") + if path.startswith("katrain"): + path = path.replace("katrain", PATHS["PACKAGE"].rstrip("/\\"), 1) + if not os.path.isdir(path): + path, _file = os.path.split(path) + slashpath = path.replace("\\", "/") + if slashpath in done or not os.path.isdir(path): + continue + done.add(slashpath) + model_files += [ + f.replace("/", os.path.sep).replace(PATHS["PACKAGE"], "katrain") + for ftype in ["*.bin.gz", "*.txt.gz"] + for f in glob.glob(slashpath + "/" + ftype) + ] + self.model_files.values = model_files - if os.path.exists(model): - if os.path.isdir(model): - path = model.rstrip("\\/") - file = None - else: - if model.startswith("katrain"): - model = find_package_resource(model) - path, file = os.path.split(model) - files = sorted( - [ - os.path.split(f)[1] - for ftype in ["*.bin.gz", "*.txt.gz"] - for f in glob.glob(path + os.path.sep + ftype) - ] - ) - self.model_files.values = files - print(file, files, file in files) - if file in files: - self.model_files.text = file - else: - self.model_files.values = [] + MODELS = { + "20b": "https://github.com/lightvector/KataGo/releases/download/v1.4.0/g170-b20c256x2-s4384473088-d968438914.bin.gz", + "30b": "https://github.com/lightvector/KataGo/releases/download/v1.4.0/g170-b30c320x2-s3530176512-d968463914.bin.gz", + "40b": "https://github.com/lightvector/KataGo/releases/download/v1.4.0/g170-b40c256x2-s3708042240-d967973220.bin.gz", + } + + def download_models(self, *_largs): + def download_complete(req, tmp_path, path, model): + os.rename(tmp_path, path) + self.katrain.log(f"Download of {model} model complete -> {path}", OUTPUT_INFO) + self.check_models() + + for name, url in self.MODELS.items(): + filename = os.path.split(url)[1] + if not any(os.path.split(f)[1] == filename for f in self.model_files.values): + savepath = os.path.expanduser(os.path.join("~/.katrain", filename)) + savepath_tmp = savepath + ".part" + self.katrain.log(f"Downloading {name} model from {url} to {savepath_tmp}", OUTPUT_INFO) + progress = ProgressLoader( + download_url=url, + path_to_file=savepath_tmp, + downloading_text=f"Downloading {name} model: " + "{}%", + download_complete=lambda req, tmp=savepath_tmp, path=savepath, model=name: download_complete( + req, tmp, path, model + ), + download_redirected=lambda req: self.katrain.log( + f"Download {name} redirected {req.resp_headers}", OUTPUT_DEBUG + ), + ) + progress.start(self.download_progress_box) def update_config(self, save_to_file=True): updated = super().update_config(save_to_file=save_to_file) diff --git a/katrain/gui/widgets/progress_loader.py b/katrain/gui/widgets/progress_loader.py new file mode 100644 index 0000000..fa09855 --- /dev/null +++ b/katrain/gui/widgets/progress_loader.py @@ -0,0 +1,108 @@ +# From KivyMD which will remove it in their next version, with some fixes +from kivy.clock import Clock +from kivy.animation import Animation +from kivy.network.urlrequest import UrlRequest +from kivy.lang import Builder +from kivy.properties import StringProperty, ObjectProperty +from kivy.uix.boxlayout import BoxLayout + +Builder.load_string( + """ +#:import Window kivy.core.window.Window + + + + opacity: 0 + spacing: 10 + MDSpinner + id: spinner + size_hint: None, 0.8 + width: dp(32) + color: 0.95,0.95,0.95,1 + MDLabel: + id: label_download + shorten: True + max_lines: 1 + halign: 'left' + valign: 'center' + text_size: self.size + color: 0.95,0.95,0.95,1 + text: root.label_downloading_text +""" +) + + +class ProgressLoader(BoxLayout): + path_to_file = StringProperty() + """The path to which the uploaded file will be saved.""" + + download_url = StringProperty() + """Link to uploaded file.""" + + label_downloading_text = StringProperty("Downloading...") + """Default text before downloading.""" + + downloading_text = StringProperty("Downloading: {}%") + """Progress text of the downloaded file.""" + + download_complete = ObjectProperty() + """Function, called after a successful file upload.""" + download_redirected = ObjectProperty() + """Function, called after a redirect event.""" + + request = ObjectProperty() + """UrlRequest object.""" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.root_instance = None + + def start(self, root_instance): + self.root_instance = root_instance + self.root_instance.add_widget(self) + self.request_download_file(self.download_url, self.path_to_file) + Clock.schedule_once(self.animation_show, 1) + + def animation_show(self, _dt): + animation = Animation(opacity=1, d=0.2, t="out_quad",) + animation.start(self) + + def request_download_file(self, url, path): + """ + :type url: str; + :param url: link to content; + + :type path: str; + :param path: path to save content; + """ + + self.request = UrlRequest( + url, + file_path=path, + chunk_size=102400, + on_progress=self.update_progress, + on_success=self.on_success, + on_redirect=self.redirected, + on_error=self.download_error, + ) + + def redirected(self, request, *_args): + new_url = request.resp_headers.get("location") + if new_url: + self.download_url = new_url + self.request_download_file(self.download_url, self.path_to_file) + if self.download_redirected: + self.download_redirected(request) + + def download_error(self, request, *_args): + pass + + def update_progress(self, request, current_size, total_size): + percent = current_size * 100 // max(total_size, 1) + self.label_downloading_text = self.downloading_text.format(percent) + + def on_success(self, request, result): + Clock.schedule_once(self.animation_hide, 0) + self.root_instance.remove_widget(self) + if self.download_complete: + self.download_complete(request) diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po index ebce081..80becd3 100644 --- a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po @@ -592,3 +592,6 @@ msgstr "" "Picks moves at random from a limited selection of moves and plays the best " "one. Stronger settings select the best move from a larger selection. Since " "there is no 0 kyu/dan, 3 dan = -2 kyu." + +msgid "Download Models" +msgstr Download Models" diff --git a/katrain/popups.kv b/katrain/popups.kv index 94a4365..04f1de4 100644 --- a/katrain/popups.kv +++ b/katrain/popups.kv @@ -81,13 +81,16 @@ : configfile: configfile + model_path: model_path + model_files: model_files + download_progress_box: download_progress_box DescriptionLabel: text: i18n._('katago settings') font_size: DESC_FONT_SIZE * 1.5 GridLayout: cols: 2 - rows: 3 - size_hint: 1,3 + rows: 4 + size_hint: 1,4 spacing: CP_SPACING DescriptionLabel: text: i18n._("engine:katago") @@ -96,18 +99,31 @@ LabelledPathInput: input_property: "engine/katago" hint_text: i18n._("engine:katago:hint") - DescriptionLabel: - text: i18n._("engine:model") - size_hint: 0.33, 1 - AnchorLayout: - LabelledPathInput: - input_property: "engine/model" DescriptionLabel: text: i18n._("engine:config") size_hint: 0.33, 1 AnchorLayout: LabelledPathInput: input_property: "engine/config" + DescriptionLabel: + text: i18n._("engine:model") + size_hint: 0.33, 1 + LabelledPathInput: + id: model_path + input_property: "engine/model" + on_text: root.check_models() + AnchorLayout: + size_hint: 0.33, 1 + AutoSizedRoundedRectangleButton: + text: i18n._("Download Models") + on_press: root.download_models() + size_hint_y: 0.8 + Spinner: + id: model_files + text: '' + on_text: if self.text: model_path.text = self.text + -font_size: self.height * 0.33 + -background_color: [*[c*255/88 for c in BOX_BACKGROUND_COLOR[:3]], 1] # compensate for texture BoxLayout: size_hint: 1,1 orientation: 'horizontal' @@ -178,13 +194,18 @@ LabelledFloatInput: input_property: "engine/wide_root_noise" hint_text: i18n._("engine:wide_root_noise:hint") - AnchorLayout: + BoxLayout: size_hint: 1,1.5 - AutoSizedRoundedRectangleButton: - padding_x: 15 - size_hint: None,0.5 - text: i18n._("update settings") - on_press: root.update_config(True) + BoxLayout: + orientation: 'vertical' + id: download_progress_box + AnchorLayout: + AutoSizedRoundedRectangleButton: + padding_x: 15 + size_hint: None,0.5 + text: i18n._("update settings") + on_press: root.update_config(True) + Widget: # for centering options_grid: options_grid From 6d8bd7206f95edc1a3d28b5bb61afecfc693a1cc Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 21 Jun 2020 19:25:12 +0200 Subject: [PATCH 05/77] model download featrue --- katrain/core/engine.py | 2 +- katrain/gui/popups.py | 26 +++++++++++----- katrain/gui/widgets/progress_loader.py | 3 +- .../i18n/locales/cn/LC_MESSAGES/katrain.mo | Bin 10277 -> 10396 bytes .../i18n/locales/cn/LC_MESSAGES/katrain.po | 8 +++++ .../i18n/locales/de/LC_MESSAGES/katrain.mo | Bin 12101 -> 12220 bytes .../i18n/locales/de/LC_MESSAGES/katrain.po | 8 +++++ .../i18n/locales/en/LC_MESSAGES/katrain.mo | Bin 11193 -> 11312 bytes .../i18n/locales/en/LC_MESSAGES/katrain.po | 7 +++-- .../i18n/locales/es/LC_MESSAGES/katrain.mo | Bin 12013 -> 12132 bytes .../i18n/locales/es/LC_MESSAGES/katrain.po | 8 +++++ .../i18n/locales/fr/LC_MESSAGES/katrain.mo | Bin 12712 -> 12831 bytes .../i18n/locales/fr/LC_MESSAGES/katrain.po | 8 +++++ .../i18n/locales/ko/LC_MESSAGES/katrain.mo | Bin 12943 -> 13062 bytes .../i18n/locales/ko/LC_MESSAGES/katrain.po | 8 +++++ .../i18n/locales/ru/LC_MESSAGES/katrain.mo | Bin 16308 -> 16427 bytes .../i18n/locales/ru/LC_MESSAGES/katrain.po | 8 +++++ katrain/popups.kv | 28 ++++++++++-------- 18 files changed, 91 insertions(+), 23 deletions(-) diff --git a/katrain/core/engine.py b/katrain/core/engine.py index 7e7332c..19b55d5 100644 --- a/katrain/core/engine.py +++ b/katrain/core/engine.py @@ -37,7 +37,7 @@ class KataGoEngine: self.query_counter = 0 self.katago_process = None self.base_priority = 0 - self.override_settings = {} # mainly for bot scripts to hook into + self.override_settings = {'reportAnalysisWinratesAs':'BLACK'} # mainly for bot scripts to hook into self._lock = threading.Lock() self.analysis_thread = None self.stderr_thread = None diff --git a/katrain/gui/popups.py b/katrain/gui/popups.py index 91b428e..c78e693 100644 --- a/katrain/gui/popups.py +++ b/katrain/gui/popups.py @@ -338,14 +338,18 @@ class ConfigAIPopup(QuickConfigGui): class ConfigPopup(QuickConfigGui): + def __init__(self, katrain): + super().__init__(katrain) + self.paths = [self.katrain.config("engine/model"), "katrain/models", "~/.katrain"] + + def build_and_set_properties(self, *_args): super().build_and_set_properties() def check_models(self, *args): # WIP - paths = [self.model_path.text, self.katrain.config("engine/model"), "katrain/models", "~/.katrain"] done = set() model_files = [] - for path in paths: + for path in self.paths + [self.model_path.text]: path = path.rstrip("/\\") if path.startswith("katrain"): path = path.replace("katrain", PATHS["PACKAGE"].rstrip("/\\"), 1) @@ -355,17 +359,25 @@ class ConfigPopup(QuickConfigGui): if slashpath in done or not os.path.isdir(path): continue done.add(slashpath) - model_files += [ + files = [ f.replace("/", os.path.sep).replace(PATHS["PACKAGE"], "katrain") for ftype in ["*.bin.gz", "*.txt.gz"] for f in glob.glob(slashpath + "/" + ftype) ] - self.model_files.values = model_files + if files and path not in self.paths: + self.paths.append(path) # persistent on paths with models found + model_files += files + models_available_msg = i18n._("models available").format(num=len(model_files)) + self.model_files.values = [models_available_msg] + model_files + self.model_files.text = models_available_msg MODELS = { - "20b": "https://github.com/lightvector/KataGo/releases/download/v1.4.0/g170-b20c256x2-s4384473088-d968438914.bin.gz", - "30b": "https://github.com/lightvector/KataGo/releases/download/v1.4.0/g170-b30c320x2-s3530176512-d968463914.bin.gz", - "40b": "https://github.com/lightvector/KataGo/releases/download/v1.4.0/g170-b40c256x2-s3708042240-d967973220.bin.gz", +# "pure 20b": "https://github.com/lightvector/KataGo/releases/download/v1.4.0/g170-b20c256x2-s4384473088-d968438914.bin.gz", +# "pure 30b": "https://github.com/lightvector/KataGo/releases/download/v1.4.0/g170-b30c320x2-s3530176512-d968463914.bin.gz", +# "pure 40b": "https://github.com/lightvector/KataGo/releases/download/v1.4.0/g170-b40c256x2-s3708042240-d967973220.bin.gz", + "final 20b":"https://github.com/lightvector/KataGo/releases/download/v1.4.5/g170e-b20c256x2-s5303129600-d1228401921.bin.gz", + "final 30b": "https://github.com/lightvector/KataGo/releases/download/v1.4.5/g170-b30c320x2-s4824661760-d1229536699.bin.gz", + "final 40b":"https://github.com/lightvector/KataGo/releases/download/v1.4.5/g170-b40c256x2-s5095420928-d1229425124.bin.gz" } def download_models(self, *_largs): diff --git a/katrain/gui/widgets/progress_loader.py b/katrain/gui/widgets/progress_loader.py index fa09855..fe2d6df 100644 --- a/katrain/gui/widgets/progress_loader.py +++ b/katrain/gui/widgets/progress_loader.py @@ -98,11 +98,12 @@ class ProgressLoader(BoxLayout): pass def update_progress(self, request, current_size, total_size): + if total_size < 1e4: + current_size = 0 percent = current_size * 100 // max(total_size, 1) self.label_downloading_text = self.downloading_text.format(percent) def on_success(self, request, result): - Clock.schedule_once(self.animation_hide, 0) self.root_instance.remove_widget(self) if self.download_complete: self.download_complete(request) diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo index 4e1041c768c8e3f8801503038fcff54969095026..b095193d049089650c4ec2514116b41c9bfd3717 100644 GIT binary patch delta 2802 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 PW@2g4_T11y9{&1d({rJ6p&*k@hp6B~KKRCVi z_&VQkV%VF8A3wk8{4NSt$(S|K|2vh(Cn%?&*SZ_0Q{In>coGxvM~uV^7>AcI9 z*qC6y45Z3u=JKEb3vmjTVKT19SggY^+=WTlit7J1-hv4H^n^A*DSRi9zkYi9!ITYBPy`R*0-Pn?Y4HH-hT(xuM4%1BewlCa+&YA zO->Nie+&~D-(0l~(Y&P>Q>`;msat?r@lw=EDs1~!RLY-1P54Dr|2J&AAJzX8)WE%{ zI6t8Jji65rS8PK#1-+1h3OpMX_-^Df%WU~kWX@(iY9+Pk#3tl2&v4TKuc0!yAJy+5 zYHPbuTl8%l`B%lSRA|6!)@a^ViqlZ-*{FbvxG`5#hFZyLTduX`X5=z$+_aE)Q4>9c zir<5ZH-Kq)A)fqCq6WIp)>oi1uo;!9X6tjPiMOLx-iZuh zx@`MVR0dDkcHbZm3N(!Bco{XJ5Gpm}NYq{>pa#l8^_yqQ#i)TwQ3F??ChA5)nI_a3 zcp3Hn>!^PFkoSD%plvve3VZ^&%%Cm*j(Xv5)XW{^Sp&vf(@`19MNPN}6{i?A;7ZgM zuCe7!sCadlpzpth2d%IT705r?U`zltkzUkR{A}AVq9$+!wbD>-Phtvc>z>6_?8MF3 zjVmydK^C9~)$T{9zW>j8xE1>_A4hRMCKIgym!nd-)0W%tcFJF3296`In1q{yTbG07 zl=H9zeK?L^V=4AY z^=+ukyk+&Hw(Min0=__{e$aX@jr?n`MyXJs2~+?F|B1fAICSGL*pG`@?pbtZa&z$g z;!iw|OlK=7A9oq^73D$Fxq@=%4E_O89=nYLh(lSy_|@6LGg6;T{&hH7shEXrsJ%Xd zItzWM37tXaV17fb#N-55l87@YW}^Bnv-RZ|L3tfc{trRL_1N+*B)R5!pKW*#HIYN8 zK%b%Zs2`QW^S1sHvM4iwN_hsOE2TN80q#J~o4FS?;m1&Cr2%zD{75p)G1MO_-!Km> z%3MXVZ!$=OwqzkbfF-EI*nvy17w^Py4u}T22bHOnsEJl$6naquG@%CUz%AP=UHopXn*o0?r_@%{Xcy>3PBabC56A z6rnO)hE9F|t9Xc^q6)PF59-C8s1@u%t?&cXfZeF`_dP1mU#P&>QJG1|55B(~BPg%J z5L|D|8&C_}imCek>v<@|S8Ruqs0sAr92`O}6GO&{xc8tco$VXt_ zw;n)k<%g(D4xvvo8RkKOM=%!0QGsrt0y!CtFUL&716YJg^)M>X1S%spY<(stPJ4bA zDl=>GL99np?5V72dU}gzldIOdy*f}DeJm{5?cL$2@m9L2a#z=Eb5%7qG}jlRasAgb7;DzHT-Qc#qsJZC Ikv9MO3meiRH*jRmveI(hv|>=E z;l@~IIV_~PwCQUr&6ZXe!Ytc@YPq+lxr*sx))quo-yiR6xy${WbMCqSbI$*q>pS^C zZ@m}I@O2pf4)B-5UuddoW@Y~Wy_Vw5)MsI}vk7ylZ^sPm!gPEO6R;Ok@f@b%1^4`t z^D1(AmYN(3kcHRKF@!U*6jN|D`mi1Y_z-Hqt#}>o#2MIw8t^2l-+-$RqZTlVNf<{^ z^-sZg#?BMLoqTlTY*|h7!|n6wXa3>YjQq{dVd?LUn^=Mo$h%za@o7wCMSsM zKY$sGZ{N5FBd8b0oe5Nwx&Ug$d8n1#gj`n1O)0NMO?W-3|7PT}=eeo>9@M~Zq2feP z{m!7L2Se_`MbrypsK9<6DsVQcU%soCAak^G)JpEgL|lVh*1$~zY(iylJE~tBYHK@C zTl7vU`B%dyG-$x@oFk|dPoM%#<;I*WfLg(PS6}Ap706}xank}CQ4`#Xira!3rxUaB z2u{cTH1f|F7NtR6M$PnZ)XEciTPvH63Y5c5feKNFr~(ye4eIOIfLzw%>_ol)w(}?w z+D@Pjb-zbp8ig-Wfrng&%cu!m#mUpkvh-pQm4RYZhANy@sEOC2R=y5-)i$~3J5U+i z>7MUJ#qoM5XeK96GyNE~H+`r9qNqT>xcWG1fOy{4fa$0S<{*!(7`5lOquyVM>US^d zy$47&wou@*wpfGhL%nbqHRI!`7e9A?jmpe0YQm$a367%%^s^t@x*4c?E-Kz4 zRKF6`0>h~DXLasDGZMqLp;EnjvV+-v)C7*8R{9|}<9XDUmC=}mHQ0n3uo{Q31oIh9 z&+AYVegcE&VMyP97lmT{0&he`RVtUF>S4SApTgOA2g>0s`2oK^ZJdg3%&i}9aTsrV&Jc>H3x6-JUgi)EPcGjV` zY&~iLPoh%a=IlUiK^H2{v260M0LN(=3_FB|Lnmh^$^cZT7o<*gw z)3tXai?U;=ln{>6FLnJIXYp8_68C1s6sCyNM+Gs9n-L0;lh1w_=wQ&I|Q9lyOs!`8C1M2$S zsCBO+*M+RrO?-q3{5kTmv#$RO>VjcZ=3&%X8~U6xQ5DKSCA=6FCkwUVW2jrW+V$6> z;+0{X-v3$}I$aU553@Feb>Rw&PBltHyfk${*d(canPGB!yz{Xi- zomfmghcK0_PhosAuWXpv4|s%&|*F#dz0CTnjmC#XCB4461 z{uar_zDGS1!>H@LX=ce7k6OP7Rf%Qj!3SMG5A{&4bmPTHk!@p$hDuR~dQJABGCbhM z-$n)QbmJ$Hs@pl#MuVslkDwBdoHJ$uD&aYpf%l=V+km>&b*>+JhX&VLw;Sk31@f@! zE}V?%n1@TS0&l~^sE6)nRKmkJ`g-yNt1>>+iKn4{YBNy}?Q&FN<;YzNS)&_x50&95 z)X6S82T%!JM-O_)CJM))Di?>EPeGORcH|6}i%P5vRiRq+;!CIm_hOXZ|ATJgE!4d} z>=tyQ66!(Sf}fB#$_7yHZ#Z?;iEl=2un@`7mZ1_VMkQQ{;aG!_xCM29I-JV>)~#WxfElelaS6Le%;oYU3L8VgvFL*dFIT)UDf(O6+S4Mbqe| zq0BB|3|@5otEfOjsNabjsNaVPw~Y0-p#n9b-il^a0w17mMHi}~7cmF>(Xs-CCDqTa z53Eft4OSMl=f-^Enc@#_4wM87{S5hwO3IVhRBYN5477WEABV?|4ir`vt}iKEQ&QC4 uG0`*0_rSmF9~xcX-kCNOkyI0?D6LKYZ{=95^zw}4;$TI<-`<)Y7x52&03*f# delta 2667 zcmYM!e`wTo9LMpuyW0;hx0|=En)}^#>g-|-(wc1*wI)n93z>JLAEe2gmDW;H!}@AJ z%+;XSSc6zZ(y||OIWCx*)eLfSW`)M29}N5>kkw2m!eL_1*WCviw|jhkf8L+>=li{b z@xw81D&+4s{`T=-%6~LdHM9D_|IVk_K>d1Lbo(7?_oZEhS~TH=HV#};;*j% z59bx+<5}J{=>>{0mw{2d7MJ5Zd;tCU2o~XEs10}E0_?#;+>hGuC~DkES3ie3z(t&k z87!*t^U%lsRz-n6i=YP7B9Yl0sFO6I7H)Oz52MC)I-fz!--#Nx7j=*U*FS`O>{Gr{ z0iwpA#1Q-2S=TX*nmFssrlQmpp-vn@o#b}pW6gXi<#ANN8&Tu8A|HF1FOBa-ZTvoJ zodjy!arD$N={kNxO}KA$Fx=P+9*D?lw&%9j?p8TAk~q84gLy&ap7kL_^|pyq$* z97UG4BdCWu=~2j{@I7kbNjG2y70?w-J*^x|6RS`es6}O{(bX#8?)H`t%j^kp? zXSZ^!Lj@d1{nD<*MSA~VpiqM!;BuTqop3%6gxV`HiuYqNK9B5e1E{O}1e@_7-h-EM z0`I(zYrrg`(|XOw($2Vy#FY#6Kbrze-_$-g3foCXE*94g{h zkXWo2^-LT^P8sH?c@>i!$k4_hv(y&Orhg;ANPN4+Jj9tEXtjq6y4 zT6n8#S3g;|*HIe{p;A183j7OHpr^1J&!fhNBI!GhqUx)WF5Bely{P%#FooMF9L2@> zH`ZYpPe?7sP!C%-D$sXOujNOmjEtdPt7E92*3+nm^#Urel11sKe;KOYh6?Cu` z2i4C0wt)iKvNv%qj;RBWp#qpjO}K>G*uNxw!b0Ssw=!o1>dLB6fvv`DjH3d36a(1d z>Rs59L&G)-`eE3Gsow)v58jksD1v$mmY@P?KwUu#DwQ30A8tvs2fpVsn$34dr# PL82%!<4aUk2Yvqmi@xp{ diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po index 80becd3..2cf712e 100644 --- a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po @@ -593,5 +593,8 @@ msgstr "" "one. Stronger settings select the best move from a larger selection. Since " "there is no 0 kyu/dan, 3 dan = -2 kyu." -msgid "Download Models" -msgstr Download Models" +msgid "download models button" +msgstr "Download Models" + +msgid "models available" +msgstr "{num} available model(s) found" diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo index 4747b030ae711bb8a627dfdcbb5804575a4a9b6a..6ceda6c3f52b20782e62b0308b139b6e4943ab83 100644 GIT binary patch delta 2802 zcmYM#eN5F=9LMo5Pl6zi2|_5M@*qCtrY@aiV}>@tHElFPu%L^EgUgJEB1U)1X)>FZ zf*TZ08WpB#byK6#O2t+=nDOD9EUIlhcOoaa{XhX z0^=qiw|q8>3k6t!(O8H{Sb}44J%-{7n1~xt<2&$P+=pZFOVorvqQ>2D?I40^0dY7Q zC!@yCz!2uQJT4e(`EI}pBr{uuTFDwzV6VI1f(o?RxgGWV>!@+PsD&JM{r$+tzU6Bq zLDcvgn85t@hwF&sDLt6t%tn=N9%{w;sFf7E{&lF5KZi=V6*a!i_4lC0e}J0!1S-yV zsByodPaU^iM;Hw~kc*gAv$-eC$QOG{LK=3U;H$?Llqr zLDUwVilP3x@iR9x;h)Y(o>qxdQT>^yfVq5;s}-VFQsUY*uHB4$Y%5<{$Szc(@1f!! zLB%_bsW=o%{qNx-h+gKlL}vyn<2k4bJcwH1GUQ{A@})o?)M08y#d!twmh>PWJK{Wz zdVbJ(4avc7`nb^P4kF!AI0_Xw8Z{sTmDpU=M2p@1VpIj5MpdfWxe=B4Hq^>rL#D7^ z*WZV#;BnXQJHv$nT|y1KiApG#o60yGwO4VdiLy}RX1jJCYN7(v#KovYJxC~PLY;vv zsONW}#&sdj`D~BtID`uPIr6bHu6+gdz%5kfLDX3j#yZENDwKmtcs?pl9%{mss4XmY z?X{?Q>oHF6e+w5{;Z{_jo{OTVO@SdNGB z2~1&`%We(yUyn`1eu(v^G z18HW5Xb(>0{{q@O)6IUO{r)7jg!cByX8pAPo?^Be`>DGUSd$q@s6LbBD#MN3n1*er zgg!%MIN-d7s?c3zJ1sja(4ULUVUHlk!Ja|I+l1PRcGMy4boO8v?fs}qedyyt51zzP zcmY+q%kKVf&fx68AxuJTSr#hr1E@etUH{{#(_V!X!Rqlo+=)6XCvgt`f)vqw|U9?Yd2A)TD%R(tP z3HdC83uT&zD&H6P6Rl<)-_-oXPE}~BTT@1&$?3)tGMJ2in zRpAvQZT5dP7g5}(L`}FJ^*}Rff>uz3hZebTjlT9@4MUC%6ZNV{AMFx-; z#fDJteH15fDUR1k%=4C4H$7kFU7KE0+gRx@i2OJ-$y58Rx4O36!!1u`bzOQzLw$X% z*B=`DQE<%2jq=9ws_OEJ>PmlKd~sszL;sFnJ~G}Pm;G31TC=yIrX~Hqk%3rK>vGc9 O)HZlM{>tf_L;eAb04Cf3 delta 2668 zcmYM#e@sTE`Eb~ zc-Hm*=A1$f&vLVq0gBMizz|-Gw_*;iMjy6fA-1C?+=er8FBae+YQj%Y<3?S347Gqu zI1N(>s_{9P%KTQ&1$`Do4G1HdSp>C`22|iCcYgzF+$Lua>iO-canGX`GT{0TBZs}m zDUl#*{3r&P-+pu*7f}yRIx}dfbcLuD2T?1jK@MBNNhM!{O1KL({t@J`U7R$&4>j>y zs5mjyxG&IC$7$Db0rkM&sK8ltDsVAs+-%ogjO1vyp;odAGqD9Z?0!y~U@NMEJ5l5I zptiOjwM9d@)L%C~rU6eiwQmZb;FQ56WIDpc=mLM6Tiwekm$M{TR? ze;QT6y{`W?R2*-R3uQ8b%JevDZ@xlJ5Jv_2#kD6<6QuIACd@}ASb}s}7`5kjqMmO= zjcZ0d*XHhbBXK=@oC^-yle}TCqaJt{mGLm@!7rVsP?Z@&C432$;3R6oEcQcNSAc4l zq2kR$ja!UbU<2y?v-PgyAtZ)vN0s{J!~nAcs05CnR{Ak+#_wFamYe$JjA9!;j2rQL zti&+0-Gm!a3GZ_4UaZphf0T=ivM8W4eaGLX1%3m zAJQHuqe`@!W|{p=yQ`cnp}pcp{^Oy2titRdK1JP?Kxr_UPzaS^EzZZ4L6)zK9^-~G z>~$VQl_-X6qn&s6|3v1njM>TWem*K-9cn99pI4baERG@!RZ~62&{3c)tF2p+2JJO9e^O4RIZo`hrOiz) z*em5?IXA*shfiPxhfx79qgEK4m&|w(YT^j0v<;~Kdy(C zW>f!6eg9QlOyfp1>N~w16QxWJuqdhm>rh+NgIdwESc7k32A)MFG>+-`JF2piuAR<) zD!v~zUlIEC{a0|I3BssA%Te#dJ*X|{K<(M1ScNa3RyvH@+aE9me?miUvkBi7k0q#JRz7bWCR^&slZq(=d3f_f>t72=j bMt!lRdB;*>zCd$ftZPoQFLrLhbE*FU;h6J7 diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po index 6dd4864..d97391f 100644 --- a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po @@ -594,3 +594,11 @@ msgstr "" msgid "extra analysis" msgstr "Realizar análisis adicional a {visits} visitas" + +#. TODO +msgid "models available" +msgstr "{num} available model(s) found" + +#. TODO +msgid "download models button" +msgstr "Download Models" diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo index 3e56b1cd8d3eca5a4f8885d2fa8b924457d28878..d5c73b35430e840e625659be1bec133961fb4c35 100644 GIT binary patch delta 2801 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 SUvS&hX#3n+UvzkWLEL}#eDf0k diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po index 33d38be..282b0a2 100644 --- a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po @@ -633,3 +633,11 @@ msgstr "" "Plus kyu_rank (-3~18) est bas, plus l'échantillon sera large et le niveau de jeu élevé. \n" "Une valeur positive équivaudra à un niveau en kyu, 0 à 1er dan, -1 à 2e dan, etc. \n" "(Niveaux estimés sur OGS avec le réseau neuronal utilisé PAR DÉFAUT)" + +#. TODO +msgid "models available" +msgstr "{num} available model(s) found" + +#. TODO +msgid "download models button" +msgstr "Download Models" diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo index 04acd92276b4860ba76d5f4365e21b1f3ebaef64..e564fc03beb6e6e2a74c2b5a9e7f379d5a54bec7 100644 GIT binary patch delta 2798 zcmYM#4@{L+7{~F0TvQOafT*Ytmp_WAAxasffmCP&ZU(X#;_})BqTpT-A&KE#Sx&=g zU~aY2a-$8JE$lAKie)8htwtkj)+*dwSj${lYO^ux`@8p4?)84&^PclQ=Q+E~}Ue^*DSWlU|<|HgB>nRXKTta~t(_EAj04=@hD#A!Hzv3LocIBEN% zXN1P3AdmbehZ_Ybz!)sY*;s?o*oqF^iL-DQYW(v!507CYevF#%JJh&~wjD+gEg%jf zF&#C2DTXt@DdvW;ro;}o56R4ILan4871(Q^ccTLBvG$?fe-Slq0JV^}Z2t&yna}wN zCWsn;5fhl-{AoL)cuOzNwPvA8cPnbeC8(8D+x~}9CEtNccrR-Fv$nq%n;*Xu|8(DBf0y=cD=;paQPuhg?lLY9%$c-E7-k$Yu8PqlFwnB|3zP zKa7euit};8N&RPX6Gks{n_1RORK|Iz3KXGMcrS99N`4f`gE~xIs5sA{zLI|AGQ-wU z)cfbGKOs4o%YJTjy2D6!Do#ZOjzJB`L?w0$YNEUB^J-KD9zs>B%lZ^5@dK!pzkp0( z25kRHR0Th>{r)j-6lfeZ@G>f)DLhoh(@=XAhni?1YFw^u7o#RBLrq+bO4NgdGM%V1 z(1UvaAZpxUhiyLJ9qhi)i1-qW)CLWYKbA z5vsifb$WN8R`4`3hdF{u@DwWHPf=U)m3ba&7xI+b%&>qTFw9Hro!b zZMUQLbT_JWFCkSouOa8ge1Zym4s~cRpbqI()c6QapvKQcieZ-7_D0lJx1hG(|0*|B z*}RWb*L;uqJSOpGoW3MXohf#rVpeD>%8rsw4sCJ>X9JP=dl`XNZKm{y79l~|?c^T^TS0D%4G@`a>7m|~C1+{f!7=sh2xPPFw z_%Dpq_n(jx+S6pzo@Aj0it4g z0%aJ96&U>gzm^;AiO&ugMD5KG-i{+!j3ze}_)aXPS&312*gAkpB!F7+2&!UdtY@*7 z_P3}+@|RP8-4t;n*P;Thw^pM9J&5{DTd@!Kp(>HOBD9h`RR0~Q@ztmb)}t1(6Sv_( zG{s(bQ|Dui-Ue5*ucIzd7WIx}w#WCVx5?-B@W@lw)aKgSzJ0sT8*n(^o)Q~;;O=lY xHo3Pp)dfz*Uzy{)``_{FgX06ovU?r#x?TT`3dLE}mhGzdwR=5*{G9t8{{Vnv8!-R? delta 2667 zcmYM#e@si`ZekC@YD7i|sv_VEQWFyOh(7D>w+GKM(qaQ2^ zr@0ZEwOY)AKh$!UWujZtm6%Ypm`%9})9^E#gWZ^j-(w2?h^cth z)sH!+k;}7`+5P|vFqwvhcrUKTBzy(~xD7-2GHSv`yboJ(9(JN8Jd0}A=gLvk0w!=4 z#xbbcCt*DETMiG@SuUzU2@;vDL9Jv1YT!!u{&`fp9nL+d{`*nw-bF2>-PM1NT=pfm zSb(VZeVE4lcEwfPM0K2UCQ?x9LZ}tzqE@mBxoi_RrF<(Y;GL-UuOpYe%}wnOqb5Fz z8YhBkcL6ZAQ)0 zj+uA@)A3R&`DYFr<%PVBiu5jOg#wFxvbgQj_Tj# zJc$f#J*Y!{$>ZT39tKbY54r}oQ2|Y3?6k5hb<9C!pahkna%Uwf@U5to*CAbPx2t~> zmBCh5e*`s-*U5t-=|M&MEoyHrq9zzc4Rphmr%)5b(_0hHK?RtFR9OjX&)1>)KZR5* z8T^9M=mpg;@VDj%)B;AZ6erMYa*yzQ}An<)H`7#;DIG-))$**bk@!mr(v1Nsj%73-O=Di6!1$p+1 z|6s$Y38JXOH}2m5g?s>Z2RX1dmuR&$1<0IMf!eZ0OvVGKOnrpf+76tBJs8CAP+Kt& zt7rdz=YjKOlbD1V#r}Y~IGb`Q>TImX%~*{ZpbL}nG^$@OYU0bN2}e-BC)ZG0G3n}; zKjd#oDXwOItK{J^Y(Wj&ha2%KD&o9lz9pzY%1{$mqB2$Md=YCY*P#MA=e+0~K#e!( zjADfb8skCVU8t3uMb%$GwU44wJB~VBiObFQU{<6i*dK_LrJjn51k(PP PA4$kR6o_;eR|Wn97|R2+ diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po index fdbdfe6..9a215c3 100644 --- a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po @@ -571,3 +571,11 @@ msgstr "모든 후보수를 균일하게 {visits} visits으로 분석합니다." msgid "extra analysis" msgstr "{visits} visits까지 추가적으로 분석하고 있습니다." + +#. TODO +msgid "models available" +msgstr "{num} available model(s) found" + +#. TODO +msgid "download models button" +msgstr "Download Models" diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo index cbbdadefa4c65451e905be8b9c8ffb457b8159e8..bf7e5f2258b472c0e4093a53d0162e9faef1b85f 100644 GIT binary patch delta 2807 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 sL Date: Mon, 22 Jun 2020 23:06:30 +0200 Subject: [PATCH 06/77] rank graph --- katrain/__main__.py | 4 +- katrain/core/game_node.py | 55 +++--- katrain/gui.kv | 23 ++- katrain/gui/kivyutils.py | 43 +++-- katrain/gui/widgets/__init__.py | 6 + katrain/gui/widgets/graph.py | 170 ++++++++++++------ .../i18n/locales/cn/LC_MESSAGES/katrain.mo | Bin 10474 -> 10517 bytes .../i18n/locales/cn/LC_MESSAGES/katrain.po | 4 + .../i18n/locales/de/LC_MESSAGES/katrain.mo | Bin 12298 -> 12341 bytes .../i18n/locales/de/LC_MESSAGES/katrain.po | 4 + .../i18n/locales/en/LC_MESSAGES/katrain.mo | Bin 11390 -> 11433 bytes .../i18n/locales/en/LC_MESSAGES/katrain.po | 3 + .../i18n/locales/es/LC_MESSAGES/katrain.mo | Bin 12196 -> 12239 bytes .../i18n/locales/es/LC_MESSAGES/katrain.po | 4 + .../i18n/locales/fr/LC_MESSAGES/katrain.mo | Bin 12909 -> 12952 bytes .../i18n/locales/fr/LC_MESSAGES/katrain.po | 4 + .../i18n/locales/ko/LC_MESSAGES/katrain.mo | Bin 13140 -> 13183 bytes .../i18n/locales/ko/LC_MESSAGES/katrain.po | 4 + .../i18n/locales/ru/LC_MESSAGES/katrain.mo | Bin 16505 -> 16548 bytes .../i18n/locales/ru/LC_MESSAGES/katrain.po | 4 + 20 files changed, 219 insertions(+), 109 deletions(-) diff --git a/katrain/__main__.py b/katrain/__main__.py index f95a893..9437e37 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -67,9 +67,7 @@ from katrain.core.sgf_parser import Move, ParseError from katrain.gui.kivyutils import * from katrain.gui.popups import ConfigPopup, LoadSGFPopup, NewGamePopup, ConfigAIPopup from katrain.gui.style import ENGINE_BUSY_COL, ENGINE_DOWN_COL, ENGINE_READY_COL, LIGHTGREY -from katrain.gui.widgets.graph import ScoreGraph -from katrain.gui.widgets.movetree import MoveTree -from katrain.gui.widgets.filebrowser import I18NFileBrowser +from katrain.gui.widgets import * from katrain.gui.badukpan import AnalysisControls, BadukPanControls, BadukPanWidget from katrain.gui.controlspanel import ControlsPanel diff --git a/katrain/core/game_node.py b/katrain/core/game_node.py index f3eae5b..c0b3ef1 100644 --- a/katrain/core/game_node.py +++ b/katrain/core/game_node.py @@ -136,6 +136,15 @@ class GameNode(SGFNode): if win_rate is not None: return f"{'B' if win_rate > 0.5 else 'W'} {max(win_rate,1-win_rate):.1%}" + def move_policy_stats(self) -> Tuple[Optional[int], float, List]: + single_move = self.move + if single_move and self.parent: + policy_ranking = self.parent.policy_ranking + for ix, (p, m) in enumerate(policy_ranking): + if m == single_move: + return ix + 1, p, policy_ranking + return None, 0.0, [] + def make_pv(self, player, pv, interactive): pvtext = f"{player}{' '.join(pv)}" if interactive: @@ -160,44 +169,24 @@ class GameNode(SGFNode): points_lost = self.points_lost if sgf and points_lost > 0.5: text += i18n._("Info:point loss").format(points_lost=points_lost) + "\n" - text += ( - i18n._("Info:top move").format( - top_move=previous_top_move["move"], - score=self.format_score(previous_top_move["scoreLead"]), - ) - + "\n" - ) + top_move = previous_top_move["move"] + score = self.format_score(previous_top_move["scoreLead"]) + text += i18n._("Info:top move").format(top_move=top_move, score=score,) + "\n" else: text += i18n._("Info:best move") + "\n" if previous_top_move.get("pv") and (sgf or details): - text += ( - i18n._("Info:PV").format( - pv=self.make_pv(single_move.player, previous_top_move["pv"], interactive) - ) - + "\n" - ) + pv = self.make_pv(single_move.player, previous_top_move["pv"], interactive) + text += i18n._("Info:PV").format(pv=pv) + "\n" if sgf or details or teach: - policy_ranking = self.parent.policy_ranking - currmove_policy_with_ix = [ - (ix + 1, p) - for (p, m), ix in zip(policy_ranking, range(len(policy_ranking))) - if m == single_move - ] - if currmove_policy_with_ix: - text += ( - i18n._("Info:policy rank").format( - rank=currmove_policy_with_ix[0][0], probability=currmove_policy_with_ix[0][1] - ) - + "\n" - ) - if not currmove_policy_with_ix or currmove_policy_with_ix[0] != 1 and (sgf or details): - text += ( - i18n._("Info:policy best").format( - move=policy_ranking[0][1].gtp(), probability=policy_ranking[0][0] - ) - + "\n" - ) + currmove_pol_rank, currmove_pol_prob, policy_ranking = self.move_policy_stats() + if currmove_pol_rank is not None: + policy_rank_msg = i18n._("Info:policy rank") + text += policy_rank_msg.format(rank=currmove_pol_rank, probability=currmove_pol_prob) + "\n" + if currmove_pol_rank is None or currmove_pol_rank != 1 and (sgf or details): + policy_best_msg = i18n._("Info:policy best") + pol_move, pol_prob = policy_ranking[0][1].gtp(), policy_ranking[0][0] + text += policy_best_msg.format(move=pol_move, probability=pol_prob) + "\n" if self.auto_undo and sgf: text += i18n._("Info:teaching undo") + "\n" top_pv = self.analysis_ready and self.candidate_moves[0].get("pv") diff --git a/katrain/gui.kv b/katrain/gui.kv index 3990b7b..0669ffe 100644 --- a/katrain/gui.kv +++ b/katrain/gui.kv @@ -498,7 +498,7 @@ spacing: CP_SPACING player_box_height: max(50, 0.075 * root.height) timer_box_height: max(50, 0.1 * root.height) - graph_box_height: max(100, 0.2 * root.height) + graph_height: max(75, 0.2 * root.height - 25) stats_box_height: max(50, 0.15 * root.height) tab_option_height: max(15,root.height / 30) BoxLayout: # -- Players @@ -540,15 +540,26 @@ opacity: 0 if self.parent.mode == MODE_PLAY else 1 CollapsablePanel: id: graph_panel - height_open: root.graph_box_height + height_open: self.header.height + graph.height + rank_graph.height closed_label: 'closedlabel:scoregraph' - options: ['score','winrate'] - option_colors: [SCORE_COLOR,WINRATE_COLOR] - option_active: [True,False] + options: ['score','winrate','rank_est'] + option_colors: [SCORE_COLOR,WINRATE_COLOR,POINTLOSS_COLOR] + option_active: [True,False,False] options_height: root.tab_option_height - on_option_state: graph.show_graphs(args[1]) + on_option_state: + graph.show_graphs(args[1]) + graph.hidden = not (args[1]['score'] or args[1]['winrate']) + rank_graph.hidden = not args[1]['rank_est'] ScoreGraph: id: graph + opacity: 0 if self.hidden else 1 + size_hint_y: None + height: 0.0001 if self.hidden else root.graph_height + RankGraph: + id: rank_graph + opacity: 0 if self.hidden else 1 + size_hint_y: None + height: 0.0001 if self.hidden else root.graph_height CollapsablePanel: id: stats_panel height_open: root.stats_box_height diff --git a/katrain/gui/kivyutils.py b/katrain/gui/kivyutils.py index b106bc2..9d5b9a8 100644 --- a/katrain/gui/kivyutils.py +++ b/katrain/gui/kivyutils.py @@ -360,6 +360,8 @@ class CollapsablePanel(MDBoxLayout): option_active = ListProperty([]) option_colors = ListProperty([]) + contents = ListProperty([]) + closed_label = StringProperty("Closed Panel") size_hint_y_open = NumericProperty(1) @@ -370,7 +372,7 @@ class CollapsablePanel(MDBoxLayout): open_icon = "img/Next-5.png" def __init__(self, **kwargs): - self.header, self.contents, self.open_close_button = None, None, None + self.open_close_button, self.header = None, None self.option_buttons = [] super().__init__(**kwargs) self.orientation = "vertical" @@ -381,10 +383,26 @@ class CollapsablePanel(MDBoxLayout): option_active=self.build_options, options_spacing=self.build_options, ) - self.bind(state=self.build, size_hint_y_open=self.build, height_open=self.build) + self.bind(state=self._on_state,size_hint_y_open=self._on_size, height_open=self._on_size) MDApp.get_running_app().bind(language=lambda *_: Clock.schedule_once(self.build_options, 0)) self.build_options() + def _on_state(self,*_args): + self.build() + self.trigger_select(ix=None) + + def _on_size(self,*_args): + height, size_hint_y = 1, None + if self.state == "open" and self.contents: + if self.height_open: + height = self.height_open + else: + size_hint_y = self.size_hint_y_open + else: + height = self.header.height + self.height, self.size_hint_y = height, size_hint_y + + @property def option_state(self): return {option: active for option, active in zip(self.options, self.option_active)} @@ -396,7 +414,7 @@ class CollapsablePanel(MDBoxLayout): button.state = "down" if state_dict[option] else "normal" self.trigger_select(ix=None) - def build_options(self, *args, **kwargs): + def build_options(self, *args): self.header = CollapsablePanelHeader( height=self.options_height, size_hint_y=None, spacing=self.options_spacing, padding=[1, 0, 0, 0] ) @@ -422,13 +440,12 @@ class CollapsablePanel(MDBoxLayout): self.bind(state=lambda *_args: self.open_close_button.setter("icon")(None, self.open_close_icon())) self.build() - def build(self, *args, **kwargs): + def build(self, *args): self.header.clear_widgets() if self.state == "open": for button in self.option_buttons: self.header.add_widget(button) self.header.add_widget(Label()) # spacer - self.trigger_select(ix=None) else: self.header.add_widget( Label( @@ -439,24 +456,16 @@ class CollapsablePanel(MDBoxLayout): super().clear_widgets() super().add_widget(self.header) - height, size_hint_y = 1, None if self.state == "open" and self.contents: - super().add_widget(self.contents) - if self.height_open: - height = self.height_open - else: - size_hint_y = self.size_hint_y_open - else: - height = self.header.height - self.height, self.size_hint_y = height, size_hint_y + for w in self.contents: + super().add_widget(w) + self._on_size() def open_close_icon(self): return self.open_icon if self.state == "open" else self.close_icon def add_widget(self, widget, index=0, **_kwargs): - if self.contents: - raise ValueError("CollapsablePanel can only have one child") - self.contents = widget + self.contents.append(widget) self.build() def set_state(self, state="toggle"): diff --git a/katrain/gui/widgets/__init__.py b/katrain/gui/widgets/__init__.py index e69de29..f5ccb45 100644 --- a/katrain/gui/widgets/__init__.py +++ b/katrain/gui/widgets/__init__.py @@ -0,0 +1,6 @@ +from katrain.gui.widgets.graph import ScoreGraph, RankGraph +from katrain.gui.widgets.movetree import MoveTree +from katrain.gui.widgets.filebrowser import I18NFileBrowser +from katrain.gui.widgets.selection_slider import SelectionSlider + +__all__ = ["ScoreGraph", "RankGraph", "MoveTree", "I18NFileBrowser", "SelectionSlider"] diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index a16b12e..5fddc40 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -1,22 +1,64 @@ import math from kivy.lang import Builder -from kivy.properties import BooleanProperty, ListProperty, NumericProperty, Clock +from kivy.properties import BooleanProperty, ListProperty, NumericProperty, Clock, StringProperty from kivymd.app import MDApp +from katrain.core.lang import i18n from katrain.gui.kivyutils import BackgroundMixin import threading -class ScoreGraph(BackgroundMixin): - whalf = NumericProperty(0) - bhalf = NumericProperty(0) - mid = NumericProperty(0) +class Graph(BackgroundMixin): + marker_font_size = NumericProperty(0) + background_image = StringProperty("img/graph_bg.png") + nodes = ListProperty([]) + hidden = BooleanProperty(False) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self._lock = threading.Lock() + self.bind(pos=self.update_graph, size=self.update_graph) + + def initialize_from_game(self, root): + self.nodes = [root] + node = root + while node.children: + node = node.ordered_children[0] + self.nodes.append(node) + self.highlighted_index = 0 + + def update_graph(self, *args): + pass + + def update_value(self, node): + with self._lock: + self.highlighted_index = index = node.depth + self.nodes.extend([None] * max(0, index - (len(self.nodes) - 1))) + self.nodes[index] = node + if index > 1 and node.parent: # sometimes things go so fast + backfill, bfnode = index - 1, node.parent + while bfnode is not None and self.nodes[backfill] != bfnode: + self.nodes[backfill] = bfnode + backfill -= 1 + bfnode = bfnode.parent + + if index + 1 < len(self.nodes) and ( + node is None or not node.children or self.nodes[index + 1] != node.ordered_children[0] + ): + self.nodes = self.nodes[: index + 1] # on branch switching, don't show history from other branch + if index == len(self.nodes) - 1: # possibly just switched branch or the line above triggered + while node.children: # add children back + node = node.ordered_children[0] + self.nodes.append(node) + Clock.schedule_once(self.update_graph, 0) + + +class ScoreGraph(Graph): show_score = BooleanProperty(True) show_winrate = BooleanProperty(True) - nodes = ListProperty([]) score_points = ListProperty([]) winrate_points = ListProperty([]) @@ -30,11 +72,6 @@ class ScoreGraph(BackgroundMixin): navigate_move = ListProperty([None, 0, 0, 0]) - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._lock = threading.Lock() - self.bind(pos=self.update_graph, size=self.update_graph) - def on_touch_down(self, touch): if self.collide_point(*touch.pos): ix, _ = min(enumerate(self.score_points[::2]), key=lambda ix_v: abs(ix_v[1] - touch.x)) @@ -58,14 +95,6 @@ class ScoreGraph(BackgroundMixin): katrain.update_state() self.navigate_move = [None, 0, 0, 0] - def initialize_from_game(self, root): - self.nodes = [root] - node = root - while node.children: - node = node.ordered_children[0] - self.nodes.append(node) - self.highlighted_index = 0 - def show_graphs(self, keys): self.show_score = keys["score"] self.show_winrate = keys["winrate"] @@ -124,27 +153,30 @@ class ScoreGraph(BackgroundMixin): ) self.winrate_dot_pos = winrate_dot_point - def update_value(self, node): - with self._lock: - self.highlighted_index = index = node.depth - self.nodes.extend([None] * max(0, index - (len(self.nodes) - 1))) - self.nodes[index] = node - if index > 1 and node.parent: # sometimes things go so fast - backfill, bfnode = index - 1, node.parent - while bfnode is not None and self.nodes[backfill] != bfnode: - self.nodes[backfill] = bfnode - backfill -= 1 - bfnode = bfnode.parent - if index + 1 < len(self.nodes) and ( - node is None or not node.children or self.nodes[index + 1] != node.ordered_children[0] - ): - self.nodes = self.nodes[: index + 1] # on branch switching, don't show history from other branch - if index == len(self.nodes) - 1: # possibly just switched branch or the line above triggered - while node.children: # add children back - node = node.ordered_children[0] - self.nodes.append(node) - Clock.schedule_once(self.update_graph, 0) +class RankGraph(Graph): + black_rank_points = ListProperty([]) + white_rank_points = ListProperty([]) + rank_scale = NumericProperty(5) + rank_mid = NumericProperty(5) + + @staticmethod + def rank_label(rank): + if rank <= 0: + return f"{1-rank}{i18n._('strength:dan')}" + else: + return f"{rank}{i18n._('strength:dan')}" + + def update_graph(self, *args): + nodes = self.nodes + if nodes: + score_values = [n.score if n and n.score else math.nan for n in nodes] + score_nn_values = [n.score for n in nodes if n and n.score] + score_values_range = min(score_nn_values or [0]), max(score_nn_values or [0]) + + self.ids.mid_marker.text = self.rank_label(5) + self.ids.bottom_marker.text = self.rank_label(10) + self.ids.top_marker.text = self.rank_label(-2) Builder.load_string( @@ -152,16 +184,17 @@ Builder.load_string( #:set GRAPH_CENTER_COLOR [0.5,0.5,0.5] #:set GRAPH_DOT_COLOR [0.85, 0.3, 0.3,1] +#:import LIGHTER_BACKGROUND_COLOR katrain.gui.style.LIGHTER_BACKGROUND_COLOR #:import BOX_BACKGROUND_COLOR katrain.gui.style.BOX_BACKGROUND_COLOR #:import SCORE_COLOR katrain.gui.style.SCORE_COLOR #:import WINRATE_COLOR katrain.gui.style.WINRATE_COLOR +#:import BLACK katrain.gui.style.BLACK +#:import WHITE katrain.gui.style.WHITE +#:import YELLOW katrain.gui.style.YELLOW -: + +: background_color: BOX_BACKGROUND_COLOR - whalf: self.y + self.height*0.25 - bhalf: self.y + self.height*0.75 - mid: self.y + self.height*0.5 - right_edge: self.x + self.width marker_font_size: 0.1 * self.height canvas: Color: @@ -169,7 +202,11 @@ Builder.load_string( Rectangle: pos: self.pos size: self.size - source: 'img/graph_bg.png' + source: root.background_image + + + +: canvas.after: Color: rgba: SCORE_COLOR @@ -202,26 +239,26 @@ Builder.load_string( GraphMarkerLabel: font_size: root.marker_font_size color: SCORE_COLOR - pos: root.right_edge - self.width-1, root.pos[1]+root.height - self.font_size - 1 + pos: root.x + root.width - self.width-1, root.pos[1]+root.height - self.font_size - 1 text: 'B+{}'.format(root.score_scale) opacity: int(root.show_score) GraphMarkerLabel: font_size: root.marker_font_size color: SCORE_COLOR - pos: root.right_edge - self.width-1, root.mid - self.height/2 + 2 + pos: root.x + root.width - self.width-1, root.y + root.height*0.5 - self.height/2 + 2 text: i18n._('Jigo') opacity: int(root.show_score) GraphMarkerLabel: font_size: root.marker_font_size color: SCORE_COLOR - pos: root.right_edge - self.width-1, root.pos[1] + pos: root.x + root.width - self.width-1, root.pos[1] text: 'W+' + str(int(root.score_scale)) opacity: int(root.show_score) # wr ticks GraphMarkerLabel: font_size: root.marker_font_size color: WINRATE_COLOR - pos: root.pos[0]+1, root.pos[1]+root.height - self.font_size - 1 + pos: root.pos[0]+1, root.pos[1] + root.height - self.font_size - 1 text: "{}%".format(50 + root.winrate_scale) opacity: int(root.show_winrate) GraphMarkerLabel: @@ -229,6 +266,39 @@ Builder.load_string( color: WINRATE_COLOR pos:root.pos[0]+1, root.pos[1] text: "{}%".format(50 - root.winrate_scale) - opacity: int(root.show_winrate) + opacity: int(root.show_winrate) + +: + background_color: LIGHTER_BACKGROUND_COLOR + canvas.after: + Color: + rgba: WHITE + Line: + points: root.white_rank_points + width: 1.1 + Color: + rgba: BLACK + Line: + points: root.black_rank_points + width: 1.1 + # rank ticks + GraphMarkerLabel: + id: mid_marker + font_size: root.marker_font_size + color: YELLOW + pos: root.x + root.width - self.width-1, root.y + root.height*0.5 - self.height/2 + 2 + text: '?' + i18n._('strength:kyu') + GraphMarkerLabel: + id: top_marker + font_size: root.marker_font_size + color: YELLOW + pos: root.x + root.width - self.width-1, root.pos[1]+root.height - self.font_size - 1 + text: '?' + i18n._('strength:kyu') + GraphMarkerLabel: + id: bottom_marker + font_size: root.marker_font_size + color: YELLOW + pos: root.x + root.width - self.width-1, root.pos[1] + text: '?' + i18n._('strength:kyu') """ ) diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo index 4b3f12524f98017ea84e528da2ddbf33a25d0a0d..98569aa4d781ed8d7cae4eb2e7dc46eab0cce58d 100644 GIT binary patch delta 2745 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 diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po index 13896bd..2a9b66c 100644 --- a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po @@ -568,3 +568,7 @@ msgstr "Download Models" #. TODO msgid "All models downloaded" msgstr "All available models already downloaded" + +#. TODO +msgid "tab:rank_est" +msgstr "Rank Estimate" diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo index 4418961d97be6f65f733adf29084e01b222e9b2b..cfda43fa2f713ddc1f12163a13e8b3ee3d23a718 100644 GIT binary patch delta 2745 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 AssI20 delta 2707 zcmYM#e@vBC9LMpmbTPOWP<|-Nk3dYpE+*9?2xk+tIm+RvFmsvO8gtT8`y-mggSGjG zBUe-AaxTY=%DLejWYQ{T{vfHzhOVNpUl=V4UCl;TSnm(_yX799*E!EQ&pF@oJ zA!-4on20Md!veEf3ODhf9(mU`dxz&xf$c!8qy;t5UVnZFHBg6p0`>mKsD9^B3%TU& zzaW?W!EG`?8Y9easT8!5TvS5|a@l?EN>u9BqWWz{t)$7@Uqz+#4OGBKQT^Zd_C8eq zFHz%rT=ot(jsG4h zgT1JJ{iv<|I)(gekAC+D3G~*4d2R_R#Y<5yJb+xbksHC=HdMgPUf<*OHsrD{Zd%A` zRG^=u#vewFcWoN^&!!OJX*w1nu~>y$jf!{;Dg#fUR=5SZ?0Ie)Xcy|^YeS859Cb?i zkjsYMYpD0*iAtsf6bQyLQD1iz;}6jH7c;jP!nzP=S`>#>_%m(%{_t&{9V+_ zPoe@j@9p2BG8p{q9mY`ug}A9>CMuvJRHO@0TUCmh=s{G!I z+QOH-{t8BP{`XN($3v(UcA*C9^XG%8Kt@qpHR0{aEKdRCpjNtoTLUh^LOg<6`32Os z@FT9q8(4#NOje}x{}zQ?upbrq71U4hIBK8_K7>WM81KafRDf^$^W#`deFSG?dS>h+ zT!`AjrC5#kV+oMZi*=;w zBh1Y;`vQmX1MDK5S8yv)e}e1t`8A!w7R})VP~TL*Zw8jlCI5=7dv5HbI)nOHE+BpE z5-K3OH5SNpRKVHDBCH7YZB(J&tHXR;j|zMTDibXj!d9>E$H{Ll@P;lV>(-6R!~p8J zTt)@>t3Uq}HE?)d?D-62H*5}Sq6$=sm!SfG$bAwO@Uyr8ccb15PEgQZ5BP)Y$Q+i= zLv<)a4OENw;6^OM{aB8t@D7|n4u&n_i&el?sAF1#%FN?9c^r}5vqt2j4Qww3MRpQ( z3WmJ?H!6Uc%%+uVR3d#Ujr~JugPGZI56gzTnSyq5?RITIq+ViO*pI4kCxZesC{i zpuHQRpvY!LV;@~EYT|hq#;DgzQ3EYT{Z1@L{XVSq`bE?@qo`AH6%{}n8?CKKLuE9I Y%dxE3I=6@CCw6wEw`X>4h_0COAHIe8AOHXW diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po index e57e440..1a7ac7a 100644 --- a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po @@ -139,6 +139,9 @@ msgstr "Score" msgid "tab:winrate" msgstr "Win Rate" +msgid "tab:rank_est" +msgstr "Rank Estimate" + msgid "tab:points" msgstr "Point Loss" diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo index cca852477738239fa673314b6fe3639da61ba3d0..0e7f4e4e210576e4f816bb097ec0fdcaf3216582 100644 GIT binary patch delta 2745 zcmYM#drZ}39LMo5$nkJELWoFs4k@>URA95(@fIT+)Pi*(x0qT-TC@>GQz&ZMeoAcC zGUg@PWa-?boUTEHGtG_ALanU`b<^<&ICW&p(qhA@Fs zi&6b6V|w-+1>UijQ32k-WNgI@+>SHw04CsJR6@s4jZwpf1t0kw4vsBs%nD`|KA`%qijjY{}i)c7A={|IXQHPpnn zQE>uPJPpjEppHd+sADK~$3^a*O^`!NxFkjIYlq4=j!J9q&# zZVYv_zo)SOI-__-sUZ_JVTrR0wZ+f6{x!&B8~9)m){07ayQ}Ya^OL5#+H6 zX9C&i{n^gB9tD!I0@Uq};B;Jp3jCPsUyVxaRn$c9xb}9`4(vtkRIl?mD)B+o%7;;j zjJp0Cs2%kFaRdBBQJ{1_G_U}b&=OR}kD!jK0yWWE)VNn%y#+PV7SzP;s6;!FDXa%| z2Tq{g{{b22**OY&am;mGM+Kfl9`m!Gs)ta47oswcpx!HYR-<+(ib}W<^o~d@u{}2TQK8$){0JYL#RKQu?Evj1l|^m9Xz#zOtBsOR*X?(Kcj= z9Yh`F09N22D$WdUsCS-1IR(8KniG3rm9qgC(%yUYp#r)o$#79xToC$n|bjFF; zfg#i-{t~s2uU-EDYNyVkc5W<8Sgqhs8g!=rq9#ZteO;DpRJ{;2ZaMa26;@*KK7!!$ zcpD>gIqNw7lbL6Bg!+nXlA%63a3?BGm+L=*y8Wjx2Zyl)y$K5JrsWlyJ&0ww635?6m1lc|@Y0#=~{u0noOY#r{ww^8FVxDbkyk2;DX z)XtP4UnqN~g!Sm__7)9~}9*Y#x= Hx5fPj*fRkQ delta 2711 zcmYM#4QQ2B7{Kx4+^%j{XPdLxbgrp$Gu@Oztpya4E$Tq|AG9UoMkJ7**X8)kK2c7=i$@X9~+~av4nOT=Hqd^1pmYym`sL{i+!;V z4o3U0Oq&YVal_a!4GmC-y>T91ii@!quEI=QhbFWU9sdy);a)7jQ|N>lyyUn%v|Wxa zU@T^14Hkq{2)A+5od@;EyJ1!wFdt293A&PHXrLG4^JX;A=IFcV`#aEad(ee^7yG-A zE1c!yVuExgnLp%l<4TIrj?u^!CPb&ArMn9qHw#_KqS(IzE$ORh!f&GE-;ezr==iVD z_$Sdg=P~8LUOcs9ARl&&KwlV-2EGLiJOjDH?ATs_uIL$b5DGoS?2Fm2afrV&76=T2KtXy)bUdt;)L&_`*U=wR++^|KG+L@N=!*$X zPy&n4(v_mmtD=+8!#Ew?x(Cs~Poi<2js2_9Q@;@@LTJTNcmOG4NF^%xk>O?tQq6EX z(ia{>jzw65u6!HTU^|Y-{Nd@tcPkojIl9sh(8NDOC*Frv_)zTs8QJCV7moCJ6;fxm zElfr;dH~II30lf#?1{TE8#~e8frDsCe~kSn(MtS*CY;0mxS(S6)K_Bcn6fmVa$_koiRz2lXr}qG-5(89iUt~v{Dy=vxCWx*WwL$_o&daAZJ*1%jUi-iW)wzENHp6YEHNR0E#gIH~;_u diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po index 94095e6..9246ba7 100644 --- a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po @@ -606,3 +606,7 @@ msgstr "Download Models" #. TODO msgid "All models downloaded" msgstr "All available models already downloaded" + +#. TODO +msgid "tab:rank_est" +msgstr "Rank Estimate" diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo index 7cb8fdbefd801140d61b0bf5a5d18d70f9315553..857c300f1918d753eb53fe2a3fd0b3112bf18ef2 100644 GIT binary patch delta 2745 zcmYM#drXye9LMo53gS5k5ke^!If!uxA>+6OPBhZQT4oCWIFOMBAu@Cc#+tH6@sDg7 z-ljDs)+D>QnPc!rtWCN~R$F0~jT+3h#3FOa2Ab)5e>lG__xQYi*YEFo9@8zy~F=h{;!Hs&TOauf0IbIkiQoX<19SsJd5e%FJS^sU_AO`%%U*`<1iEN!4Rr` zSy<1?NYKZsQ31AMEVkl(cmQYMyBLW*s0sC=+JB45_$wyj9n^pcbW*zvRK5VUfD-iK zI!rXrY$J)O6f`4!ZHH^{GHPN6Q7buy3iP%s??weW>--Yc{{pJr5NaXUT>Wk2u_y{B zCx|hOZy6-Ck~~*Yj6C*?vl6v+^{93`P%CM3^>3lJ^aIp{`%vw_a`l6#_E%8@|AUI- zBjc%IDhXBO@>0cOREO25z|W!rzkocp)8$`9t>`Ff<()Vk&mfO|$V>72Q9F1E)ovJd zw4-tCzs_h1tyC}zHQ)kgF=~s~y7~&_v2DCqgtek3e8A;9UA_x>>~mgP$VJpdub|?O zp~j1hXa7@3q)<8&3y{g#Q_gbKjO$Q4upPC+UC3jv@KT_5)aC0!#W{!il?)<}jX5Kk zjru1!=XfNTj0I7*y8x$Q5i0O~WznOo6AZ+QeWBe9)=g?vX9@O{)LwU^f;coADMhM$U7+=eHx1NG%BW;Ctv zaommTP?vZRbrhFTXa5K4&&v(x%~Z}`XL6SUb?~Kyk0Kt`Ar%#19_q~VuoD+!Ee>OV z1+1LIj^U27-Uqk~xIc^iuO!jVLFo){W}C4~7WH6w zMQKP(%SWC4TGS3yqjsth_5Ij`IeB8(1=^4E@pV+Y&roqMpeFh=Y67FEJ8}!v-pk^hL8266 zun9R0+lB0wbvQr881f^iTReukw8=zUie;$4hj=MoAL>XiqZV)#bqQ~{@(JYrdUlrt zyJJatoG(7gi{r5dWDGlu+R8rEivB`BPM`*uLZt%xP~~wLhqF;Tn2mF>2=lSZl^?`t z#1L{uvhT4HU$jr^RAbb=7j3b|i+(Rou^{+rppblB1wIH7W zJB1pr5B-d9=Ul;n^C#2*!^nNKanu>7KOFv}62vU>#mHQ&7WIieidyM=s2%wl^>h2q z`6FtFenIZMji9H*?&S G9q}I{<^z=g delta 2707 zcmYM#eP~uy9KiA8%hc_0bLyO>o12sp_t~opFFux> z8q|AZ;NN!s7xDl0ENdYw&iUUV6rZ3z5TD0uaAUL;OQ;{hLHIr9hU0a4-(Q zQD}R4#w5(5K;LjLI>E!3i}hHD4cH%>u{XYk2DAh1e*lN!5iG!q=z={NWWRxEJ&kUl z0<-Z>EC@*mcT?!agIZ*4sEZw*K?7TZ?qnT0(JS%!Hguxa=stA(L9|~dx{iOxn-_W;_j4&BM>*xrPubTb<8F0}unqV6y@2j~1NOr$$Q9n;=KOon3?4%J z9Y&9~E06qpM!&`f{pjt2CDEy9if=;)R3lfI&yC`@OpkGM`a)mR| zOX&D4qSCyC0>Ol#=-ZvfJ~#=T_=ebCg$8y%y3phC`D!!+FQb{-8hsNDd=I+w579t6 zWBX||gUPwr;W9c=Z*F!RiUu?ejdU`4R2Aq#HE6%Nv0jTVvC_!ZRienqZ4(+=f}}N&Z9?lHMZxnJp(90cRHS19adr*cOqTG5gdzWaX#j< z$O2r7)BXOxq%hhB4*XgyL+jO8ic9b&Y{EI%fd(>sXbAV-N`UtGWgILNACX66s zSX~mrlen;yG9DjEP&kG(7US&e2n_4SurSth9uMNGH2L=oyTTx1IhxU6Nop=u#=m%&3o#;Dq678SOZ6qesD5NOdhh#l0K(ZAY zqT4Y=y#vWwIEK^lDpq0zvpVr|Zcf;O?zA1YD(4W>7=uTfkGt!EFZf(*1=z<53mp*hv zKf}cDypw_hv&fSjbI|$_G~!|Cgd@>2O=CWm;bxqPdDx8x{Bx{diS{kev=^e|$KcI4 Wr98B)%o&^A);hSUxUHtFrpLcEk@^(? diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po index 26a8e68..21e3d57 100644 --- a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po @@ -645,3 +645,7 @@ msgstr "Download Models" #. TODO msgid "All models downloaded" msgstr "All available models already downloaded" + +#. TODO +msgid "tab:rank_est" +msgstr "Rank Estimate" diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo index f055540cb71242fa13d831313e1f6e8424057c10..473ea528d5d3b8ee043c614f7b1c504937fedc89 100644 GIT binary patch delta 2746 zcmYM#drX#P7{~F;gg6@^6agY{77%!ZCX$DtCS^wtb8}!J4RFY^0vao^xBSP7!;GdY zR92xi=|BWin-lGzSZx)?=7IvNtz22ITskXP-yiR_F-=ir3+GjKw!F4Es<4y^m`D1*YKln21+U6DH6}?E#w z(Sl|Vk(fw91Jc(vdxLGLz+OPDWH)M{y+S-S0kt+PWH4yUnPTw0iw3s4aa574T71`%k@o7}fqPYU008<3y1O zs$m)lRZQoliutGx_n`(}j~e(f)!G0>Af*Lp%6?p-w-!gX3nVN(WH`{^aEsaXcWFr6Ws0)#soFEJPkF=2e5Gn1utVfX`zNUcn~J zW|BJn{`*KQQUM2^i5pN8v|<1|a0(v7`8bNpG3ENum#+fz$p>*B_99K}9O@EInLtV?a;lb9WFsO(`u%0{^PgMD_Xo^2Wr4KQIVfOo!yt%gCn>aThh(`!0#}? zLWZWAt-^~LW|t{nexunM@=K?M+P!=e_l11(&Fl{Kr!v|99VCu&P>N_xHphU~yx2W! z!wl^5{AZ{g`T^DcPYhtxEujE&Q321zd@SM^$688DgztIz0o3h3f;Zq0>WF?rVzl^~p`%-Xam;TkNNC^(QD zQMd)8aU1GL+PuCKxo375V{rs^7tW#r{s&_)byn!EWZ??(g{X0!$2i=DvCMD#Nhp8= zsELoDJ~RWUBN_Jk#Mz;vNyXbJpN)&L2IKK8Ockqjqc&29;Ps zVjUKv0_k+S+#b|`hfo82Tw;R(bAHj+EGhUDX;B^=ZnI&Q} zPQdA?_JW9kE#QH^_AqLIa!kN_ya5|A9y>4^-$ezq7uEkD-h{_+5?(+}cnvS9UlOVu zMlGNSV{s8qvcT*i9NkQ~$oJlU z4!P_PZdU`OF~t0q#Di9nhHA)1F1y=Zf=b=vsD9O`l{9$!CR9qdqXK>()&FB}A4c^* zfg1lTYMj3@P{(*`)sV_f4LPV6N>Bqoh#I&8xonk}*P&L_j9Pgcj>jFyWqY`3{C-ph zkD&UEqPF(iMDnja`pqlG(_0g!yZNXT&qKX%KXO?mH-fi%RKSg1Zu4>{a#YM9+DB11bX>QJLy=_oD(ofLi$= zDv%LxKZVL*@QZgCLk$$oO&wEE0nI{1nupq|BGg1>sD8`6T!osb4mEKDD$w=F(6$40 z26|BMe}wc4Y={TFIO+}Gp$0yKTsG$AIP$82r=lVcqu#sSosY`U1E_$XLXA^}n($@R z7QW`?Rt)L)-@$`AcA-|-hZ<rbKrIg8q=OWvNq@)STSYNgrSs<8yaxDOTRF}wwT z#0?l?k{7W9OZ58>vC&yrikhGfGqDNNunUWE5EtVm)E94I3g-hWaW1}%G}$QX@SQ=e z{0csUu~b&z3phK<>?;h))nQ6xkIEU9AgvDXz-Da0&rks^;ioc!t5ILf68>P&Wb^S5 zK8ZTK8APou2%|ETkIL{}Nb+ncDl?U7R1r6n~EDe*!b{JSxD%>`1^8L9<=9+ zFczQ2ID8(pC2PFB898UR9piBTHQ*PhfKOovFQd*%+^ziO#8lKcHJE@6sP|hihWTwP z51M!<>O->+wI!db1CF7#<}%)i$@v70OHl)F!o}E$3jCb=Cn}JrIgyEzQ5nm0XJYUK z71=y!qGq?%-GUnM9d|cs1wE*r=|S9#-=H#4QxI856RLd!s{d|OhWk(p`4a2#rvmF; T7nc>=+nqdlTJO<0-BJGmb6)_$ diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po index 5230335..dce0fe4 100644 --- a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po @@ -583,3 +583,7 @@ msgstr "Download Models" #. TODO msgid "All models downloaded" msgstr "All available models already downloaded" + +#. TODO +msgid "tab:rank_est" +msgstr "Rank Estimate" diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo index 04e099c9793c155d129b3dca0c93682af5995c04..7ad03ae74a5e4fd29ba2e5bf58720656e69857a1 100644 GIT binary patch delta 2747 zcmYM#drZ}39LMo5r=wg9I9wD_P>z@&7b!0xB1xze;c~$mf|1}-2Lu^V%&>k+b6H*z z%WQPrq}ci+Bdpcj>_*vTUUPHlgq2;Km)v66s5N_kJkOTH&Oh9zX^3F>3rVoPST|}Xuc9XEb?pPFi4HlxM!kO$HEsyCkt?qM zALOwyG~Ngh$FaTzDQG9TuA>-vY`L=*mAWR>xK7kgcDnvIQ7L^F74Rph@!z`sGpO+w zPz(Q!nkSNqr-3OH)RDu7Ito!Q+>4sH4mI(k$YUE_eH&^=FQIn64qpJ=73y1Z z26=46=_eY!Ki)aTqd+j0fx6xKI2IS8CSL6NYfynTq88fV+IONd@H#3}{mz4^z`sQ8 z`~)hHA=iHul|gUR4G3ozO_a!o241`O?&|L!l$U6o;cgtp9oU*o{vQsb#P8=8XlC^pID$bn^B+P9F2sxI;WTVayK=9UsWXaxl0}dlW+ApsE zIu1}zN;7*Azrz+5tRvbEJVtb{;9b-C!}GWBhuNG%E5^8d6=uEYu5yNbpvPx^x>{`*zeN>O}?kgKHl}=C-)mp_G@PGFgtXScf{=^>{n( z#VYiUx`r8Z%qnQ8MVjnAyc^FW*|Ho~)n%%}E_@tyi$}2y*UbxE*0ZQ1O=A_Ec_GH* zJ*YrxF&&$+nDwoj!a^EOq85zJ=PQIOkWJYe$eeZp^{oh>AIew&bp%0Fdm(C}C8#5+ zLau?eq27NPOYkTrVO)VS$obEtpb5%QFIJ$w&n=jMofyRDU4Or;AHhW0ucB_he?cfi zb5J|2z%*Qq)%Y}K;yKhE7+XldtZ&g2l&Un0!aSUY3s9G@*7f&cGW8FUWY}@MA0tSg z?!tq(8lOUa&3<%VN9{PZD72w@=%ZeSo(jt-G~seoCJy2R97F|n7B#^|j7Hxbp>Y9J zhLVvrEEg|hBkB%SEed7wIn*86i@MaG~OIzC3-q2j%zGmR(qCLL<0N^DI5dZ)H delta 2709 zcmYM#3rv<(9LMoLFL>c1_dt`2Qt}dk5(*V0cS|rA<)u-E!KT!}GGeo0@gS|-Shf~0 zYHm8FWtWYl7qeQKk<-Q&mRrsi)ut;ZS}UM2-JE@Yy=TikKA-1Yo^$@^f6nuAXm`lf z&JgdH_^>|X-`o68;eXW}mCRZq{`V#xcTkSQmvABWIR`PF@@b6635>;SI1eKu&7v^@ zV=x2No*&e+N*?HI8&Lyn#Rb@kN!W=|*pFd&2o=zKsQzE#&3G0Q@ekC5K3-D4I8@n> zT0kL&<62BK&+H){LaAs%-nC}eVHYZ}-KdqkiW;cb)gMF+H0b;U_5KK|-xz8kKfCtJ z$YuX<`!7HmBbndgc+g5xQ4M*>Wu?ybsMI}*>eq}~Nr!8H9hK7ksDM92_5aMZkD~gY zM~y#;8s{o{>KH|>8dA8a;dayuC8&XGQ3E$1m$kTZJ8DI*pjO_4^YH+3*}L2{{xMVr zPow&sL2d0%(d1uyH03Jh(_0g!JM&N}E=Rqv4!Nw68^K#ED&S66?s4S-J6GQ$N^AJgG0{W3ytk_wDintDyfo-T2?nEwoo|^{hMjgHZ)HolbzLHVo zvWw0c)cbRYN=AD;5R4_GPPZTDVh(EHm9D)871$%FiJo-z9jFYvhRW1{^8-}iM^P*P z3KhthYyTCMLGP05FpC-}jGH#^1s(}}yBKM=-D{xkzGV}l{;Cj?JO{fW< zLv3M~EAPcfegFMDsN+G@3Wrexjk@|DQGraNw(7cTU%>JdKniN5w{dI267=Ics53Bz zt8fB$U<#A8VK-Lm`@g}14q-JLuE@7x6+VTlF@O#DH*UcV$-$H!!8*z#xDHd8Z3RAo zY4|2;#mDds9KpS~X%U}r2!|5Am#L^__Dnp7OYu5ZU^;&gCb1DSaDA%T8wKiWH+Xhe}cCcsUSPn=Q_NP1ZUr%+Am-LuVF6^ zE+IQiSV^?I@FSwzhuOE9-G`@^u{w-qA6juLi-q~fY&O51l=tMY0G!VCf)mWLj9dne zA%DzatFam%L*BK+s4e;q71$NzvblM|y)HzZoqE((w4nk&fgBe*kIKvy)R~#aSTygB zU_~72g)}61D?}Z-Ew27)Tt#^gD!|WO{Wvm~&7jWCvix8smtzd&O4QbFz(RZhAI2e9 z_QF^47m$iFq|SP9EuKQMWiiaE!&HP_Scf{r; z-;NbHgvmIA%3##pdY}Ez;XyAJp!ViLOu%|vimk4_*OiZ8BK2ocr+)&Kp`@bVN(*oa zH1k_%aqv5h#|+A;sKZx=N@X`L!ncscTL3rVWzqkTjT8KW(ajrnGnTmWKl;ZuEgojZRA4d)Ft!w`cHPD~P*ybzYL}MxH s?BtXNGq@FXc3Mz}xgX2%3)ISQ;C75Jvp{=9W_VyQVPke6wxrkhFDEYr+5i9m diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po index 3869eda..1fcfe38 100644 --- a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po @@ -608,3 +608,7 @@ msgstr "Download Models" #. TODO msgid "All models downloaded" msgstr "All available models already downloaded" + +#. TODO +msgid "tab:rank_est" +msgstr "Rank Estimate" From 9dc58cf43fa3ddfd1c8a9928d642f44a384f0956 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Tue, 23 Jun 2020 16:53:22 +0200 Subject: [PATCH 07/77] rg --- katrain/gui.kv | 1 + katrain/gui/controlspanel.py | 1 + katrain/gui/widgets/graph.py | 129 +++++++++++++++++++++++++++++------ 3 files changed, 110 insertions(+), 21 deletions(-) diff --git a/katrain/gui.kv b/katrain/gui.kv index 0669ffe..2f76090 100644 --- a/katrain/gui.kv +++ b/katrain/gui.kv @@ -488,6 +488,7 @@ engine_status_col: [1,1,1,1] timer:timer graph: graph + rank_graph: rank_graph stats: stats info: info note: note diff --git a/katrain/gui/controlspanel.py b/katrain/gui/controlspanel.py index 25e25cd..80290c4 100644 --- a/katrain/gui/controlspanel.py +++ b/katrain/gui/controlspanel.py @@ -132,6 +132,7 @@ class ControlsPanel(BoxLayout): self.stats.player = "" self.graph.update_value(current_node) + self.rank_graph.update_value(current_node) self.note.text = current_node.note self.info.text = info diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index 5fddc40..db588f2 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -1,6 +1,7 @@ import math from kivy.lang import Builder +from kivy.metrics import dp from kivy.properties import BooleanProperty, ListProperty, NumericProperty, Clock, StringProperty from kivymd.app import MDApp @@ -20,6 +21,7 @@ class Graph(BackgroundMixin): super().__init__(**kwargs) self._lock = threading.Lock() self.bind(pos=self.update_graph, size=self.update_graph) + self.redraw_trigger = Clock.create_trigger(self.update_graph, 1) def initialize_from_game(self, root): self.nodes = [root] @@ -52,7 +54,7 @@ class Graph(BackgroundMixin): while node.children: # add children back node = node.ordered_children[0] self.nodes.append(node) - Clock.schedule_once(self.update_graph, 0) + self.redraw_trigger() class ScoreGraph(Graph): @@ -65,7 +67,7 @@ class ScoreGraph(Graph): score_dot_pos = ListProperty([0, 0]) winrate_dot_pos = ListProperty([0, 0]) highlighted_index = NumericProperty(None) - highlight_size = NumericProperty(6) + highlight_size = NumericProperty(dp(6)) score_scale = NumericProperty(5) winrate_scale = NumericProperty(5) @@ -154,29 +156,116 @@ class ScoreGraph(Graph): self.winrate_dot_pos = winrate_dot_point +def averagemod(data): + sorteddata = sorted(data) + lendata = len(data) + return sum(sorteddata[int(lendata * 0.2) : int(lendata * 0.8) + 1]) / ( + (int(lendata * 0.8) + 1) - int(lendata * 0.2) + ) # average without the best and worst 20% of ranks + + class RankGraph(Graph): black_rank_points = ListProperty([]) white_rank_points = ListProperty([]) - rank_scale = NumericProperty(5) - rank_mid = NumericProperty(5) + segment_length = NumericProperty(50) + + def __init__(self, **kwargs): + super().__init__(**kwargs) + self.calculate_trigger = Clock.create_trigger(lambda *args: self.rank_game(), 10) + self.rank_by_player = {} @staticmethod def rank_label(rank): - if rank <= 0: - return f"{1-rank}{i18n._('strength:dan')}" + if rank > 0: + return f"{rank:.0f}{i18n._('strength:dan')}" else: - return f"{rank}{i18n._('strength:dan')}" + return f"{1-rank:.0f}{i18n._('strength:kyu')}" + + @staticmethod + def calculate_rank_for_player(segment_stats, num_intersec, player): + non_obvious_moves = [ + (nl, r, val) + for nl, r, val, pl in segment_stats + if nl is not None and val < (0.8 * (1 - (num_intersec - nl) / num_intersec * 0.5)) and pl == player + ] + if not non_obvious_moves: + return None + num_legal, rank, value = zip(*non_obvious_moves) + averagemod_rank = averagemod(rank) + averagemod_len_legal = averagemod(num_legal) + # the averagemod_rank is the outlier free average of the best move from a selection of n_moves with averagemod_len_legal of total legal moves + n_moves = math.floor(0.40220696 + averagemod_len_legal / (1.313341 * (averagemod_rank + 1) - 0.088646986)) + # using the calibration curve of p:pick:rank + rank_kyu = (math.log10(n_moves * 361 / num_intersec) - 1.9482) / -0.05737 + return 1 - rank_kyu # dan rank + + @staticmethod + def calculate_ranks(segment_stats, num_intersec): + return {pl: RankGraph.calculate_rank_for_player(segment_stats, num_intersec, pl) for pl in "BW"} + + def rank_game(self): + + nodes = self.nodes + parent_policy_per_move = [node.parent.policy_ranking if node.parent else None for node in nodes] + num_legal_moves = [ + sum(pv >= 0 for pv, _ in policy_ranking) if policy_ranking else 0 + for policy_ranking in parent_policy_per_move + ] + policy_stats = [ + [(num_mv, rank, value, mv.player) for rank, (value, mv) in enumerate(policy_ranking) if mv == move.move][0] + if policy_ranking + else (None, None, None, None) + for move, policy_ranking, num_mv in zip(nodes, parent_policy_per_move, num_legal_moves) + ] + size = self.nodes[0].board_size + num_intersec = size[0] * size[1] + half_seg = self.segment_length // 2 + + ranks = {"B": [], "W": []} + for segment_mid in range(half_seg, len(nodes), half_seg): + bounds = (segment_mid - half_seg, min(segment_mid + half_seg, len(nodes))) + for pl, rank in self.calculate_ranks(policy_stats[bounds[0] : bounds[1]], num_intersec).items(): + ranks[pl].append((segment_mid, rank)) + self.rank_by_player = ranks + self.redraw_trigger() + + def update_value(self, node): + super().update_value(node) + self.calculate_trigger() # recalc here on trigger for speed def update_graph(self, *args): - nodes = self.nodes - if nodes: - score_values = [n.score if n and n.score else math.nan for n in nodes] - score_nn_values = [n.score for n in nodes if n and n.score] - score_values_range = min(score_nn_values or [0]), max(score_nn_values or [0]) + if self.rank_by_player: + xscale = self.width / max(len(self.nodes) - 1, 15) + available_height = self.height - self.ids.mid_marker.text = self.rank_label(5) - self.ids.bottom_marker.text = self.rank_label(10) - self.ids.top_marker.text = self.rank_label(-2) + all_ranks = [rank for lst in self.rank_by_player.values() for seg, rank in lst if rank is not None] + if not all_ranks: + return + + min_rank = math.floor(min(all_ranks)) + max_rank = math.ceil(max(all_ranks)) + if (max_rank - min_rank) % 2 != 0: # make midpoint whole integer + if abs(max_rank - max(all_ranks)) < abs(min(all_ranks) - min_rank): + max_rank += 1 + else: + min_rank -= 1 + rank_range = max_rank - min_rank + + self.ids.mid_marker.text = self.rank_label((max_rank + min_rank) / 2) + self.ids.top_marker.text = self.rank_label(max_rank) + self.ids.bottom_marker.text = self.rank_label(min_rank) + + graph_points = {} + for pl, rank_points in self.rank_by_player.items(): + graph_points[pl] = [ + [ + self.x + i * xscale, + self.y + available_height * (val - min_rank) / rank_range if val is not None else math.nan, + ] + for i, val in rank_points + ] + self.black_rank_points = sum(graph_points["B"], []) + self.white_rank_points = sum(graph_points["W"], []) Builder.load_string( @@ -204,20 +293,18 @@ Builder.load_string( size: self.size source: root.background_image - - : canvas.after: Color: rgba: SCORE_COLOR Line: points: root.score_points if root.show_score else [] - width: 1.1 + width: dp(1.1) Color: rgba: WINRATE_COLOR Line: points: root.winrate_points if root.show_winrate else [] - width: 1.1 + width: dp(1.1) Color: rgba: [0.5,0.5,0.5,1] if root.navigate_move[0] else [0,0,0,0] Line: @@ -275,12 +362,12 @@ Builder.load_string( rgba: WHITE Line: points: root.white_rank_points - width: 1.1 + width: dp(1.1) Color: rgba: BLACK Line: points: root.black_rank_points - width: 1.1 + width: dp(1.1) # rank ticks GraphMarkerLabel: id: mid_marker From 76c5a6f6afc0e5e5533eaf6b4fa8b8f5f91899d3 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Tue, 23 Jun 2020 22:06:11 +0200 Subject: [PATCH 08/77] graph --- katrain/gui/widgets/graph.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index db588f2..cb20ea5 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -167,11 +167,11 @@ def averagemod(data): class RankGraph(Graph): black_rank_points = ListProperty([]) white_rank_points = ListProperty([]) - segment_length = NumericProperty(50) + segment_length = NumericProperty(60) def __init__(self, **kwargs): super().__init__(**kwargs) - self.calculate_trigger = Clock.create_trigger(lambda *args: self.rank_game(), 10) + self.calculate_trigger = Clock.create_trigger(lambda *args: self.rank_game(), 2) self.rank_by_player = {} @staticmethod @@ -222,11 +222,13 @@ class RankGraph(Graph): half_seg = self.segment_length // 2 ranks = {"B": [], "W": []} - for segment_mid in range(half_seg, len(nodes), half_seg): - bounds = (segment_mid - half_seg, min(segment_mid + half_seg, len(nodes))) - for pl, rank in self.calculate_ranks(policy_stats[bounds[0] : bounds[1]], num_intersec).items(): + dx = self.segment_length//4 + for segment_mid in range(0, len(nodes), dx): + bounds = (max(0,segment_mid - half_seg), min(segment_mid + half_seg, len(nodes))) + for pl, rank in self.calculate_ranks(policy_stats[bounds[0] : bounds[1]+1], num_intersec).items(): ranks[pl].append((segment_mid, rank)) self.rank_by_player = ranks + print(ranks) self.redraw_trigger() def update_value(self, node): From 57922ddcb4abafe3c5fb480e15db0106d8650483 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Tue, 23 Jun 2020 23:34:58 +0200 Subject: [PATCH 09/77] timing --- katrain/gui/widgets/graph.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index cb20ea5..eff006d 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -21,7 +21,7 @@ class Graph(BackgroundMixin): super().__init__(**kwargs) self._lock = threading.Lock() self.bind(pos=self.update_graph, size=self.update_graph) - self.redraw_trigger = Clock.create_trigger(self.update_graph, 1) + self.redraw_trigger = Clock.create_trigger(self.update_graph, 0.1) def initialize_from_game(self, root): self.nodes = [root] @@ -171,7 +171,7 @@ class RankGraph(Graph): def __init__(self, **kwargs): super().__init__(**kwargs) - self.calculate_trigger = Clock.create_trigger(lambda *args: self.rank_game(), 2) + self.calculate_trigger = Clock.create_trigger(lambda *args: self.rank_game(), 0.25) self.rank_by_player = {} @staticmethod From f57dbcdee9e3309c9bcb2b6aa1a76d77d64e1a28 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Thu, 25 Jun 2020 01:17:58 +0200 Subject: [PATCH 10/77] extra analysis options --- README.md | 4 ++- katrain/__main__.py | 28 ++++++++++++------ katrain/core/game.py | 21 +++++++++++-- katrain/core/game_node.py | 5 ++-- katrain/gui.kv | 10 +++++++ katrain/gui/kivyutils.py | 7 ++--- katrain/gui/style.py | 2 +- katrain/gui/widgets/graph.py | 7 ++--- .../i18n/locales/cn/LC_MESSAGES/katrain.mo | Bin 10517 -> 10707 bytes .../i18n/locales/cn/LC_MESSAGES/katrain.po | 12 ++++++++ .../i18n/locales/de/LC_MESSAGES/katrain.mo | Bin 12341 -> 12531 bytes .../i18n/locales/de/LC_MESSAGES/katrain.po | 12 ++++++++ .../i18n/locales/en/LC_MESSAGES/katrain.mo | Bin 11433 -> 11681 bytes .../i18n/locales/en/LC_MESSAGES/katrain.po | 13 ++++++++ .../i18n/locales/es/LC_MESSAGES/katrain.mo | Bin 12239 -> 12429 bytes .../i18n/locales/es/LC_MESSAGES/katrain.po | 12 ++++++++ .../i18n/locales/fr/LC_MESSAGES/katrain.mo | Bin 12952 -> 13142 bytes .../i18n/locales/fr/LC_MESSAGES/katrain.po | 12 ++++++++ .../i18n/locales/ko/LC_MESSAGES/katrain.mo | Bin 13183 -> 13373 bytes .../i18n/locales/ko/LC_MESSAGES/katrain.po | 12 ++++++++ .../i18n/locales/ru/LC_MESSAGES/katrain.mo | Bin 16548 -> 16738 bytes .../i18n/locales/ru/LC_MESSAGES/katrain.po | 12 ++++++++ katrain/img/analysis.png | Bin 0 -> 6733 bytes katrain/img/off.png | Bin 0 -> 5770 bytes 24 files changed, 145 insertions(+), 24 deletions(-) create mode 100644 katrain/img/analysis.png create mode 100644 katrain/img/off.png diff --git a/README.md b/README.md index c6db987..411ae17 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ Keyboard shortcuts are shown with **[key]**. * **[a]**: Deeper analysis: Re-evaluate the position using more visits, usually resulting in a more accurate evaluation. * **[s]**: Equalize visits: Re-evaluate all currently shown next moves with the same visits as the current top move. Useful to increase confidence in the suggestions with high uncertainty. * **[d]**: Analyze all moves: Evaluate all possible next moves. This can take a bit of time even though 'fast_visits' is used, but can be useful to see how many reasonable next moves are available. + * **[f]**: Re-analyze game: ... + * **[spacebar]**: Turn continuous analysis on/off. * **[enter]** AI move. Makes the AI move for the current player regardless of current player selection. ## Keyboard and mouse shortcuts @@ -112,7 +114,7 @@ In addition to shortcuts mentioned above and those shown in the main menu: * **[Shift]**: Open the main menu. * **[~]** or **[ ` ]** or **[m]**: Cycles through more minimalistic UI modes. * **[p]**: Pass -* **[spacebar]**: Pause/Resume timer +* **[b]**: Pause/Resume timer * **[arrow left]** or **[z]**: Undo move. Hold shift for 10 moves at a time, or ctrl to skip to the start. * **[arrow right]** or **[x]**: Redo move. Hold shift for 10 moves at a time, or ctrl to skip to the start. * **[arrow up/down]** Switch branch, as would be expected from the move tree. diff --git a/katrain/__main__.py b/katrain/__main__.py index 9437e37..1034003 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -89,6 +89,7 @@ class KaTrainGui(Screen, KaTrainBase): self.teacher_settings_popup = None self.timer_settings_popup = None + self.idle_analysis = False self.message_queue = Queue() self._keyboard = Window.request_keyboard(None, self, "") @@ -113,6 +114,10 @@ class KaTrainGui(Screen, KaTrainBase): def play_analyze_mode(self): return self.play_mode.mode + def toggle_continuous_analysis(self): + self.idle_analysis = not self.idle_analysis + self.update_state() + def start(self): if self.engine: return @@ -182,7 +187,9 @@ class KaTrainGui(Screen, KaTrainBase): and not (teaching_undo and cn.auto_undo is None) ): # cn mismatch stops this if undo fired. avoid message loop here or fires repeatedly. self._do_ai_move(cn) - Clock.schedule_once(lambda _dt: self.update_gui(cn, redraw_board=redraw_board), -1) + if len(self.engine.queries) == 0 and self.idle_analysis: + self("analyze-extra", "extra", continuous=True) + Clock.schedule_once(lambda _dt: self.update_gui(cn, redraw_board=redraw_board), -1) # trigger? def update_player(self, bw, **kwargs): super().update_player(bw, **kwargs) @@ -197,7 +204,7 @@ class KaTrainGui(Screen, KaTrainBase): def _message_loop_thread(self): while True: - game, msg, *args = self.message_queue.get() + game, msg, args, kwargs = self.message_queue.get() try: self.log(f"Message Loop Received {msg}: {args} for Game {game}", OUTPUT_EXTRA_DEBUG) if game != self.game.game_id: @@ -206,19 +213,19 @@ class KaTrainGui(Screen, KaTrainBase): ) continue fn = getattr(self, f"_do_{msg.replace('-','_')}") - fn(*args) + fn(*args, **kwargs) self.update_state() except Exception as exc: self.log(f"Exception in processing message {msg} {args}: {exc}", OUTPUT_ERROR) traceback.print_exc() - def __call__(self, message, *args): + def __call__(self, message, *args, **kwargs): if self.game: if message.endswith("popup"): # gui code needs to run in main kivy thread. fn = getattr(self, f"_do_{message.replace('-', '_')}") - Clock.schedule_once(lambda _dt: fn(*args), -1) + Clock.schedule_once(lambda _dt: fn(*args, **kwargs), -1) else: # game related actions - self.message_queue.put([self.game.game_id, message, *args]) + self.message_queue.put([self.game.game_id, message, args, kwargs]) def _do_new_game(self, move_tree=None, analyze_fast=False): mode = self.play_analyze_mode @@ -266,8 +273,8 @@ class KaTrainGui(Screen, KaTrainBase): except IllegalMoveException as e: self.controls.set_status(f"Illegal Move: {str(e)}") - def _do_analyze_extra(self, mode): - self.game.analyze_extra(mode) + def _do_analyze_extra(self, mode, **kwargs): + self.game.analyze_extra(mode, **kwargs) def _do_new_game_popup(self): self.controls.timer.paused = True @@ -397,6 +404,7 @@ class KaTrainGui(Screen, KaTrainBase): "a": ("analyze-extra", "extra"), "s": ("analyze-extra", "equalize"), "d": ("analyze-extra", "sweep"), + "f": ("analyze-extra", "game"), "p": ("play", None), "down": ("switch-branch", 1), "up": ("switch-branch", -1), @@ -429,8 +437,10 @@ class KaTrainGui(Screen, KaTrainBase): elif keycode[1] == "shift": self.nav_drawer.set_state("toggle") elif keycode[1] == "spacebar": + self.toggle_continuous_analysis() + elif keycode[1] == "b" and "ctrl" not in modifiers: self.controls.timer.paused = not self.controls.timer.paused - elif keycode[1] in ["`", "~", "m"]: + elif keycode[1] in ["`", "~", "m"] and "ctrl" not in modifiers: self.zen = (self.zen + 1) % 3 elif keycode[1] in ["left", "z"]: self("undo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999) diff --git a/katrain/core/game.py b/katrain/core/game.py index 95e2e5b..4e9f753 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -317,16 +317,29 @@ class Game: f.write(sgf) return i18n._("sgf written").format(file_name=file_name) - def analyze_extra(self, mode): + def analyze_extra(self, mode, **kwargs): stones = {s.coords for s in self.stones} cn = self.current_node engine = self.engines[cn.next_player] if mode == "extra": - visits = cn.analysis_visits_requested + engine.config["max_visits"] + if kwargs.get("continuous", False): + visits = max(engine.config["fast_visits"], math.ceil(cn.analysis_visits_requested * 1.1)) + else: + visits = cn.analysis_visits_requested + engine.config["fast_visits"] + self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits)) cn.analyze(engine, visits=visits, priority=-1_000, time_limit=False) return + if mode == "game": + nodes = self.root.nodes_in_tree + min_visits = min(node.analysis_visits_requested for node in nodes) + visits = min_visits + engine.config["max_visits"] + for node in nodes: + node.analyze(engine, visits=visits, priority=-1_000_000, time_limit=False) + self.katrain.controls.set_status(i18n._("game re-analysis").format(visits=visits)) + return + elif mode == "sweep": board_size_x, board_size_y = self.board_size if cn.analysis_ready: @@ -354,7 +367,7 @@ class Game: visits = engine.config["fast_visits"] self.katrain.controls.set_status(i18n._("sweep analysis").format(visits=visits)) priority = -1_000_000_000 - else: # mode=='equalize': + elif mode == "equalize": if not cn.analysis_ready: self.katrain.controls.set_status(i18n._("wait-before-equalize"), self.current_node) return @@ -363,6 +376,8 @@ class Game: visits = max(d["visits"] for d in cn.analysis["moves"].values()) self.katrain.controls.set_status(i18n._("equalizing analysis").format(visits=visits)) priority = -1_000 + else: + raise ValueError("Invalid analysis mode") for move in analyze_moves: cn.analyze( engine, priority, visits=visits, refine_move=move, time_limit=False diff --git a/katrain/core/game_node.py b/katrain/core/game_node.py index c0b3ef1..a0e31e3 100644 --- a/katrain/core/game_node.py +++ b/katrain/core/game_node.py @@ -69,8 +69,9 @@ class GameNode(SGFNode): # various analysis functions def analyze(self, engine, priority=0, visits=None, time_limit=True, refine_move=None, analyze_fast=False): - if visits and not refine_move: - self.analysis_visits_requested = max(visits, engine.config["max_visits"]) + if not refine_move: + req_visits = visits or (engine.config["fast_visits"] if analyze_fast else engine.config["max_visits"]) + self.analysis_visits_requested = max(req_visits, self.analysis_visits_requested) engine.request_analysis( self, lambda result: self.set_analysis(result, refine_move), diff --git a/katrain/gui.kv b/katrain/gui.kv index 2f76090..cd30c74 100644 --- a/katrain/gui.kv +++ b/katrain/gui.kv @@ -721,6 +721,16 @@ icon: 'img/Sweep.png' shortcut: 'd' on_action: root.katrain("analyze-extra", "sweep") + AnalysisMenuItem: + text: i18n._("analysis:game") + icon: 'img/analysis.png' + shortcut: 'f' + on_action: root.katrain("analyze-extra", "game") + AnalysisMenuItem: + text: i18n._("analysis:continuous") + icon: 'img/off.png' + shortcut: 'space' + on_action: root.katrain.toggle_continuous_analysis() AnalysisMenuItem: text: i18n._("analysis:aimove") icon: 'img/ai.png' diff --git a/katrain/gui/kivyutils.py b/katrain/gui/kivyutils.py index 9d5b9a8..8eb4c03 100644 --- a/katrain/gui/kivyutils.py +++ b/katrain/gui/kivyutils.py @@ -383,15 +383,15 @@ class CollapsablePanel(MDBoxLayout): option_active=self.build_options, options_spacing=self.build_options, ) - self.bind(state=self._on_state,size_hint_y_open=self._on_size, height_open=self._on_size) + self.bind(state=self._on_state, size_hint_y_open=self._on_size, height_open=self._on_size) MDApp.get_running_app().bind(language=lambda *_: Clock.schedule_once(self.build_options, 0)) self.build_options() - def _on_state(self,*_args): + def _on_state(self, *_args): self.build() self.trigger_select(ix=None) - def _on_size(self,*_args): + def _on_size(self, *_args): height, size_hint_y = 1, None if self.state == "open" and self.contents: if self.height_open: @@ -402,7 +402,6 @@ class CollapsablePanel(MDBoxLayout): height = self.header.height self.height, self.size_hint_y = height, size_hint_y - @property def option_state(self): return {option: active for option, active in zip(self.options, self.option_active)} diff --git a/katrain/gui/style.py b/katrain/gui/style.py index e3523af..1d84822 100644 --- a/katrain/gui/style.py +++ b/katrain/gui/style.py @@ -59,4 +59,4 @@ ENGINE_BUSY_COL = EVAL_COLORS[2] ENGINE_READY_COL = EVAL_COLORS[-1] # info -INFO_PV_COLOR = to_hexcol(RED) +INFO_PV_COLOR = to_hexcol(YELLOW) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index eff006d..691d5ff 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -222,13 +222,12 @@ class RankGraph(Graph): half_seg = self.segment_length // 2 ranks = {"B": [], "W": []} - dx = self.segment_length//4 + dx = self.segment_length // 4 for segment_mid in range(0, len(nodes), dx): - bounds = (max(0,segment_mid - half_seg), min(segment_mid + half_seg, len(nodes))) - for pl, rank in self.calculate_ranks(policy_stats[bounds[0] : bounds[1]+1], num_intersec).items(): + bounds = (max(0, segment_mid - half_seg), min(segment_mid + half_seg, len(nodes))) + for pl, rank in self.calculate_ranks(policy_stats[bounds[0] : bounds[1] + 1], num_intersec).items(): ranks[pl].append((segment_mid, rank)) self.rank_by_player = ranks - print(ranks) self.redraw_trigger() def update_value(self, node): diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo index 98569aa4d781ed8d7cae4eb2e7dc46eab0cce58d..837684882b0747d11e89cd065cf7682638d33f44 100644 GIT binary patch delta 2909 zcmYk-3rv<(9LMp)9T5}(@lwJ|5(1)WGjFLaU0OLyF4LGs^YV^fL}(xvHG4*_G7Cz{ zRY1a)Lz%i#a;;Su(sf>2=7pKHaNS4R#+H?^J&Yg} zT{3DT8K{NMj3@t^;2v&Tz%slE*P~MR9BSpSoBNT=y~j-pKaR@8dDKq(kjwqbP4TXy zUdkk9RlF?J*E1K@Z<%L>VpM=yvktZ5ZK$`t2_vx;6}a7=e}G!x3DiUvt^F!06CvD` zu_QAEweU<-2E1t$w3556Lmn!XE3HEba=99A>bDKGpk~y9+fYZ=ftu(Ds^8~U??p{? z88z`$)IzT#YxG2>Np)WaK5!ahzeYYjCCbe--7DjfLeGHYTRD)Fe*bw zQ49V86{i=6===YLg3fRNHBigbLgG+?N1+CcN9}YPD&YOrUWkJWLLJ!_dtQ%<*MeNG zgIf)D;RKvcuvmTnMHCcx9k)4HhYPU_GcbY!*Grj$+QGf3{<)Zn#W)q8#hLgnY9YU) zCJG(Jd1E$eTt1R|7eG%h)n*Fw@JYN2yRijBcvWZME>ys?sGWX~%Fs1jieY1d87aWW zsjopj|JK@n#wztk%~#IfYh}E8EcsW8>S&M+X0y51JZv5{PoV-`w0a*lP``rOfJeGs z!Xx+=F6BSlF$@{!-1B$>&tqW<`B%r=$zwbA;C3wW@t+F+MxFH|X~EQ&q0X`f^|HNy zT6mMW2g$m78@0n@$fn&{ROYUr#)aJybc9Di9a2$&X4!*A2GBb;u@M6RO|)n1ZM5 zdB3$sF=z_yV=)I8<3y}QrgXcJcgl00QkYD`IaCHBZw;MxotsD*c6BpyM{^9kzxb7!q#z>MIdB^hocYQov5z>6>xb1@PN%oV7kSdBWG zD%7}F%-xtx{UGuoxQo_)4L$u~h~%aLx0!QL6WnJmHJ6)3sI&B2eY^Rp*@}wSj!Jo_ zwRfAJp)znPldrXw!Z$Q%z(ZNViT$Y0t`yb28I{7VsGaUY?et()=cbrISj?L8vJHW< zs`9GJ#tU(?!i!g}_jg7Q^M}UzD*TgHm8~k>SQ)77d^CQ4eA0p&T}wCm1|MzmHzuZK zCFfbsO@5!BXcd0nV3<48;=(51corxt{{K^7Ri#=sR|hHs8!ET>f_5%SKN74B;?pLvNwRk6|u;hgtY9YQanPc;4taYN73i4z32OdlsBu%MgIw_b%gAGM zXq??3rm(&hQqW13dq*Ym*gfvUsMI}<8n+#Fk~Z(}L#1>Owc+_8 zapqD9G%%lnI+pQLM>%T3-KfADP=U81k2QO}6?LL6)X5KG5*|Swdy|*qkDxMm8a3_= z>T1uYk$>G$9HZ2bgIciEtwg2xKJTwZ9(#%xhp-mZhTFY<(Cb6UV;}L-K|V)q^eig= zG-|z2I{D9|kVor0j3Aq{wQdb+$D2_ZXh5BCC-T^CUJBHM`uK)WaXv= zqxsoxIH160tO)gWM=${^P=W98{uQM_dd3zfw1HGtB4Y}{2Hhvs+@^REgro8_* zR0e~;d_W>m6ex?A1{R?>ba=EC8(W8Q1e!~)u;^Bp*H*kYF;C1!52_hxED3= zz-&ABKR`i&hfxzgK%I0P74S!IpTXG;aV%X~E~)(ND--Qj>h5Cq7 z`Ms=^&qHM(?3TD?x!k`xR(V6UTjy>;1#0s8PHdz866yr`q^lj*;~RJZPhu~B$y%}g zI62J?b$Fc!r+uQ{DG`xDGOr(mLu7+O4NipZ{LP1sJG%Wd>2dbCnSVr z6IBbAp-#RAwZZ$nUgvH>4iZ=s1ue7(wc|ld$I;jT``W#TN?GFKSl~2N%JWd|*L(X4 zcO_~gce*vmWA(gr#qF5N`qoWB0S?bLn7!`4joSH$*T>x{)D@gZ&AVbrY@sC7L2|qv zaaSXIw+B%fX-37}jd84RofHzV%RPX)!$YVmIf|P2m3tPKQlCaXbPMytul{n>&;J^) zZ*`x=1ln8Oy>2%K>gc1OJM8z433t-{7PY`RRLcMK_IOs){A5(BGjJOgqQ>`le?RK? z!=Sgnhsxk+G1GL?Z)ng-r;DQl$(!Rli_%J?o6>6IqMxSsr+2M5VSbQq< EADOfIM*si- diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po index 2a9b66c..eaa954a 100644 --- a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po @@ -572,3 +572,15 @@ msgstr "All available models already downloaded" #. TODO msgid "tab:rank_est" msgstr "Rank Estimate" + +#. TODO +msgid "game re-analysis" +msgstr "Re-analyzing entire game using {visits} visits" + +#. TODO +msgid "analysis:game" +msgstr "Re-analyze entire game" + +#. TODO +msgid "analysis:continuous" +msgstr "Continuously analyze" diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo index cfda43fa2f713ddc1f12163a13e8b3ee3d23a718..bcfc2c69e0debdfd40d42c42b1066839115b42ce 100644 GIT binary patch delta 2908 zcmYk-eN5F=9LMpmV1SngiFnXdyr!TiDh$+Ynr68c#vGb#F5INN;WfnoG11vCb!&;x zn#5vI;cTV|{v#4KjX+!JG9Na}hbe98dPEzgTe>-Vf4FCs`|*37bAIRdJ>T>2yLPPn zyV&bZ7+P=qYvFG+f2$r+&8*G$pGg!CQGXau;0XNQc?-u}cfvcCG zCa@8Q;oF#Kp4n~+;WX?=Vp*%}(1}W{2Q`z^sK6Io`*l>HTh4z_?}w4S`o*CpGKQP_ zO+mWY3>-`lqZ!`{C}<`t+=JJU%RFZ-YU!F#{aR5o>2lA{p_aTCmGGaa{`c?^^f4Mm zOGUkxiAvx}ZW_b#DJbxZs259-%ieJ9wWt^OpawdO8t4Ra*%?>AhMM88sFk>bQ8$Mcn(1}qvOaE# zcOP{q6B$+UGEiU7TvWe8&lO5g0X8|ep)#&Uo%%+Mz-Cn7Pu%k_PzipA8tAHP??vySB#Fx1##LgG#&+_1*zz z2Wo|mqY^%kigN{r>HGhKg7)wZ>P4leL}F2aQ&2CAN6j=B6)?}WFUP@zP+PXuJ+DQ@ z`vAGDh1;vxg%fcy!IJg;FQG6JgSZ-Z;9~5<890Xx*Yk4J3~F#3)?+4iVm98u9869L zo$@?Xy$Gk{yEq2BkdWn6$dJf)~Z^AWO0b_DfHb_rP|>qqT%3I{=3l!MRWGpNH^gY&TqXW;-c zj%TwtNz-U}9<^j!P#IU_Fl;~#v==q-QOv?0k#lD8Q$qDAsDz8qk5w3nji@+#Q4?!J z9r_;h>HGhhf&!gKeaC&MnG7IF+NjLXj25DgdLi;tVZ|7Ol^BKFF&cNFCh$ILCHA2v z@F{A7XHnzzVyeFXyA)z@#MDsWG}IE!Mg=HCorUcfhC5xo1~tPv)LCf4T|?Gn}>X0mN(tz***&EY0#1y;{T?T+)jQnw&PSh=i-j}-5_cqf4Ta9k;f)b zI6grPFuvuG&`JtiMG5lQ3THKH>uOQ$8c-`~b@d;hw)8X9guh0$Kke!VQ0;$54SWj~ zCxMKohFK(3k$9i~aA-|v|`Wq_# z2x`386!t%pL?)$EFpNyjmN_d?Gv0{Wfmcy0d<}W5nU@0XMP0rwRGjZnU&#RS*oZTh z*{FZIbDBqj$yg9|yTjU25LkN+=`m$UStS6gt`OA zQTyW5k^p3JM8kK7$RSAhuK|NjfAuY z)KRwKD%^uh@iKbXNi5*1F2gyDP7Hemsk0W$!w!4{&*EHsg5TR?_&VyBvy@LrU(pg? zyRZgziLav;GK$)v82-S>WYiI6WpVymK^_G{RX z#0$7(8n4^T9-VG>o@7NfM?yZxHl8A1JcBJK@1M!fGWo}5vHvY3N@qt0IENbeBI>eS z#W^^NI`bL1(K9bZO=uA^7h8^6NG)n1+firVjB3~A%Dd4|{xs@q==De_aK9_~1KDN! z+m#21qKRap0_CEPs0g)%OI`UIWO240wdGByiL{~O??djNeTtg!H>kVf4Uy0#NoJYs zip@s-a+M>CwspwySrck!4&cLh1a%q5umnR~j4-~08t7xx*YOov0UL1^zKI913$^8sbHfyG z3uMH=$z G{r>^F`~*k< diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po index 63f6973..cbab7b4 100644 --- a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po @@ -615,3 +615,15 @@ msgstr "All available models already downloaded" #. TODO msgid "tab:rank_est" msgstr "Rank Estimate" + +#. TODO +msgid "game re-analysis" +msgstr "Re-analyzing entire game using {visits} visits" + +#. TODO +msgid "analysis:game" +msgstr "Re-analyze entire game" + +#. TODO +msgid "analysis:continuous" +msgstr "Continuously analyze" diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo index 27579736cbce6c6f0097fcc330e317396327662b..c6ca5caed7b04233d17b29435605f641c0701937 100644 GIT binary patch delta 2932 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 Ef6Kf7F#rGn diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po index 1a7ac7a..518a233 100644 --- a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po @@ -98,6 +98,15 @@ msgstr "Equalize visits of potential moves" msgid "analysis:sweep" msgstr "Fast analysis of all moves" +msgid "analysis:sweep" +msgstr "Fast analysis of all moves" + +msgid "analysis:game" +msgstr "Re-analyze entire game" + +msgid "analysis:continuous" +msgstr "Continuously analyze" + msgid "analysis:aimove" msgstr "Force AI Move" @@ -113,6 +122,10 @@ msgstr "Analyzing entire board using {visits} visits/move" msgid "equalizing analysis" msgstr "Equalizing analysis of candidate moves to {visits} visits" +#. status message +msgid "game re-analysis" +msgstr "Re-analyzing entire game using {visits} visits" + #. right panel buttons and below board msgid "pass-button-text" msgstr "Pass" diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo index 0e7f4e4e210576e4f816bb097ec0fdcaf3216582..74e83dfb6a04349f2bac509c30fcade708e02abe 100644 GIT binary patch delta 2909 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 F{{#8u0EGYm diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po index 9246ba7..0ee6641 100644 --- a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po @@ -610,3 +610,15 @@ msgstr "All available models already downloaded" #. TODO msgid "tab:rank_est" msgstr "Rank Estimate" + +#. TODO +msgid "game re-analysis" +msgstr "Re-analyzing entire game using {visits} visits" + +#. TODO +msgid "analysis:game" +msgstr "Re-analyze entire game" + +#. TODO +msgid "analysis:continuous" +msgstr "Continuously analyze" diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo index 857c300f1918d753eb53fe2a3fd0b3112bf18ef2..145fba755fd584670ccc431b151308c39515b35e 100644 GIT binary patch delta 2908 zcmYk-4NO*59LMoLpec{Mh4_LAKBl6`OZk#vOZ#uX*HZ;jn-Ub-yhGJ<*xU0&b{aU&;R_-y$|mP zelGI5;)m23|62Gzp8uuF!EFPy~96o`=@Og~E zWf+SE?tXbl&)%Xz4BL$g(1_7^1V`gBjKcFc1aF`c`VBSy9*)C!1|?tyYQhDmaoMh4 zj9S1ZjKr;&V4m478WCJ*Mq*j38}Jz_v9D1pIfDv(&RxHb3Utf)7wY+Nve&p6)IyRt zXnd8h}=ki*_^*K1G@?ng~@1U1nKs6AWm`lYA|tDU=0rEGHd4-W2U z1VL!pXw*VdP>E*6QGZSFEGH$f0w2P4sM2jiWxm(hh8)((Nr|69RpJ6_rPqOkxKplw z2{q9T)WmmCi4Gt+dKOPz^fF9F1xQB?oP&CBfxEsG6*w1p)=FK!3N?NQD)D;Mb00Z7 zP!&3kO85s9dl46- z5^2C?*ox_R5A_qD&KpQ*dk#JALs3+6xxbslBmD-ehC-!cjqN%9%@VSQ4g#|ZAB0@ z-b3x>yQn>X51Viw7UP0sD#HQ}VIrQMz_Zxn=MNI*KFk*2fl1UqiN@K7R& zKUU%Z8>KzWOXWSl)u_FE6ScBBB(}An-hordZrEj1MgBllE_{0EcO@RRg_)@Fi#!^c zH1g1g+fgg3L#@0CwcGHl^jI9oFBXE9jMZNfmGFgL=GF^#I9KyLCDRrQSqucX<=>X!wyvZ zW2m?%2d}gLXK2K5;XJC8zaa02-NhN0^hoIXQd9{GQGwQ=_OKkI@g0oCTGYGJgsMa* zD)EyTg+f{#K~1n1r{SllM6V!U zvh`pJ{)4Q^rm!l0dTlv+T4@mtm1G<0Gu+|ajhe6?`55egvl-{mZ$UkG6*aEM_4`l> z_oEi}H!7}gW@u~UFpmDXnf8?Jqmjl1mE>Dg<`-T6nzPS&5A|-u%;HA`$Ifc6^p!;T z3fGryEGes4UlFXm7(I7LasIkM`|y}Rc$B|9kda@Ozjkx5B-p+>u48P%!uvzlZuSq} zstnYQPhB)B$BnEE_ya^M5BLewUXbcVWZu73Qda!Gn*o1CP*+~xR1z%N7_9P#Y`>cR H`Otp>Mc_P2 delta 2741 zcmYM#drXye9LMpmT%>c*(}|#vi+~tMkP~^_0w)@2VJ&A0|2UA@j6`(i%8WH-kK!NM zD!eeQG0_&;h0Tl|wqkA4RkGR=Gi}shwj~yoOE%C<*ZafyZMnzi^}Bw5*Yj|G{p(M8 zLn#Rdjel?RKa2m%6O}XD;s4(>lFj7rz&1?BW6qP9L;eD$-~_JVa7uEhd%)nnT6>p*jOrew7<)QMWs0CD_ z4_9NVd1mWK#8c3O^tJ7-!HcMg?MJQTFe=bnt~`ngbkg}1s{dJ3yCKv<{&e*>kjD}! zoSYygGrr}K&`QFtq5^sBac3QB>o%d?2-^-;dhC3#fL( zsG}VXu>U%vI9jP79W`KyvjVlnt6Y68^4PPyScJ8pCcMw(J6*mTdF%^bTF5!nL@%M@ zkDz zSAPYygWh%5Ac-gnl*&sDL#PRrp=P`YbySt8f!3hft#|n*)IhsX1Gl0k+KvoiCs23b zQ&j)cNITEIC!vnRuHp|=;Bn-!B=%GJ3{>E`sF|0d`aSHdLhaC6)P$c#^=m>6xCeEF z2T=VwCd)bhE)ojdgX-`JYNZ3HfFrJa949BlvUFq_sQMyQzy-)-mAp1#Erzfk>1tQ7 z0OJY14vX+f{r+DiQHFnGzA8BQ8Mp$K-+;OJ3NFCoxDrQD6DhmftQsG|Vmyrcl%GaI z+9lLc{)^2xg`2qrJ@l4NVP+(@Q81tHr~-u0?bC8c^Er!AvWMJ z=CXigGubiRkYlzLTXOjc<9HrNfJ@lMY}`1Tad6K)+y&fQ!2Z{fXy>4GhS%npu}hX% z7+X;`64U0P&VChY2kKEfwH5XK*o``}qo{VhcrSj7THp=T0;U$l7MhA$V5UbxE6jHl z#i%ozkJ{2l(T8=Yfg4a0eI9jbccTLB#ku$zs@>VGo3A8x6uB56A4cw>m81ICq9)LYtkIg0 zPk?oy#_K~rTL3-Q4!e!C1!shts(eQ_f F{{ca-1A71f diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po index 21e3d57..7e66bcf 100644 --- a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po @@ -649,3 +649,15 @@ msgstr "All available models already downloaded" #. TODO msgid "tab:rank_est" msgstr "Rank Estimate" + +#. TODO +msgid "game re-analysis" +msgstr "Re-analyzing entire game using {visits} visits" + +#. TODO +msgid "analysis:game" +msgstr "Re-analyze entire game" + +#. TODO +msgid "analysis:continuous" +msgstr "Continuously analyze" diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo index 473ea528d5d3b8ee043c614f7b1c504937fedc89..34593263bbff9aa18d762a16502e385c7ba073f1 100644 GIT binary patch delta 2908 zcmYk-3ry8z7{~DkAumTkB|g{@?%eKJWAX&*Ao> zBZa}m#;Zfd5w@7Wx7;@3{BR zp%%J=TDTRJ=q)72z>=wpUWS3F0NJRCV^9xH_3@dgz-kYvRp;{01=4 z@&Mmm`lC6ZjW{49me8lj$4+ul1=^6RS}4;ji~~^pX{Zv-?gs&!MqWaIE&b}B`iI0%evCojI+ga?2f8kKj$D55@xbMV)y%>MSQ<2&bctW|rU2 zN8UrLL~U#{D&8(kV|_bJLnUiOy)?h#Ec_c4=<(sPh4WAW3Q#*MM=iV(^#k-a>S#9k z{U1?B)QID;8K+=oPAu+X3}!I!It^w1t$P}K(m#*daWkr9H(eXS-v{~$s6_JJLbntZ zZ#nAuRo-9k{Vk~9g*_wqeXF5S&wxrYkFSDuRE(NXjta0IRqE}i9Ua6nJW*E}Dvl4m zQd&}8R8mn|5#7+3I4+?ee`%zyb5f*3w{TfxXnslllFDdNw63s6R_~N)ccw0>47cA} z5!vv5Ms8}Z&s-4+M~GGy3Acx7%BYPWcIRGENx}c`hAW~PS-HF@T2vmb3dgLg&c4?1 EU$F!}UH||9 delta 2740 zcmYM#drZ}39LMo*0wRY?gir*Co=mPHgiRzbBQ1)u5}lg^3u~kSTNkXc68n|^SOI4S z(-kVKuo_qy9fmds*hR70N{r0~1y)ry{9e!f`97EPv;UEQH+ka; z;k%4~Z}FGL->L}Z%o=_Fn?!OO`D?HZlkrXG0L~_V8WZpm#-lIVED}>O4(H<4Scs}$ z5z@1@B0i_s) z>oL(hvxi88QP6<&wXLqf4pd+-qgJvPHPC)n9z+c^;5>oqe+t!Z9JP?&T>an3V-XZi z1&GniZ+;S5NuH}HK_0u$c|U6F>QL>rqE-@c^{=6}^gUF-gQ)hOyZTX7`?IKt|3Zxu zMaEOZG!m+q&r20cP#x|?4ZIOG@Ds>mjV|AWTG4LQ$~$oe9z-5H%uC}Bp?2^zs@(+Y zXn&1k|8+*wXr+Q=)P(uY64Vx#yZTDxv3g!C!nUIV-s$q4F5iPZ_9-tdWCRuHPpI)P zqUH;aXa7@4q*6K)i;&>#Zf6B5;u_QrJcU}}bI4;a^3p)bGyIob&G{p@Dl*9gd<_I*c0dXIDOnQvtCo9a##hJ_j{mG4fa`uR5&6OdLW5d>(V~ z3N~RDlWfuNzn8>v6>#7gxEVD;0R7m3v+yV`!Er3ZlNRSr5V(i3b{0 z#%EBMco|XaC~iaT&^@Rfu0S@^>Sl5NQ@7Anw77;Hr~wb4B0r8gyOY?1W4IPu(#`(B z@6pdfMrNC>!O1yhmnkp1!E7D*m2*SwUcHg~LcVz(yF>k{4EBE)i9rrZ5v|MO7_f#H zyJxL92fJPV3)Bw%h-&{Q`Y|dy6kskY;6=C)OI-PrIJHn`3u=ePe+CPhIhI#F-U=VfIW2m!F z$_-t*9AtOxPK?IQn1@?Xm-cnM1J7UyX3Y-`T!Wf0fHAn&<-44RQ1{P{k>Ig$URpsk zqc1`~YU1_I2Gq)SJNIEM`TeL%_<<|$L*4#9o)zx<*_sou94344h!dX2!8Gdr$)&Mh)aty;pdMp_UB23|w#>NvV0B#OwBX73dC6_Q?DAlJ_JOeAs=N!+{{vCX3xfav diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po index dce0fe4..9ce8469 100644 --- a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po @@ -587,3 +587,15 @@ msgstr "All available models already downloaded" #. TODO msgid "tab:rank_est" msgstr "Rank Estimate" + +#. TODO +msgid "game re-analysis" +msgstr "Re-analyzing entire game using {visits} visits" + +#. TODO +msgid "analysis:game" +msgstr "Re-analyze entire game" + +#. TODO +msgid "analysis:continuous" +msgstr "Continuously analyze" diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo index 7ad03ae74a5e4fd29ba2e5bf58720656e69857a1..70212a2629aa6dc8da1e6482cf9ed2d041d00f8a 100644 GIT binary patch delta 2910 zcmYk-e@s?Y9LMo5_yc?tB?OdEeEboDzwihCz!a6rEN4zi8cVj&o)APV9+a@?rr9zJ zY)Z@2hFi@vWo?5(eME>mhE#LK1K!hDQYDrQ3IcK?UzskU3K0-y&p;R>em;w zkip#4Z#2@yCSzBC*qixn5e2QJz&&^dxy*Ccpi;L3)o&kaC5PPe)2NhRL6Y_UtiNFF{SX&bbAZ%JpB zcc3y6!A%*9cc!2MPeWzE%cP)4=C}^Is8lX-9ZHbPD!HlOCR9K>Pyz2kZP{MbM4zDg z9e4E$sEID4ChkB5dJBosvpDji!!QChz!+4=DX157T>C=Q!1>6#R^sZ_sQ$I6z_+8` z+v7Zd%Fq#1z-Lh7T)i)CgxNBFS6`uin%Pq^k3SGY zu^Mx*9)H7D9EPU`n>~qtpng#oG0QkyjFF7D0dMm z%F|g{B&i)`wt{-pSXO{x!q)_quGy)ZzLW z*JF>#{-NH8Vd{UO4sQvAvBjQ!L_vq-1nMl@#5jze>IamJY@dzB*|-pA;G3w4PvKll zoW|b~EI{V4&8QWAhRR$EYAb$p?Kd$_-+yG5zgG#!`LGG70rD^h-@pO*B`TG_pazbb z?!Om{+M+SoAG2^ME^yCRx%%rEPkRGu!ACHK`K^P3QXhM-SrCJmk8>~$cc9L~F;r&0 zKxOJ8>ht{_hv5y>VN51HdLF_gtU!`vwYV5RM=fL!KOWvv3Nt9^^Q(6@qgMPKYDIry zBt~WX25<%S7*r;zFcH_ICVmI?{vM3M!>E2OsPWDtG1!%C%eHn3X|w#plrY;*$fpk-HQrWhMJ=Lh9dg}%R^%e z%L+@Y!o}gH<+0aO;_tiDwX`bO^=NHqYko#uVy^4CHWUmoT6rkgHB5WP!k**qJS#3M b`v245>abd#UsD_|t_W8LeVe9FDGB@w4c$tQ delta 2742 zcmYM#drZ}39LMpmh#(knKoq%i#6$tbydeTfXcXae!5WHD$ed0LGOU$Jo$}UrZ+*~?g=_;p^TP_>5X73N@*>XS5>-jy`@AG}0b3ETZp=Z|w zZ#XXWCF9>a{H5@>dXnDE+9LmV8pX$XkHs#Wfp0sHVmj}qF%JJiA4X0w3&&)P#!QUC zg{bz@pq^DxV2o`-P4FN_VLML8eRvc0VkjO)1@tMZ|4B^3^B9l+q85x}kox&i@A;?= zlwky}!+7(|>M2a5q6Hait**nQVF zM$Hq!i>Hn$6x5K*hZ+h|1Ju0u`y5b{`?d*6fF(aWfvAH>Oc2zl%iJ~aO^R0dC@ z`i-ED_Hs1&*BOP-OBFLv3+6dXP$|CKwQoQkYvzMZSUW1*qroIfV-J zSJeF1Q0s;I$bT}0WNN2kJ`$W&IBQT5Z$)L`5!4Q!L>_yV4^7mKx_o`8dA>n?OU@vV zU2}#KjmA%Nrg;InCv#=SmX&-uScK@%TF4fq_j(?QgPzq$JBI35t&(vc;h+Ots;79fw6@v#LrU>1Ib zx(kmsQu$55y{KQ# z-*GJU1?z~m1y2&)t9ZvO{_w)h{9z8~(2Oy22`+@pX7g*xdrz*%4k-8tUjf9E zw|SV0tYtN*qj?PHU_a`JMp1!9EC{AL6Lr>AsJqjOI*LxzyyuYXV}GJD6LCxM&iFhE zJ}OdO#eCF&LL_*rLS4F@uKsz{CF(&1_>-$2L*};Fg~61UqB2>5F<6H>+U>X$4`4NV zCtStsMP`*$)FM^(0p5uhkZf5ltLidUV>>>Iy2WF-1Gg**Ue>dyBh6qHop~X~;a#Xe zYB3WVv4r)llR^;{L#PE~^7#tkI%HG!CNifDqP`X3%Yqs6p^m_hsxL$>vO$<9d7w=ioWi9SAEVVAi)N3QAQ5Mq(b$#O0{VSL@n)F_HI=k!08a-h-1#pYFna zSdUMjzGgo=Z=iOZS{&Ta5)9$J96c3QQ`m-UQJFY`)3F~F*jdyBmoN%LZVmSHp)!<+ ztYP!;3O1nbQ1yynCZ9vyp#!K({UzRp7gsR7&MdoxUp*`iyc^jtu`4S&FR&?kLr7r2 VH=5Qpl-Ct#Td*c9Ft&7S?c}%8UUL1O-GuMIp@~ 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 A00000 literal 0 HcmV?d00001 From 34c067b931c03f3e981543d96ae0d139600e3659 Mon Sep 17 00:00:00 2001 From: bale-go Date: Thu, 25 Jun 2020 10:33:56 +0200 Subject: [PATCH 11/77] move_rank_cap --- katrain/gui/widgets/graph.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index 691d5ff..6fa267c 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -191,6 +191,11 @@ class RankGraph(Graph): if not non_obvious_moves: return None num_legal, rank, value = zip(*non_obvious_moves) + rank = list(rank) + for (i, item) in enumerate(rank): + if item > num_legal[i]*0.07: + rank[i] = num_legal[i]*0.07 + rank = tuple(rank) averagemod_rank = averagemod(rank) averagemod_len_legal = averagemod(num_legal) # the averagemod_rank is the outlier free average of the best move from a selection of n_moves with averagemod_len_legal of total legal moves @@ -226,7 +231,8 @@ class RankGraph(Graph): for segment_mid in range(0, len(nodes), dx): bounds = (max(0, segment_mid - half_seg), min(segment_mid + half_seg, len(nodes))) for pl, rank in self.calculate_ranks(policy_stats[bounds[0] : bounds[1] + 1], num_intersec).items(): - ranks[pl].append((segment_mid, rank)) + if bounds[1]-bounds[0]>self.segment_length * .75: + ranks[pl].append((segment_mid, rank)) self.rank_by_player = ranks self.redraw_trigger() From bfdee37b5e8df288aabc7e192a1024fcfbf09a76 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Fri, 26 Jun 2020 00:01:25 +0200 Subject: [PATCH 12/77] track requested visits better --- katrain/core/engine.py | 1 + katrain/core/game.py | 2 +- katrain/core/game_node.py | 3 --- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/katrain/core/engine.py b/katrain/core/engine.py index 875a9ff..b0c81fc 100644 --- a/katrain/core/engine.py +++ b/katrain/core/engine.py @@ -239,3 +239,4 @@ class KataGoEngine: "overrideSettings": settings, } self.send_query(query, callback, error_callback, next_move) + analysis_node.analysis_visits_requested = max(analysis_node.analysis_visits_requested,visits) \ No newline at end of file diff --git a/katrain/core/game.py b/katrain/core/game.py index 4e9f753..1f9291e 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -324,7 +324,7 @@ class Game: engine = self.engines[cn.next_player] if mode == "extra": if kwargs.get("continuous", False): - visits = max(engine.config["fast_visits"], math.ceil(cn.analysis_visits_requested * 1.1)) + visits = max(engine.config["fast_visits"], math.ceil(cn.analysis_visits_requested * 1.25)) else: visits = cn.analysis_visits_requested + engine.config["fast_visits"] diff --git a/katrain/core/game_node.py b/katrain/core/game_node.py index a0e31e3..74decf9 100644 --- a/katrain/core/game_node.py +++ b/katrain/core/game_node.py @@ -69,9 +69,6 @@ class GameNode(SGFNode): # various analysis functions def analyze(self, engine, priority=0, visits=None, time_limit=True, refine_move=None, analyze_fast=False): - if not refine_move: - req_visits = visits or (engine.config["fast_visits"] if analyze_fast else engine.config["max_visits"]) - self.analysis_visits_requested = max(req_visits, self.analysis_visits_requested) engine.request_analysis( self, lambda result: self.set_analysis(result, refine_move), From dcbc02fe0ad0bc0b21c58f04e34b3c6ff2671920 Mon Sep 17 00:00:00 2001 From: bale-go Date: Fri, 26 Jun 2020 17:51:30 +0200 Subject: [PATCH 13/77] user_data_based_AI --- katrain/core/ai.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/katrain/core/ai.py b/katrain/core/ai.py index ffcbb59..3e8bb92 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -143,7 +143,7 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, if ai_mode != AI_RANK: n_moves = int(ai_settings["pick_frac"] * len(legal_policy_moves) + ai_settings["pick_n"]) else: - n_moves = int(round(board_squares / 361 * 10 ** (-0.05737 * ai_settings["kyu_rank"] + 1.9482))) + n_moves = int(round(board_squares / 361 * len(legal_policy_moves)/(1.311648546930214*((0.31164467+0.55726218*(len(legal_policy_moves)/board_squares)*math.exp(-1*(3.0308747*(len(legal_policy_moves)/board_squares)*(len(legal_policy_moves)/board_squares)-(len(legal_policy_moves)/board_squares)-0.045792218*ai_settings["kyu_rank"]-0.31164467)**2)-0.0064860256*ai_settings["kyu_rank"])*(0.0630149+0.762399*board_squares/(10**(-0.05737*ai_settings["kyu_rank"]+1.9482))))-0.08265346672884874))) if ai_mode in [AI_INFLUENCE, AI_TERRITORY, AI_LOCAL, AI_TENUKI]: if cn.depth > ai_settings["endgame"] * board_squares: From 0afe2202be1571dca0e8a866b17dab2f7914b9ac Mon Sep 17 00:00:00 2001 From: bale-go Date: Fri, 26 Jun 2020 18:01:15 +0200 Subject: [PATCH 14/77] user_data_based_AI_based_rank_estimation --- katrain/gui/widgets/graph.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index 6fa267c..c1a75cc 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -193,15 +193,15 @@ class RankGraph(Graph): num_legal, rank, value = zip(*non_obvious_moves) rank = list(rank) for (i, item) in enumerate(rank): - if item > num_legal[i]*0.07: - rank[i] = num_legal[i]*0.07 + if item > num_legal[i]*0.09: + rank[i] = num_legal[i]*0.09 rank = tuple(rank) averagemod_rank = averagemod(rank) averagemod_len_legal = averagemod(num_legal) # the averagemod_rank is the outlier free average of the best move from a selection of n_moves with averagemod_len_legal of total legal moves - n_moves = math.floor(0.40220696 + averagemod_len_legal / (1.313341 * (averagemod_rank + 1) - 0.088646986)) - # using the calibration curve of p:pick:rank - rank_kyu = (math.log10(n_moves * 361 / num_intersec) - 1.9482) / -0.05737 + rank_kyu = -0.62842816*math.log(averagemod_rank)/(0.17050253+averagemod_rank*math.exp(-1*(3.373914*(averagemod_len_legal/num_intersec))**2))+13.588577*(averagemod_len_legal/num_intersec)+10.405252*math.log(averagemod_rank)+12.417778*math.exp(-1*(2.5190649*(averagemod_len_legal/num_intersec))**2)-14.579846 + if rank_kyu<-10: + rank_kyu=-10 return 1 - rank_kyu # dan rank @staticmethod From 9778708c0a1d995294bbb84fe187767ce302931e Mon Sep 17 00:00:00 2001 From: sergio villegas Date: Fri, 26 Jun 2020 11:54:51 -0500 Subject: [PATCH 15/77] Added TODO es translations, improved explanations of AI styles --- .../i18n/locales/es/LC_MESSAGES/katrain.po | 35 +++++++++---------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po index 0ee6641..b92652a 100644 --- a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po @@ -23,7 +23,7 @@ msgid "menu:settings" msgstr "Conf General y Motores" msgid "menu:clocksettings" -msgstr "Conf de Reloj" +msgstr "Conf Reloj" msgid "menu:teachsettings" msgstr "Conf Juego Enseñanza" @@ -469,7 +469,7 @@ msgid "aihelp:default" msgstr "" "Fuerza máxima de KataGo IA. La fuerza es afectada por el número `máximo " "visitas` y `modelo` en conf general del `motor` y el archivo de conf del " -"motor. No hay opciones disponibles acá." +"motor. No hay opciones disponibles acá." msgid "ai:jigo" msgstr "KataJigo" @@ -479,12 +479,12 @@ msgstr "" "Trata de ganar por una cantidad de puntos (default 0.5), sin restriciones." msgid "ai:scoreloss" -msgstr "Perdia de puntos" +msgstr "Perdida de puntos" msgid "aihelp:scoreloss" msgstr "" -"Jugadas que conllevan a mayor pérdida de puntos con una menor probabilidad. " -"Probablemente lleva a jugada mas variadasçdebiles con un mayor número de " +"Las jugadas que conllevan a mayor pérdida de puntos se juegan con una menor probabilidad. " +"Probablemente juega mas variado/débil con un mayor número de " "visitas." msgid "ai:policy" @@ -492,8 +492,8 @@ msgstr "Jugadas intuitivas" msgid "aihelp:policy" msgstr "" -"Juega la jugada que se considera la más intuivida sin leer. La fuerza esta " -"dada por el `model` en la conf del motor. El parámetro opening_moves afecta" +"Juega la jugada que se considera la más intuitvda sin leer. La fuerza esta " +"dada por el `model` en la conf del motor. El parámetro `opening_moves` afecta" " cuantas jugadas en la apertura son al azar." msgid "ai:p:weighted" @@ -513,8 +513,8 @@ msgstr "Politica Ciega" msgid "aihelp:p:pick" msgstr "" "Escoge `pick_n + pick_frac * <#jugadas legales>` al azar y juega la " -"mejor.For `pick_frac=1` juega como la politica intuitia del IA. Juega la " -"mejor jugada si la probabilidad de la politica es por encima de " +"mejor.El `pick_frac=1` juega como la politica intuitiva del IA. Juega la " +"mejor jugada si la probabilidad del IA es por encima de " "`pick_override`. Esto con el fin de prevenir errores obvios." msgid "ai:p:local" @@ -558,8 +558,8 @@ msgstr "" msgid "aihelp:p:rank" msgstr "" "Seleciona jugadas al azar de una limitada selección y juega la " -"mejorConfiguraciones más fuertes amplia su selección y juega la mejor " -"jugada. Como no hay 0 kyu/dan, 3 dan = -2 kyu." +"mejor. Configuraciones más fuertes toma la mejor jugada de una selección más amplia " +"de jugadas. Como no hay 0 kyu/dan, 3 dan = -2 kyu." msgid "Kata model not found" msgstr "El modelo KataGo {model} no existe, revisar conf general." @@ -580,7 +580,7 @@ msgid "tab:info-details" msgstr "Detalles" msgid "Kata exe not found" -msgstr "El binario KataGo {exe} no existe, revisar conf general." +msgstr "El binario KataGo {exe} no existe, revisar conf general."f msgid "sweep analysis" msgstr "Analizando tablero entero usando {visits} visitas/jugada" @@ -595,21 +595,18 @@ msgstr "" msgid "extra analysis" msgstr "Realizar análisis adicional a {visits} visitas" -#. TODO msgid "models available" -msgstr "{num} available model(s) found" +msgstr "{num} de modelo(s) disponibles" -#. TODO msgid "download models button" -msgstr "Download Models" +msgstr "Descargar Modelos" -#. TODO msgid "All models downloaded" -msgstr "All available models already downloaded" +msgstr "Todos los modelos fueron descargados" #. TODO msgid "tab:rank_est" -msgstr "Rank Estimate" +msgstr "Estimación Rango" #. TODO msgid "game re-analysis" From 0cc3b437e23b8415879d2777e9a42a2256ff9d08 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Fri, 26 Jun 2020 19:29:19 +0200 Subject: [PATCH 16/77] try ffpyplayer --- katrain/__main__.py | 2 +- setup.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/katrain/__main__.py b/katrain/__main__.py index 1034003..f833d5a 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -3,7 +3,7 @@ import os os.environ["KCFG_KIVY_LOG_LEVEL"] = os.environ.get("KCFG_KIVY_LOG_LEVEL", "warning") -os.environ["KIVY_AUDIO"] = "sdl2" # force working audio +os.environ["KIVY_AUDIO"] = "ffpyplayer" # force working audio # next, icon from katrain.core.utils import find_package_resource, PATHS diff --git a/setup.py b/setup.py index 518719c..0519510 100644 --- a/setup.py +++ b/setup.py @@ -41,12 +41,13 @@ setup( "wheel", "setuptools", "importlib_resources ;python_version<'3.7'", - "pygame", # some versions need this for kivy + "pygame", # some mac versions need this for kivy "cython>=0.24,<=0.29.14,!=0.27,!=0.27.2", # kivy wants this "kivy_deps.glew;platform_system=='Windows'", "kivy_deps.sdl2;platform_system=='Windows'", "kivy_deps.gstreamer;platform_system=='Windows'", - "kivy>=2.0.0rc2", + "kivy==2.0.0rc2", # rc3 failing on mac + "ffpyplayer>=4.3.1", "kivymd>=0.104.1", "screeninfo;platform_system!='Darwin'", # for screen resolution, has problems on macos ], From 20809d7c4d6fdab14d1922c58ab8ccb9bd36e362 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Fri, 26 Jun 2020 21:57:09 +0200 Subject: [PATCH 17/77] Implements PDA strategy / issue #102 --- katrain/__main__.py | 11 +++- katrain/config.json | 4 ++ katrain/core/ai.py | 54 +++++++++++++++++- katrain/core/constants.py | 22 ++----- katrain/core/engine.py | 15 +++-- katrain/gui/popups.py | 16 ++++-- .../i18n/locales/cn/LC_MESSAGES/katrain.mo | Bin 10707 -> 11005 bytes .../i18n/locales/cn/LC_MESSAGES/katrain.po | 12 ++++ .../i18n/locales/de/LC_MESSAGES/katrain.mo | Bin 12531 -> 12829 bytes .../i18n/locales/de/LC_MESSAGES/katrain.po | 12 ++++ .../i18n/locales/en/LC_MESSAGES/katrain.mo | Bin 11681 -> 11980 bytes .../i18n/locales/en/LC_MESSAGES/katrain.po | 10 ++++ .../i18n/locales/es/LC_MESSAGES/katrain.mo | Bin 12429 -> 12727 bytes .../i18n/locales/es/LC_MESSAGES/katrain.po | 12 ++++ .../i18n/locales/fr/LC_MESSAGES/katrain.mo | Bin 13142 -> 13440 bytes .../i18n/locales/fr/LC_MESSAGES/katrain.po | 12 ++++ .../i18n/locales/ko/LC_MESSAGES/katrain.mo | Bin 13373 -> 13671 bytes .../i18n/locales/ko/LC_MESSAGES/katrain.po | 12 ++++ .../i18n/locales/ru/LC_MESSAGES/katrain.mo | Bin 16738 -> 17036 bytes .../i18n/locales/ru/LC_MESSAGES/katrain.po | 12 ++++ setup.py | 3 +- 21 files changed, 172 insertions(+), 35 deletions(-) diff --git a/katrain/__main__.py b/katrain/__main__.py index f833d5a..76961c8 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -3,7 +3,8 @@ import os os.environ["KCFG_KIVY_LOG_LEVEL"] = os.environ.get("KCFG_KIVY_LOG_LEVEL", "warning") -os.environ["KIVY_AUDIO"] = "ffpyplayer" # force working audio +if "KIVY_AUDIO" not in os.environ: + os.environ["KIVY_AUDIO"] = "sdl2" # seems to be most stable / some players hard crash # next, icon from katrain.core.utils import find_package_resource, PATHS @@ -161,7 +162,10 @@ class KaTrainGui(Screen, KaTrainBase): # update move tree self.controls.move_tree.current_node = self.game.current_node - def update_state( + def update_state(self, redraw_board=False): # redirect to message queue thread + self("update_state", redraw_board=redraw_board) + + def _do_update_state( self, redraw_board=False ): # is called after every message and on receiving analyses and config changes # AI and Trainer/auto-undo handlers @@ -214,7 +218,8 @@ class KaTrainGui(Screen, KaTrainBase): continue fn = getattr(self, f"_do_{msg.replace('-','_')}") fn(*args, **kwargs) - self.update_state() + if msg != "update_state": + self._do_update_state() except Exception as exc: self.log(f"Exception in processing message {msg} {args}: {exc}", OUTPUT_ERROR) traceback.print_exc() diff --git a/katrain/config.json b/katrain/config.json index 8b111d4..7b18d4f 100644 --- a/katrain/config.json +++ b/katrain/config.json @@ -69,6 +69,10 @@ }, "ai": { "ai:default": {}, + "ai:handicap": { + "automatic": true, + "pda": 0 + }, "ai:jigo": { "target_score": 0.5 }, diff --git a/katrain/core/ai.py b/katrain/core/ai.py index ffcbb59..6918295 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -21,6 +21,8 @@ from katrain.core.constants import ( AI_TERRITORY, AI_PICK, AI_RANK, + AI_HANDICAP, + OUTPUT_ERROR, ) from katrain.core.game import Game, GameNode, Move @@ -93,8 +95,51 @@ def generate_local_tenuki_weights(ai_mode, ai_settings, policy_grid, cn, size): return weighted_coords, ai_thoughts +def request_ai_analysis(game: Game, cn: GameNode, extra_settings: Dict) -> Dict: + error = False + analysis = None + + def set_analysis(a): + nonlocal analysis + analysis = a["moveInfos"] + + def set_error(a): + nonlocal error + game.katrain.log("Error in PDA-based analysis", a) + error = True + + engine = game.engines[cn.player] + engine.request_analysis( + cn, + callback=set_analysis, + error_callback=set_error, + priority=1_000, + ownership=False, + extra_settings=extra_settings, + ) + while not (error or analysis): + time.sleep(0.01) + engine.check_alive(exception_if_dead=True) + return analysis + + def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, GameNode]: cn = game.current_node + + if ai_mode == AI_HANDICAP: + pda = ai_settings["pda"] + if ai_settings["automatic"]: + n_handicaps = len(game.root.get_list_property("AB")) + MOVE_VALUE = 14 # could be rules dependent + b_stones_advantage = max(n_handicaps - 1, 0) - (cn.komi - MOVE_VALUE/2) / MOVE_VALUE + pda = min(3, max(-3, b_stones_advantage * (3/8) )) # max PDA at 8 stone adv, normal 9 stone game is 8.46 + handicap_analysis = request_ai_analysis( + game, cn, {"playoutDoublingAdvantage": pda, "playoutDoublingAdvantagePla": "BLACK"} + ) + if not handicap_analysis: + game.katrain.log(f"Error getting handicap-based move", OUTPUT_ERROR) + ai_mode = AI_DEFAULT + while not cn.analysis_ready: time.sleep(0.01) game.engines[cn.next_player].check_alive(exception_if_dead=True) @@ -187,7 +232,12 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, else: raise ValueError(f"Unknown Policy-based AI mode {ai_mode}") else: # Engine based move + handicap_or_default = "Default" candidate_ai_moves = cn.candidate_moves + if ai_mode == AI_HANDICAP: + candidate_ai_moves = handicap_analysis + handicap_or_default = "Handicap" + top_cand = Move.from_gtp(candidate_ai_moves[0]["move"], player=cn.next_player) if top_cand.is_pass: # don't play suicidal to balance score - pass when it's best aimove = top_cand @@ -214,11 +264,11 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, aimove = topmove[2] ai_thoughts += f"ScoreLoss strategy found {len(candidate_ai_moves)} candidate moves (best {top_cand.gtp()}) and chose {aimove.gtp()} (weight {topmove[1]:.3f}, point loss {topmove[0]:.1f}) based on score weights." else: - if ai_mode != AI_DEFAULT: + if ai_mode not in [AI_DEFAULT, AI_HANDICAP]: game.katrain.log(f"Unknown AI mode {ai_mode} or policy missing, using default.", OUTPUT_INFO) ai_thoughts += f"Strategy {ai_mode} not found or unexpected fallback." aimove = top_cand - ai_thoughts += f"Default strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move" + ai_thoughts += f"{handicap_or_default} strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move" game.katrain.log(f"AI thoughts: {ai_thoughts}", OUTPUT_DEBUG) played_node = game.play(aimove) played_node.ai_thoughts = ai_thoughts diff --git a/katrain/core/constants.py b/katrain/core/constants.py index 1e5c0fe..f90a925 100644 --- a/katrain/core/constants.py +++ b/katrain/core/constants.py @@ -1,6 +1,6 @@ VERSION = "1.3.0" HOMEPAGE = "https://github.com/sanderland/katrain" -CONFIG_MIN_VERSION = "1.2.0" # keep config files from this version +CONFIG_MIN_VERSION = "1.3.0" # keep config files from this version OUTPUT_ERROR = -1 OUTPUT_KATAGO_STDERR = -0.5 @@ -17,6 +17,7 @@ GAME_TYPES = [PLAYING_NORMAL, PLAYING_TEACHING] MODE_PLAY, MODE_ANALYZE = "play", "analyze" AI_DEFAULT = "ai:default" +AI_HANDICAP = "ai:handicap" AI_SCORELOSS = "ai:scoreloss" AI_WEIGHTED = "ai:p:weighted" AI_JIGO = "ai:jigo" @@ -30,13 +31,14 @@ AI_RANK = "ai:p:rank" AI_CONFIG_DEFAULT = AI_SCORELOSS -AI_STRATEGIES_ENGINE = [AI_DEFAULT, AI_SCORELOSS, AI_JIGO] +AI_STRATEGIES_ENGINE = [AI_DEFAULT, AI_HANDICAP, AI_SCORELOSS, AI_JIGO] AI_STRATEGIES_PICK = [AI_PICK, AI_LOCAL, AI_TENUKI, AI_INFLUENCE, AI_TERRITORY, AI_RANK] AI_STRATEGIES_POLICY = [AI_WEIGHTED, AI_POLICY] + AI_STRATEGIES_PICK AI_STRATEGIES = AI_STRATEGIES_ENGINE + AI_STRATEGIES_POLICY AI_STRATEGIES_RECOMMENDED_ORDER = [ AI_DEFAULT, AI_RANK, + AI_HANDICAP, AI_SCORELOSS, AI_POLICY, AI_WEIGHTED, @@ -49,20 +51,6 @@ AI_STRATEGIES_RECOMMENDED_ORDER = [ ] -AI_STRENGTH = { # not used - AI_DEFAULT: "9d", - AI_POLICY: "4d", - AI_JIGO: "?d", - AI_SCORELOSS: "5k", - AI_WEIGHTED: "5k", - AI_PICK: "8k", - AI_LOCAL: "5k", - AI_TENUKI: "8k", - AI_INFLUENCE: "8k", - AI_TERRITORY: "5k", - AI_RANK: "15k - 3d", -} - AI_OPTION_VALUES = { "kyu_rank": [(k, f"{k}[strength:kyu]") for k in range(15, 0, -1)] + [(k, f"{1-k}[strength:dan]") for k in range(0, -3, -1)], @@ -77,4 +65,6 @@ AI_OPTION_VALUES = { "stddev": [x / 2 for x in range(21)], "line_weight": range(0, 11), "threshold": [2, 2.5, 3, 3.5, 4, 4.5], + "automatic": "bool", + "pda": [(x / 10, f"{'W' if x<0 else 'B'}+{abs(x/10):.1f}") for x in range(-30, 31)], } diff --git a/katrain/core/engine.py b/katrain/core/engine.py index b0c81fc..69065be 100644 --- a/katrain/core/engine.py +++ b/katrain/core/engine.py @@ -5,7 +5,7 @@ import subprocess import threading import time import traceback -from typing import Callable, Optional +from typing import Callable, Optional, Dict from katrain.core.constants import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_KATAGO_STDERR from katrain.core.game_node import GameNode @@ -206,9 +206,11 @@ class KataGoEngine: time_limit=True, priority: int = 0, ownership: Optional[bool] = None, - next_move=None, + next_move: Optional[GameNode] = None, + extra_settings: Optional[Dict] = None, ): - moves = [m for node in analysis_node.nodes_from_root for m in node.move_with_placements] + moves = [m for node in analysis_node.nodes_from_root for m in node.moves] + initial_stones = analysis_node.root.placements if next_move: moves.append(next_move) if ownership is None: @@ -233,10 +235,11 @@ class KataGoEngine: "komi": analysis_node.komi, "boardXSize": size_x, "boardYSize": size_y, - "includeOwnership": ownership, + "includeOwnership": ownership and not next_move, "includePolicy": not next_move, + "initialStones": [[m.player, m.gtp()] for m in initial_stones], "moves": [[m.player, m.gtp()] for m in moves], - "overrideSettings": settings, + "overrideSettings": {**settings, **(extra_settings or {})}, } self.send_query(query, callback, error_callback, next_move) - analysis_node.analysis_visits_requested = max(analysis_node.analysis_visits_requested,visits) \ No newline at end of file + analysis_node.analysis_visits_requested = max(analysis_node.analysis_visits_requested, visits) diff --git a/katrain/gui/popups.py b/katrain/gui/popups.py index 23d3ee0..5a7b618 100644 --- a/katrain/gui/popups.py +++ b/katrain/gui/popups.py @@ -322,12 +322,16 @@ class ConfigAIPopup(QuickConfigGui): self.options_grid.add_widget(DescriptionLabel(text=k, size_hint_x=0.25)) if k in AI_OPTION_VALUES: values = AI_OPTION_VALUES[k] - if isinstance(values[0], Tuple): # with descriptions, possibly language-specific - fixed_values = [(v, re.sub(r"\[(.*?)\]", lambda m: i18n._(m[1]), l)) for v, l in values] - else: # just numbers - fixed_values = [(v, str(v)) for v in values] - widget = LabelledSelectionSlider(values=fixed_values, input_property=f"ai/{strategy}/{k}") - widget.set_value(v) + if values == "bool": + widget = LabelledCheckBox(input_property=f"ai/{strategy}/{k}") + widget.active = v + else: + if isinstance(values[0], Tuple): # with descriptions, possibly language-specific + fixed_values = [(v, re.sub(r"\[(.*?)\]", lambda m: i18n._(m[1]), l)) for v, l in values] + else: # just numbers + fixed_values = [(v, str(v)) for v in values] + widget = LabelledSelectionSlider(values=fixed_values, input_property=f"ai/{strategy}/{k}") + widget.set_value(v) self.options_grid.add_widget(wrap_anchor(widget)) else: self.options_grid.add_widget( diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo index 837684882b0747d11e89cd065cf7682638d33f44..78189a9d0dec28c099c648fc0279353f8070faca 100644 GIT binary patch delta 3064 zcmYM#3v3is6vpuj6f6ZwLBvwfjy%e{P!d#>LNEkGqai#b5)GYpx7`if?y|cDT7?a$ z2tg>KMKmNt8c@JQ(h?1lY6L_JLR1pU+b1GXRE&t!p#DGhh-5my`@ZL%nOTm_ygoa1 zu4hKQT8MVYj2btkJNc*VW^+?&gQ$U(sApe;dWP$)z74fuvw0Mi(l4$5B68grq!Z>aSO>Wi zRWGvoMAWz$Ipkj_DWyS?CXvf6gcb<%uP z{}9xA<4`Z@bkur5W$iPFzOi=p(c*E_GzevXQIZ>L!C5+T5yH6*W-%&ux*u|27JPG0til@EU62H@J<*W}Jd;Sb&pw;Cd-TsFXj88ovhfu>p&4 zKi-R1P=OTQp5A8y4xwI-DNU@Spww?gy;Sew1biPS;&t4F#k{Ha;3?FCJ^Q3j+8>p< zu{a$kqB61$pT^DB-;dvEwHIJDj?E+g+HfZgTCfq7`hBQO95GLs7tG(ycBDb$vdzAz z^@dn|6t1N{9?#-o)Pc71KixXK*pK}0qELGWojCkX=T_rYJb}-UPYry+;|0S@_#(c1 z7rz-;&_8{mRj5a?5%p|eN43jx_H_^-n`B zRA%ilRLYm4PQ23k>#V-X>J8>Dv)Md^di1GQYq*43uniSq#=vwyeUYrWLewLA$lB*3 zuYs#XF1Nz!JCWqL1E{aydnB3en)T-nO1Br`Xnp^aDU4u1H4euuNK)KU)P|R_5Ze`` zGcXzzXtC9&nzK+R@=+TuLIu77bFk6+_nRlNlfM67C@8gmpiU$*nyMq)I`7zYQi>Qr$MV&++tLpjYC?uP1 zB5K0`YT*Ug9-l(3v($VJ^(bCIJ(|s^c^_g*g`*UP;c4W{aGi#x2Mj=(+-R#mZkD4q zNSL+eGIJ&BQPx>~uer}`LGAn5P&e;J%FkNIkLGWv6JNu{*qP`wVL58!I@Axz25aAn z%3vevq@SQpdU{yXj;ulLGedq=AYA6hUOL=$&CN>F^lrntb(tFV9<<)3>b$IWsfYcf zPh&Ke3|EG0g8{ER8uwxmzb0H&;f2B#piIeebcK|b}$Br>e#@U#Ji!l~gVj>pcSX}SV zw}cJs84Ao{`%x1#U;-Y;iTEML<9Uq6OQ?W;LG}L^Q!$xNDR>=f!R4ra`CczYZD1RY z#wtv)z-%vtC>jnSbJ-E^@D3`l4%AM%P!pf?_I}hv1MXj_@sUKYeu=1!q;XTfIY<|~ z2}c6NF|2R-6tt5E{lVkNWr16bN?jeQ-x1VK+Wh%hRLU=+0{#Ql|8E?RajZts(oy4b zQ2{LDrZsF81x>sbHE=U>*;C$LjT+d9TIe`xp-$woFTDOOYKK3gGBJdsFoH=bS{!Qr zDcqFN?6Kru9p}-YvtEiivqGZMF! zRn3=;`g)e4`mGMUP>PzM!mUI_ybJZ#*J218P!l)#^AAt~oi3z~dr=ErLM=Rq z3UnBWF|cIvqL*PNYJyx;#|5Z?%f0RBOLQLJgRK+Ub1Mgm-&;5sn0eIno;`RL(Q_#d4xh=s;T!C$vi7_0wUdmk54(>$t&&Lcb#d-KVF2Z+Ff&7VDC~^|# zjX9`s1xV`c5e)QFZKtpdpT=9T1M4w@S9KvCKuy?#+Ubv|3|+xIjG7Y8NFhE+{bAJe z@4fw3+^YVl^&aEzwKBdfmHaD3l{Cm2x6VE6o^VgP-KdHByxxyB)Gwnp5Rk5yupQsR zJpQwt#)vet7w`;Tz@l{WuZ}m8$0qE=JyZ~8SHk|s+sIx3by=*U{0WS}Ox*&nPxrLYjS(_(*K;`J?Ff5Lsv-RCx< z&br0xov8WFq5|wi1r!WY(90D)BYeiwP!HxJ$7{FYOkC&nN@NpOi|Y42ren81AMo~A z2F;~C6?1VV&cO;~Njr$VQ-OU#;d&a*qcRYh6^?W~s-EWNpmwwnwcsjL;F~ZBtNi(E z?whF0eTa2n*e{sH0eqI-0Ghaj&=s zF^BpwEW7pdxLJ|=uEy_d-JdW$q4iX1 MOhjw*)WXF70Q`RgNdN!< diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po index eaa954a..fc88241 100644 --- a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po @@ -584,3 +584,15 @@ msgstr "Re-analyze entire game" #. TODO msgid "analysis:continuous" msgstr "Continuously analyze" + +#. TODO +msgid "ai:handicap" +msgstr "KataGo Handicap" + +#. TODO +msgid "aihelp:handicap" +msgstr "" +"KataGo optimized for playing higher handicap games.The `pda` setting " +"corresponds to `playoutDoublingAdvantage` in KataGo, from black's " +"perspective.When `automatic` is set, KaTrain will find a suitable value " +"automatically." diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo index bcfc2c69e0debdfd40d42c42b1066839115b42ce..cc9fac0a0b1e73c94fd5788b929d8bb630464447 100644 GIT binary patch delta 3064 zcmYM$4NO&K9LMoT(RdLAl}JJ5q!_4(QmNDBOqQBD)A^Dc&dGs$;9kAld%O37p|jh| zrc7N|A9L|h%*MH>1uIegp0oA! zr~qEWRNRBvCSlC`6q0yw99hG(+YV<@8~ffqzk!PAHfjOwQtg?j33IIlsCi0I{U#%q znZ-^0mLO9Y7yGil2~kjL)}jV(Mn$$m9dI9VnUAb(sQzbB{jQ-R>$2_X4AK$gqBb}h z)qesGz!^waQ;rD@^i$w6t8K#u)Wol&2JS^J^Pzp-h8lMswb0L~h5kV8hr)(B;Q))d_$6D0cH=xdNtF1Sq7CdPE0+rH>w*5!sy4Oh&X3<%J z3`NyTZG9@L--0aiuShC+pq<8%%e=%*8)(Eld>fUzqo|#qvtCCobBmicp2B2`JR22h zKB~P4HQyxEOFADlpQrrmb80|!Y_#q|P0(sRh}!Wn)K_o@AHZ)>6JNINcTgKlC2TD; z5H)TLDic$ zy9_m>a1@r|My$n=cpE>#VSGaR4SNOkHC^Mj8IuN+f4#-8@|Q=cYC=V{2bHS#tRJJ! zvK_9DaJx3BRUzfqZ6D zZyjb#8}+u~90B!h1;+eD{mKYqHsQ%fx))l;`Dp_yP-nXa^$u;tBHW1z@CYh%r%~@r zXM%!u+JyvRlIg7o3s4bGK$=V$s^1Fxd=)CvS1=hjq84bh^|w&($X@&W2x>#8QS)3x zZ6tAnf>N8rfvKS%60{kLDL4(ak=dvPo<&7ijoR=U9E(k;cjyF?EOP;QT})4243cH? zP-j04m8oSoQ{R7xg5K8s_#|G(i8yj}_d+g~P_IX&Y&UAdt(c03Q44jT7QTw(Ftxb* z{{@p#^~I3#Je@FRB@iny3sl@jO(9s!$8> zM7<09uqU?I`hL{AaS-(`9L2}+qHWLS+!C4~j{<9&VkAgYj@s#Zq_5eAnRwWG3Kh|1 zRK%Ug$6@}o-bNj17b=s*vk)YcU2-P_LK#kvvI{(e*)VQ@?tYnejb1EV} zx59~e@woa`g`-g~772%ZF()3Tk@4ZW_*3D!${_92eD!W9?$&q}P9WrTuUPCy_ delta 2780 zcmYM#e@vBC9LMo5X#BW<5F%h8Vv2%X69!^6O>(&mVh&9<7H-)ng_Z?dWo!1N{u5Vd zO=U62a5@tN|M5!HECOw%MT*VRoYI!8pG2c%OPizjhkLf%!}B`lJm>kI@A>gu4^;oX z*&9xr-faBqbq1k>-PQcEQ-ge-;HN56~A?k;auvGF=mOl2ovx@jKor$fg3O$ zpLWmdLwdHG0x@hqDnL8N;R#H}evHM-I2}h&3H^%dKZ*BXBAt?O32MOAsD90--pDnX24d@HA*nQU?oUPLbQoK2{uYeV(xLd~SlJs(0X`7kQsKT-W}<4p818b!-M zz2`?I@DMkRVPzB)_({}@+mOp%ckNB67Z0KaI)NJK4073dSHF&$;V-C_n7}9uBPd1l zq2kZxrWMVJXZ_VNp9byqO4Ob`;p(-h0e3q0qL%Wodwv|b>@#kf$R*T7esuK-SD!`@ zik6I;$U;=2_b0Ia8ek1KB~XEB_&jRq8c>-ZaCRe?o#Lj%2T?0=1vS$f$YrD46z>k| zP$n^|;^m;eo|UM66`m_>K?T_E+=0*FKI~i7;+j zu_R{(D)DU83V4MSl*z-c!+O+GR=Ezf$Yrl^Q@;Qzp*B>)2T@zri5lorRKIhseib#) z2x{PQRHAo~96d{9U33^0paS?&9g9#eu6FI~P=PCvcdgddccA*eg-W~?^n`u=~Xpgo*Gy{Pn*NE|9~3hIUVsF@a`0+zV;N}NgvwPicp^Cnchcah6F zxjlz{xCj>$ELq?GdJ40#2DjpFT!*8$3|Fw>dR~v3K_kw?X7pn(=Hmz!U~)?6l$W6D zRk#%2#yQxJgtlSyw3jz2Y`|Msf@K`hzpw>MaSx*t+b$zrYz!A;1i!lh%*P!382j-j zoccxOQ_|Pe#jOdyKpo`U)IVCltnp}8 zXn=x+p@EB0ds&V;GtXi+)}i*k6}3{wP-kQSmEaJPn_Wju>^5p*u^uNuhar`lIu@fI zlwkx`VkAC=8eprdZ%3VpH(YxwDxqVjI6bIDPNP=zd)NLmGFQ8a+5#_y^fh26Y5+fK zhAU7Rm!l5PHq@bc4_PETiTWkGf-I7aqxL$5gP<)cz(?^B)ZuKzV(i1^IEjqo*>X6k;)TNhoj@YKa~|1*k)vg~YV3L%lzaTo%qU zGnD0_65WIze^$dyd*9;hK+Whw)Qks^kHZF?=TUn)gj&jQ4u}$oLcQTFZiV|D}3L-u?@lf SaZ&NX?)0@`!EZ83XZ{DXt_;rr diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po index cbab7b4..c7548c4 100644 --- a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po @@ -627,3 +627,15 @@ msgstr "Re-analyze entire game" #. TODO msgid "analysis:continuous" msgstr "Continuously analyze" + +#. TODO +msgid "ai:handicap" +msgstr "KataGo Handicap" + +#. TODO +msgid "aihelp:handicap" +msgstr "" +"KataGo optimized for playing higher handicap games.The `pda` setting " +"corresponds to `playoutDoublingAdvantage` in KataGo, from black's " +"perspective.When `automatic` is set, KaTrain will find a suitable value " +"automatically." diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo index c6ca5caed7b04233d17b29435605f641c0701937..8090a202c6169f4d837854abfb3e01e104d627b3 100644 GIT binary patch delta 3097 zcmYM#e@sy2bH2eV~9F0&3bf(ThWaY?;;j15f4-=k@$L&+~o0&pFrq zOV2J(Tp5_W%kb|l{tEbeK1DTSE~o$RAc|j8AArB&P)y0_wGfM_PsV|`7;|tnrs8v$ zjZN4eci8?ndQF%%3R>$E)C6B*7Ji3=@n_7$RBp+biwYEj?#!u3*?~si%=7mS|_6BnTZ;= z0J+T5+%&ERnaZriY}Pj$C@3{sQ4hADc6LYuupPO~=hiON_-@p=+o+wTGnk?&KpjCT zD!}Qe@$>LLT!b388WVbOJq0ea#df@cn)o2nYR(Us*4s0=|y=8g5}f{1Y{C65CY& zP*i}UQ43AB?F*61EaRq(CBnAQh>E-km4R)jK=#`Hw^6Bl-}ZMPmpR8xywam5@r?!ExZsF=@Mi!rVjNkBv2E)h#I#Y6<~{PKa86A7_z46 zu=R_m@!z8YzlD0PFN??=%+~imoPr`QLrpXZwctF|8CIYkSZdpAQ4>c|<2Rsox($`` zH*Nbd>VxQJjnG zct{g|fW&4xu>`-x^_a@5$9oYd}KY~49g>COZPR(?pGIbSu zzOGvol-j$t!^fMWiHD)uOOWF<51|%Xh&qC&P&@Hk7)4# zpk9phC(Pp%G*LB9#u##$BiyFqc^r?~%&M33VH}H$3q89iN$*wtz3ivlv zz{za85Be|#Gf@iBv}wFgmWw+j_{yKO&>3g9Mc{9V+-zVhCk z<{=-68EGv>9bpM7uqBv^%gf2XBJ*g_PS;=-HlW&@QAe@`^#ijL^}}+=)<^OpXu>I| zuVe-)kP6h1RADAI;-k13&Fo;%sSEqPV9fD{+d9(+^+~IBL;gUu8`*g&bL+iITUC}X zJ7a#>dCc~<`G-wQN<87lT^hrYc%Uxutmk)Dg`-X+=r#sIHBN1yrq+u(|IO#rxOHA^ zY-O$IR7L!5l@s&gagD1EN26XW5)S!ePCQH}&xh;dkA&-22I-&eZ*W6#x5le-0wJe& z#WH7AG+gJbSQ&Jy*Nloe5ic5xc-8ShgE!V$TG-gE9ft+2K)=@T4+E3 delta 2796 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 WxT&Kl=~Qw@P5MJo9d8FSQvU;HcnOaH diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po index 0ee6641..50d8e35 100644 --- a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po @@ -622,3 +622,15 @@ msgstr "Re-analyze entire game" #. TODO msgid "analysis:continuous" msgstr "Continuously analyze" + +#. TODO +msgid "ai:handicap" +msgstr "KataGo Handicap" + +#. TODO +msgid "aihelp:handicap" +msgstr "" +"KataGo optimized for playing higher handicap games.The `pda` setting " +"corresponds to `playoutDoublingAdvantage` in KataGo, from black's " +"perspective.When `automatic` is set, KaTrain will find a suitable value " +"automatically." diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo index 145fba755fd584670ccc431b151308c39515b35e..6a4ecda50a7b46c0e16abcd715b69e8843de9a40 100644 GIT binary patch delta 3063 zcmYM#eQXp(7{~G9z4Yx0(h8N*RSN}aiv>a`7T+Me1YfFv5E4SS?Y4K}dUxJk(SmS* z0b>ZHsa8deK`@{dHKF_g8mxaPq#~k}w7wu!v7!V`h++|=`2F<;CENYX%+5SB^UUti zRl6~eI6HE1i{am!{Eg)=K14NRK1u!GY>FqSkHB*{8ZTQj(~QZdUWg-cK4#$x9EPhg z6JzMZ&Gvb7(uCPVL315M1!%_%JcT*0&rg z$YmCCQ@>?M2;*WV^P3O_rDi>9;C9r`cB%vJM=tZ8wFA|^8`bYTYG+sN^K=I32y#&i zoQ&!}18=~2NLRB06B_8Jz-89j2T!8{KaU#Nid^Ph+ung1cLp`luc(QxA(u&Ikm_Sl zJ1szMtQ1plHmct|ANi+jmU2^SJygef)Y&(p&albW_o5~|V*M1A(jNQ#XXNVdq#b6_ zSsNLLs+ZdOU8sH!XOVyHq>2WuG>%+m6E`iO8E?WjP^tR}wel~mKOvXtiI-eyfV~FT7in^DgXMM8c`jat$R=b4qA_(R(uro6?EZH{2CSbd;9!P)B=aI zZA~;9HLe(yiP_wgu?lNq1qH3V29<#bY9Z@whaITYy4yHJT5;72VmiW<1Vwr@iPZb9ZSdu{zN zs{hBRg?FLG{b0R>%FsWUqVGS0*%c@UHDMv@3`m+9rUTHpUw3WZopG!|uc-~`-_RoH>mSj0ik#3sB|?KlR1vGrj$ zl2+=Iu?+9S3fziXNH;FV^H_v;@Zu%(oA0N<9A*pZEMLO)xC_HLj#=gmU}4xm{S?2q z3b=qzLqEOCaR$b4Gq$2KQcUpOSdRLI?Llqu9B#tCvE*NGabsRG;6~I-)P#BmcB1}2 zdeyoQbu@3G#vMW(Njs|lan$%u)Y+fGJ$M1X!5!m_sl#ACnPNky^U41h3YQ&LkG@-s zS%goL-$FcDU`!tNjb}p4=A2S->_lT$V&SCZnQpnw7_w@bQD@tV+G#s7m+3^k6Tcy+ zWrj{pW+*Q~L8&Z4vS!LrXSy6UAc`fp1yk`LYG>`J44gpi{51A|ezv_Ab)*+j3mM3P z_kVtuR0u!da+12R1kITva7h|qyIRF3v delta 2779 zcmYM#e@sCRKsDyq)jUUHZ7-Ucg7osLCM2#zQ z{R-3qc40j3#gKVs`)R~+p$&;;?QXzlsKmZPt)v$f_@cXh0~P4D^Dor%v1G4t$*6^d zIcZ!DGQ=Lli3Bl$`K^eCRO@U+8aeD6*T05Z;jgGljNsiE zLr|LLL&Z5(yslNs;=7RRR0JUc&u3w3ou-dsFRmxU(|2T5k*POJFi>QV4yZ(sl z#}S04O-C&x6P0Lwfck5Kr#LBrb$BnngeqMPD)WQR4&<;NPD;ENRf)@}mEJ%OyTwWI z#!xS1h*=dc8};=RpvJB9T%!UN;B{v;D&tzzTi=9tVGAnoQFs3fRDu^!6J2%Jhf$S? z;iQU%oT;e9=c6j%Eu*1K9(MzZQKfv|4X8v8dySLE)uIw=MkRb0wPg|1L|>xDopt>y zsEKZ(CLTs5I)>!vS&+KuWtfKwkc%3ahkCHkU0;I=T#7tvm9Aff8ov*fcoXWmkDT48 z3Y|hF{2eOJ6^z&SKS)D+ID&dm=_!#URNxt?2hve1U4{y{%3UwTiG)yFR^{&3q2hgj z92ViU6;EImE+E)cegDNYGO-*tVjXV6AH0pW4}&hY#T% z+>WZqFn)-dMZc)6NKvf~tFZ_55*LJ{Tk&L=`fKl3b3uQ1o^zI=wqyh9fo-U*cm*}y zL+$0es6BrlTk#N9U|}kiVFAZ51<$APEDp@!4-%H%#}?p`bm||babX@4;a~<6*KZ?KD49Jj-X4TW;p~QH!DKLtKy`Eb)XNsQSncr z;+~nf&i?n(Nan&NR4IQ!-VYna#TdRndVLM5gqu)-wxafMCnn)L=*N20yV8oPL=P(Q zGnj~dsQCslmHBOyh61EK5S=IsRiZ~xFWVN>gjJ}OyzTn$IS-&FIEeG{Q&ghYkT2N= zFcbep)?^v1il1I9K~F0!r=gP6pgzNW&IZ(kO~}V!N1Sb#M?Zpk?q}4v0oNZwB|L&! z*gvSaz9rGE4Pb!&tR?oC9ioxL1(oC+D)XOQ|GIO?IgWZal9%!$f-{$Po%3bH*8iPY T)76kP(>VtUm!8Zn zE7T=PWpmbYveGUq{D;i7Y3j^!<{wzgGHLW;LlQ6cB6FDiw!vXkV8`tHGpH3^Kuw@1RiB9(Fwa_q8mA1^ z?hfQKRov8W5i*2vaR~F9AP-8-GE~RasFkf(1AGs;%y#QORQtoIcBfG*yJYLr>7*^l zLj^bq)qWZd#|osWsl~WD`gq_nOKruAsDWQYb=-nn<`esVAFAIG)I{e{6a9l+CXG%i z=b~0xgj!fRrr<18yNXQm&$D@$n^Nnc8n&YLz5}&~FWYhtYQi1X1E`c9we>$E*R@7k zVHSI|Q^u;T@md}fc@ruFVN@W?Y=bqZ)V*%&dyvcYa#OoKsDKWm0zQJ; zvQwyuE~DBFV!0}hM9t$M^Ttgn4~ldK61;g3br$MT9ek(&BdCtc?fXvDz}?6krpK21 zQ0>1!1>TS9chdSBDnnN>Mc;o0vumJ{s0oWvdsvR@IMcqbLJeGlYF~$1X&5!&^Y(o= zUJD47`aWB~4>jI5$YoA(TcYp(5)Z}L%xH>u4Nk-j*ok{FiZyKXY?W~wCXcdvi-nYD z;TWvLJ8>CSVIS%jGKqsW1@mz_F2J}BPbUvt=521uz;m48qU;3U*9>?KqnS8*rijv@a##YYHRsXBo=MCVbdx`Ik| zYJTE#$wOtR4E25{s@;6lz_q9aw4t^zjvwJm_&v@nAd8si81p*@9r9nu%3Q)-fGfuu zbCHVvoB2_qe6T3da2)x*mvS!qbcXu6@fV z{5ezxH^h11Aes-b5O-o4o4wZ=m$S#@_ z$S#-xrHQY|L9Vzd;Xa#+TCBwNI0uiR1|D0+eq$xK3~a}AeAbrNTHi#i=zU}^vj?@1 z^EeGtZ%qU+$6ANU`u>++dtgsc6Rbj=);H|?ji^1_jC=s*Q`A=VBS|tBP+M4ZTcZDT zROTvBXJ9^RD?QZKhA<14;wa`fojh9E?fji9?f#)szb{3nQq& zSEBl_MFp@CHSuQD57LLIt=VbouV7qzlr)+DrC<)u#TwMW-B^V^sK|e{UdEx6lR251 zI0u!nan_0W2<0+VAf47O>w469Z%yInL<4NKFZxjB&r#p=A?(7lI2@mznpn{~RQ*O& z`#w~v_hL34#R&c}wRcO}EjOh2++dTx(G9<{KYjJ}LT`1(;;g}yA?H3@+uJkd{sHj^ z+?dPDP&npq^)L0BoaRu(2?yK`f3U^z`CEKm#QASLr^RjcqGbzxo>L!ga_gO_7mKM~ zV<-~wqTx`mDeA;RRMJ1x7P}|Z))1h6Zd1D(jJYjdz2gr$i5W|r=18d3d9)$mHZCcS zI$cqxW3?LnHnLqiJft delta 2780 zcmYM#4@^~M7{~GF3Ta%th=iCXXyU(%Xrf}K6UDN!GXG#SvQ{`3?z+MmSGq?~nUzx#Rh~?>X;z-sgSZb1uKe znpOwr(-OBC{|@kf1pimvrk+_t_7qzoW+A!r_?4pmdysns7d9+*0qC zp%$*cS>`T;2+EIZ|`SnYvKwa)N)bstwUgJ_w3(4f9 zaTAdtmXCc2VlwmFQW{#xGk#+&a#-Njph~wBHSQp4CCz^SdsNBKqY~~$jlYg}V3^rx zS{CZLTvP%DoHU0mqM^VmP!GO{9QKA^uR%Sy7d6pg)I_bwVIAH-hg#ups7mzW?HD2` zO$(#q596eYMh8)U4b0<$_IeI#&z5^XhMKU-twojc1HXR=IqWM=TF5EXLN0p0*ZTI)}p4;ypK~<;) zmGDVaoHLlD@Ba@P+QVMdgGx_{L{Nc;q8`Xbt#mpn;3Iy$1p5*~ZCRz?uR+Dzg&cN( z(;95X@t8xfh`#@2G;;7c+>D#?B^=5|7pRZpunjdq58jQTq5QaDHqORET!@vZU&L>5 z8eYVin8BOJqqZ3J5}#$MS4)Jl5rKJ3q@@-EIp zC3FHiup9M@+RPsmCDMW|cnKD>wt## z=sUlWz>A=jr??|fCCl-Cp7-aXD)ylJq+eg{{c>a%?M+m?9muZOKIHYXR#e=elZM{r zpHVMYH|l{~s0Shw`7|+#>OYR!`xvScA0oSBpCVPa)0m8Z<0R~t8-IzX;6wDwaULE; z;s$n&#$+yJGFlTqh6C{_@0YnRqgJ#LdBk?1R&pF?;006y83dF0s1+`ES73zxO4Q4_ zw(mOoUqM5AwgGwJtroRc4ag#_1GR_OQ2|o!i3c8m+VgDGUe3laE()@x;@G2_M!&BlDm!JZap;lIbns@{12k33o)@=9tKcKd# z6K7!;7Gg9n9(N4}3%T$*4Q2j~dlCoJKZ{y%7pi18T${?@2l|PqL`vP&ZaFI6derk( z-rwT=I@IsN{;4+CYH74`K_w~XQ_zZHr~wtI09#O{-i=z(5nP9D(;C~tWBa}T@4)KD Tt&yVC#;chNLXE>mEgJG4F3t_w diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po index 9ce8469..5f1eedd 100644 --- a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po @@ -599,3 +599,15 @@ msgstr "Re-analyze entire game" #. TODO msgid "analysis:continuous" msgstr "Continuously analyze" + +#. TODO +msgid "ai:handicap" +msgstr "KataGo Handicap" + +#. TODO +msgid "aihelp:handicap" +msgstr "" +"KataGo optimized for playing higher handicap games.The `pda` setting " +"corresponds to `playoutDoublingAdvantage` in KataGo, from black's " +"perspective.When `automatic` is set, KaTrain will find a suitable value " +"automatically." diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo index 70212a2629aa6dc8da1e6482cf9ed2d041d00f8a..1229229e9fdb23cadf4fa455a880c97c5138cd1f 100644 GIT binary patch delta 3066 zcmYM#e@vBC9LMp4LV=9nEMnrotIS(>Aq*^lY`)vEW0douQLU*|l}`SCs9bDn!I zY>0bhV_fw6wD_%tzYqBz%YP(6HDkUQ^uHr0eo1{8Uc^y&-8yuzFDCZW%I!ad9Z~8$Si5W*w?y8){|S)c|)RmpN$dLACEiwd+Hz?6!TLOebx@ zXjFg?pxRHz;aH9|H7hWxjtvyJ%v$^4MbyA=qB`zEE_1-%??LrDhnnarYNCIT%M7BE z>Y1pO=Asr>h>2K&YFC~@{wbTKd?>XZRKq6J-nXLm@HJcSL``_e`V}gr=k4=fkgLCu zR+vg-Eo3~ZUTEv3sCJ7}$-h=o%?(8wK`!$=9}1uy$KVI3)Ez}de#ZJUa+w=^DDXrE z)5_CPE6qYZpM)B3ChCw@qQ>(m|9YKTQ4QOz9jF2JSr4HiK8AV=PT@Ux7B%n%`}`Iv zz$BKfiAJINO+jU%gb!t`(i&YsL6O&@G7v-svd%Vm6_vWT?DJ0KGF^PA-7!=^y{LfC zp|DVT|3jEv17)Bl%tP&AA*$mnd%prT@RO+at5GWrq6U1y z-rtJ-0ijagZJ+m`#ygE%<`N(EdjD@z$ir$z(^+^4i?9Qm@H9rSoQv0jD#^_WExtY8oxCFKGFt+1*Y{OxEzxv{gc@H;I zPv(c>K5WNq?8Y)Yifv|I3LqVp5LuQ^d;)=*fP zYs}mD%|w2|Fvxyf#Ql>vc(`LSD;+?P`TU|$FJu`5@g35qiFQ87D~8>ufclV_&0nZ3 z8(YYC0M?;i*UhLc?2J-S#D`H^aSnABlBdNER}pHj7NREFh8$e82bH zejXQ7&v=-hWc1BmK!wI+)XJaeQ!UL#@Z(upjp2@c1CL{*ajs5TcZxoceo0y47#j%x7z%=UlsJ$vh zj){2&HNbONhM(X_yoJi-ut#J43sL=wQCsvB4#yhI#x*#G^Jli(8y{mj^)JJF;(){k*I(8c!3mNs~Pz*%6QcC}^gjEgREBQ7@s!HBoXyVg_d)CEFL(C4;#{q;_R zx4yv>a{e37sdt+^;esU%9;Yf;>sC2oPb8vtHGxpb6AlLawP7a`;3543&5^l*=4v0$ z=hU{i{)k)esdBu2CpKfgQx^&}IjgFDZcSre*a><<;h?7`;%)I1ENk%iohr9E5@>QG z-Wqy^HB~-cmxNq~SmX8ioI0<+)^VM1vp3>a`#esI>udHncYC=$Uu#!>_H^f8Z>e;< delta 2782 zcmYM#eN0zX9LMo5sDVF4=qHb%fT%q9ln6csD6EWf=CGt#vXx~te5lYxwd^M8QWI=M z%Y+778%e_2V6HzLW=d_6l9tX!MH^UAl4gyjIcM*W_iVZAeVudex#xV(_niA<@6M2c z>JYC#IQm`VUn~D7@&D;Ds+qL~{x^nVJM}y91jgfe=VeT$9yZP_i0K%GOE3&q-~@aa zBXOO3Ug_)E8x$DB>QMtUVFY$yEFQ!0cmYS_MN~k)q59v#yD&(nXq=6jun^U+*wxEX z3wRO7;x3Ff&#aEZC>lOO#6dgK1Z$O6l&meu6+>?(pFyR(9~JNwRR4c*A_ka^ zq9vi;n}rG>pPS~el@v7aQ~~qLOE)Hmz>*C5${2r`U7|eHlYS?anHL@0iH%p^n+`^ipoR? zH)Sl^nS=^F1C;?Uhk_z0a2-~oQn}W3s6Z~O;--FkPyscd0&YfaSu1Ly&rtnNx_TdK zqKl}Buc89IiNxqxki6(HOhXMY3)OJ}>cv9Wz6v#PDe|sWxcW9!|5{Yw2T<=FbRI@! z=r}6iv#4?UaIC)nOBA$+Bd8Y@o&t$L4IGDhVJd2+Ij8|2bnT^hJ0R4SZFA3SP~$Zs zm$h;$$D^2zX$%&x@BbkRIarRHuo1UnfQ^0tJ=CE*iCV$WcrRYYJj{+WdlFy3wfHTP zTARyBE5$-wfsJ@K4q+0;Cor~FzK}v47Ge#a$BiLoPw~6DllmEEPs0-aKup1HScpyd zI}Ttfo}O&>1pbNoMJ;BQIk*Nx8E*&b5Wku1FQhh^{4163(ZD;_;_OEKrVk*Cv71QN zEGEV8Hv@^$icrtjVJE(V^*DzpH#6Z0tipLyIk5QgG_(70Ijb4O+H}vXl0s!BD+?vH zGt4$n51Y*ja0}sUf-3SxlkGzV(uKri-=MbWFU-K)x&G(59<^n!q5|EIbg>T9-VY$V zYymIJ-rNFXgg4;+K)O!O&Eo3uHJ>}--FX}0Cfn%=lSi4m_t1q73j0BeHSvf zHKWdwcb0-y)Q6FH1+~|=Z~>-en>~tUsQO`Cjl)P6Tgog&ScxRhj-U?LFIbIZ=KF_w z7jB{c59;t%Fc@3x*(VfqNP19b;W`E}e4!suBC>rp6PMvC%*D4+6Q9QA7`uqSBUpya zVf#=k`~sD^Ues3n?#r~yi_5cgsnevL}yuc(2;miX^Qp|)ri zPQ=AH1y{P~&$;?r7)^USYQe`biTUj+1*Jaf9F=4zCvZHANBeE zfvI>6br=&#kDjl`1l)=w$!c*8eu-MhBz`=+brfivTlfyYq&dQszDKw_{<`L@)CDP%154^zxCKb1wO!?Xr=4.3.1", + "kivy==2.0.0rc2", # rc3 failing on mac "kivymd>=0.104.1", "screeninfo;platform_system!='Darwin'", # for screen resolution, has problems on macos ], From 8d732dc2c3a5e4cd36642ff2a36d5537f38839e9 Mon Sep 17 00:00:00 2001 From: bale-go Date: Fri, 26 Jun 2020 22:49:45 +0200 Subject: [PATCH 18/77] user_data_based_AI_based_rank_estimation-perfect-play-patch --- katrain/gui/widgets/graph.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index c1a75cc..7816f14 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -198,9 +198,13 @@ class RankGraph(Graph): rank = tuple(rank) averagemod_rank = averagemod(rank) averagemod_len_legal = averagemod(num_legal) +# n_moves = math.floor(0.40220696 + averagemod_len_legal / (1.313341 * (averagemod_rank + 1) - 0.088646986)) +# # using the calibration curve of p:pick:rank +# rank_kyu = (math.log10(n_moves * 361 / num_intersec) - 1.9482) / -0.05737 # the averagemod_rank is the outlier free average of the best move from a selection of n_moves with averagemod_len_legal of total legal moves - rank_kyu = -0.62842816*math.log(averagemod_rank)/(0.17050253+averagemod_rank*math.exp(-1*(3.373914*(averagemod_len_legal/num_intersec))**2))+13.588577*(averagemod_len_legal/num_intersec)+10.405252*math.log(averagemod_rank)+12.417778*math.exp(-1*(2.5190649*(averagemod_len_legal/num_intersec))**2)-14.579846 - if rank_kyu<-10: + if averagemod_rank>0.4: + rank_kyu = -0.62842816*math.log(averagemod_rank)/(0.17050253+averagemod_rank*math.exp(-1*(3.373914*(averagemod_len_legal/num_intersec))**2))+13.588577*(averagemod_len_legal/num_intersec)+10.405252*math.log(averagemod_rank)+12.417778*math.exp(-1*(2.5190649*(averagemod_len_legal/num_intersec))**2)-14.579846 + else: rank_kyu=-10 return 1 - rank_kyu # dan rank @@ -231,7 +235,7 @@ class RankGraph(Graph): for segment_mid in range(0, len(nodes), dx): bounds = (max(0, segment_mid - half_seg), min(segment_mid + half_seg, len(nodes))) for pl, rank in self.calculate_ranks(policy_stats[bounds[0] : bounds[1] + 1], num_intersec).items(): - if bounds[1]-bounds[0]>self.segment_length * .75: + if bounds[1]-bounds[0]>self.segment_length * .75 and bounds[0]<250: ranks[pl].append((segment_mid, rank)) self.rank_by_player = ranks self.redraw_trigger() From 9708680504b6c784abef92e437937de9645ea5ed Mon Sep 17 00:00:00 2001 From: bale-go Date: Fri, 26 Jun 2020 22:50:07 +0200 Subject: [PATCH 19/77] user_data_based_AI_based_rank_estimation-perfect-play-patch --- katrain/gui/widgets/graph.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index 7816f14..2e9719f 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -198,9 +198,6 @@ class RankGraph(Graph): rank = tuple(rank) averagemod_rank = averagemod(rank) averagemod_len_legal = averagemod(num_legal) -# n_moves = math.floor(0.40220696 + averagemod_len_legal / (1.313341 * (averagemod_rank + 1) - 0.088646986)) -# # using the calibration curve of p:pick:rank -# rank_kyu = (math.log10(n_moves * 361 / num_intersec) - 1.9482) / -0.05737 # the averagemod_rank is the outlier free average of the best move from a selection of n_moves with averagemod_len_legal of total legal moves if averagemod_rank>0.4: rank_kyu = -0.62842816*math.log(averagemod_rank)/(0.17050253+averagemod_rank*math.exp(-1*(3.373914*(averagemod_len_legal/num_intersec))**2))+13.588577*(averagemod_len_legal/num_intersec)+10.405252*math.log(averagemod_rank)+12.417778*math.exp(-1*(2.5190649*(averagemod_len_legal/num_intersec))**2)-14.579846 From 142ae577a120162742a1a749b5c3900aee0d28ec Mon Sep 17 00:00:00 2001 From: bale-go Date: Sat, 27 Jun 2020 00:49:42 +0200 Subject: [PATCH 20/77] user_data_based_AI_based_rank_estimation-simplification --- katrain/gui/widgets/graph.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index 2e9719f..7b32a7d 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -163,11 +163,13 @@ def averagemod(data): (int(lendata * 0.8) + 1) - int(lendata * 0.2) ) # average without the best and worst 20% of ranks +def gauss(data): + return math.exp(-1*(data)**2) class RankGraph(Graph): black_rank_points = ListProperty([]) white_rank_points = ListProperty([]) - segment_length = NumericProperty(60) + segment_length = NumericProperty(80) def __init__(self, **kwargs): super().__init__(**kwargs) @@ -196,13 +198,11 @@ class RankGraph(Graph): if item > num_legal[i]*0.09: rank[i] = num_legal[i]*0.09 rank = tuple(rank) - averagemod_rank = averagemod(rank) + averagemod_rank = averagemod(rank)+1 averagemod_len_legal = averagemod(num_legal) - # the averagemod_rank is the outlier free average of the best move from a selection of n_moves with averagemod_len_legal of total legal moves - if averagemod_rank>0.4: - rank_kyu = -0.62842816*math.log(averagemod_rank)/(0.17050253+averagemod_rank*math.exp(-1*(3.373914*(averagemod_len_legal/num_intersec))**2))+13.588577*(averagemod_len_legal/num_intersec)+10.405252*math.log(averagemod_rank)+12.417778*math.exp(-1*(2.5190649*(averagemod_len_legal/num_intersec))**2)-14.579846 - else: - rank_kyu=-10 + norm_avemod_len_legal = (averagemod_len_legal/num_intersec) + rank_kyu = -0.6284*math.log(averagemod_rank)/(0.1705+averagemod_rank*gauss(3.374*(norm_avemod_len_legal)))+13.59*(norm_avemod_len_legal)+10.41*math.log(averagemod_rank)+12.42*gauss(2.519*(norm_avemod_len_legal))-14.58 + return 1 - rank_kyu # dan rank @staticmethod @@ -232,7 +232,7 @@ class RankGraph(Graph): for segment_mid in range(0, len(nodes), dx): bounds = (max(0, segment_mid - half_seg), min(segment_mid + half_seg, len(nodes))) for pl, rank in self.calculate_ranks(policy_stats[bounds[0] : bounds[1] + 1], num_intersec).items(): - if bounds[1]-bounds[0]>self.segment_length * .75 and bounds[0]<250: + if bounds[1]-bounds[0]>self.segment_length * .75: ranks[pl].append((segment_mid, rank)) self.rank_by_player = ranks self.redraw_trigger() From 399e5ffd87caa82463e9ccb8f49611128fc1d212 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sat, 27 Jun 2020 01:04:08 +0200 Subject: [PATCH 21/77] fix pda sign --- katrain/core/ai.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/katrain/core/ai.py b/katrain/core/ai.py index 6918295..882f1c3 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -101,7 +101,7 @@ def request_ai_analysis(game: Game, cn: GameNode, extra_settings: Dict) -> Dict: def set_analysis(a): nonlocal analysis - analysis = a["moveInfos"] + analysis = a def set_error(a): nonlocal error @@ -129,10 +129,10 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, if ai_mode == AI_HANDICAP: pda = ai_settings["pda"] if ai_settings["automatic"]: - n_handicaps = len(game.root.get_list_property("AB")) + n_handicaps = len(game.root.get_list_property("AB", [])) MOVE_VALUE = 14 # could be rules dependent - b_stones_advantage = max(n_handicaps - 1, 0) - (cn.komi - MOVE_VALUE/2) / MOVE_VALUE - pda = min(3, max(-3, b_stones_advantage * (3/8) )) # max PDA at 8 stone adv, normal 9 stone game is 8.46 + b_stones_advantage = max(n_handicaps - 1, 0) - (cn.komi - MOVE_VALUE / 2) / MOVE_VALUE + pda = min(3, max(-3, -b_stones_advantage * (3 / 8))) # max PDA at 8 stone adv, normal 9 stone game is 8.46 handicap_analysis = request_ai_analysis( game, cn, {"playoutDoublingAdvantage": pda, "playoutDoublingAdvantagePla": "BLACK"} ) @@ -232,11 +232,9 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, else: raise ValueError(f"Unknown Policy-based AI mode {ai_mode}") else: # Engine based move - handicap_or_default = "Default" candidate_ai_moves = cn.candidate_moves if ai_mode == AI_HANDICAP: - candidate_ai_moves = handicap_analysis - handicap_or_default = "Handicap" + candidate_ai_moves = handicap_analysis["moveInfos"] top_cand = Move.from_gtp(candidate_ai_moves[0]["move"], player=cn.next_player) if top_cand.is_pass: # don't play suicidal to balance score - pass when it's best @@ -268,7 +266,10 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, game.katrain.log(f"Unknown AI mode {ai_mode} or policy missing, using default.", OUTPUT_INFO) ai_thoughts += f"Strategy {ai_mode} not found or unexpected fallback." aimove = top_cand - ai_thoughts += f"{handicap_or_default} strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move" + if ai_mode == AI_HANDICAP: + ai_thoughts += f"Handicap strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move. PDA based score {cn.format_score(handicap_analysis['rootInfo']['scoreLead'])} and win rate {cn.format_winrate(handicap_analysis['rootInfo']['winrate'])}" + else: + ai_thoughts += f"Default strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move" game.katrain.log(f"AI thoughts: {ai_thoughts}", OUTPUT_DEBUG) played_node = game.play(aimove) played_node.ai_thoughts = ai_thoughts From ea14f1f22874365445cf3ded26474ef6201311a4 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sat, 27 Jun 2020 01:07:08 +0200 Subject: [PATCH 22/77] black --- katrain/core/ai.py | 38 +++++++++++++++++++++++++++++++++++- katrain/core/engine.py | 2 +- katrain/gui/widgets/graph.py | 6 +++--- setup.py | 2 +- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/katrain/core/ai.py b/katrain/core/ai.py index 3e8bb92..6e4b437 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -143,7 +143,43 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, if ai_mode != AI_RANK: n_moves = int(ai_settings["pick_frac"] * len(legal_policy_moves) + ai_settings["pick_n"]) else: - n_moves = int(round(board_squares / 361 * len(legal_policy_moves)/(1.311648546930214*((0.31164467+0.55726218*(len(legal_policy_moves)/board_squares)*math.exp(-1*(3.0308747*(len(legal_policy_moves)/board_squares)*(len(legal_policy_moves)/board_squares)-(len(legal_policy_moves)/board_squares)-0.045792218*ai_settings["kyu_rank"]-0.31164467)**2)-0.0064860256*ai_settings["kyu_rank"])*(0.0630149+0.762399*board_squares/(10**(-0.05737*ai_settings["kyu_rank"]+1.9482))))-0.08265346672884874))) + n_moves = int( + round( + board_squares + / 361 + * len(legal_policy_moves) + / ( + 1.311648546930214 + * ( + ( + 0.31164467 + + 0.55726218 + * (len(legal_policy_moves) / board_squares) + * math.exp( + -1 + * ( + 3.0308747 + * (len(legal_policy_moves) / board_squares) + * (len(legal_policy_moves) / board_squares) + - (len(legal_policy_moves) / board_squares) + - 0.045792218 * ai_settings["kyu_rank"] + - 0.31164467 + ) + ** 2 + ) + - 0.0064860256 * ai_settings["kyu_rank"] + ) + * ( + 0.0630149 + + 0.762399 + * board_squares + / (10 ** (-0.05737 * ai_settings["kyu_rank"] + 1.9482)) + ) + ) + - 0.08265346672884874 + ) + ) + ) if ai_mode in [AI_INFLUENCE, AI_TERRITORY, AI_LOCAL, AI_TENUKI]: if cn.depth > ai_settings["endgame"] * board_squares: diff --git a/katrain/core/engine.py b/katrain/core/engine.py index b0c81fc..88bf628 100644 --- a/katrain/core/engine.py +++ b/katrain/core/engine.py @@ -239,4 +239,4 @@ class KataGoEngine: "overrideSettings": settings, } self.send_query(query, callback, error_callback, next_move) - analysis_node.analysis_visits_requested = max(analysis_node.analysis_visits_requested,visits) \ No newline at end of file + analysis_node.analysis_visits_requested = max(analysis_node.analysis_visits_requested, visits) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index 6fa267c..b9878ed 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -193,8 +193,8 @@ class RankGraph(Graph): num_legal, rank, value = zip(*non_obvious_moves) rank = list(rank) for (i, item) in enumerate(rank): - if item > num_legal[i]*0.07: - rank[i] = num_legal[i]*0.07 + if item > num_legal[i] * 0.07: + rank[i] = num_legal[i] * 0.07 rank = tuple(rank) averagemod_rank = averagemod(rank) averagemod_len_legal = averagemod(num_legal) @@ -231,7 +231,7 @@ class RankGraph(Graph): for segment_mid in range(0, len(nodes), dx): bounds = (max(0, segment_mid - half_seg), min(segment_mid + half_seg, len(nodes))) for pl, rank in self.calculate_ranks(policy_stats[bounds[0] : bounds[1] + 1], num_intersec).items(): - if bounds[1]-bounds[0]>self.segment_length * .75: + if bounds[1] - bounds[0] > self.segment_length * 0.75: ranks[pl].append((segment_mid, rank)) self.rank_by_player = ranks self.redraw_trigger() diff --git a/setup.py b/setup.py index 0519510..dba65be 100644 --- a/setup.py +++ b/setup.py @@ -46,7 +46,7 @@ setup( "kivy_deps.glew;platform_system=='Windows'", "kivy_deps.sdl2;platform_system=='Windows'", "kivy_deps.gstreamer;platform_system=='Windows'", - "kivy==2.0.0rc2", # rc3 failing on mac + "kivy==2.0.0rc2", # rc3 failing on mac "ffpyplayer>=4.3.1", "kivymd>=0.104.1", "screeninfo;platform_system!='Darwin'", # for screen resolution, has problems on macos From 6d623765013213b6479242030dead55ada5f81eb Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sat, 27 Jun 2020 01:20:18 +0200 Subject: [PATCH 23/77] no config for test --- tests/test_board.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/tests/test_board.py b/tests/test_board.py index 83839a3..9f11c4c 100644 --- a/tests/test_board.py +++ b/tests/test_board.py @@ -2,6 +2,7 @@ import pytest from katrain.core.game import Game, IllegalMoveException, Move from katrain.core.base_katrain import KaTrainBase, OUTPUT_INFO +from katrain.core.game_node import GameNode class MockKaTrain(KaTrainBase): @@ -17,12 +18,17 @@ class MockEngine: pass +@pytest.fixture +def new_game(): + return GameNode(properties={"SZ": 19}) + + class TestBoard: def nonempty_chains(self, b): return [c for c in b.chains if c] - def test_merge(self): - b = Game(MockKaTrain(), MockEngine()) + def test_merge(self, new_game): + b = Game(MockKaTrain(), MockEngine(), move_tree=new_game) b.play(Move.from_gtp("B9", player="B")) b.play(Move.from_gtp("A3", player="B")) b.play(Move.from_gtp("A9", player="B")) @@ -30,8 +36,8 @@ class TestBoard: assert 3 == len(b.stones) assert 0 == len(b.prisoners) - def test_collide(self): - b = Game(MockKaTrain(), MockEngine()) + def test_collide(self, new_game): + b = Game(MockKaTrain(), MockEngine(), move_tree=new_game) b.play(Move.from_gtp("B9", player="B")) with pytest.raises(IllegalMoveException): b.play(Move.from_gtp("B9", player="W")) @@ -39,8 +45,8 @@ class TestBoard: assert 1 == len(b.stones) assert 0 == len(b.prisoners) - def test_capture(self): - b = Game(MockKaTrain(), MockEngine()) + def test_capture(self, new_game): + b = Game(MockKaTrain(), MockEngine(), move_tree=new_game) b.play(Move.from_gtp("A2", player="B")) b.play(Move.from_gtp("B1", player="W")) b.play(Move.from_gtp("A1", player="W")) @@ -60,8 +66,8 @@ class TestBoard: assert 4 == len(b.stones) assert 2 == len(b.prisoners) - def test_snapback(self): - b = Game(MockKaTrain(), MockEngine()) + def test_snapback(self, new_game): + b = Game(MockKaTrain(), MockEngine(), move_tree=new_game) for move in ["C1", "D1", "E1", "C2", "D3", "E4", "F2", "F3", "F4"]: b.play(Move.from_gtp(move, player="B")) for move in ["D2", "E2", "C3", "D4", "C4"]: @@ -78,8 +84,8 @@ class TestBoard: assert 12 == len(b.stones) assert 4 == len(b.prisoners) - def test_ko(self): - b = Game(MockKaTrain(), MockEngine()) + def test_ko(self, new_game): + b = Game(MockKaTrain(), MockEngine(), move_tree=new_game) for move in ["A2", "B1"]: b.play(Move.from_gtp(move, player="B")) From 02b71cbb585931dda14fe41bb388ee9f3926e9bd Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sat, 27 Jun 2020 01:21:06 +0200 Subject: [PATCH 24/77] no config for test --- tests/test_board.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/test_board.py b/tests/test_board.py index 9f11c4c..f177304 100644 --- a/tests/test_board.py +++ b/tests/test_board.py @@ -9,10 +9,6 @@ class MockKaTrain(KaTrainBase): pass -# def log(self, message, level=OUTPUT_INFO): -# pass - - class MockEngine: def request_analysis(self, *args, **kwargs): pass @@ -28,7 +24,7 @@ class TestBoard: return [c for c in b.chains if c] def test_merge(self, new_game): - b = Game(MockKaTrain(), MockEngine(), move_tree=new_game) + b = Game(MockKaTrain(force_package_config=True), MockEngine(), move_tree=new_game) b.play(Move.from_gtp("B9", player="B")) b.play(Move.from_gtp("A3", player="B")) b.play(Move.from_gtp("A9", player="B")) @@ -37,7 +33,7 @@ class TestBoard: assert 0 == len(b.prisoners) def test_collide(self, new_game): - b = Game(MockKaTrain(), MockEngine(), move_tree=new_game) + b = Game(MockKaTrain(force_package_config=True), MockEngine(), move_tree=new_game) b.play(Move.from_gtp("B9", player="B")) with pytest.raises(IllegalMoveException): b.play(Move.from_gtp("B9", player="W")) @@ -46,7 +42,7 @@ class TestBoard: assert 0 == len(b.prisoners) def test_capture(self, new_game): - b = Game(MockKaTrain(), MockEngine(), move_tree=new_game) + b = Game(MockKaTrain(force_package_config=True), MockEngine(), move_tree=new_game) b.play(Move.from_gtp("A2", player="B")) b.play(Move.from_gtp("B1", player="W")) b.play(Move.from_gtp("A1", player="W")) @@ -67,7 +63,7 @@ class TestBoard: assert 2 == len(b.prisoners) def test_snapback(self, new_game): - b = Game(MockKaTrain(), MockEngine(), move_tree=new_game) + b = Game(MockKaTrain(force_package_config=True), MockEngine(), move_tree=new_game) for move in ["C1", "D1", "E1", "C2", "D3", "E4", "F2", "F3", "F4"]: b.play(Move.from_gtp(move, player="B")) for move in ["D2", "E2", "C3", "D4", "C4"]: @@ -85,7 +81,7 @@ class TestBoard: assert 4 == len(b.prisoners) def test_ko(self, new_game): - b = Game(MockKaTrain(), MockEngine(), move_tree=new_game) + b = Game(MockKaTrain(force_package_config=True), MockEngine(), move_tree=new_game) for move in ["A2", "B1"]: b.play(Move.from_gtp(move, player="B")) From edc174e63d5efd1504bc9a84ef091ef1bed5b36d Mon Sep 17 00:00:00 2001 From: sergio villegas Date: Fri, 26 Jun 2020 21:21:25 -0500 Subject: [PATCH 25/77] Redfined es translations and TODOS --- .../i18n/locales/es/LC_MESSAGES/katrain.mo | Bin 12429 -> 12392 bytes .../i18n/locales/es/LC_MESSAGES/katrain.po | 50 ++++++++---------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo index 74e83dfb6a04349f2bac509c30fcade708e02abe..59a9a63762c691832264e2477a3f95218f3c570b 100644 GIT binary patch delta 1843 zcmYk+YiJx*6u|Mbc{JT^(mZTTQd2w8yqaXQX*JXmG(;QUt*KVj5>2MrX*zLtX5E)G z7Pd4{3Sz}hq+l(Yib{Q3_|pX($i^p#+}NC_HvE4H8}is3i-GU~v|23hKV()V-hr2Qi1aOG-| z8a#v_;|vyK^eT~R+>OuTBre0MMn>sQ>R66;Ft8jq;7aUA-N9C@z!B65;#h|J{P-a1 z_@np%zKM6?qiaOI!}st{JaV;2ARuyht;hoLbj}c2O~WHiA{S`ba}B#D-f^wSr^NHr z`#2tIDJ)=pYhgkCNEKxWb)mbl4If8NOI|=-&@AeL=Y7wi_Pd074u#M;^ooP9oKn*YG-=N2)7-A-Sbtoe0-O22eM)18>JT-h`)6FXL|*(Oa{= zqp(vGX_Ex%4xYmh&Y+(C8>kg|AGtrWfL-_la-34bP1J>LKwa=Xs1e1y94Z&7#l6YAFxWVSNA8AEs{>O$|wavb(!8&?n~P$%4rx`6|z z`KM9yypB1&EO{Qdn6iL+HjAid`3H7mIUm37xDWM+9>5}e74JKY_ZVDdgjjr+uG6t&E3yNxwiH_Z8|w z&-w8M)QbLyn(uGad?k_F3MHxHW*wuU4J+_0YND@E2cE}DyzKiA>ifOhqi)KG@r%1rOWl>RlCEP!t<;#A95;ELzqfWpSI@2s)oxF%Gop4X;X3xtn3eK=@9HkLyDhIhJW{Q- zNo1U~IYDo8kDJUN4V!_Q!BpCgQ(&!?9@V7j1tU$NK__L+y<<9ry^%$)qNm*J@7ek< DfGIx- delta 1829 zcmZA1U2GIp6u|M*7AXBzS_(peTiY%z^ecgagam_HP#Zz>C<0s~bgHF^f}iUlhNN-Na|`CcKPGv3G{Z zb+{RqV;+~``>5;uHbV-+SgD>VG6`F76}F=$@^KvBM4dQtgG`J+7I_XA^85l8@cLOI z)9^+791md`CT8;vx8h4Uf>W@*iCManS(Kv#TCf`XuoiDY-N9O{!9mmohOr!bU?qpdDFTmSa6W zf|@vudNez*0(V8efRl;$p?2;7>b#>k1y7@P@@(|{+sI$Bj_0NA69pDz8b35~2Wp~4 zr~~@367NTvBO9>?cOu=DqqqpaM7k?w9U|nGX5_la9jF@{#M?28EATk#W&8(w^wun+ zZ@N<#yYVs99UR6=d>i%bKSCY%1#*AnE9}JIk>iwk^i>PH8MWYtQ9JqsPQ`;*fk&_k z-@{3)FDLk+15Tly**Vn8FQD%13hLL;$ZF-d2DP9EQ44jj8oemaV-@j}s0%)aI({!| z{zIsFKEZ-smN9;CG36W7)?YzA%c))0I?{x?;{nt&Phcq~Q41JGO}Gs;PXTr3`=j?K zQ9E}AIW74bwbT45OuXnJT1vbEbzly)BcrH^pGV!%UgYDD*CG$0cIFM#OL`u4-cP87 z{u0H1U=8u#sQD_nNSbe2&uS6=8OdB8yn;(m2acmA`VsXge#SbyihPMu+Z%m`NDApM zzE!!ia{fxsGd5>k&klNyN&0ce3yk&Bj*aI{+~1b+d>eP-#WmB{mWKzMJ}f>o=jqb2 zRen1BeqL8tZThN>XU(whxfyrJhW%zw_??+kVUxq2YfX?1KQ&F|W=l3<e4?y0 z=Z2SC@2fTbu#<9Ai493V=LF&Uw#6lPhGAQ8c(!dptLHg6Yiu;nByC`FHtmul7&{zy z23?OSb+MHVxB97!o67pxz{?w(vR;0>6DDG&H134EVoT}>jl+<%V-hy$gr{T6^wrf^ zV@Y?hVScP+wrL;o(`hH+#{CYtE!wcTOB+!fXzwhI<+oGif0LMOpidspxq+JrM$Cl8 zmlqr>(bRW$?wI+%hPDSCW|N;y#lwr8-DOU9BAnT^p&>AF#|&pv8M}qX+q|E4g5on> zwxnjAO>H&(LB>tmj1&IV-CV`Ud(!?UC!p`Ko=c57HSJ{5zV+NB`?EvtSTPmW_OAaI De=azx diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po index b92652a..975575c 100644 --- a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po @@ -93,10 +93,10 @@ msgid "analysis:extra" msgstr "Profundizar Análisis" msgid "analysis:equalize" -msgstr "Igualar visitas de potenciales movimientos" +msgstr "Igualar visitas potenciales movimientos" msgid "analysis:sweep" -msgstr "Análisis rápido de todos los movimientos." +msgstr "Análisis rápido todos los movimientos" msgid "analysis:aimove" msgstr "Forzar jugada IA" @@ -297,7 +297,7 @@ msgstr "" #. hints on new game msgid "non square board hint" -msgstr "Usa x:y (e.g. 19:9) para tableros no cuadrados" +msgstr "Usa x:y (i.e. 19:9) para tableros no cuadrados" #. submit new game msgid "new game" @@ -305,16 +305,16 @@ msgstr "Empezar Nuevo Juego" # teacher settings from here msgid "teacher settings" -msgstr "Editar Conf de Profesor" +msgstr "Editar Conf Enseñanza" msgid "point loss threshold" -msgstr "Pérdidas de puntaje mayores a" +msgstr "Pérdida de puntaje mayor a" msgid "num undos" msgstr "Número de jugadas desechas" msgid "dot color" -msgstr "Color (corregido)" +msgstr "Color" msgid "show dots" msgstr "Mostrar Puntos" @@ -339,7 +339,7 @@ msgstr "" #. submit button msgid "update teacher" -msgstr "Actualizar conf de retroalimentación" +msgstr "Actualizar conf" # main config settings msgid "restarting engine" @@ -483,8 +483,8 @@ msgstr "Perdida de puntos" msgid "aihelp:scoreloss" msgstr "" -"Las jugadas que conllevan a mayor pérdida de puntos se juegan con una menor probabilidad. " -"Probablemente juega mas variado/débil con un mayor número de " +"Las jugadas que conllevan a mayor pérdida de puntos se juegan con una menor " +"probabilidad. Probablemente juega mas variado/débil con un mayor número de " "visitas." msgid "ai:policy" @@ -493,8 +493,8 @@ msgstr "Jugadas intuitivas" msgid "aihelp:policy" msgstr "" "Juega la jugada que se considera la más intuitvda sin leer. La fuerza esta " -"dada por el `model` en la conf del motor. El parámetro `opening_moves` afecta" -" cuantas jugadas en la apertura son al azar." +"dada por el `model` en la conf del motor. El parámetro `opening_moves` " +"afecta cuantas jugadas en la apertura son al azar." msgid "ai:p:weighted" msgstr "Parcial" @@ -512,10 +512,10 @@ msgstr "Politica Ciega" msgid "aihelp:p:pick" msgstr "" -"Escoge `pick_n + pick_frac * <#jugadas legales>` al azar y juega la " -"mejor.El `pick_frac=1` juega como la politica intuitiva del IA. Juega la " -"mejor jugada si la probabilidad del IA es por encima de " -"`pick_override`. Esto con el fin de prevenir errores obvios." +"Escoge `pick_n + pick_frac * <#jugadas legales>` al azar y juega la mejor.El" +" `pick_frac=1` juega como la politica intuitiva del IA. Juega la mejor " +"jugada si la probabilidad del IA es por encima de `pick_override`. Esto con " +"el fin de prevenir errores obvios." msgid "ai:p:local" msgstr "Estilo Local" @@ -557,9 +557,9 @@ msgstr "" msgid "aihelp:p:rank" msgstr "" -"Seleciona jugadas al azar de una limitada selección y juega la " -"mejor. Configuraciones más fuertes toma la mejor jugada de una selección más amplia " -"de jugadas. Como no hay 0 kyu/dan, 3 dan = -2 kyu." +"Seleciona jugadas al azar de una limitada selección y juega la mejor. " +"Configuraciones más fuertes toma la mejor jugada de una selección más amplia" +" de jugadas. Como no hay 0 kyu/dan, 3 dan = -2 kyu." msgid "Kata model not found" msgstr "El modelo KataGo {model} no existe, revisar conf general." @@ -580,7 +580,7 @@ msgid "tab:info-details" msgstr "Detalles" msgid "Kata exe not found" -msgstr "El binario KataGo {exe} no existe, revisar conf general."f +msgstr "El binario KataGo {exe} no existe, revisar conf general." msgid "sweep analysis" msgstr "Analizando tablero entero usando {visits} visitas/jugada" @@ -604,18 +604,14 @@ msgstr "Descargar Modelos" msgid "All models downloaded" msgstr "Todos los modelos fueron descargados" -#. TODO msgid "tab:rank_est" -msgstr "Estimación Rango" +msgstr "Estimación fuerza" -#. TODO msgid "game re-analysis" -msgstr "Re-analyzing entire game using {visits} visits" +msgstr "Re-analizar juego usando {visits} visitas" -#. TODO msgid "analysis:game" -msgstr "Re-analyze entire game" +msgstr "Re-analizar juego" -#. TODO msgid "analysis:continuous" -msgstr "Continuously analyze" +msgstr "Analizar continuamente " From ee04751ab6d6846cc9c344c5ba7a9c514719f6d4 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sat, 27 Jun 2020 12:15:36 +0200 Subject: [PATCH 26/77] linux sound --- INSTALL.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/INSTALL.md b/INSTALL.md index 20efcf4..0b76d40 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -48,7 +48,7 @@ The following packages may help resolve missing OS packages for Kivy or KataGo. ``` sudo apt-get install python3-pip build-essential git python3 python3-dev ffmpeg libsdl2-dev libsdl2-image-dev\ libsdl2-mixer-dev libsdl2-ttf-dev libportmidi-dev libswscale-dev libavformat-dev libavcodec-dev zlib1g-dev\ - libgstreamer1.0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good\ + libgstreamer1.0 gstreamer1.0-plugins-base gstreamer1.0-plugins-good libpulse\ pkg-config libgl-dev opencl-headers ocl-icd-opencl-dev python3-pygame ``` Then, try installing python package dependencies using: @@ -57,6 +57,8 @@ pip3 install -U cython wheel setuptools pip3 install kivy==2.0.0rc2 kivymd==0.104.1 ``` +You can also install kivy from source using `pip install git+https://github.com/kivy/kivy.git@2.0.0rc3` which may help issues with audio on linux. + In case KataGo does not start, an alternative is to go [here](https://github.com/lightvector/KataGo) and compile KataGo yourself. # Configuring the GPU(s) KataGo uses From 6feae1a86053019f2cca613aa08f2ee896c029ba Mon Sep 17 00:00:00 2001 From: bale-go Date: Sat, 27 Jun 2020 12:50:33 +0200 Subject: [PATCH 27/77] user_data_based_AI_obvious-move-patch --- katrain/core/ai.py | 41 ++++------------------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/katrain/core/ai.py b/katrain/core/ai.py index 38c61f9..323df1e 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -188,43 +188,10 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, if ai_mode != AI_RANK: n_moves = int(ai_settings["pick_frac"] * len(legal_policy_moves) + ai_settings["pick_n"]) else: - n_moves = int( - round( - board_squares - / 361 - * len(legal_policy_moves) - / ( - 1.311648546930214 - * ( - ( - 0.31164467 - + 0.55726218 - * (len(legal_policy_moves) / board_squares) - * math.exp( - -1 - * ( - 3.0308747 - * (len(legal_policy_moves) / board_squares) - * (len(legal_policy_moves) / board_squares) - - (len(legal_policy_moves) / board_squares) - - 0.045792218 * ai_settings["kyu_rank"] - - 0.31164467 - ) - ** 2 - ) - - 0.0064860256 * ai_settings["kyu_rank"] - ) - * ( - 0.0630149 - + 0.762399 - * board_squares - / (10 ** (-0.05737 * ai_settings["kyu_rank"] + 1.9482)) - ) - ) - - 0.08265346672884874 - ) - ) - ) + orig_calib_avemodrank = 0.063015 + 0.7624 * board_squares/(10**(-0.05737*ai_settings["kyu_rank"]+1.9482)) + norm_leg_moves = len(legal_policy_moves) / board_squares + modified_calib_avemodrank = (0.3931+0.6559*norm_leg_moves*math.exp(-1*(3.002*norm_leg_moves*norm_leg_moves-norm_leg_moves-0.034889*ai_settings["kyu_rank"]-0.5097)**2)-0.01093*ai_settings["kyu_rank"]) * orig_calib_avemodrank + n_moves = int(round(board_squares * norm_leg_moves/(1.31165*(modified_calib_avemodrank+1)-0.082653))) if ai_mode in [AI_INFLUENCE, AI_TERRITORY, AI_LOCAL, AI_TENUKI]: if cn.depth > ai_settings["endgame"] * board_squares: From 6de3fd17c98819aa2128234a2e76389f97163910 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sat, 27 Jun 2020 12:52:56 +0200 Subject: [PATCH 28/77] keep comments/update todo --- i18n.py | 27 ++++++++++++++---- .../i18n/locales/cn/LC_MESSAGES/katrain.mo | Bin 11005 -> 11006 bytes .../i18n/locales/cn/LC_MESSAGES/katrain.po | 19 ++++++------ .../i18n/locales/de/LC_MESSAGES/katrain.mo | Bin 12829 -> 12830 bytes .../i18n/locales/de/LC_MESSAGES/katrain.po | 19 ++++++------ .../i18n/locales/en/LC_MESSAGES/katrain.mo | Bin 11980 -> 11922 bytes .../i18n/locales/en/LC_MESSAGES/katrain.po | 10 +++++-- .../i18n/locales/es/LC_MESSAGES/katrain.mo | Bin 12690 -> 12691 bytes .../i18n/locales/es/LC_MESSAGES/katrain.po | 4 +-- .../i18n/locales/fr/LC_MESSAGES/katrain.po | 17 +++++------ .../i18n/locales/ko/LC_MESSAGES/katrain.mo | Bin 13671 -> 13672 bytes .../i18n/locales/ko/LC_MESSAGES/katrain.po | 19 ++++++------ .../i18n/locales/ru/LC_MESSAGES/katrain.mo | Bin 17036 -> 17037 bytes .../i18n/locales/ru/LC_MESSAGES/katrain.po | 19 ++++++------ 14 files changed, 79 insertions(+), 55 deletions(-) diff --git a/i18n.py b/i18n.py index a76def2..be21dd0 100644 --- a/i18n.py +++ b/i18n.py @@ -1,6 +1,7 @@ import os import re import sys +import copy from collections import defaultdict import polib @@ -27,14 +28,13 @@ for lang in locales: for entry in po[lang].translated_entries(): if "TODO" in entry.comment: todos[lang].append(entry) - else: - strings_to_langs[entry.msgid][lang] = entry.msgstr + strings_to_langs[entry.msgid][lang] = entry strings_to_keys[entry.msgid][lang] = set(re.findall("{.*?}", entry.msgstr)) if entry.msgid in lang_to_strings[lang]: print("duplicate", entry.msgid, "in", lang) errors = True lang_to_strings[lang].add(entry.msgid) - if todos[lang]: + if todos[lang] and any('todo' in a for a in sys.argv): print(f"========== {lang} has {len(todos[lang])} TODO entries ========== ") for item in todos[lang]: print(item) @@ -60,14 +60,29 @@ for lang in locales: print("Message id", msgid, "found as ", strings_to_langs[msgid], "but missing in default", DEFAULT_LANG) errors = True elif DEFAULT_LANG in strings_to_langs[msgid]: - copied_msg = strings_to_langs[msgid][DEFAULT_LANG] + copied_entry = copy.copy(strings_to_langs[msgid][DEFAULT_LANG]) print("Message id", msgid, "missing in ", lang, "-> Adding it from", DEFAULT_LANG) - entry = polib.POEntry(msgid=msgid, msgstr=copied_msg, comment="TODO") - po[lang].append(entry) + if copied_entry.comment: + copied_entry.comment = f"TODO - {copied_entry.comment}" + else: + copied_entry.comment = "TODO" + po[lang].append(copied_entry) errors = True else: print(f"MISSING IN DEFAULT AND {lang}", strings_to_langs[msgid]) errors = True + + for msgid, lang_entries in strings_to_langs.items(): + if lang in lang_entries and 'TODO' in lang_entries[lang].comment: + if any(e.msgstr==lang_entries[lang].msgstr for l,e in lang_entries.items() if l!=lang): + todo_comment = f"TODO - {lang_entries[DEFAULT_LANG].comment}" if lang_entries[DEFAULT_LANG].comment else 'TODO' # update todo + if lang_entries[lang].msgstr != lang_entries[DEFAULT_LANG].msgstr or lang_entries[lang].comment.replace('\n',' ')!=todo_comment: + print([lang_entries[lang].msgstr, lang_entries[DEFAULT_LANG].msgstr, lang_entries[lang].comment, todo_comment]) + lang_entries[lang].msgstr = lang_entries[DEFAULT_LANG].msgstr # update + lang_entries[lang].comment =todo_comment + print(f"{lang}/{msgid} todo entry updated") + + po[lang].save(pofile[lang]) mofile = pofile[lang].replace(".po", ".mo") po[lang].save_as_mofile(mofile) diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo index 78189a9d0dec28c099c648fc0279353f8070faca..67fba6b14f4aa7ec96ca291f3480c199aaa782dd 100644 GIT binary patch delta 1043 zcmXZbOGs346vy#1QezF}W2k6wwDH|TOlupKgb=li*%Y-9qm30sgkf;OqR@z@MM(=$ zGF(K9iCF{^Eh-UnA%+Wy!IpxAWI=^d`2F=iFz~tO{9osP?%coGf7yI79xa*KY?awj ze33TG;R@!lznU>@sWH2czi|=o>Wnt z8&1_(^#9=#iCrYVU;=+)3S;$VNvuLGY;}*I#!sR)>hb(l+)5l_90z?o=kbWg&)rw` zQP|+ECuVUc3l}|JMQ!*8Rp91^P*56anjOS)JmvYbxSRMg^0Rv$zeJj2Q>Y6qBh9q+ z$S0&4!@wbIW1tIL@CF{hail3WhdS^(wqi+BxIjCq&<>AJxLMSNx={!Ap$Z?y3XBRq z;e-1b;|zR5we}aP^_!YQK8ebw+$K~(huz~yZFU;)3}0s?+@2sDpE;&u-ZB6Q~XrP=C4^)J2PEoxQ!8OILad3yGo1Sf+43JzBN_z0-57 delta 1041 zcmXZbO-NK>6vpwB#-L)BL&cPu=A`9EHnoiqC5T!^E|MTJaivs5C%8aA{nzxjDf2lN&70ZlBC~qD zUu<>*7cqm~C5&OsHnXev8$V)yX_$AS%xs)^32)-9?Pe`lS#DN+lig z<8--2s}FBUY$ovq^Y9mLK}(v&VJm9kZg)Rw{4i>x)1E(zxx_sf!vP;pdwkd9Q8$x} z!Uj)0@d`Jw@Po%os15(13S7G*6jX#X&FXMH9`SrLCWt$bpI!F&0n!|sMqOwDX{N12 zJ|R&N2KHi#frD6!7qA9Lkfzuy>cAgZhifXs1yZO&4|sgYJ%+l_Db#_TsKSSE14a`* z;ko+>V+?#lwRRcR`s|${Ux>;l+zM1d``kvPHam)X_g$#@y{LV!dwe?>*$5A+{Gks# zc3+?>e}`JQh&t#y>L#`;#HH>Yq?uNaIWrEi>Q5i+)Ehe|7X{D(7PE%Eu3;^ zv6^@e`5-J-6~>cD%2FO5ciT`0oOiq3UUvZXD$^c6k(_VOJn<4$z#CM{=RN<`{fX+p y3U*;G)oFeo>fki$vm5gK7^;I4s6X8d>Y{UK4ejk^9cNl6-{%b$PM$0tF8B}D=5r1J diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po index fc88241..34a3acd 100644 --- a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po @@ -557,35 +557,36 @@ msgid "Starting Kata failed" msgstr "" "用'{command}'命令启动katago出错 {error}. 请确认引擎设置里'katago'的值指向了 正确的KataGo运行文件." -#. TODO +#. TODO - shown on on the model select dropdown msgid "models available" msgstr "{num} available model(s) found" -#. TODO +#. TODO - button in general settings for downloading models msgid "download models button" msgstr "Download Models" -#. TODO +#. TODO - error message on trying to download models when already done msgid "All models downloaded" msgstr "All available models already downloaded" -#. TODO +#. TODO - shown on a graph tab msgid "tab:rank_est" msgstr "Rank Estimate" -#. TODO +#. TODO - status message msgid "game re-analysis" msgstr "Re-analyzing entire game using {visits} visits" -#. TODO +#. TODO - Analyze every node in the game more deeply msgid "analysis:game" msgstr "Re-analyze entire game" -#. TODO +#. TODO - Keep improving current position analysis when there is nothing else +#. to do msgid "analysis:continuous" msgstr "Continuously analyze" -#. TODO +#. TODO - ai which handles handicap games better msgid "ai:handicap" msgstr "KataGo Handicap" @@ -594,5 +595,5 @@ msgid "aihelp:handicap" msgstr "" "KataGo optimized for playing higher handicap games.The `pda` setting " "corresponds to `playoutDoublingAdvantage` in KataGo, from black's " -"perspective.When `automatic` is set, KaTrain will find a suitable value " +"perspective. When `automatic` is set, KaTrain will find a suitable value " "automatically." diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo index cc9fac0a0b1e73c94fd5788b929d8bb630464447..d9969637bc19a8634ff1f526ed79e50b6126e31a 100644 GIT binary patch delta 1043 zcmXZbPe@cz6vy$OC8nd3=4fS2I+dnPO<4p+ZA`(476m~r3c(l%(y&d`LU;v%fo&oQ ziYbErY(fYoa~F|JWFSO3v=KFmAQ%@x5M_(LKjv-bbI+ao&b{Zn`yQkpreDmL1q+gX zZIRMA+bF%qgH6&rmL&WmEnuGE;#R4Scw(D0P5fcIbb@%SSz0Fk)*_AKryWui_O(hi zIDmCHj1jzzZJ23QplSv=Ce~vf*J1&kUxRq*+?Tjkr2VF&GaRY-Z4fZdD&1#Ub2>lh}zrumh93iW{B7J;Ybg9eaj8@B~)i z6ndi>^v2(@9V5G?O?Vg`2d5eM#3Pu%C+ODY(1~Wym3_uiEMNqGq7(hYTHKH-7Lr7Y zR1dnKOX&62F^0FW3Lhbz3^dN5l8ILs#kc4OKcXx6g0Ao{dcz9J=ANnD+I|c`sSPB=^^WvN!j_j!vQl!qH4|)?>sxhp_snA*U z-S~>GxPW{d`W^ZQ{YqERolLcJ5cb!822OkkT~QB4@f12?KRV$B7@3qM9Dvza8Dnx?dLYAS_DYEvUf)J6zuQ3ysukXUY_7A|fOROBWS zq8KVD+=Po@%w0q>fr1d()XI-2f?x_-M3im%Kg?YWzw@3s_dVx*@41h14|C5Ks)I#I zzqUv@oKH$K*u7O+K&AMtk{0m;!=-IfoOp7(G)p|&D)kYMwMjpSzwD4k@!d|T0sGsf zbvTG|9Ki_Q!4Ax~D^MeYA`{J6!nIgNCs@JFSi|ZjY{Pmyfc&K%^t!=t{wh`zU&k7} zjn02JjGtf&@w0Hg*b!7tRAR!3meB|Kj!|5luFN+fMJtK5m_;AtIC_Iq=nBuH4?KeV z@IG$98KhZS!i`w9OQKmy1Pl@kvglUz<59eXT{wkD@EdkxdUs``v$&V|3c6$C=mSq; z9ZsV+nnQ2=6+5vmBW=P%=r}mZz$d(xlCa?+%VLXK{@Fljezuqv|&%|=LAkK?(f&{XsGDwj+jXvm2WU0om5vN1v z(RbrBy5chOap-sGAM`6-L3c9K$wAm(T@0M~AiAPnjN%D&!U1%`3&@wI%Q%h?(0AuB pS)K3Ec!mG~ diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po index c7548c4..1c7de17 100644 --- a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po @@ -600,35 +600,36 @@ msgstr "Analyse der Kandidatenzüge wird abgeglichen mit {visits} Visits" msgid "sweep analysis" msgstr "Analysiere gesamtes Brett mit {visits} Visits/Zug" -#. TODO +#. TODO - shown on on the model select dropdown msgid "models available" msgstr "{num} available model(s) found" -#. TODO +#. TODO - button in general settings for downloading models msgid "download models button" msgstr "Download Models" -#. TODO +#. TODO - error message on trying to download models when already done msgid "All models downloaded" msgstr "All available models already downloaded" -#. TODO +#. TODO - shown on a graph tab msgid "tab:rank_est" msgstr "Rank Estimate" -#. TODO +#. TODO - status message msgid "game re-analysis" msgstr "Re-analyzing entire game using {visits} visits" -#. TODO +#. TODO - Analyze every node in the game more deeply msgid "analysis:game" msgstr "Re-analyze entire game" -#. TODO +#. TODO - Keep improving current position analysis when there is nothing else +#. to do msgid "analysis:continuous" msgstr "Continuously analyze" -#. TODO +#. TODO - ai which handles handicap games better msgid "ai:handicap" msgstr "KataGo Handicap" @@ -637,5 +638,5 @@ msgid "aihelp:handicap" msgstr "" "KataGo optimized for playing higher handicap games.The `pda` setting " "corresponds to `playoutDoublingAdvantage` in KataGo, from black's " -"perspective.When `automatic` is set, KaTrain will find a suitable value " +"perspective. When `automatic` is set, KaTrain will find a suitable value " "automatically." diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo index 8090a202c6169f4d837854abfb3e01e104d627b3..d7e6040d736e50119a430f7dd2489faa70b58646 100644 GIT binary patch delta 2802 zcmYM!duW$c7{KvIow`lk)>>14+P2E()MiXVnJ9yzQpu9aONN;@64Fw$P(y!dpoEpO zu!|W{f~3|THTy%&E{K<@ITLY8;x%!K1Qszf(!SsAK;zx}Ip;mM=RD_qe}|^jPsyGx z&RG}yZRNi&{|h@?i#U}3zjsjlnfh&b0ej(J$%1YX<<$FQF+Pe#I2pU*i&%iKU?HwZ z?e*1NOk9Sc+${JGNpD=1?)9JhXo~W^f3W;8=9Q>1eGH8H<(Eda5c6hLa_x(`$PCgh3+ZuZ-Y2GooOd?9k?t#UyV+@4p}2MrTTWX{{b}cyt0gr^P5D>og4@x4`>`#RXupf;c`G_!SJL4T z7U0*|8&BgwoPb;IR3j4`tGp-`+?{I1bziQa`F50l$-4S&iB97r<>LvXm z-oOiJfHUuHf7#}tJ6wjoOYb2=;uAFRACXguBWQq^vJ~9OA4pc>7W(pJ$fpxjV+9UF z2TVdUGy^@VIjKG$eJPiu=c|#^inVBlHlttFPBh@%>3Oz^f)k%h4Ofxli|gn@#k^6b zz7M*Cfyv=$z+-R#&O*nnL59ZWR6l|wDXye?VO9G)18|t%e=P;Bn9r>i*JBMHLtn}+ z%sv=1Xyl{O?|MAu;S6-=bCKhZW$4?z9u06Ga=LLo)jJGq5410~eg8w-3Y;&x!%=8z zCt(+yf~Ih0YJUw)?Mmdd;!`xRJ!qzW!CX9xd3YI}=g;)~272V32GQ>KUqr#wWiTHf zKwpwNH1Y}Pj$cI=coWHH)T06KKm$I29kB^J<56_s6UY}GXON^uYih3`P1$ZdsH9-S z5cKSxMI&F4p1+L-@GaVZH@a{Wy3;er#}O^bE9enkMFT6VZh!giK?56vZnUO4o@<+U zL~0m=PB;$zVR;7qL3ugVThIw_qF+fTUJL^%MUSKsyJHT?)RnBp6T)nB zH4^5uCS`5m)QSkVoKZ6i3u3jIn^0sebx>PTE7E0T?+^EExrgU<&Uwyr&i8!Jd9Gdc zqxIgUWZwbf-&_1;^7m|%YGxC$|9dON@2D@pKX4&N#f8nnZ0aSLjJ24A+b{;7!9;Aw z`Pk*2zY*560ScPyQ`7)oVFLb$srVblV+=PRrl0~!NA)kl3|x;XSdW@;2dZD2s~i7cc3>-r3WuL1Lp(dPg{(?&FzwWu8hZHRvwbCL~ zz~!!9!dLq*z+Ty}(;0_ev@_&F+tS5SdpcgFBCMN35mz6_O#m8g}j zb)sPBI<1x369HPC9*gd0(NScQ7wVb}f`YTzBH{;jB$cA`@LrfWZma{-~Y zWXwIELXCF~Q}q4&csqpYxD5BAGVl)SOq{`59LM{yn2pZIHq64~s09q8e!|bA#+k+< zOiMGn3pb$x+=FW0kKPIjA5%!jUy&@CpINn+nOK2Ka6N|bGwj0+_%ttRzz>kvYy|W0 z2i%P@{4QtXbEy7DP`|WSkj2|n2KhfsVU`A+;#c^_l&aTJE9ysO;xsDdqo@hTQ2i!x zE|sW$K9;ZcRMdERs4XnSek{dH_%qhv@M7|RjzW+;euR^F5qk-H5L<&}1qM=v2X&U-Lte2#RDj)&=e`QpK7{O=ji54h8Rx#P84616ta}jPI)oOk0LSd9v>9p_Sx0qQ-dl^;cpg`Gkj@(ZW{BMQP_O(yF6Ss4XI zT7!Ok(%FIvY!526T^NlAQ7L@YJ%1OK+B3**+Bhn(o2X31-5wq{0|V4=Lyc3YcILNL z6twr{u0sVXplXc8Cs2o_1v#Jg5^BYLs0ltqvTLKLfd51V>|@#UFo01QkD53c`O+;N zJ*B$Xb$AHlsBb~FhfsTW02TS5Yd?nyU>enb7Bz98Fuc-q(^b diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po index 76c75cb..48ca4f8 100644 --- a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po @@ -98,12 +98,11 @@ msgstr "Equalize visits of potential moves" msgid "analysis:sweep" msgstr "Fast analysis of all moves" -msgid "analysis:sweep" -msgstr "Fast analysis of all moves" - +#. Analyze every node in the game more deeply msgid "analysis:game" msgstr "Re-analyze entire game" +#. Keep improving current position analysis when there is nothing else to do msgid "analysis:continuous" msgstr "Continuously analyze" @@ -152,6 +151,7 @@ msgstr "Score" msgid "tab:winrate" msgstr "Win Rate" +#. shown on a graph tab msgid "tab:rank_est" msgstr "Rank Estimate" @@ -518,6 +518,7 @@ msgstr "" "in the general settings `engine` section and engine configuration file. No " "options are available here." +#. ai which handles handicap games better msgid "ai:handicap" msgstr "KataGo Handicap" @@ -619,11 +620,14 @@ msgstr "" "one. Stronger settings select the best move from a larger selection. Since " "there is no 0 kyu/dan, 3 dan = -2 kyu." +#. button in general settings for downloading models msgid "download models button" msgstr "Download Models" +#. shown on on the model select dropdown msgid "models available" msgstr "{num} available model(s) found" +#. error message on trying to download models when already done msgid "All models downloaded" msgstr "All available models already downloaded" diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo index 0cd01020eba7e5fd05ea62579ba7651a51c1bf5a..cef9e9913510152f94e4efeebe43cf44a26af68b 100644 GIT binary patch delta 1043 zcmXZbTS!z<6vpw-YSbAWn;NshDVwq!v&$3(M$!!eCEtR4G6)Qb80o2qdYCAr!stOG zB9lP6yh$-o6xl$~m3oOT1ff>+78E7~64C$T95~ExuYJy0d+oK)4E7B5-1%Hyn2>aT zsnm&=8>C76inI7+nN(ILjc25n#HHm@J@NS!(iHKHMk!C+zfyWeJiSU9#RpAN9J{kp z0uSS2Jc%_pgj^cUs-Q&-UJ)(8DO`xtp^@g`!4%dqzY~3bA5w!3VG;+>iLc{49K$FU zL&q^f{2aZ`8}xgV%>}8P!EYw2aW=eBu{wAljq{n$VIA(kD0ZO}9zZAT#TXt(nxr$B z!<)DS-{U5nMw+drmSAJM3k*_Bvqc`vrWB3}~ z`j6=6zGE6^a4jbI;Z(H^U07j116OTnD3W~{(IbYVx)g`UD{JR8IXU1Hz`hS3Y&#yH+ZFHk}!`hfmcCXnw_Gw7p9t`Cl? z5xLaLk9XXQKBCh&2hX7k7(nM6#5CWZt}^h>?}ZP1!W!Z!#$@==sz?DWe1c|E9Ev-c-%%T(K&>d;R5!{P$ t{Dm&yANswDjX`JXLt8Mx{8ntoPSmb^KGS#PVDVk-Sh6fr+|$sJ{12WbcZ2`{ delta 1041 zcmXZbOGs2v9LMpWVv|menv+dwsad{yOoh-JBZ87#1-UY4VG_|qn<#2?DWt+^krI(T zpyy7IBr39jAPlKZV9}y!EZPOdBm@;f-yiqFWj^PB?!Eu>KmT)QxNoR$>|-p*NqW3g z>c%VeQXW6!AAHdu6%|PnjnWixc9~R5e4$C2A-=m@$`B8%kS2)>&C(rw(jq0Ww^gdb z!k^a;RCf;$$T0YVkeei4?5uibi#g&<4L4RI*V)Z z9xlOY?7#xjY&EY8H?}iiu!xBx=$#E<7hc6qoWZTwuqsU0i{9x)B$r0e8+eIve2s4X zd-QW(u?}Z(HCFP&scIX#uwXv}S9}WH((71?SuDd>I1k^VTbqmSe?fQT8@lk~HQ~-` za1(I{&czJ6u;b`L11!gLVH{|fffpD-FZcixIEG#zi%#?o{jKDX?^Cnrqp41ZN7aN} z+Q5%@+>bt@Ggyr0(FF{m^IgU|zCT@K;GI8;9{7M2#Cha%(0AlgiUalhF5HMcQG5yA zu^Z^b_t6^~MgAOm8u<*}ndj(E{=!n$*Ix#%v}9eFD2{Gv5}mLGoiL5=$QHbbdoh7O q&;|TMzZY8{c4l#8J618j8F%9z)Q(K1@#wLGlNvs8=kk(7R7 z6up$R5%-~o9Vz+$6MZB$kQl`pyp2}iF(z;Z!?=j6@C)w2UoPL4ma2&}&Ky<|ce%LR z#d(a7zv#T0_JfY&E>Xfwba;SPa2D(F1=isbTH#QOv=&$6I*gpP(Jp0u(KV&2wq0(FoxVq-9ua1ELy>Nv>jVS+p*BD;Lb$wByl4~a1d?c zBWV8@&?ZnuyF(MWp7AwF!_H>Lb@+>RMxou(L5yJ*JJAZ?z~guyZQ@^@f3SvlB{$Os zj-l;X(wW9?;&!x&jG2tDahE8e6}*d9;Gv7BUHlZ|U|m#`ME>!-{K|wfy(ywdKdc7O}z^Lj#hY6a}77^{sG7Ocl!VU delta 1041 zcmXZaOGs346vy$O3=BABjyl?mQ8QyQ>GqPXm@C^j zC^QjM5Vk0KfGeSuhV5dCaFs+Q*di$`BC$o^mmh=PvZNDn zDTCDsX&tX)vPim|l+NK;jkH02X|L2xT&VT?rBc!<;@CdvJKuZiq-pG`mn!fJlF}cH zU_34D#1>3oS6bdyqMt++iE%8$`)CE8V-n{vgezE#KkzVaxO`KCR6^Y9?7$%Lc^6-F z@ih#SA9UVo@cfQhm&jq14o}buF5oVFhn2X7Ryfco?Z6P0VhpW+JzD>x$R;V{;v0C3 zcnWPN-r{cj>e1Lkqo_&Rh6&_RBd?Qq0Xs2=ZTK0ja80wsqfTBWIE>rzwu>j756~9$ z3>iv`Xbbs;N6;(Y?@yr3*@L#iQRg_?0CzBe_gp@Yc4RXc!&hiWwS;Vv{-PaW?E(M) zRTJYyquc0d2=t&~_|v(7!XKc$PScVH`r6 z_!!#%lV}sjqurqyEN6VZpkZe-?>hWLJEOoM={QEQ9W!W!r|~pCMw|Fg=O&gB2f3Lx za1?FFQqDA9B5p>T$b`xG%DO}jt>8nn0#98$=i=8`P5wP*aSbc*W{ba~DYWl-wEySO fcJd8I@DmQt&0W#jYw!gz6$BL1$536y7`)2woHj z6+vM^1YJZF3<~Yi=)$g|qKJwhh(YfcMn;0)!}Eab^FNROw{!mI;;8S^sPEzXfb&t( zNKCqdy>aP;Px^sp@JK?sfv?v|-FRxf^cg3crPDakBE|etWvetuoZKKS!kZhV2>ab` zm)dX&tFer0aUS<#eTN*em-;)TFpgs#-o+Z6M)uMNV_y6T_Y+6bR0#)g4^B7?sG`1L1FmHC1#HJ5Ea5Q5wn)42D%RpVq}rOr<(TO7 z5<$CA9vkrxW^n|!;S;oj|6(4UT$e<3HHO?lQ)n6fLMz=I)?md}_j>}ZVr^(ol||Y_ zhtbxb$6fdsBlr)kl2sXZeG+Y526-Y!Jq$v8=))ME#X7w1#gDL>_$^w--_a_yhIA~` z3`Vhly;#I%e1_HwKhZAy2dz>|x04L6#yIDzk%4t%9&N%nTE@4K>S_`X;%~G}x_3y& zupjGj#p@DhXg6>Z`iT7~>s_YK4N zP0|6hX3FN=ZXZVLrE_TAeGmE53x2EuA>QRUHre~+ipA*A$-dI7imr$+S_;OU=mLa} Beh>fv delta 1043 zcmXZaOGs346vy%3Sc%D`=3|CwvgVOxVcFFwb9&4ejj3%yum{zqVjxBz@8MpM`J8k8zs~vpZwv>n4+oykm%1M% zU5QI$cq}2E4@lo|2v2U1?%-U5bPz9Yl0M^9qcn))Tcmi2RM{jI$enG{GQ8U?g<0?Z zc4;e4V=WeOJ^sUPT?IHFU@ts}T{!L%&_#X0by&shYuJnfIDwZi)+QamF_7hVc(tCs*(G<~wNf(#RFLIzmvxhcg(*A&lZ}KYxa`OkqUuc~w+bfl07!&NT1cANA9NK_Uw2JQ`-PIKK;ZL+mI`>JZ zF^}~)>sv&tctyr5s2)qm9p4ld$lK65`~X8P!D9m3_$Ar`Z*diVMH{$;)}cVwyTdwM zAn!$cOqmYvwGX1bB^T{=KSKWWh9B!dHTROo2D_h*{{Gm&g|m}$ Date: Sat, 27 Jun 2020 13:29:40 +0200 Subject: [PATCH 29/77] user_data_based_AI_obvious-move-patch_rank_estimation --- katrain/gui/widgets/graph.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index 5f6e597..8c27147 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -198,11 +198,12 @@ class RankGraph(Graph): if item > num_legal[i]*0.09: rank[i] = num_legal[i]*0.09 rank = tuple(rank) - averagemod_rank = averagemod(rank)+1 + averagemod_rank = averagemod(rank) averagemod_len_legal = averagemod(num_legal) norm_avemod_len_legal = (averagemod_len_legal/num_intersec) - rank_kyu = -0.6284*math.log(averagemod_rank)/(0.1705+averagemod_rank*gauss(3.374*(norm_avemod_len_legal)))+13.59*(norm_avemod_len_legal)+10.41*math.log(averagemod_rank)+12.42*gauss(2.519*(norm_avemod_len_legal))-14.58 - + rank_kyu = -0.97222*math.log(averagemod_rank)/(0.24634+averagemod_rank*gauss(3.3208*(norm_avemod_len_legal)))+12.703*(norm_avemod_len_legal)+11.198*math.log(averagemod_rank)+12.28*gauss(2.379*(norm_avemod_len_legal))-16.544 + if rank_kyu<-5: + rank_kyu=-5 return 1 - rank_kyu # dan rank @staticmethod From ee38505e60ecfe0cefbd1a9fda5da416e1c13ed6 Mon Sep 17 00:00:00 2001 From: bale-go Date: Sat, 27 Jun 2020 14:22:26 +0200 Subject: [PATCH 30/77] black&real5d --- katrain/core/ai.py | 25 ++++++++++++++++++++++--- katrain/gui/widgets/graph.py | 22 +++++++++++++++------- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/katrain/core/ai.py b/katrain/core/ai.py index 323df1e..e9704d2 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -188,10 +188,29 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, if ai_mode != AI_RANK: n_moves = int(ai_settings["pick_frac"] * len(legal_policy_moves) + ai_settings["pick_n"]) else: - orig_calib_avemodrank = 0.063015 + 0.7624 * board_squares/(10**(-0.05737*ai_settings["kyu_rank"]+1.9482)) + orig_calib_avemodrank = 0.063015 + 0.7624 * board_squares / ( + 10 ** (-0.05737 * ai_settings["kyu_rank"] + 1.9482) + ) norm_leg_moves = len(legal_policy_moves) / board_squares - modified_calib_avemodrank = (0.3931+0.6559*norm_leg_moves*math.exp(-1*(3.002*norm_leg_moves*norm_leg_moves-norm_leg_moves-0.034889*ai_settings["kyu_rank"]-0.5097)**2)-0.01093*ai_settings["kyu_rank"]) * orig_calib_avemodrank - n_moves = int(round(board_squares * norm_leg_moves/(1.31165*(modified_calib_avemodrank+1)-0.082653))) + modified_calib_avemodrank = ( + 0.3931 + + 0.6559 + * norm_leg_moves + * math.exp( + -1 + * ( + 3.002 * norm_leg_moves * norm_leg_moves + - norm_leg_moves + - 0.034889 * ai_settings["kyu_rank"] + - 0.5097 + ) + ** 2 + ) + - 0.01093 * ai_settings["kyu_rank"] + ) * orig_calib_avemodrank + n_moves = int( + round(board_squares * norm_leg_moves / (1.31165 * (modified_calib_avemodrank + 1) - 0.082653)) + ) if ai_mode in [AI_INFLUENCE, AI_TERRITORY, AI_LOCAL, AI_TENUKI]: if cn.depth > ai_settings["endgame"] * board_squares: diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index 8c27147..63e1807 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -163,8 +163,10 @@ def averagemod(data): (int(lendata * 0.8) + 1) - int(lendata * 0.2) ) # average without the best and worst 20% of ranks + def gauss(data): - return math.exp(-1*(data)**2) + return math.exp(-1 * (data) ** 2) + class RankGraph(Graph): black_rank_points = ListProperty([]) @@ -195,15 +197,21 @@ class RankGraph(Graph): num_legal, rank, value = zip(*non_obvious_moves) rank = list(rank) for (i, item) in enumerate(rank): - if item > num_legal[i]*0.09: - rank[i] = num_legal[i]*0.09 + if item > num_legal[i] * 0.09: + rank[i] = num_legal[i] * 0.09 rank = tuple(rank) averagemod_rank = averagemod(rank) averagemod_len_legal = averagemod(num_legal) - norm_avemod_len_legal = (averagemod_len_legal/num_intersec) - rank_kyu = -0.97222*math.log(averagemod_rank)/(0.24634+averagemod_rank*gauss(3.3208*(norm_avemod_len_legal)))+12.703*(norm_avemod_len_legal)+11.198*math.log(averagemod_rank)+12.28*gauss(2.379*(norm_avemod_len_legal))-16.544 - if rank_kyu<-5: - rank_kyu=-5 + norm_avemod_len_legal = averagemod_len_legal / num_intersec + rank_kyu = ( + -0.97222 * math.log(averagemod_rank) / (0.24634 + averagemod_rank * gauss(3.3208 * (norm_avemod_len_legal))) + + 12.703 * (norm_avemod_len_legal) + + 11.198 * math.log(averagemod_rank) + + 12.28 * gauss(2.379 * (norm_avemod_len_legal)) + - 16.544 + ) + if rank_kyu < -4: + rank_kyu = -4 return 1 - rank_kyu # dan rank @staticmethod From ec345dbff967d94b8ecbfae1c7deac71152a964b Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sat, 27 Jun 2020 14:34:15 +0200 Subject: [PATCH 31/77] contrib/readme to md --- CONTRIBUTIONS.md | 38 ++++++++++++++++++++++++++++++++++++++ CONTRIBUTORS | 23 ----------------------- README.md | 2 +- 3 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 CONTRIBUTIONS.md delete mode 100644 CONTRIBUTORS diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md new file mode 100644 index 0000000..f468c99 --- /dev/null +++ b/CONTRIBUTIONS.md @@ -0,0 +1,38 @@ += Contributing + +TODO + += Contributors + +== Primary author and project maintainer (https://github.com/sanderland/katrain): +Sander Land + +== Contributors + +Many thanks to these additional authors: + +* Matthew Allred ("Kameone") for design of the v1.1 UI, MacOS installation instructions, and working on promotion and YouTube videos. +* "bale-go" for development and continued work on the 'calibrated rank' AI. +* "Dontbtme" for detailed feedback and early testing of v1.0+. +* "nowoowoo" for a fix to the parser for SGF files with extra line breaks. +* "nimets123" for the timer sound effects. +* "fohristiwhirl" for the Gibo format parsing code. + +== Translations + +Many thanks to the following contributors for translations. + +* French: "Dontbtme" with contributions from "wonderingabout" +* Korean: "isty2e" +* German: "nimets123" +* Spanish: Sergio Villegas ("serpiente") with contributions from the Spanish OGS community +* Russian: Dmitry Ivankov and Alexander Kiselev +* Simplified Chinese: Qing Mu with contributions from "Medwin" + +== Additional thanks to + +* David Wu ("lightvector") for creating KataGo and providing assistance with making the most of KataGo's amazing capabilities. +* 깁원식 ("Seventeen") for explanatory YouTube videos in Korean. + + + diff --git a/CONTRIBUTORS b/CONTRIBUTORS deleted file mode 100644 index 43d2745..0000000 --- a/CONTRIBUTORS +++ /dev/null @@ -1,23 +0,0 @@ -Primary author and project maintainer (https://github.com/sanderland/katrain): -Sander Land - -Many thanks to these additional authors: -Matthew Allred ("Kameone") for design of the v1.1 UI, many suggestions, providing MacOS installation instructions, and working on promotion and youtube videos. -"isty2e" for the Korean translation. -"Dontbtme" for detailed feedback and early testing of v1.0, and the French translation. -"wonderingabout" for helping with the French translation. -"bale-go" for implementing and calibrating the 'calibrated rank' AI. -"nimets123" for the German translation and sound effects. -"nowoowoo" for a fix to the parser for SGF files with extra line breaks. -Sergio Villegas ("serpiente") for the Spanish translation. -Dmitry Ivankov and Alexander Kiselev for the Russian translation. -Qing Mu for the simplified Chinese translation. -"Medwin" for checking technical terms and suggesting improvements for the simplified Chinese translation. -"fohristiwhirl" for the Gibo format parsing code. - -Additional thanks to: -"Seventeen" for explanatory youtube videos in Korean. - - - - diff --git a/README.md b/README.md index 411ae17..20682df 100644 --- a/README.md +++ b/README.md @@ -140,7 +140,7 @@ In addition to shortcuts mentioned above and those shown in the main menu: ## Support / Contribute [![GitHub issues](https://img.shields.io/github/issues/sanderland/katrain)](https://github.com/sanderland/katrain/issues) -[![Contributors](https://img.shields.io/static/v1?label=contributors&message=12&color=dcb424)](CONTRIBUTORS) +[![Contributors](https://img.shields.io/static/v1?label=contributors&message=15&color=dcb424)](CONTRIBUTIONS.md) [![Liberapay patrons](https://img.shields.io/liberapay/patrons/sanderbaduk)](https://liberapay.com/sanderbaduk/) [![Github sponsors](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=dcb424&link=https://github.com/sponsors/sanderland/)](https://github.com/sponsors/sanderland) From 0e17a7ccf12da90c348cf4133700dca88e70b9fb Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sat, 27 Jun 2020 14:59:25 +0200 Subject: [PATCH 32/77] instructions for contrib --- CONTRIBUTIONS.md | 49 ++++++++++++++++++++++++++++++++++++++---------- README.md | 30 +++++++++++++++++++---------- 2 files changed, 59 insertions(+), 20 deletions(-) diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md index f468c99..0890610 100644 --- a/CONTRIBUTIONS.md +++ b/CONTRIBUTIONS.md @@ -1,13 +1,44 @@ -= Contributing +# Contributing -TODO +If you are a new contributor, please first discuss the change you wish to make via + an issue, reddit or discord before making a change. -= Contributors +## Python contributions + +Python code is formatted using [black](https://github.com/psf/black) with the settings `-l 120`. +This is not enforced, and contributions with incorrect formatting will be accepted, but formatting this way is appreciated. + +## Translations + +Adding a translation requires making a new `.po` file with entries for that languages. + +* Copy the [English .po file](https://github.com/sanderland/katrain/blob/master/katrain/i18n/locales/en/LC_MESSAGES/katrain.po) +* Change all the `msgstr` entries to your target language. + * Note that anything between `{}` should be left as-is. + * The information at the top of the file should also not be translated. + +You can send me the resulting `.po` file and I will integrate it into the program. + +For those who have some python experience as well, you can: + +* Replicate the directory structure `katrain/i18n//LC_MESSAGES/katrain.po`. +* Run `python i18.py` which will generate the `.mo` files and check for any errors. +* Find `gui.kv` and add a button near the others: + ``` + LangButton: + icon: 'img/flaticon/flag-.png' + on_press: app.language = '' + ``` +* The language should now show up in the app. +* Pull request your changes to the latest minor version branch. + +# Contributors + +## Primary author and project maintainer (https://github.com/sanderland/katrain): -== Primary author and project maintainer (https://github.com/sanderland/katrain): Sander Land -== Contributors +## Contributors Many thanks to these additional authors: @@ -18,7 +49,7 @@ Many thanks to these additional authors: * "nimets123" for the timer sound effects. * "fohristiwhirl" for the Gibo format parsing code. -== Translations +## Translators Many thanks to the following contributors for translations. @@ -29,10 +60,8 @@ Many thanks to the following contributors for translations. * Russian: Dmitry Ivankov and Alexander Kiselev * Simplified Chinese: Qing Mu with contributions from "Medwin" -== Additional thanks to +## Additional thanks to * David Wu ("lightvector") for creating KataGo and providing assistance with making the most of KataGo's amazing capabilities. -* 깁원식 ("Seventeen") for explanatory YouTube videos in Korean. - - +* "세븐틴" for including KaTrain in the Baduk Megapack and making explanatory YouTube videos in Korean. diff --git a/README.md b/README.md index 4a8115b..02296be 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ - -# KaTrain v1.2 +# KaTrain [![Latest Release](https://img.shields.io/github/release/sanderland/katrain?label=download)](https://github.com/sanderland/katrain/releases) [![License:MIT](https://img.shields.io/pypi/l/katrain)](https://en.wikipedia.org/wiki/MIT_License) @@ -9,6 +8,19 @@ [![Liberapay patrons](https://img.shields.io/liberapay/patrons/sanderbaduk)](https://liberapay.com/sanderbaduk/) [![Discord](https://img.shields.io/discord/417022162348802048?logo=discord)](https://discord.com/channels/417022162348802048/629446365688365067) + +* [Introduction](#intro) +* [Previews and YouTube tutorials](#preview) +* [Installation](#install) +* [Manual](#ai) + * [Play against AI](#ai) + * [Analyzing your Games](#analysis) + * [Keyboard shortcuts](#keyboard) +* [FAQ and Troubleshooting](#faq) +* [Contributing](#support) + +## Introduction + KaTrain is a tool for analyzing and playing go with AI feedback from KataGo. The original idea was to give immediate feedback on the many large mistakes we make in terms of inefficient moves, @@ -20,7 +32,7 @@ but has since grown to include a wide range of features, including: * Play against a stronger player and use the retry option instead of handicap stones. * Automatically generate focused SGF reviews which show your biggest mistakes. -## Previews and Youtube Videos +## Previews and Youtube Videos ### Play against an AI Teacher @@ -36,16 +48,14 @@ but has since grown to include a wide range of features, including: |:-----------------------------------------------------------------------------------------------------:|:------------------------------------------------------------------------------------------------------------:| | [![New Features Video](https://i.imgur.com/gCY6hMH.png)](https://www.youtube.com/watch?v=wFl4Bab_eGM) | [![ Teaching Game Tutorial](https://i.imgur.com/jAdcSL5.png)](https://www.youtube.com/watch?v=wFl4Bab_eGM) | - - -## Installation +## Installation * See the [releases tab](https://github.com/sanderland/katrain/releases) for pre-built installers for windows. * Alternatively use `pip3 install -U katrain` to install the latest version from PyPI on any 64-bit OS. * Note that on MacOS you will need to set up KataGo using brew, as described [here](INSTALL.md). * See [here](INSTALL.md#MacPrereq) for detailed instructions for running from source files on Window, Linux and MacOS, as well as setting up KataGo to use multiple GPUs. -## Play against AI +## Play against AI * Select the players in the main menu, or under 'New Game'. * In a teaching game, KaTrain will analyze your moves and automatically undo those that are sufficiently bad. @@ -88,7 +98,7 @@ The Engine based AIs (KataGo, ScoreLoss, KataJigo) are affected by both the mode Further technical details and discussion on these AIs can be found on [this](https://lifein19x19.com/viewtopic.php?f=10&t=17488&sid=b11e42c005bb6f4f48c83771e6a27eff) thread at the life in 19x19 forums. -## Analysis +## Analysis Keyboard shortcuts are shown with **[key]**. @@ -114,7 +124,7 @@ Keyboard shortcuts are shown with **[key]**. * **[spacebar]**: Turn continuous analysis on/off. * **[enter]** AI move. Makes the AI move for the current player regardless of current player selection. -## Keyboard and mouse shortcuts +## Keyboard and mouse shortcuts In addition to shortcuts mentioned above and those shown in the main menu: @@ -132,7 +142,7 @@ In addition to shortcuts mentioned above and those shown in the main menu: * **[Ctrl-V]**: Load SGF from clipboard and do a 'fast' analysis of the game (with a high priority normal analysis for the last move). * **[Ctrl-C]**: Save SGF to clipboard. -## FAQ +## Date: Sat, 27 Jun 2020 15:05:50 +0200 Subject: [PATCH 33/77] Update CONTRIBUTIONS.md --- CONTRIBUTIONS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md index 0890610..3d33562 100644 --- a/CONTRIBUTIONS.md +++ b/CONTRIBUTIONS.md @@ -34,9 +34,9 @@ For those who have some python experience as well, you can: # Contributors -## Primary author and project maintainer (https://github.com/sanderland/katrain): +## Primary author and project maintainer: -Sander Land +[Sander Land]((https://github.com/sanderland/) ## Contributors From 2062ff779f067820655ae9651997e92a6d88268f Mon Sep 17 00:00:00 2001 From: sanderland <48946947+sanderland@users.noreply.github.com> Date: Sat, 27 Jun 2020 15:06:41 +0200 Subject: [PATCH 34/77] Update CONTRIBUTIONS.md --- CONTRIBUTIONS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md index 3d33562..48e13bd 100644 --- a/CONTRIBUTIONS.md +++ b/CONTRIBUTIONS.md @@ -36,7 +36,7 @@ For those who have some python experience as well, you can: ## Primary author and project maintainer: -[Sander Land]((https://github.com/sanderland/) +[Sander Land](https://github.com/sanderland/) ## Contributors From 10d4484c35d6e874a504456d6cb36dc60f030525 Mon Sep 17 00:00:00 2001 From: bale-go Date: Sat, 27 Jun 2020 15:28:36 +0200 Subject: [PATCH 35/77] perfect_play_ln0 --- katrain/gui/widgets/graph.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index 63e1807..0293cec 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -203,13 +203,18 @@ class RankGraph(Graph): averagemod_rank = averagemod(rank) averagemod_len_legal = averagemod(num_legal) norm_avemod_len_legal = averagemod_len_legal / num_intersec - rank_kyu = ( - -0.97222 * math.log(averagemod_rank) / (0.24634 + averagemod_rank * gauss(3.3208 * (norm_avemod_len_legal))) - + 12.703 * (norm_avemod_len_legal) - + 11.198 * math.log(averagemod_rank) - + 12.28 * gauss(2.379 * (norm_avemod_len_legal)) - - 16.544 - ) + if averagemod_rank > 0.1: + rank_kyu = ( + -0.97222 + * math.log(averagemod_rank) + / (0.24634 + averagemod_rank * gauss(3.3208 * (norm_avemod_len_legal))) + + 12.703 * (norm_avemod_len_legal) + + 11.198 * math.log(averagemod_rank) + + 12.28 * gauss(2.379 * (norm_avemod_len_legal)) + - 16.544 + ) + else: + rank_kyu = -4 if rank_kyu < -4: rank_kyu = -4 return 1 - rank_kyu # dan rank From eb9aab5db83ba0128f48adcf100ba26f436fe6d6 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 28 Jun 2020 01:06:12 +0200 Subject: [PATCH 36/77] status hiding --- katrain/__main__.py | 18 +++++++++++------- katrain/core/constants.py | 5 +++++ katrain/core/game.py | 18 +++++++++++------- katrain/gui.kv | 4 +++- katrain/gui/controlspanel.py | 29 ++++++++++++++++------------- katrain/gui/kivyutils.py | 3 +++ katrain/gui/popups.py | 4 ++-- 7 files changed, 51 insertions(+), 30 deletions(-) diff --git a/katrain/__main__.py b/katrain/__main__.py index 76961c8..04bc2c2 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -59,6 +59,8 @@ from katrain.core.constants import ( MODE_ANALYZE, HOMEPAGE, VERSION, + STATUS_ERROR, + STATUS_INFO, ) from katrain.gui.popups import ConfigTeacherPopup, ConfigTimerPopup, I18NPopup from katrain.core.base_katrain import KaTrainBase @@ -100,16 +102,18 @@ class KaTrainGui(Screen, KaTrainBase): super().log(message, level) if level == OUTPUT_KATAGO_STDERR and "ERROR" not in self.controls.status.text: if "starting" in message.lower(): - self.controls.set_status(f"KataGo engine starting...") + self.controls.set_status(f"KataGo engine starting...", STATUS_INFO) if message.startswith("Tuning"): - self.controls.set_status(f"KataGo is tuning settings for first startup, please wait." + message) + self.controls.set_status( + f"KataGo is tuning settings for first startup, please wait." + message, STATUS_INFO + ) return if "ready" in message.lower(): - self.controls.set_status(f"KataGo engine ready.") + self.controls.set_status(f"KataGo engine ready.", STATUS_INFO) if (level == OUTPUT_ERROR or (level == OUTPUT_KATAGO_STDERR and "error" in message.lower())) and getattr( self, "controls", None ): - self.controls.set_status(f"ERROR: {message}") + self.controls.set_status(f"ERROR: {message}", STATUS_ERROR) @property def play_analyze_mode(self): @@ -276,7 +280,7 @@ class KaTrainGui(Screen, KaTrainBase): try: self.game.play(Move(coords, player=self.next_player_info.player)) except IllegalMoveException as e: - self.controls.set_status(f"Illegal Move: {str(e)}") + self.controls.set_status(f"Illegal Move: {str(e)}",STATUS_ERROR) def _do_analyze_extra(self, mode, **kwargs): self.game.analyze_extra(mode, **kwargs) @@ -367,7 +371,7 @@ class KaTrainGui(Screen, KaTrainBase): def load_sgf_from_clipboard(self): clipboard = Clipboard.paste() if not clipboard: - self.controls.set_status(f"Ctrl-V pressed but clipboard is empty.") + self.controls.set_status(f"Ctrl-V pressed but clipboard is empty.",STATUS_ERROR) return try: move_tree = KaTrainSGF.parse(clipboard) @@ -459,7 +463,7 @@ class KaTrainGui(Screen, KaTrainBase): self("output-sgf") elif keycode[1] == "c" and "ctrl" in modifiers: Clipboard.copy(self.game.root.sgf()) - self.controls.set_status(i18n._("Copied SGF to clipboard.")) + self.controls.set_status(i18n._("Copied SGF to clipboard."),STATUS_INFO) elif keycode[1] == "v" and "ctrl" in modifiers: self.load_sgf_from_clipboard() elif keycode[1] in shortcuts.keys() and "ctrl" not in modifiers: diff --git a/katrain/core/constants.py b/katrain/core/constants.py index f90a925..bb72447 100644 --- a/katrain/core/constants.py +++ b/katrain/core/constants.py @@ -8,6 +8,11 @@ OUTPUT_INFO = 0 OUTPUT_DEBUG = 1 OUTPUT_EXTRA_DEBUG = 2 +STATUS_ANALYSIS = 1.0 # same priority for analysis/info +STATUS_INFO = 1.1 +STATUS_TEACHING = 2.0 +STATUS_ERROR = 1000.0 + PLAYER_HUMAN, PLAYER_AI = "player:human", "player:ai" PLAYER_TYPES = [PLAYER_HUMAN, PLAYER_AI] diff --git a/katrain/core/game.py b/katrain/core/game.py index 1f9291e..9a944c5 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -5,7 +5,9 @@ import threading from datetime import datetime from typing import Dict, List, Optional, Union -from katrain.core.constants import HOMEPAGE, OUTPUT_DEBUG, OUTPUT_INFO +from kivy.clock import Clock + +from katrain.core.constants import HOMEPAGE, OUTPUT_DEBUG, OUTPUT_INFO, STATUS_ANALYSIS, STATUS_INFO, STATUS_TEACHING from katrain.core.engine import KataGoEngine from katrain.core.game_node import GameNode from katrain.core.lang import i18n @@ -322,13 +324,15 @@ class Game: cn = self.current_node engine = self.engines[cn.next_player] + Clock.schedule_once(self.katrain.analysis_controls.hints.activate,0) + if mode == "extra": if kwargs.get("continuous", False): visits = max(engine.config["fast_visits"], math.ceil(cn.analysis_visits_requested * 1.25)) else: visits = cn.analysis_visits_requested + engine.config["fast_visits"] - self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits)) + self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS) cn.analyze(engine, visits=visits, priority=-1_000, time_limit=False) return if mode == "game": @@ -337,7 +341,7 @@ class Game: visits = min_visits + engine.config["max_visits"] for node in nodes: node.analyze(engine, visits=visits, priority=-1_000_000, time_limit=False) - self.katrain.controls.set_status(i18n._("game re-analysis").format(visits=visits)) + self.katrain.controls.set_status(i18n._("game re-analysis").format(visits=visits), STATUS_ANALYSIS) return elif mode == "sweep": @@ -365,16 +369,16 @@ class Game: if (x, y) not in stones ] visits = engine.config["fast_visits"] - self.katrain.controls.set_status(i18n._("sweep analysis").format(visits=visits)) + self.katrain.controls.set_status(i18n._("sweep analysis").format(visits=visits), STATUS_ANALYSIS) priority = -1_000_000_000 elif mode == "equalize": if not cn.analysis_ready: - self.katrain.controls.set_status(i18n._("wait-before-equalize"), self.current_node) + self.katrain.controls.set_status(i18n._("wait-before-equalize"), STATUS_INFO, self.current_node) return 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(i18n._("equalizing analysis").format(visits=visits)) + self.katrain.controls.set_status(i18n._("equalizing analysis").format(visits=visits), STATUS_ANALYSIS) priority = -1_000 else: raise ValueError("Invalid analysis mode") @@ -406,6 +410,6 @@ class Game: if undo: self.undo(1) self.katrain.controls.set_status( - i18n._("teaching undo message").format(move=move.gtp(), points_lost=points_lost) + i18n._("teaching undo message").format(move=move.gtp(), points_lost=points_lost), STATUS_TEACHING ) self.katrain.update_state() diff --git a/katrain/gui.kv b/katrain/gui.kv index cd30c74..54fe607 100644 --- a/katrain/gui.kv +++ b/katrain/gui.kv @@ -6,6 +6,8 @@ #:import PLAYER_AI katrain.core.constants.PLAYER_AI #:import MODE_PLAY katrain.core.constants.MODE_PLAY #:import MODE_ANALYZE katrain.core.constants.MODE_ANALYZE +#:import STATUS_INFO katrain.core.constants.STATUS_INFO + #:import WHITE katrain.gui.style.WHITE #:import BLACK katrain.gui.style.BLACK @@ -253,7 +255,7 @@ Line: circle: self.pos[0] + self.width/2, self.pos[1] + self.height/2, self.width/6 width: 1 - on_press: app.gui.controls.set_status(i18n._('ai-ponder')) + on_press: app.gui.controls.set_status(i18n._('ai-ponder'),STATUS_INFO) : diff --git a/katrain/gui/controlspanel.py b/katrain/gui/controlspanel.py index 80290c4..9cfb0c8 100644 --- a/katrain/gui/controlspanel.py +++ b/katrain/gui/controlspanel.py @@ -6,7 +6,7 @@ from kivy.properties import ObjectProperty, OptionProperty from kivy.uix.boxlayout import BoxLayout from kivymd.uix.floatlayout import MDFloatLayout -from katrain.core.constants import MODE_PLAY, MODE_ANALYZE +from katrain.core.constants import MODE_PLAY, MODE_ANALYZE, STATUS_ERROR, STATUS_ANALYSIS from katrain.gui.kivyutils import AnalysisToggle, CollapsablePanel @@ -66,8 +66,7 @@ class ControlsPanel(BoxLayout): def __init__(self, **kwargs): super(ControlsPanel, self).__init__(**kwargs) - self.status_msg = None - self.status_node = None + self.status_state = (None, -1e9, None) self.active_comment_node = None self.last_timer_update = (None, 0, False) self.beep = SoundLoader.load("beep.wav") @@ -81,24 +80,28 @@ class ControlsPanel(BoxLayout): self.players[bw].player_type = player_info.player_type self.players[bw].player_subtype = player_info.player_subtype - def set_status(self, msg, at_node=None): - self.status_msg = msg - self.status_node = at_node or self.katrain and self.katrain.game and self.katrain.game.current_node - self.status.text = msg - self.update_evaluation() + def set_status(self, msg, status_type, at_node=None): + at_node = at_node or self.katrain and self.katrain.game and self.katrain.game.current_node + if at_node != self.status_state[2] or int(status_type) >= int(self.status_state[1]): + self.status_state = (msg, status_type, at_node) + self.status.text = msg + Clock.schedule_once(self.update_evaluation,0) # handles showing completed analysis and score graph - def update_evaluation(self): + def update_evaluation(self,*_args): katrain = self.katrain game = katrain and katrain.game if not game: return current_node, move = game.current_node, game.current_node.move - if game.current_node is not self.status_node and not ( - self.status is not None and self.status_node is None and game.current_node.is_root - ): # startup errors on root + if ( + game.current_node is not self.status_state[2] + and not (self.status_state[1] == STATUS_ERROR and self.status_state[2] is None) + ) or ( + len(game.engines["B"].queries)==0 and self.status_state[1] == STATUS_ANALYSIS + ): # clear status if node changes, except startup errors on root. also clear analysis message when no queries self.status.text = "" - self.status_node = None + self.status_state = (None, -1e9, None) last_player_was_ai_playing_human = katrain.last_player_info.ai and katrain.next_player_info.human both_players_are_robots = katrain.last_player_info.ai and katrain.next_player_info.ai diff --git a/katrain/gui/kivyutils.py b/katrain/gui/kivyutils.py index 8eb4c03..734ce1e 100644 --- a/katrain/gui/kivyutils.py +++ b/katrain/gui/kivyutils.py @@ -317,6 +317,9 @@ class AnalysisToggle(MDBoxLayout): def trigger_action(self, *args, **kwargs): return self.checkbox.trigger_action(*args, **kwargs) + def activate(self,*_args): + self.checkbox.active = True + @property def active(self): return self.checkbox.active diff --git a/katrain/gui/popups.py b/katrain/gui/popups.py index 5a7b618..76de150 100644 --- a/katrain/gui/popups.py +++ b/katrain/gui/popups.py @@ -22,7 +22,7 @@ from katrain.core.constants import ( OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_INFO, - AI_OPTION_VALUES, + AI_OPTION_VALUES, STATUS_INFO, ) from katrain.core.engine import KataGoEngine from katrain.core.lang import i18n @@ -432,7 +432,7 @@ class ConfigPopup(QuickConfigGui): def restart_engine(_dt): self.katrain.log(f"Restarting Engine after {detected_restart} settings change") - self.katrain.controls.set_status(i18n._("restarting engine")) + self.katrain.controls.set_status(i18n._("restarting engine"),STATUS_INFO) old_engine = self.katrain.engine # type: KataGoEngine old_proc = old_engine.katago_process From 7a4a8e268b4817459ba4ea672afda9d3cc84f8ff Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 28 Jun 2020 14:10:20 +0200 Subject: [PATCH 37/77] rank model start and gibo file support --- katrain/__main__.py | 10 +- katrain/core/ai.py | 21 +- katrain/core/constants.py | 13 + katrain/core/game.py | 33 +-- katrain/core/sgf_parser.py | 178 +++++++++++- katrain/gui/popups.py | 15 + .../i18n/locales/en/LC_MESSAGES/katrain.po | 4 + katrain/popups.kv | 12 + tests/data/test.gib | 271 ++++++++++++++++++ tests/test_parser.py | 20 +- 10 files changed, 525 insertions(+), 52 deletions(-) create mode 100644 tests/data/test.gib diff --git a/katrain/__main__.py b/katrain/__main__.py index 04bc2c2..0311fb1 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -325,7 +325,7 @@ class KaTrainGui(Screen, KaTrainBase): self.controls.timer.paused = True if not self.ai_settings_popup: self.ai_settings_popup = I18NPopup( - title_key="ai settings", size=[dp(600), dp(600)], content=ConfigAIPopup(self) + title_key="ai settings", size=[dp(600), dp(650)], content=ConfigAIPopup(self) ).__self__ self.ai_settings_popup.content.popup = self.ai_settings_popup self.ai_settings_popup.open() @@ -366,7 +366,7 @@ class KaTrainGui(Screen, KaTrainBase): def _do_output_sgf(self): msg = self.game.write_sgf(self.config("general/sgf_save")) self.log(msg, OUTPUT_INFO) - self.controls.set_status(msg) + self.controls.set_status(msg,OUTPUT_INFO) def load_sgf_from_clipboard(self): clipboard = Clipboard.paste() @@ -374,7 +374,7 @@ class KaTrainGui(Screen, KaTrainBase): self.controls.set_status(f"Ctrl-V pressed but clipboard is empty.",STATUS_ERROR) return try: - move_tree = KaTrainSGF.parse(clipboard) + move_tree = KaTrainSGF.parse_sgf(clipboard) except Exception as exc: self.controls.set_status( i18n._("Failed to import from clipboard").format(error=exc, contents=clipboard[:50]) @@ -497,7 +497,7 @@ class KaTrainApp(MDApp): Builder.load_file(kv_file) Window.bind(on_request_close=self.on_request_close) - Window.bind(on_dropfile=lambda win, file: self.gui.load_sgf_file(file)) + Window.bind(on_dropfile=lambda win, file: self.gui.load_sgf_file(file.decode('utf8'))) self.gui = KaTrainGui() Builder.load_file(popup_kv_file) @@ -510,7 +510,7 @@ class KaTrainApp(MDApp): self.gui.save_config() if self.gui.game: self.gui.update_state() - self.gui.controls.set_status("") + self.gui.controls.set_status("",STATUS_INFO) def webbrowser(self, site_key): websites = {"homepage": HOMEPAGE + "#manual", "support": HOMEPAGE + "#support"} diff --git a/katrain/core/ai.py b/katrain/core/ai.py index e9704d2..31f3baa 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -2,7 +2,7 @@ import heapq import math import random import time -from typing import Dict, List, Tuple +from typing import Dict, List, Tuple, Optional from katrain.core.utils import var_to_grid from katrain.core.constants import ( @@ -22,11 +22,22 @@ from katrain.core.constants import ( AI_PICK, AI_RANK, AI_HANDICAP, - OUTPUT_ERROR, + OUTPUT_ERROR, AI_STRENGTH, ) from katrain.core.game import Game, GameNode, Move +def ai_rank_estimation(strategy,settings) -> Tuple[int,bool]: + if strategy in [AI_DEFAULT,AI_HANDICAP,AI_JIGO]: + return 9, True + if strategy == AI_RANK: + return 1-settings['kyu_rank'], True + if strategy == AI_WEIGHTED: + dan_rank = -4 + return dan_rank, True + else: + return AI_STRENGTH[strategy], False + def weighted_selection_without_replacement(items: List[Tuple], pick_n: int) -> List[Tuple]: """For a list of tuples where the second element is a weight, returns random items with those weights, without replacement.""" elt = [(math.log(random.random()) / item[1], item) for item in items] # magic @@ -95,7 +106,7 @@ def generate_local_tenuki_weights(ai_mode, ai_settings, policy_grid, cn, size): return weighted_coords, ai_thoughts -def request_ai_analysis(game: Game, cn: GameNode, extra_settings: Dict) -> Dict: +def request_ai_analysis(game: Game, cn: GameNode, extra_settings: Dict) -> Optional[Dict]: error = False analysis = None @@ -259,9 +270,9 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, candidate_ai_moves = handicap_analysis["moveInfos"] top_cand = Move.from_gtp(candidate_ai_moves[0]["move"], player=cn.next_player) - if top_cand.is_pass: # don't play suicidal to balance score - pass when it's best + if top_cand.is_pass and not ai_mode not in [AI_DEFAULT, AI_HANDICAP]: # don't play suicidal to balance score aimove = top_cand - ai_thoughts += f"Top move is pass, so passing regardless of strategy." + ai_thoughts += f"Top move is pass, so passing regardless of strategy. " else: if ai_mode == AI_JIGO: sign = cn.player_sign(cn.next_player) diff --git a/katrain/core/constants.py b/katrain/core/constants.py index bb72447..86bcbd5 100644 --- a/katrain/core/constants.py +++ b/katrain/core/constants.py @@ -55,6 +55,19 @@ AI_STRATEGIES_RECOMMENDED_ORDER = [ AI_JIGO, ] +AI_STRENGTH = { # dan ranks + AI_DEFAULT: 9, + AI_POLICY: 4, + AI_JIGO: float('nan'), + AI_SCORELOSS: -4, + AI_WEIGHTED: -4, + AI_PICK: -7, + AI_LOCAL: -4, + AI_TENUKI: -7, + AI_INFLUENCE: -7, + AI_TERRITORY: -7, + AI_RANK: float('nan') +} AI_OPTION_VALUES = { "kyu_rank": [(k, f"{k}[strength:kyu]") for k in range(15, 0, -1)] diff --git a/katrain/core/game.py b/katrain/core/game.py index 9a944c5..cb31682 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -48,7 +48,7 @@ class Game: self.komi = self.root.komi handicap = int(self.root.get_property("HA", 0)) if handicap and not self.root.placements: - self.place_handicap_stones(handicap) + self.root.place_handicap_stones(handicap) else: board_size = katrain.config("game/size") self.komi = katrain.config("game/komi") @@ -61,7 +61,7 @@ class Game: ) handicap = katrain.config("game/handicap") if handicap: - self.place_handicap_stones(handicap) + self.root.place_handicap_stones(handicap) if not self.root.get_property("RU"): self.root.set_property("RU", katrain.config("game/rules")) @@ -184,34 +184,7 @@ class Game: ix = (ordered_children.index(cn) + len(ordered_children) + direction) % len(ordered_children) self.set_current_node(ordered_children[ix]) - def place_handicap_stones(self, n_handicaps): - board_size_x, board_size_y = self.board_size - near_x = 3 if board_size_x >= 13 else min(2, board_size_x - 1) - near_y = 3 if board_size_y >= 13 else min(2, board_size_y - 1) - far_x = board_size_x - 1 - near_x - far_y = board_size_y - 1 - near_y - middle_x = board_size_x // 2 # what for even sizes? - middle_y = board_size_y // 2 - if n_handicaps > 9 and board_size_x == board_size_y: - stones_per_row = math.ceil(math.sqrt(n_handicaps)) - spacing = (far_x - near_x) / (stones_per_row - 1) - if spacing < near_x: - far_x += 1 - near_x -= 1 - spacing = (far_x - near_x) / (stones_per_row - 1) - coords = list({math.floor(0.5 + near_x + i * spacing) for i in range(stones_per_row)}) - stones = sorted( - [(x, y) for x in coords for y in coords], - key=lambda xy: -((xy[0] - (board_size_x - 1) / 2) ** 2 + (xy[1] - (board_size_y - 1) / 2) ** 2), - ) - else: # max 9 - stones = [(far_x, far_y), (near_x, near_y), (far_x, near_y), (near_x, far_y)] - if n_handicaps % 2 == 1: - stones.append((middle_x, middle_y)) - stones += [(near_x, middle_y), (far_x, middle_y), (middle_x, near_y), (middle_x, far_y)] - self.root.set_property( - "AB", list({Move(stone).sgf(board_size=(board_size_x, board_size_y)) for stone in stones[:n_handicaps]}) - ) + @property def board_size(self): diff --git a/katrain/core/sgf_parser.py b/katrain/core/sgf_parser.py index 8574139..f475504 100644 --- a/katrain/core/sgf_parser.py +++ b/katrain/core/sgf_parser.py @@ -1,4 +1,5 @@ import copy +import math import re from collections import defaultdict from typing import Any, Dict, List, Optional, Tuple @@ -285,6 +286,37 @@ class SGFNode: else: return "W" + def place_handicap_stones(self, n_handicaps, tygem=False): + board_size_x, board_size_y = self.board_size + near_x = 3 if board_size_x >= 13 else min(2, board_size_x - 1) + near_y = 3 if board_size_y >= 13 else min(2, board_size_y - 1) + far_x = board_size_x - 1 - near_x + far_y = board_size_y - 1 - near_y + middle_x = board_size_x // 2 # what for even sizes? + middle_y = board_size_y // 2 + if n_handicaps > 9 and board_size_x == board_size_y: + stones_per_row = math.ceil(math.sqrt(n_handicaps)) + spacing = (far_x - near_x) / (stones_per_row - 1) + if spacing < near_x: + far_x += 1 + near_x -= 1 + spacing = (far_x - near_x) / (stones_per_row - 1) + coords = list({math.floor(0.5 + near_x + i * spacing) for i in range(stones_per_row)}) + stones = sorted( + [(x, y) for x in coords for y in coords], + key=lambda xy: -((xy[0] - (board_size_x - 1) / 2) ** 2 + (xy[1] - (board_size_y - 1) / 2) ** 2), + ) + else: # max 9 + stones = [(far_x, far_y), (near_x, near_y), (far_x, near_y), (near_x, far_y)] + if n_handicaps % 2 == 1: + stones.append((middle_x, middle_y)) + stones += [(near_x, middle_y), (far_x, middle_y), (middle_x, near_y), (middle_x, far_y)] + if tygem: + stones[2],stones[3] = stones[3],stones[2] + self.set_property( + "AB", list({Move(stone).sgf(board_size=(board_size_x, board_size_y)) for stone in stones[:n_handicaps]}) + ) + class SGF: @@ -293,23 +325,31 @@ class SGF: SGFPROP_PAT = re.compile(r"\s*(?:\(|\)|;|(\w+)((\s*\[([^\]\\]|\\.)*\])+))", flags=re.DOTALL) @classmethod - def parse(cls, input_str) -> SGFNode: + def parse_sgf(cls, input_str) -> SGFNode: """Parse a string as SGF.""" return cls(input_str).root @classmethod def parse_file(cls, filename, encoding=None) -> SGFNode: + is_gib = filename.lower().endswith('.gib') + """Parse a file as SGF, encoding will be detected if not given.""" with open(filename, "rb") as f: bin_contents = f.read() if not encoding: - match = re.search(rb"CA\[(.*?)\]", bin_contents) - if match: - encoding = match[1].decode("ascii", errors="ignore") + if not is_gib: + match = re.search(rb"CA\[(.*?)\]", bin_contents) + if match: + encoding = match[1].decode("ascii", errors="ignore") + else: + encoding = "ISO-8859-1" # default else: - encoding = "ISO-8859-1" # default - decoded = bin_contents.decode(encoding=encoding, errors="ignore") - return cls.parse(decoded) + encoding = 'utf8' # ? + decoded = bin_contents.decode(encoding=encoding, errors="ignore") + if is_gib: + return cls.parse_gib(decoded) + else: # sgf + return cls.parse_sgf(decoded) def __init__(self, contents): self.contents = contents @@ -341,3 +381,127 @@ class SGF: if self.ix < len(self.contents): raise ParseError(f"Parse Error: unexpected character at {self.contents[self.ix:self.ix+25]}") raise ParseError("Parse Error: expected ')' at end of input.") + + + # GIB parser adapted from https://github.com/fohristiwhirl/gofish/ + @classmethod + def parse_gib(cls,gib): + def parse_player_name(raw): + name = raw + rank = "" + foo = raw.split("(") + if len(foo) == 2: + if foo[1][-1] == ")": + name = foo[0].strip() + rank = foo[1][0:-1] + return name, rank + + def gib_make_result(grlt, zipsu): + easycases = {3: "B+R", 4: "W+R", 7: "B+T", 8: "W+T"} + + if grlt in easycases: + return easycases[grlt] + + if grlt in [0, 1]: + return "{}+{}".format("B" if grlt == 0 else "W", zipsu / 10) + + return "" + + def gib_get_result(line, grlt_regex, zipsu_regex): + try: + grlt = int(re.search(grlt_regex, line).group(1)) + zipsu = int(re.search(zipsu_regex, line).group(1)) + except: + return "" + return gib_make_result(grlt, zipsu) + + root = cls._NODE_CLASS() + node = root + + lines = gib.split("\n") + for line in lines: + line = line.strip() + if line.startswith("\\[GAMEBLACKNAME=") and line.endswith("\\]"): + s = line[16:-2] + name, rank = parse_player_name(s) + if name: + root.set_property("PB", name) + if rank: + root.set_property("BR", rank) + + if line.startswith("\\[GAMEWHITENAME=") and line.endswith("\\]"): + s = line[16:-2] + name, rank = parse_player_name(s) + if name: + root.set_property("PW", name) + if rank: + root.set_property("WR", rank) + + if line.startswith("\\[GAMEINFOMAIN="): + result = gib_get_result(line, r"GRLT:(\d+),", r"ZIPSU:(\d+),") + if result: + root.set_property("RE", result) + try: + komi = int(re.search(r"GONGJE:(\d+),", line).group(1)) / 10 + if komi: + root.set_property("KM", komi) + except: + pass + + if line.startswith("\\[GAMETAG="): + if "DT" not in root.properties: + try: + match = re.search(r"C(\d\d\d\d):(\d\d):(\d\d)", line) + date = "{}-{}-{}".format(match.group(1), match.group(2), match.group(3)) + root.set_property("DT", date) + except: + pass + + if "RE" not in root.properties: + result = gib_get_result(line, r",W(\d+),", r",Z(\d+),") + if result: + root.set_property("RE", result) + + if "KM" not in root.properties: + try: + komi = int(re.search(r",G(\d+),", line).group(1)) / 10 + if komi: + root.set_property("KM", komi) + except: + pass + + if line[0:3] == "INI": + if node is not root: + raise ParseError("Node is not root") + setup = line.split() + try: + handicap = int(setup[3]) + except IndexError: + continue + + if handicap < 0 or handicap > 9: + raise ParseError(f"Handicap {handicap} out of range") + + if handicap >= 2: + root.set_property("HA", handicap) + root.place_handicap_stones(handicap,tygem=True) + + if line[0:3] == "STO": + move = line.split() + key = "B" if move[3] == "1" else "W" + try: + x = int(move[4]) + y = 18 - int(move[5]) + if not (0 <= x < 19 and 0 <= y < 19): + raise ParseError(f"Coordinates for move ({x},{y}) out of range on line {line}") + value = Move(coords=(x,y)).sgf(board_size=(19,19)) + except IndexError: + continue + + node = cls._NODE_CLASS(parent=node) + node.set_property(key, value ) + + if len(root.children) == 0: # We'll assume we failed in this case + raise ParseError("No valid nodes found") + + return root \ No newline at end of file diff --git a/katrain/gui/popups.py b/katrain/gui/popups.py index 76de150..c3bac16 100644 --- a/katrain/gui/popups.py +++ b/katrain/gui/popups.py @@ -15,6 +15,7 @@ from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.selectioncontrol import MDCheckbox from kivymd.uix.textfield import MDTextField +from katrain.core.ai import ai_rank_estimation from katrain.core.constants import ( AI_CONFIG_DEFAULT, AI_DEFAULT, @@ -29,6 +30,7 @@ from katrain.core.lang import i18n from katrain.core.utils import PATHS, find_package_resource from katrain.gui.kivyutils import BackgroundMixin, I18NSpinner from katrain.gui.style import DEFAULT_FONT, EVAL_COLORS +from katrain.gui.widgets import RankGraph from katrain.gui.widgets.progress_loader import ProgressLoader @@ -313,6 +315,16 @@ class ConfigAIPopup(QuickConfigGui): self.build_ai_options() self.ai_select.bind(text=self.build_ai_options) + def estimate_rank_from_options(self,*_args): + strategy = self.ai_select.selected[1] + options = self.collect_properties(self) # [strategy] + print(strategy,options) + prefix = f"ai/{strategy}/" + options = {k[len(prefix):]:v for k,v in options.items() if k.startswith(prefix)} + print(options) + dan_rank, model_based = ai_rank_estimation(strategy, options) + self.estimated_rank_label.text = ('' if model_based else '~') + RankGraph.rank_label(dan_rank) + def build_ai_options(self, *_args): strategy = self.ai_select.selected[1] mode_settings = self.katrain.config(f"ai/{strategy}") @@ -325,6 +337,7 @@ class ConfigAIPopup(QuickConfigGui): if values == "bool": widget = LabelledCheckBox(input_property=f"ai/{strategy}/{k}") widget.active = v + widget.bind(active=self.estimate_rank_from_options) else: if isinstance(values[0], Tuple): # with descriptions, possibly language-specific fixed_values = [(v, re.sub(r"\[(.*?)\]", lambda m: i18n._(m[1]), l)) for v, l in values] @@ -332,6 +345,7 @@ class ConfigAIPopup(QuickConfigGui): fixed_values = [(v, str(v)) for v in values] widget = LabelledSelectionSlider(values=fixed_values, input_property=f"ai/{strategy}/{k}") widget.set_value(v) + widget.textbox.bind(text=self.estimate_rank_from_options) self.options_grid.add_widget(wrap_anchor(widget)) else: self.options_grid.add_widget( @@ -339,6 +353,7 @@ class ConfigAIPopup(QuickConfigGui): ) for _ in range((self.max_options - len(mode_settings)) * 2): self.options_grid.add_widget(Label(size_hint_x=None)) + Clock.schedule_once(self.estimate_rank_from_options) class ConfigPopup(QuickConfigGui): diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po index 48ca4f8..36f86a4 100644 --- a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po @@ -620,6 +620,10 @@ msgstr "" "one. Stronger settings select the best move from a larger selection. Since " "there is no 0 kyu/dan, 3 dan = -2 kyu." +#. in AI settings +msgid "estimated strength" +msgstr "Estimated Strength" + #. button in general settings for downloading models msgid "download models button" msgstr "Download Models" diff --git a/katrain/popups.kv b/katrain/popups.kv index 8cd339c..97e73b7 100644 --- a/katrain/popups.kv +++ b/katrain/popups.kv @@ -410,6 +410,7 @@ : options_grid: options_grid help_label: help_label + estimated_rank_label: estimated_rank_label ai_select: ai_select BoxLayout: size_hint: 1,1 @@ -420,6 +421,17 @@ I18NSpinner: size_hint: 0.8, 0.5 id: ai_select + BoxLayout: + size_hint: 1,1 + DescriptionLabel: + font_size: DESC_FONT_SIZE + size_hint: 1, 1 + text: i18n._('estimated strength') + DescriptionLabel: + size_hint: 0.8, 1 + font_size: DESC_FONT_SIZE + id: estimated_rank_label + text: '?' + i18n._('strength:kyu') SmallDescriptionLabel: id: help_label size_hint: 1,1.25 diff --git a/tests/data/test.gib b/tests/data/test.gib new file mode 100644 index 0000000..8405b20 --- /dev/null +++ b/tests/data/test.gib @@ -0,0 +1,271 @@ +\HS +\[GIBOKIND=Global\] +\[TYPE=0\] +\[SZAUDIO=0\] +\[GAMECONDITION=Even : Black 6.5 Dum\] +\[GAMETIME=Time limit 10minute : 30 second countdown 3 time\] +\[GAMERESULT=white wins by time\] +\[GAMEZIPSU=0\] +\[GAMEDUM=0\] +\[GAMEGONGJE=65\] +\[GAMETOTALNUM=232\] +\[GAMENAME=�°��� �뱹\] +\[GAMEDATE=2020- 6-14- 1-26-14\] +\[GAMEPLACE=Ÿ���� �ٵ�\] +\[GAMELECNAME=\] +\[GAMEWHITENAME=wildsim1 (2D)\] +\[GAMEWHITELEVEL=19\] +\[GAMEWHITENICK=wildsim1\] +\[GAMEWHITECOUNTRY=0\] +\[GAMEWAVATA=60011\] +\[GAMEWIMAGE=\] +\[GAMEBLACKNAME=kim (2D)\] +\[GAMEBLACKLEVEL=19\] +\[GAMEBLACKNICK=kim\] +\[GAMEBLACKCOUNTRY=3\] +\[GAMEBAVATA=114\] +\[GAMEBIMAGE=\] +\[GAMECOMMENT=\] +\[GAMEINFOMAIN=GBKIND:3,GTYPE:0,GCDT:0,GTIME:600-30-3,GRLT:8,ZIPSU:0,DUM:0,GONGJE:65,TCNT:232,AUSZ:0\] +\[GAMEINFOSUB=GNAMEF:0,GPLCF:0,GNAME:�°��� �뱹,GDATE:2020- 6-14- 1-26-14,GPLC:Ÿ���� �ٵ�,GCMT:\] +\[WUSERINFO=WID:楷摬楳ㅭ,WLV:19,WNICK:楷摬楳ㅭ,WNCD:0,WAID:60011,WIMG:\] +\[BUSERINFO=BID:楫獭慳浥,BLV:19,BNICK:楫獭慳浥,BNCD:3,BAID:114,BIMG:\] +\[GAMETAG=S0,R0,D0,G65,W8,Z0,T30-3-600, C2020:06:14:01:26, I:楷摬楳ㅭ,L:19,M:楫獭慳浥,N:19,A:楷摬楳ㅭ,B:楫獭慳浥,J:0,K:3\] +\HE +\GS +2 5 0 +233 0 &4 +INI 0 1 0 &4 +STO 0 2 1 15 3 +STO 0 3 2 3 15 +STO 0 4 1 16 15 +STO 0 5 2 13 2 +STO 0 6 1 3 3 +STO 0 7 2 2 5 +STO 0 8 1 5 2 +STO 0 9 2 1 3 +STO 0 10 1 2 2 +STO 0 11 2 15 1 +STO 0 12 1 14 2 +STO 0 13 2 14 1 +STO 0 14 1 13 3 +STO 0 15 2 12 2 +STO 0 16 1 12 3 +STO 0 17 2 11 2 +STO 0 18 1 13 16 +STO 0 19 2 16 9 +STO 0 20 1 16 7 +STO 0 21 2 16 12 +STO 0 22 1 2 13 +STO 0 23 2 2 11 +STO 0 24 1 2 16 +STO 0 25 2 3 16 +STO 0 26 1 2 15 +STO 0 27 2 3 14 +STO 0 28 1 2 14 +STO 0 29 2 4 12 +STO 0 30 1 9 15 +STO 0 31 2 8 16 +STO 0 32 1 8 15 +STO 0 33 2 9 16 +STO 0 34 1 10 16 +STO 0 35 2 10 17 +STO 0 36 1 11 16 +STO 0 37 2 11 17 +STO 0 38 1 7 16 +STO 0 39 2 7 17 +STO 0 40 1 6 17 +STO 0 41 2 8 17 +STO 0 42 1 6 15 +STO 0 43 2 5 16 +STO 0 44 1 6 16 +STO 0 45 2 12 16 +STO 0 46 1 12 15 +STO 0 47 2 12 17 +STO 0 48 1 14 15 +STO 0 49 2 13 17 +STO 0 50 1 14 17 +STO 0 51 2 13 14 +STO 0 52 1 13 15 +STO 0 53 2 2 17 +STO 0 54 1 1 17 +STO 0 55 2 3 17 +STO 0 56 1 7 12 +STO 0 57 2 1 18 +STO 0 58 1 1 12 +STO 0 59 2 1 11 +STO 0 60 1 0 17 +STO 0 61 2 2 12 +STO 0 62 1 0 13 +STO 0 63 2 6 13 +STO 0 64 1 14 12 +STO 0 65 2 17 14 +STO 0 66 1 7 13 +STO 0 67 2 17 15 +STO 0 68 1 16 16 +STO 0 69 2 15 8 +STO 0 70 1 15 11 +STO 0 71 2 16 11 +STO 0 72 1 6 12 +STO 0 73 2 6 14 +STO 0 74 1 7 14 +STO 0 75 2 5 15 +STO 0 76 1 4 11 +STO 0 77 2 5 12 +STO 0 78 1 5 11 +STO 0 79 2 3 11 +STO 0 80 1 3 13 +STO 0 81 2 4 13 +STO 0 82 1 3 12 +STO 0 83 2 5 14 +STO 0 84 1 15 7 +STO 0 85 2 2 8 +STO 0 86 1 14 8 +STO 0 87 2 15 10 +STO 0 88 1 14 10 +STO 0 89 2 14 9 +STO 0 90 1 17 10 +STO 0 91 2 13 10 +STO 0 92 1 14 11 +STO 0 93 2 13 8 +STO 0 94 1 16 10 +STO 0 95 2 15 9 +STO 0 96 1 17 9 +STO 0 97 2 17 16 +STO 0 98 1 16 13 +STO 0 99 2 17 13 +STO 0 100 1 16 14 +STO 0 101 2 17 11 +STO 0 102 1 14 7 +STO 0 103 2 13 7 +STO 0 104 1 13 6 +STO 0 105 2 13 9 +STO 0 106 1 16 8 +STO 0 107 2 11 7 +STO 0 108 1 12 6 +STO 0 109 2 10 10 +STO 0 110 1 12 7 +STO 0 111 2 11 8 +STO 0 112 1 12 8 +STO 0 113 2 11 9 +STO 0 114 1 12 11 +STO 0 115 2 12 10 +STO 0 116 1 11 11 +STO 0 117 2 16 2 +STO 0 118 1 16 3 +STO 0 119 2 17 2 +STO 0 120 1 17 3 +STO 0 121 2 8 10 +STO 0 122 1 11 10 +STO 0 123 2 12 9 +STO 0 124 1 10 9 +STO 0 125 2 9 9 +STO 0 126 1 10 8 +STO 0 127 2 9 7 +STO 0 128 1 10 11 +STO 0 129 2 9 10 +STO 0 130 1 3 10 +STO 0 131 2 2 10 +STO 0 132 1 3 9 +STO 0 133 2 2 9 +STO 0 134 1 8 8 +STO 0 135 2 9 8 +STO 0 136 1 8 2 +STO 0 137 2 1 2 +STO 0 138 1 1 1 +STO 0 139 2 2 3 +STO 0 140 1 3 2 +STO 0 141 2 3 4 +STO 0 142 1 4 4 +STO 0 143 2 4 5 +STO 0 144 1 5 4 +STO 0 145 2 5 5 +STO 0 146 1 6 4 +STO 0 147 2 6 5 +STO 0 148 1 7 4 +STO 0 149 2 6 11 +STO 0 150 1 7 11 +STO 0 151 2 6 10 +STO 0 152 1 7 10 +STO 0 153 2 7 9 +STO 0 154 1 6 9 +STO 0 155 2 5 10 +STO 0 156 1 8 9 +STO 0 157 2 8 7 +STO 0 158 1 7 8 +STO 0 159 2 4 10 +STO 0 160 1 7 7 +STO 0 161 2 10 5 +STO 0 162 1 10 2 +STO 0 163 2 11 3 +STO 0 164 1 11 6 +STO 0 165 2 10 6 +STO 0 166 1 11 4 +STO 0 167 2 10 3 +STO 0 168 1 10 4 +STO 0 169 2 9 4 +STO 0 170 1 9 3 +STO 0 171 2 11 5 +STO 0 172 1 12 4 +STO 0 173 2 8 5 +STO 0 174 1 8 4 +STO 0 175 2 9 5 +STO 0 176 1 7 5 +STO 0 177 2 8 6 +STO 0 178 1 10 1 +STO 0 179 2 15 2 +STO 0 180 1 14 3 +STO 0 181 2 11 1 +STO 0 182 1 17 17 +STO 0 183 2 18 11 +STO 0 184 1 18 16 +STO 0 185 2 18 15 +STO 0 186 1 18 13 +STO 0 187 2 18 17 +STO 0 188 1 17 18 +STO 0 189 2 18 12 +STO 0 190 1 18 2 +STO 0 191 2 18 1 +STO 0 192 1 18 3 +STO 0 193 2 10 0 +STO 0 194 1 9 1 +STO 0 195 2 17 0 +STO 0 196 1 13 18 +STO 0 197 2 12 18 +STO 0 198 1 14 18 +STO 0 199 2 5 17 +STO 0 200 1 7 18 +STO 0 201 2 8 18 +STO 0 202 1 6 18 +STO 0 203 2 5 18 +STO 0 204 1 7 15 +STO 0 205 2 9 18 +STO 0 206 1 12 0 +STO 0 207 2 13 0 +STO 0 208 1 9 0 +STO 0 209 2 0 1 +STO 0 210 1 11 0 +STO 0 211 2 13 1 +STO 0 212 1 1 0 +STO 0 213 2 5 7 +STO 0 214 1 5 8 +STO 0 215 2 4 8 +STO 0 216 1 5 9 +STO 0 217 2 4 9 +STO 0 218 1 9 11 +STO 0 219 2 8 11 +STO 0 220 1 8 12 +STO 0 221 2 12 5 +STO 0 222 1 13 5 +STO 0 223 2 18 9 +STO 0 224 1 18 8 +STO 0 225 2 18 10 +STO 0 226 1 17 8 +STO 0 227 2 13 11 +STO 0 228 1 13 12 +STO 0 229 2 10 7 +STO 0 230 1 15 12 +STO 0 231 2 18 14 +STO 0 232 1 6 7 +STO 0 233 2 6 6 +\GE diff --git a/tests/test_parser.py b/tests/test_parser.py index 707f53a..d84f77d 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -5,7 +5,7 @@ from katrain.core.sgf_parser import SGF, SGFNode def test_simple(): input_sgf = "(;GM[1]FF[4]SZ[19]DT[2020-04-12]AB[dd][dj];B[dp];W[pp];B[pj])" - root = SGF.parse(input_sgf) + root = SGF.parse_sgf(input_sgf) assert "4" == root.get_property("FF") assert root.get_property("XYZ") is None assert "dp" == root.children[0].get_property("B") @@ -14,13 +14,13 @@ def test_simple(): def test_branch(): input_sgf = "(;GM[1]FF[4]CA[UTF-8]AP[Sabaki:0.43.3]KM[6.5]SZ[19]DT[2020-04-12]AB[dd][dj](;B[dp];W[pp](;B[pj])(;PL[B]AW[jp]C[sdfdsfdsf]))(;B[pd]))" - root = SGF.parse(input_sgf) + root = SGF.parse_sgf(input_sgf) assert input_sgf == root.sgf() def test_dragon_weirdness(): # dragon go server has weird line breaks input_sgf = "\n(\n\n;\nGM[1]\nFF[4]\nCA[UTF-8]AP[Sabaki:0.43.3]KM[6.5]SZ[19]DT[2020-04-12]AB[dd]\n[dj]\n(\n;\nB[dp]\n;\nW[pp]\n(\n;\nB[pj]\n)\n(\n;\nPL[B]\nAW[jp]\nC[sdfdsfdsf]\n)\n)\n(\n;\nB[pd]\n)\n)\n" - root = SGF.parse(input_sgf) + root = SGF.parse_sgf(input_sgf) assert input_sgf.replace("\n", "") == root.sgf() @@ -29,7 +29,7 @@ def test_weird_escape(): [ or \\] ])""" - root = SGF.parse(input_sgf) + root = SGF.parse_sgf(input_sgf) assert input_sgf == root.sgf() @@ -44,7 +44,7 @@ def test_backslash_escape(): node = SGFNode(properties={"C1": nasty_string}) node.set_property("C2", c2) assert "(;C1[[\\]\\]\\\\]C2[\\]][\\\\])" == node.sgf() - assert {"C1": [nasty_string], "C2": c2} == SGF.parse(node.sgf()).properties + assert {"C1": [nasty_string], "C2": c2} == SGF.parse_sgf(node.sgf()).properties def test_alphago(): @@ -94,3 +94,13 @@ def test_pandanet(): def test_ogs(): file = os.path.join(os.path.dirname(__file__), "data/ogs.sgf") tree = SGF.parse_file(file) + + +def test_gibo(): + file = os.path.join(os.path.dirname(__file__), "data/test.gib") + root = SGF.parse_file(file) + assert {'PW': ['wildsim1'], 'WR': ['2D'], 'PB': ['kim'], 'BR': ['2D'], 'RE': ['W+T'], 'KM': [6.5], 'DT': ['2020-06-14']} == root.properties + assert "pp" == root.children[0].get_property("B") + + + From d6be82ecba284a47cf9a80776dddeed1449e5b8c Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 28 Jun 2020 14:41:10 +0200 Subject: [PATCH 38/77] analyze game popup --- i18n.py | 29 ++++++--- katrain/__main__.py | 13 ++-- katrain/core/ai.py | 12 ++-- katrain/core/constants.py | 8 +-- katrain/core/game.py | 11 ++-- katrain/core/sgf_parser.py | 21 +++--- katrain/gui.kv | 5 +- katrain/gui/badukpan.py | 9 ++- katrain/gui/controlspanel.py | 6 +- katrain/gui/kivyutils.py | 2 +- katrain/gui/popups.py | 22 ++++--- katrain/gui/widgets/graph.py | 61 +++++++++++------- .../i18n/locales/cn/LC_MESSAGES/katrain.mo | Bin 11006 -> 11123 bytes .../i18n/locales/cn/LC_MESSAGES/katrain.po | 8 +++ .../i18n/locales/de/LC_MESSAGES/katrain.mo | Bin 12830 -> 12947 bytes .../i18n/locales/de/LC_MESSAGES/katrain.po | 8 +++ .../i18n/locales/en/LC_MESSAGES/katrain.mo | Bin 11922 -> 12039 bytes .../i18n/locales/en/LC_MESSAGES/katrain.po | 4 ++ .../i18n/locales/es/LC_MESSAGES/katrain.mo | Bin 12691 -> 12808 bytes .../i18n/locales/es/LC_MESSAGES/katrain.po | 8 +++ .../i18n/locales/fr/LC_MESSAGES/katrain.mo | Bin 13440 -> 13557 bytes .../i18n/locales/fr/LC_MESSAGES/katrain.po | 8 +++ .../i18n/locales/ko/LC_MESSAGES/katrain.mo | Bin 13672 -> 13789 bytes .../i18n/locales/ko/LC_MESSAGES/katrain.po | 8 +++ .../i18n/locales/ru/LC_MESSAGES/katrain.mo | Bin 17037 -> 17154 bytes .../i18n/locales/ru/LC_MESSAGES/katrain.po | 8 +++ katrain/popups.kv | 23 +++++++ tests/test_parser.py | 13 ++-- 28 files changed, 202 insertions(+), 85 deletions(-) diff --git a/i18n.py b/i18n.py index be21dd0..b1ca000 100644 --- a/i18n.py +++ b/i18n.py @@ -34,7 +34,7 @@ for lang in locales: print("duplicate", entry.msgid, "in", lang) errors = True lang_to_strings[lang].add(entry.msgid) - if todos[lang] and any('todo' in a for a in sys.argv): + if todos[lang] and any("todo" in a for a in sys.argv): print(f"========== {lang} has {len(todos[lang])} TODO entries ========== ") for item in todos[lang]: print(item) @@ -73,16 +73,27 @@ for lang in locales: errors = True for msgid, lang_entries in strings_to_langs.items(): - if lang in lang_entries and 'TODO' in lang_entries[lang].comment: - if any(e.msgstr==lang_entries[lang].msgstr for l,e in lang_entries.items() if l!=lang): - todo_comment = f"TODO - {lang_entries[DEFAULT_LANG].comment}" if lang_entries[DEFAULT_LANG].comment else 'TODO' # update todo - if lang_entries[lang].msgstr != lang_entries[DEFAULT_LANG].msgstr or lang_entries[lang].comment.replace('\n',' ')!=todo_comment: - print([lang_entries[lang].msgstr, lang_entries[DEFAULT_LANG].msgstr, lang_entries[lang].comment, todo_comment]) - lang_entries[lang].msgstr = lang_entries[DEFAULT_LANG].msgstr # update - lang_entries[lang].comment =todo_comment + if lang in lang_entries and "TODO" in lang_entries[lang].comment: + if any(e.msgstr == lang_entries[lang].msgstr for l, e in lang_entries.items() if l != lang): + todo_comment = ( + f"TODO - {lang_entries[DEFAULT_LANG].comment}" if lang_entries[DEFAULT_LANG].comment else "TODO" + ) # update todo + if ( + lang_entries[lang].msgstr != lang_entries[DEFAULT_LANG].msgstr + or lang_entries[lang].comment.replace("\n", " ") != todo_comment + ): + print( + [ + lang_entries[lang].msgstr, + lang_entries[DEFAULT_LANG].msgstr, + lang_entries[lang].comment, + todo_comment, + ] + ) + lang_entries[lang].msgstr = lang_entries[DEFAULT_LANG].msgstr # update + lang_entries[lang].comment = todo_comment print(f"{lang}/{msgid} todo entry updated") - po[lang].save(pofile[lang]) mofile = pofile[lang].replace(".po", ".mo") po[lang].save_as_mofile(mofile) diff --git a/katrain/__main__.py b/katrain/__main__.py index 0311fb1..18a78c3 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -280,7 +280,7 @@ class KaTrainGui(Screen, KaTrainBase): try: self.game.play(Move(coords, player=self.next_player_info.player)) except IllegalMoveException as e: - self.controls.set_status(f"Illegal Move: {str(e)}",STATUS_ERROR) + self.controls.set_status(f"Illegal Move: {str(e)}", STATUS_ERROR) def _do_analyze_extra(self, mode, **kwargs): self.game.analyze_extra(mode, **kwargs) @@ -366,12 +366,12 @@ class KaTrainGui(Screen, KaTrainBase): def _do_output_sgf(self): msg = self.game.write_sgf(self.config("general/sgf_save")) self.log(msg, OUTPUT_INFO) - self.controls.set_status(msg,OUTPUT_INFO) + self.controls.set_status(msg, OUTPUT_INFO) def load_sgf_from_clipboard(self): clipboard = Clipboard.paste() if not clipboard: - self.controls.set_status(f"Ctrl-V pressed but clipboard is empty.",STATUS_ERROR) + self.controls.set_status(f"Ctrl-V pressed but clipboard is empty.", STATUS_ERROR) return try: move_tree = KaTrainSGF.parse_sgf(clipboard) @@ -413,7 +413,6 @@ class KaTrainGui(Screen, KaTrainBase): "a": ("analyze-extra", "extra"), "s": ("analyze-extra", "equalize"), "d": ("analyze-extra", "sweep"), - "f": ("analyze-extra", "game"), "p": ("play", None), "down": ("switch-branch", 1), "up": ("switch-branch", -1), @@ -463,7 +462,7 @@ class KaTrainGui(Screen, KaTrainBase): self("output-sgf") elif keycode[1] == "c" and "ctrl" in modifiers: Clipboard.copy(self.game.root.sgf()) - self.controls.set_status(i18n._("Copied SGF to clipboard."),STATUS_INFO) + self.controls.set_status(i18n._("Copied SGF to clipboard."), STATUS_INFO) elif keycode[1] == "v" and "ctrl" in modifiers: self.load_sgf_from_clipboard() elif keycode[1] in shortcuts.keys() and "ctrl" not in modifiers: @@ -497,7 +496,7 @@ class KaTrainApp(MDApp): Builder.load_file(kv_file) Window.bind(on_request_close=self.on_request_close) - Window.bind(on_dropfile=lambda win, file: self.gui.load_sgf_file(file.decode('utf8'))) + Window.bind(on_dropfile=lambda win, file: self.gui.load_sgf_file(file.decode("utf8"))) self.gui = KaTrainGui() Builder.load_file(popup_kv_file) @@ -510,7 +509,7 @@ class KaTrainApp(MDApp): self.gui.save_config() if self.gui.game: self.gui.update_state() - self.gui.controls.set_status("",STATUS_INFO) + self.gui.controls.set_status("", STATUS_INFO) def webbrowser(self, site_key): websites = {"homepage": HOMEPAGE + "#manual", "support": HOMEPAGE + "#support"} diff --git a/katrain/core/ai.py b/katrain/core/ai.py index 31f3baa..ab7abc4 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -22,22 +22,24 @@ from katrain.core.constants import ( AI_PICK, AI_RANK, AI_HANDICAP, - OUTPUT_ERROR, AI_STRENGTH, + OUTPUT_ERROR, + AI_STRENGTH, ) from katrain.core.game import Game, GameNode, Move -def ai_rank_estimation(strategy,settings) -> Tuple[int,bool]: - if strategy in [AI_DEFAULT,AI_HANDICAP,AI_JIGO]: +def ai_rank_estimation(strategy, settings) -> Tuple[int, bool]: + if strategy in [AI_DEFAULT, AI_HANDICAP, AI_JIGO]: return 9, True if strategy == AI_RANK: - return 1-settings['kyu_rank'], True + return 1 - settings["kyu_rank"], True if strategy == AI_WEIGHTED: dan_rank = -4 return dan_rank, True else: return AI_STRENGTH[strategy], False + def weighted_selection_without_replacement(items: List[Tuple], pick_n: int) -> List[Tuple]: """For a list of tuples where the second element is a weight, returns random items with those weights, without replacement.""" elt = [(math.log(random.random()) / item[1], item) for item in items] # magic @@ -270,7 +272,7 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, candidate_ai_moves = handicap_analysis["moveInfos"] top_cand = Move.from_gtp(candidate_ai_moves[0]["move"], player=cn.next_player) - if top_cand.is_pass and not ai_mode not in [AI_DEFAULT, AI_HANDICAP]: # don't play suicidal to balance score + if top_cand.is_pass and not ai_mode not in [AI_DEFAULT, AI_HANDICAP]: # don't play suicidal to balance score aimove = top_cand ai_thoughts += f"Top move is pass, so passing regardless of strategy. " else: diff --git a/katrain/core/constants.py b/katrain/core/constants.py index 86bcbd5..b331ba7 100644 --- a/katrain/core/constants.py +++ b/katrain/core/constants.py @@ -8,7 +8,7 @@ OUTPUT_INFO = 0 OUTPUT_DEBUG = 1 OUTPUT_EXTRA_DEBUG = 2 -STATUS_ANALYSIS = 1.0 # same priority for analysis/info +STATUS_ANALYSIS = 1.0 # same priority for analysis/info STATUS_INFO = 1.1 STATUS_TEACHING = 2.0 STATUS_ERROR = 1000.0 @@ -55,10 +55,10 @@ AI_STRATEGIES_RECOMMENDED_ORDER = [ AI_JIGO, ] -AI_STRENGTH = { # dan ranks +AI_STRENGTH = { # dan ranks AI_DEFAULT: 9, AI_POLICY: 4, - AI_JIGO: float('nan'), + AI_JIGO: float("nan"), AI_SCORELOSS: -4, AI_WEIGHTED: -4, AI_PICK: -7, @@ -66,7 +66,7 @@ AI_STRENGTH = { # dan ranks AI_TENUKI: -7, AI_INFLUENCE: -7, AI_TERRITORY: -7, - AI_RANK: float('nan') + AI_RANK: float("nan"), } AI_OPTION_VALUES = { diff --git a/katrain/core/game.py b/katrain/core/game.py index cb31682..ef5c09f 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -184,8 +184,6 @@ class Game: ix = (ordered_children.index(cn) + len(ordered_children) + direction) % len(ordered_children) self.set_current_node(ordered_children[ix]) - - @property def board_size(self): return self.root.board_size @@ -297,7 +295,7 @@ class Game: cn = self.current_node engine = self.engines[cn.next_player] - Clock.schedule_once(self.katrain.analysis_controls.hints.activate,0) + Clock.schedule_once(self.katrain.analysis_controls.hints.activate, 0) if mode == "extra": if kwargs.get("continuous", False): @@ -310,8 +308,11 @@ class Game: return if mode == "game": nodes = self.root.nodes_in_tree - min_visits = min(node.analysis_visits_requested for node in nodes) - visits = min_visits + engine.config["max_visits"] + if "visits" in kwargs: + visits = kwargs["visits"] + else: + min_visits = min(node.analysis_visits_requested for node in nodes) + visits = min_visits + engine.config["max_visits"] for node in nodes: node.analyze(engine, visits=visits, priority=-1_000_000, time_limit=False) self.katrain.controls.set_status(i18n._("game re-analysis").format(visits=visits), STATUS_ANALYSIS) diff --git a/katrain/core/sgf_parser.py b/katrain/core/sgf_parser.py index f475504..a39fe84 100644 --- a/katrain/core/sgf_parser.py +++ b/katrain/core/sgf_parser.py @@ -312,7 +312,7 @@ class SGFNode: stones.append((middle_x, middle_y)) stones += [(near_x, middle_y), (far_x, middle_y), (middle_x, near_y), (middle_x, far_y)] if tygem: - stones[2],stones[3] = stones[3],stones[2] + stones[2], stones[3] = stones[3], stones[2] self.set_property( "AB", list({Move(stone).sgf(board_size=(board_size_x, board_size_y)) for stone in stones[:n_handicaps]}) ) @@ -331,7 +331,7 @@ class SGF: @classmethod def parse_file(cls, filename, encoding=None) -> SGFNode: - is_gib = filename.lower().endswith('.gib') + is_gib = filename.lower().endswith(".gib") """Parse a file as SGF, encoding will be detected if not given.""" with open(filename, "rb") as f: @@ -344,11 +344,11 @@ class SGF: else: encoding = "ISO-8859-1" # default else: - encoding = 'utf8' # ? + encoding = "utf8" # ? decoded = bin_contents.decode(encoding=encoding, errors="ignore") if is_gib: return cls.parse_gib(decoded) - else: # sgf + else: # sgf return cls.parse_sgf(decoded) def __init__(self, contents): @@ -382,10 +382,9 @@ class SGF: raise ParseError(f"Parse Error: unexpected character at {self.contents[self.ix:self.ix+25]}") raise ParseError("Parse Error: expected ')' at end of input.") - # GIB parser adapted from https://github.com/fohristiwhirl/gofish/ @classmethod - def parse_gib(cls,gib): + def parse_gib(cls, gib): def parse_player_name(raw): name = raw rank = "" @@ -484,7 +483,7 @@ class SGF: if handicap >= 2: root.set_property("HA", handicap) - root.place_handicap_stones(handicap,tygem=True) + root.place_handicap_stones(handicap, tygem=True) if line[0:3] == "STO": move = line.split() @@ -494,14 +493,14 @@ class SGF: y = 18 - int(move[5]) if not (0 <= x < 19 and 0 <= y < 19): raise ParseError(f"Coordinates for move ({x},{y}) out of range on line {line}") - value = Move(coords=(x,y)).sgf(board_size=(19,19)) + value = Move(coords=(x, y)).sgf(board_size=(19, 19)) except IndexError: continue node = cls._NODE_CLASS(parent=node) - node.set_property(key, value ) + node.set_property(key, value) - if len(root.children) == 0: # We'll assume we failed in this case + if len(root.children) == 0: # We'll assume we failed in this case raise ParseError("No valid nodes found") - return root \ No newline at end of file + return root diff --git a/katrain/gui.kv b/katrain/gui.kv index 54fe607..e65d14b 100644 --- a/katrain/gui.kv +++ b/katrain/gui.kv @@ -553,6 +553,7 @@ graph.show_graphs(args[1]) graph.hidden = not (args[1]['score'] or args[1]['winrate']) rank_graph.hidden = not args[1]['rank_est'] + rank_graph.trigger_calculate() ScoreGraph: id: graph opacity: 0 if self.hidden else 1 @@ -726,8 +727,8 @@ AnalysisMenuItem: text: i18n._("analysis:game") icon: 'img/analysis.png' - shortcut: 'f' - on_action: root.katrain("analyze-extra", "game") + shortcut: '' + on_action: root.open_game_analysis_popup() AnalysisMenuItem: text: i18n._("analysis:continuous") icon: 'img/off.png' diff --git a/katrain/gui/badukpan.py b/katrain/gui/badukpan.py index 5d89b22..f88953b 100644 --- a/katrain/gui/badukpan.py +++ b/katrain/gui/badukpan.py @@ -7,9 +7,11 @@ from kivy.clock import Clock from kivy.core.window import Window from kivy.graphics.context_instructions import Color from kivy.graphics.vertex_instructions import Ellipse, Line, Rectangle +from kivy.metrics import dp from kivy.properties import ListProperty, ObjectProperty, BooleanProperty from kivy.uix.dropdown import DropDown from kivy.uix.widget import Widget +from kivymd.app import MDApp from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.floatlayout import MDFloatLayout @@ -18,6 +20,7 @@ from katrain.core.game import Move from katrain.core.lang import i18n from katrain.core.utils import evaluation_class, var_to_grid from katrain.gui.kivyutils import draw_circle, draw_text, BackgroundMixin +from katrain.gui.popups import I18NPopup, ReAnalyzeGamePopup from katrain.gui.style import * @@ -467,7 +470,11 @@ class BadukPanWidget(Widget): class AnalysisDropDown(DropDown): - pass + def open_game_analysis_popup(self, *_args): + analysis_popup = I18NPopup(title_key="analysis:game", size=[dp(500), dp(300)], content=ReAnalyzeGamePopup()) + analysis_popup.content.popup = analysis_popup + analysis_popup.content.katrain = MDApp.get_running_app().gui + analysis_popup.open() class AnalysisControls(MDBoxLayout): diff --git a/katrain/gui/controlspanel.py b/katrain/gui/controlspanel.py index 9cfb0c8..8905b56 100644 --- a/katrain/gui/controlspanel.py +++ b/katrain/gui/controlspanel.py @@ -85,10 +85,10 @@ class ControlsPanel(BoxLayout): if at_node != self.status_state[2] or int(status_type) >= int(self.status_state[1]): self.status_state = (msg, status_type, at_node) self.status.text = msg - Clock.schedule_once(self.update_evaluation,0) + Clock.schedule_once(self.update_evaluation, 0) # handles showing completed analysis and score graph - def update_evaluation(self,*_args): + def update_evaluation(self, *_args): katrain = self.katrain game = katrain and katrain.game if not game: @@ -98,7 +98,7 @@ class ControlsPanel(BoxLayout): game.current_node is not self.status_state[2] and not (self.status_state[1] == STATUS_ERROR and self.status_state[2] is None) ) or ( - len(game.engines["B"].queries)==0 and self.status_state[1] == STATUS_ANALYSIS + len(game.engines["B"].queries) == 0 and self.status_state[1] == STATUS_ANALYSIS ): # clear status if node changes, except startup errors on root. also clear analysis message when no queries self.status.text = "" self.status_state = (None, -1e9, None) diff --git a/katrain/gui/kivyutils.py b/katrain/gui/kivyutils.py index 734ce1e..90f23a1 100644 --- a/katrain/gui/kivyutils.py +++ b/katrain/gui/kivyutils.py @@ -317,7 +317,7 @@ class AnalysisToggle(MDBoxLayout): def trigger_action(self, *args, **kwargs): return self.checkbox.trigger_action(*args, **kwargs) - def activate(self,*_args): + def activate(self, *_args): self.checkbox.active = True @property diff --git a/katrain/gui/popups.py b/katrain/gui/popups.py index c3bac16..c37ed93 100644 --- a/katrain/gui/popups.py +++ b/katrain/gui/popups.py @@ -4,7 +4,7 @@ import re from typing import Any, Dict, List, Tuple, Union from kivy.clock import Clock -from kivy.properties import BooleanProperty, NumericProperty, StringProperty, ListProperty +from kivy.properties import BooleanProperty, NumericProperty, StringProperty, ListProperty, ObjectProperty from kivy.metrics import dp from kivy.uix.anchorlayout import AnchorLayout from kivy.uix.boxlayout import BoxLayout @@ -23,7 +23,8 @@ from katrain.core.constants import ( OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_INFO, - AI_OPTION_VALUES, STATUS_INFO, + AI_OPTION_VALUES, + STATUS_INFO, ) from katrain.core.engine import KataGoEngine from katrain.core.lang import i18n @@ -315,15 +316,15 @@ class ConfigAIPopup(QuickConfigGui): self.build_ai_options() self.ai_select.bind(text=self.build_ai_options) - def estimate_rank_from_options(self,*_args): + def estimate_rank_from_options(self, *_args): strategy = self.ai_select.selected[1] - options = self.collect_properties(self) # [strategy] - print(strategy,options) + options = self.collect_properties(self) # [strategy] + print(strategy, options) prefix = f"ai/{strategy}/" - options = {k[len(prefix):]:v for k,v in options.items() if k.startswith(prefix)} + options = {k[len(prefix) :]: v for k, v in options.items() if k.startswith(prefix)} print(options) dan_rank, model_based = ai_rank_estimation(strategy, options) - self.estimated_rank_label.text = ('' if model_based else '~') + RankGraph.rank_label(dan_rank) + self.estimated_rank_label.text = ("" if model_based else "~") + RankGraph.rank_label(dan_rank) def build_ai_options(self, *_args): strategy = self.ai_select.selected[1] @@ -447,7 +448,7 @@ class ConfigPopup(QuickConfigGui): def restart_engine(_dt): self.katrain.log(f"Restarting Engine after {detected_restart} settings change") - self.katrain.controls.set_status(i18n._("restarting engine"),STATUS_INFO) + self.katrain.controls.set_status(i18n._("restarting engine"), STATUS_INFO) old_engine = self.katrain.engine # type: KataGoEngine old_proc = old_engine.katago_process @@ -472,3 +473,8 @@ class LoadSGFPopup(BoxLayout): ] self.filesel.path = os.path.abspath(os.path.expanduser(app.gui.config("general/sgf_load"))) self.filesel.select_string = i18n._("Load File") + + +class ReAnalyzeGamePopup(BoxLayout): + katrain = ObjectProperty(None) + popup = ObjectProperty(None) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index 0293cec..2d1c78c 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -5,6 +5,7 @@ from kivy.metrics import dp from kivy.properties import BooleanProperty, ListProperty, NumericProperty, Clock, StringProperty from kivymd.app import MDApp +from katrain.core.constants import OUTPUT_ERROR from katrain.core.lang import i18n from katrain.gui.kivyutils import BackgroundMixin import threading @@ -224,36 +225,46 @@ class RankGraph(Graph): return {pl: RankGraph.calculate_rank_for_player(segment_stats, num_intersec, pl) for pl in "BW"} def rank_game(self): + try: + nodes = self.nodes + parent_policy_per_move = [node.parent.policy_ranking if node.parent else None for node in nodes] + num_legal_moves = [ + sum(pv >= 0 for pv, _ in policy_ranking) if policy_ranking else 0 + for policy_ranking in parent_policy_per_move + ] + policy_stats = [ + [ + (num_mv, rank, value, mv.player) + for rank, (value, mv) in enumerate(policy_ranking) + if mv == move.move + ][0] + if policy_ranking + else (None, None, None, None) + for move, policy_ranking, num_mv in zip(nodes, parent_policy_per_move, num_legal_moves) + ] + size = self.nodes[0].board_size + num_intersec = size[0] * size[1] + half_seg = self.segment_length // 2 - nodes = self.nodes - parent_policy_per_move = [node.parent.policy_ranking if node.parent else None for node in nodes] - num_legal_moves = [ - sum(pv >= 0 for pv, _ in policy_ranking) if policy_ranking else 0 - for policy_ranking in parent_policy_per_move - ] - policy_stats = [ - [(num_mv, rank, value, mv.player) for rank, (value, mv) in enumerate(policy_ranking) if mv == move.move][0] - if policy_ranking - else (None, None, None, None) - for move, policy_ranking, num_mv in zip(nodes, parent_policy_per_move, num_legal_moves) - ] - size = self.nodes[0].board_size - num_intersec = size[0] * size[1] - half_seg = self.segment_length // 2 - - ranks = {"B": [], "W": []} - dx = self.segment_length // 4 - for segment_mid in range(0, len(nodes), dx): - bounds = (max(0, segment_mid - half_seg), min(segment_mid + half_seg, len(nodes))) - for pl, rank in self.calculate_ranks(policy_stats[bounds[0] : bounds[1] + 1], num_intersec).items(): - if bounds[1] - bounds[0] > self.segment_length * 0.75: - ranks[pl].append((segment_mid, rank)) - self.rank_by_player = ranks + ranks = {"B": [], "W": []} + dx = self.segment_length // 4 + for segment_mid in range(0, len(nodes), dx): + bounds = (max(0, segment_mid - half_seg), min(segment_mid + half_seg, len(nodes))) + for pl, rank in self.calculate_ranks(policy_stats[bounds[0] : bounds[1] + 1], num_intersec).items(): + if bounds[1] - bounds[0] > self.segment_length * 0.75: + ranks[pl].append((segment_mid, rank)) + self.rank_by_player = ranks + except Exception as e: + MDApp.get_running_app().gui.log(f"Exception while calculating rank: {e}", OUTPUT_ERROR) self.redraw_trigger() def update_value(self, node): super().update_value(node) - self.calculate_trigger() # recalc here on trigger for speed + self.trigger_calculate() + + def trigger_calculate(self): + if self.opacity != 0: # recalc here on trigger and only if visible for speed + self.calculate_trigger() def update_graph(self, *args): if self.rank_by_player: diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo index 67fba6b14f4aa7ec96ca291f3480c199aaa782dd..13fcb958bcd3abbbdff44a5cc3db627d59df13d2 100644 GIT binary patch delta 2927 zcmYM#drZ}39LMoT6in_OJc(#>BCklGZF!5Z5#Si%LMMM%rYNX1ZiB-`(w%|WWhT?? zMs``Tv^I52HcPi!&1I#eOD)O5W}6Y-GHfHNLGKUeTd^PK^?RP@`+J_}`+a@~hF0D# z3|#VzdCBo}kl!i%wuGwX-1YGP^>TZI_IMl};aog=%~VXJy%asT5o56t$Kno*#yuE= z@7Va^paFNB8?AL7HNlSCSnc6jBH0!%oZd95#P!AqNWp-2n*o9o~d$SJ}KY)rGOOP^4K*gn?j$korgL$a< zLX5)=sJKQ9=)q^X;c~Cp!276)KR`Wr61m((>+eH7_cv;xXo9p*A}TJ~+KW(`W}y-* zz&mg?DsDpz_2=Hz@}bghM+NRgoqZeX3_GlS0<~a|c@tIAVH+REAh*tk%5W|!kqm1W zSbGgBt~!?bE0bnAw9^-n%e~EqHgFgxo(P26Tgc3S{*=5^qKiBYJy(#7Ha2%sBgnKb`yrtsENI(@f6g?(oqZL zS^rw(a#eh&a)E7jvlF%RS5TE`M{T6T#y>@s^puT%hg|MwJ`^{I+E6&zXu~n6ql-r^ zl!}U5Xzg5Noq$`xjTT;u+G&~Dge2)+M!g&Fp(g4;#UDd$^n~@FLoLvQ+SpBNk782A zdr%2XMLm}?BImz|8tweyvziJm|$Scy8z&8P=ktbaFZ;=QQ&{iuvTM9p{F`g?F> zL#U$}MUckFW2CA3V<-omgPAx3pF_Q*9jFAlQ1Rbj zB3{FJ7%`FZkP%qaoW?&d^=n~Av*Ks>8z@$mR30I*q zeG*lak;2cu17ssXSUe*3#f?> zSpQK}$v;PB{I!jDTlZ`5HQ~dk4d$aZRE$*5)uWDRkM*}B zuY)^+T<(mue@BYrqNWF*PeY38GLiUzE3$z`oXfyYOu>(F4qmhIi8FZ7XeXl<&c{?- zjjF`+s13JSyWQ+SCH4_&;d7`2`Z1RET{K?-1tgmHV;Jo`)WpkCN3h2F%dOvUHla4u zYQBl=*&RZ?1Lslm^q}UwYVF@7>$@RtwDSnQg9`MTNvMsbp&raeE%XQ~lgF+7l(_>{ z`n{-mK0(d<6^7si)I1l>%NWp^{=$u3y1!6o<((azV1b#1$@CW>Ulg~+`rD94+&*ic zF@HcU)Mwr@2h1VV(S{~de+ABCcDc}8g32HVRr(dyUu14VRiY9bFo25x!^T6IU4K_1 zQT>xqm7Il2I0Kbv!JN*M;jv-kYy73CBI7_IR@Yi(m0?{ORduLnbY&Enins?W^2i(Y`8L<&||Moo~%r6Y>uU C?j?Qz delta 2813 zcmYM#eN0t#9LMn=H$WhP>lH~J5;D3xsOYs>*?6e6C~GyAqc&PuOp2}Ll*lBtv&iYl zp*J=}TdmeDD)kTZx;ZUu)6gNA(={Pt*ps7}W0@ItUDo@<{cgF#dHtS$-{1F~bG`id z^*V1fkl5w;w}-!c{#ue$bMEW3|D8qg6!jVSGtR~$G$JR!(QS%m{ zGEvD*8LKtDdJ2ksB`O1}QGsl<0ozci+iCqh$mOEkH10Smpg~l?BdAAq0kzRJ)VL&$ zt9mwSpF(6`&n=*!NS7kPyGK#)!ZWA|O{f5yQ4?RY_N}OeyO2FxkJS&M#-Bt5ehxM7 z7xNM-L)S4y-+wy0YoTn^hQ+977(z{~u=ZuBg_onouRxu2HEO}l*4~9v0ijZV$ol(H z>kT27yTI)geg82E#n{Yh8t^u1;g7jhU@z8S41;(t4_q&06DsAKP~$tW03#T}V|W){ zK?PENQ+%H)ETR4!dYZVMf>Qqx>ZLk}Rd@ue@gIB#D|u7z#9`Eeft>hB=b|#V1Z%Mx zm60%R!1t^_pWkV<2k~WGl1u)z;T{^a;C@up>knJ~U8{d+er6sq2T+gR8?uH8)PmPg5hfMI1IkCT<`$wJ(ZkmMB=Q=#RmkPG zSbZ;&9M^~X3dWFRx|sFn+!k+NjEnUB-%nva1J>d^j37yIr%@aJf#v86#xoE?1zKhG z2hBRvi5gHFu0sXhjed+;|5xTPChPnEoq|$3iAsGU-(oTbQ0+Np5Eanv=6y(PZaM1N zx1z?kq2_P5`VKRK3Vff{4@>rUCnzZLZ&4G+Q5#)Cog{&eRrNx15t2=J4{F0k)WXf^ z!wsl)JaaSZQFNdl&HJc%N6}N^G=(x8LB0$(tu#KM2x)R5tJj;)qc&(U*PCr-JL*w} zt^S31%sh$O_e`l$qrMMx8i@>oASzG~o@@#$nVCNw>B4pfVUmowOfy(vh<0 cc$z;snm%K#FS>K47l@Vx&-kLVN}u-q2PIPpT>t<8 diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po index 34a3acd..d1f3a99 100644 --- a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po @@ -597,3 +597,11 @@ msgstr "" "corresponds to `playoutDoublingAdvantage` in KataGo, from black's " "perspective. When `automatic` is set, KaTrain will find a suitable value " "automatically." + +#. TODO - in AI settings +msgid "estimated strength" +msgstr "Estimated Strength" + +#. TODO - label in little popup for analyzing entire game +msgid "reanalyze max visits" +msgstr "Number of visits per move" diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo index d9969637bc19a8634ff1f526ed79e50b6126e31a..d7b8517d2e1367875e5d69431a2874e50ba4e17b 100644 GIT binary patch delta 2926 zcmYM#4NR3)9LMp47;gfX*La6ZnY;>!vQ$`Dv4}8B9o4iKF)lB~1$IG0ws!M{t>I{6 zl$9-;w4_!h$&6msqGe9Sxzu8jyjZK48P3^?v2yhN!D+IG`#I-4|L2_ZKj%F69;oVe zdB-CbzGC=0%zq;Pdu~z9m_LGU8clJEdN}$nGA0(ItvQ%Ny%Zy`2E(uo{c$gbVmCUl z$KF3Wuh)D{L1TS~>Rd2BbWKnwqHg)_b+OoP=Yj23Mwws)>ol2EkPx= z9s}?RR9v-#^`~sM@u8({M+J7G_WpI$9`@P#3DkgN)~l!`owfG^xk=GDQ5oi;61mIP z*W3D2s5nm;>#t09(V&^Wj9lg*ADX~XOu$p9rTZ1t;S4H+U>=qUNR!D#B~XZ3ky2E~ z8|?kfsDA5Fhjq8sUmd@OdaXV{b@YvO7}dd~^%`pCGpM&AklFZQD5~RV)ctJK#EMV@ zmD~0$$Yq-N(8_st+d?O5=C7hw;%(GK`t1F$P)j;w?~fpt`Hc_7&7dX}L^hhR1GROr zsDW}&aVu@T3>n93Hd4^QTTnBtx9&ueH2Y9z<6TroeW>`)P!m02+b^I77(-3$s;&Fb zsp2D02_&PQ%bt_{UqwMnbsuWx6{wD$L=D)8+RGiN2lv?aE>y>FpyGQ`8GnN6@2qVf z!?_8ew#J7b-4DlLz5h-M+%rq@S-c0+@I%y|4r3fnVJ-U8_z*T>0S=<>|BXr@EXJ5r zjKgd!#AR5E%W*$yWzV8l4VNh7ViYGZ2}_W%%vRK%w&F&79v{GScpVcNwHzIM|1!4O zjC5r@n281V8d%u;(9 ziOM7uwH3>)#i%VQNA>qO>iMT}2%B&mi;`LYDhdfH{2<^9IErOXK75Q>!a9CWG3_>1 zhx!dXMLl9MTR?pjr>JKy;g=5M**6Vz2sMFz)YcwHouLs-$6rthhIpxI>5@=~Vg;&$ zdy!<#!>A11s0${&)d3z(rfXf;uCA+V&9kLlg2QQBX(esEHJy zmUgXe--IM>YA^shP!s7!4e%B!!y~8(AIDt$33Z0TIS9978ggt*Io^R)$kuyJ2L&zF zAuPoK)M=f?HMlHm{`K+Toz&Y=1Ac)ycpkNKKH2jV55qv}$*2L-Q4_cu^U#HyN3(CP z&i;QyK{Njuoj8kHLg&)?j?z&B=A+(*QVhbisE!^*z26?xVQxo~HwRIP4WZ&kk?$lk ziNW{}1~9$}AdN*Bjv<(YdN2!>K@o;x1!}-5OhzxNqXVdp-$$*`m#F8bQD-86{aAnw zR6PQfU>tgNC{iiNLR^6rsK5?XM_tHdO%Jj-=3CT+Zy;lsz`Xghu+n-z>a0A5%DfVJ zSxmjP0ky?VdF;QIw1)=Gs2A1o5maWMqgLc3s>5MahZD$~XMV?b&_Qon`eUg6&Y@Ol z*tY+LIs@VP^D9+=Wz@^^`%edj`Gq&P)VV8LTvg8I7LTi;rlq$3hp^TK5tR*<^*h>J zPIqOib9-HLT}yNSgOTGgH^VpnAD)pl;2XDYn|rIv<7}+9!OkXX?#At|{)wDo-~Rxk C&?J`t delta 2813 zcmYM$4@{L+7{~D^FUke^Cm<-2kYNybg=?83+|}; zpfYeiF{07YssWI0uvQVbp{bsD978`X*EW z?_w10!DI`VeL=y)gJZ}X*5x|%q84_+Js(0vbR9K;R;l)Q)PQNuEYvu&QT-MmmlbnU zzZJ+3R*rGZZ_iRtYBr!=+=7a1mpb65$YozUL#Y0}sD77Gk&U?a7+%sAq@fm=hw6V1 zCSeiM)ygoS7pp07*{iN$Giu=XP%nOhT=tcF9zwm>hnnbj)I|RxmqqiE>QhmXW}yP} zqZb#V`W3~Kf6BI;n^Iea>bM%U_v=u5xXsmDQ4=0?euqkFziYpOTw|RSVFH~M$aGZQ z@9Gbt`YlT!|B9r72U_VG(sITA*PQY`hfq!!CH&6?VB5X}G1@)c} zm5GJil(7 za8*x1&6A1D8!#UQt#m%JczXnO7M?`CP>ovP3#b=gbIl?V%s_;{EP2saRR2||NNZ68*16}6IJO{E>JPZ~ z5Nf<1k;^V|tI_vALLnQUVl?gLR-B5vu^JEKqnJbETwIH4{|uG#Z}B$#0cYW#=*P@d zv%7E^DzkO2-h}zoPhj9y3O6Y*hox~6wU-50igWQH+>C$VXE!WLi!Cmi29l?bK8QRbn>rLyq&*1N>vjoqCKcoeeOJj+RH9fBxg}u z@tboPwIv>+(Rj(I_orbO=HPk!3oG%|bh3u`W|03r3b$nHMknl{*iSx_skhHC3sFCQ zJ6k|~XO`Jj>KA94)#J%K!V|4z|FnP_)ZVT~ouTcRgUzS_J5ZVHL7kbQ00pgd6bZt- z^j3s3Q4!8VnrtzuUyXad78U867>V_$33j-83+jyQb3w4G?K@nU)ML2?*Fp97_sFsBqXfbNw5>$pNQ4=?#&cZ$%hi$IDA9Xek zq0T}l-i-sUeKPwN&;aQanA3bnkoGugrEej9Z70U#QRgXCM1!b^hmeoMhMhN1TRMu$ zq>ub-A#+gUEol6bY<+Xc)%kv-b{0EMI6AJ(U diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po index 1c7de17..d13727a 100644 --- a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po @@ -640,3 +640,11 @@ msgstr "" "corresponds to `playoutDoublingAdvantage` in KataGo, from black's " "perspective. When `automatic` is set, KaTrain will find a suitable value " "automatically." + +#. TODO - in AI settings +msgid "estimated strength" +msgstr "Estimated Strength" + +#. TODO - label in little popup for analyzing entire game +msgid "reanalyze max visits" +msgstr "Number of visits per move" diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo index d7e6040d736e50119a430f7dd2489faa70b58646..139e259a87e26290dfe9bebecb02af1287a46ca4 100644 GIT binary patch delta 2927 zcmYM#eN5F=9LMo7S+qc%v`TYSx}+JS+Uos*Q@QJX{m%J)f9HJ9_nhBlfAO9Y z?`Y(VR>Qw8{?FzA?wM*C^Go3WMssj2@ZFiy; z@F6cN+|;A7hCjU%XmKSDh?hkFS#P6C`nSFBF+cL9xJgt(fviRehoQF4 zg_Iqcl>rexgpeC$B?d49?gS%~i3u@qYRD36D#eJypF53QU zIF%4;Ya9gWegp>V{daT0J(GqNxB~CT4^R~tMxBi@T#r6%^cpO}M0_9T;}B{A_k%~XzY>Z(xs%I9X_B01q;VN8#t@tU9;N#fALmKcp zlB4+z6VS=;asw{HI6Q)izkvG14T)v{wekqwu|t@EI?ZQMr5Z%7=nAS5KcGr}7d4@S zWhyQlwKZ;3T$=4)h8k}TY6}ao2R%57QS+#OAs6HGI7qmXI-bIC(jCSx@MG+BbD(g` zz5L<8f(88HaZu*_*k{@s;*EI$Qx;Aqcno#e`cMlTK%J$l$fM>4D)9hs!t{IVLS>kN zT1ggCC6kLfJeyGi)T0~qoZ>rB6*`LAsuQ-|k2;i}*#048x6BBtLU&MamDiUOqLK!o zZp5GlPO|+Eq0Yo|)I>$7(m#V*L5;N;mGFK{!YKweH#8k?;neNY6G;#m>f3ccrJn zy}i7)ysozQ$*4Wikvadx7yKJPlo;`(kKi+94 AR{#J2 delta 2812 zcmYM!drZ}39LMo5SGnIrk|O~naS@O1xTb^Y*lJm@ELKyArY4u=78aPfw8=?ovPLC4 z4Kua1a$|w@M;-eE%r0|?XbCwdwj6kg368Z@YY;8%{W<5^Vn2Sb=lMOi@AG|rztg@s zu*pA`5#43{9p^uX|D7?anf;LPzY8e-NIe~I;zIn(nUZLhN4*F$@KH>|TAYV3U<$sB zso3V)17ZDkjDprWftui3Ova0th1W0%XD}M0sVJa$RR28m;0nydb*KgFQ2kn5y&biI zgBXvWVy5}czN8SvgF$2sJMTJ-q5`|_o=>B8G>ck5QK~%^HKE5@fSP9+s$V&BStU31 z+lWkITQP<8?L`Vo&0f^NcThX~KppTiFjhQ?~Wol-fpA$L*-I-;FxMPFL?nEqKa#0hQ8m*FJ?@p-$Ri8lAO~TvXlX z>J_MdPo4YB0huq3NB+T{(_qLhHL*D6<|Eu)CqsIN} z{2i5{I~b?$Kbh4vQ5I^!BGeiBPy<)H=as05pGNiHg4$^_YQp{Qc^8HPLZ!aXwGW}@ zyMkOc!L3Q({~U!Pe4g2qfdi;_;$z&1{rDtiaL~nAhq>5+N_j8pr#pz6X9SnyES6*O zJ>dZBQO|eaQv3w{*%XE;kPVwco#nq+g^@(@5U#^P?8Jw$hC$l#Q6x4yj)nL=zKWBW zhgzN(X`fKR*U{$UE5__}MDMvl*B zQ43}8Mk)1+P&-)SEJp=gg(cXC8g~F0YCWz#f+WdqyLxJIc%BkmrSHFt0++RLE5pO+ z!%L`_GM3qwp$8RtCF;9gkMUTK+WB_m_^lQ7b{|FsIEb9C-Ej4YCE-AGF!cSe3>7$E z)D9m*rM3oRaT6+q4X%9`Dz*EO)3T3HfeoNC^%KV7HH^n8)I5K>=X0ndk1C~I-+vkf zrOty1_#o;fsX#@(0kz{-Pz$_)WYYqufcsGapTkHT#u&VaTKEd`McX)%RGV?_`J~C8 z$b&)(YFL3fyJu06x4GwUqXPH})&DeV;bGKH$B~c2CY`rYM>vfNEY}-;`4*!BD@AS8 z=e5nDiC4RZD%6D4s2`SRP(LUyx%wn(!aJz1B#IY9fn=eMq!5#^0w2N0gHs9VvBBi@ WU6H{<^WR($Twby`D%iC2NaTNUc?zWf diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po index 36f86a4..a0dedd5 100644 --- a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po @@ -635,3 +635,7 @@ msgstr "{num} available model(s) found" #. error message on trying to download models when already done msgid "All models downloaded" msgstr "All available models already downloaded" + +#. label in little popup for analyzing entire game +msgid "reanalyze max visits" +msgstr "Number of visits per move" diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo index cef9e9913510152f94e4efeebe43cf44a26af68b..3d5b857e3b4a0c6bb0bf549b37dee12249658f6f 100644 GIT binary patch delta 2927 zcmYM#e@vBC9LMqR1p&b;7p{;9BDb`Z-;i7Kqft4NoKvRG{>Y`0fUp36fQqbj^GAzT z8SX-Fc}wP0@k4>+=`0Z>G}sy z3wQ^ku?rI{X!a$Ia4uX%=Cog2z%^829A4ffS7CG!YcYOf$+~25)VhPejX{fji*Dpk^bPj4^ zi*PJHh>EN7QGeRDfs;!6I4ZCewfE1X_OQeC&!8skb6!D}^oF}1%}tsXK&>zjwU83m zU*!5LP;u+ysJ~XSl?%#r4|3QmoRq*(Ouc(goY+wiORIrxdmCIJ&Srb4x$F?K*hg@O7x7o-i?}|50%&z*Y_}} z;{B)vOhG-DH7fgGNJFKXi^_Z{YM|w)3D=VK7ktVGk3iY zM-xJAjfWuJk4LY*{{Ri{SvJ<KFH?6mDye2f45pucO}PJ!GL$?MJQXbyVCj9NlwN3A<4% zxro}^-%%5JQbX@d0;)e5^?WXVfcIcO-bf|A`84{c@P`L8(x?W;1Q^6)7I8D*KK-a$ z_<5!O1zw;(IF+rUf1bLZpGn#^LQU#z!FU74JyWJ)ctnUz{hcP#mF2sj9N(`E0oA|)VpvyD(-INwX!ngu!lLZ z%eD_E>-9WALz#btD(y8Ki^+Uj(Kr>yVK%D7g{b=_sLGV05?qd2;X2e;vIC>A3uEwe zRHD7;#f$1Qzx_%>6AYs!49f|vz>At788uKo>ZK_}-d8I_ZBadHPaj1N+r>#MK84z< zJ`BSjPzhW@jW>ir1^z*UPuSwAuPTs<3S5Y>xDxbusD&t2@ zkb+v+RODl^TxUM2LNoKIze-xm1(kFiD&uC?e+*UHCs6~wh#IgRRhbTK!;f)1R_2Gs zX+S;q2>Nh`^JR>qe*{bMWPaz_n7GLJhQ^w@^2W-FKttpD%KFuf)t%qOHHG=h>&t65 zH&+Jg%9{ckYZ_`A8#)(E@Fn^0{V#sWzwyg5mxWJSxS{T$%JqRYRW3TPmR{YOjg_5m I=k$mD1GA4RsQ>@~ delta 2812 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 delta 2813 zcmYM$e@s?Y9LMpKJR(2kK?vm``JogBfq~|rz_KlgKX43nZR7?rbxxL{SdEJAbY?5i zwZ~ybH7bjK==$~e4>;DABGC_oAN8?1DYNf?CmEs0kFM+LKTNhRj^lI7O&_cO#dp z;--ENAw#(JIGg$1Rtic@8|uZqsFl5}4tNB)+%av~0S0Y_q9eR3kGX*a9q%}N`8u%sDi|-?s>$B%WsP{%t6OE%L`WLxe951P!j#_Ch zYGK6~gB7TLE0f4SWw(}_Qo9k=@e$PCx1#p&MXUFqChRv)p;G#jwf}-#)19=!Af2_4 zOjNzt>i44h)dtDGR?^4=MY;pI+)i!^AdDIK9x8PMsK~!JFC&+m;HJQ17)&cqMXfXo z)xHQdUK#3;)}Y4QsQl}5YDIMnn}<*X95wq<5uZeT1!plDe?Sd<(b^|b0miayO*9Yn zULh(I72K4u8q=$zpvX6%GSG|)q|G|)L#6JZwf7*Gi*QrFlc<1(Q2~#jwrmVF(ch?k zGg+?cX{dR!k$F8=NI{X7A;G&av{T@6Z*o%xj^PFz!1b7)&ac@`&UIkD+8Iq-mBFW>l@wwb z-j5ylG%BE2{y{l~1*l)xDO4ag@H0%GPyTg^M+jS~8buwVKTxTPSPh5s^i~Rsrm<<@M z@86p)uqUVq_M%SfEB3q_wP!uZ2jKcqTQ!U%$xWiR@YXy1_sdY3t3;iFRj94pgxcC> z4B}Hbm-$@>1rDZr3$?Nnr~yx-QZ|P17%27+O%Q9SFTfzSpaS2GdOwT`pc^%D59$Z$ zBh=O$xAuR~(;fv%_+JX9;|g4Z8u$QK;XA0vubS5}nffeFrY25DWh~Duz;)CYqXOwL z_nTd)@!nX%&xr;&Vh{RI^#RoP{4IuY98+-TQh!CAsP=AD|2|ZzKgU!&kK6Iu(#WrI b!RSb0^5Zii2UEhKNJY_ws7Pta+fn}kN+b?D diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po index f60e681..202d993 100644 --- a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po @@ -612,3 +612,11 @@ msgstr "" "corresponds to `playoutDoublingAdvantage` in KataGo, from black's " "perspective. When `automatic` is set, KaTrain will find a suitable value " "automatically." + +#. TODO - in AI settings +msgid "estimated strength" +msgstr "Estimated Strength" + +#. TODO - label in little popup for analyzing entire game +msgid "reanalyze max visits" +msgstr "Number of visits per move" diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo index 51bb52b42744870435b85c95ceb2b5df9a119c90..7b84b28bf785944c0aa539557e4ed41bd3f03a52 100644 GIT binary patch delta 2929 zcmYM#eN5F=9LMqR1@Qq8;Q}g>@&mQhh$!;`6fuU1Fl3c-L~U*k zHXpJ~`-57VE|)=NW~)_;%vx)j%_sB^r{+WE+RQoI>iyw&DtEcBbAG??@0{=VoO6G+ zZVI%#5{O<1_IlO$caXma_}dt#p4rX#|4pa)8~qfFO)wjb>CQaNqCW$JxD=DI8T;TC zOv3Fr0N--=JA9+|H4V-6Girc~*dPDEfoO?69~*?d=#D@oG#V9Oj6-k^rePR0VGAm5 ztLyJW74R|k!(*6cQL_^?V!3b?nbU5#fV-%~0%W7>iKvPOqbBfB_a8+KSm-Q8#m`2? zJ&Sy-jxWWnMuxWan8f_nMng;UChEb1sLGBg0DnL}cFuVX6@M2M*M}fgHUt%yi`s%h zRD$KG_-8N`Yf*8{7}bNDY4EW(+>QOHfe)h|JdS+qth;^<_1s^miIND?L|Le~F|J>L zsMX0#OWY%Amv~WS0?m#})&X*GCz)buOwRD$J1KvYbkjTR_6S-t#Pz6jv zt;h^i#dF;K=TPI;qYmo^t-l6-9rd$%4>iy?&Rj+?Q+*oq9*7@ zC3eg80}QJ8AgX|2sONHfWd93jXsM>7GOt7p^a5(a<*2<}i+XUQyS^1Q@NQK6K2*h@ zpvL>rUGK)8giu=(AV~L9Fj2pMj|TVb5nPHT_z)gIorx3p1m3_fdTewA+i)hv4)PD_ zEK~t?Sc;8Ugom*P@8I(|pFy>bd3Jd4>F7)lX19Is<7=3oVbR$~P9#yvHR{m-Luh6^za*f+~R%?X~rR6$fl zIjALj7#Yh7oeNO!{#8hEwiUIK`%!V9BRSi7)crrP6O+jD0)Cav`tP98PI?U_Fn&1i zXMAS_`-(#;YX%-+KQ3WlG|P&Y$53gE*@9fNW%Qe=A{MW(e46NbKHG^gkNFAZBemHy z)Ru*C6uuv&q2KLk)QjeKRK^L9`&%&*brvd-?YEauTeTfE(M9B-+g;RqAYrV3NQ0V$|L@;&|MJ^YJIw zA2p7*AN@s0j@E%?co|tl_MfNNeyhS3T!T90_pu4CN! zyOHB!hf&X;#}W)q@|V04wW49v3btYJ=TTH(+!X(M8nWt^ zhm}~1s^~LZjOTGE78m<2#6k4ep$glMF}Tlp0GH7}Sj_rsNiwGTzk(dp#097U%CJ9H zqv9G+1FlAjv@LiOkD(6NM^E~v|5xO2*&Wnr_e#uY+H~yMI;^F?yQK4Yd~)xUrbu15 zCK6iWHANaj4ND_0cAidN6BDdysHtDu9P+|7YrIu;O?8o`&RMBtBZAZZi=X>%{Ei77 daT&8$h8Kq#z2&to+FL;{ynI!tb6QbZ+&@O%HH-iN delta 2814 zcmYM#4NO*59LMoL$U{K9Lm()xK8YhG!B^{JSq(=d zX*F|;MlWWq9#=!=YO`oywN*<<%`CF8%(TrEdj0;mXNz6#=bU@bJ?DS^=iK{j-x=1r zBP@6(Iikb(JIMb;{+oQNnSB!Tzc*0)lzI{l;ds2_jKAJ2hk5}fV+AH+EnbJ~FdiE* z0k^yR?VdsFq@b}5qdIsWq^Z?nP!F!9z+uhq#uKQH_o5zr9XafXyWWR-?lfwki>QJAMGlMMA=NWbGxeh; zR*X?thH4i`ApewY87HN74XWXK)Y@-Btznz1ccBJ6>ii6q(r?}U^T;vQNHa{Nu_ls@ zsu#QZLR7n@iR521spEnoZ9)#)#z_IR;{<#SmAa2mk$>$RMh?5mNr6YvnP#4fnrRN| z{uESyrKnB11l8Xfc!=clSq70Y)=z4KyD0 z+%!}s$~Y-wOPs-43W|I+Dgzr)foydRcB4|a&)x4r4hwNoyIxd41E_#cqn7L(YM?(+ z?R-pE^)%EtxyZOdn?^yAmLkF1ov6LA67|4ZRDcho9&B;fpF(xqfsA2YuHKDm-;WCX z1nRjToR?7<`Uj)*{>L%8I!Z$gSb$o?V$_2R-1SOS$9JRJ-;bK+q%Zs3nV9Q+ z>x+=dStXJ@TZL-38Hw5UpzgnkA?(F7*i5v0u!iV15WvO!pxGl7?(>_yfM4CnFBooO zJ%+G*3L6ji7c$du)_p3!Xw*xX#yEVD^l6}%rt^y75mZ3KNX+&(YRR&S`37J;>UC{H zE#WIc3X1p`YAL=!?S;4!Z*$E;tyL9jpuNb(wYP9AbEr+#kBKcgn^SCBbb+zfA1 z&Oluc;7kmzprA;faW_Ip&~_ZP6n~*+=jNl9s2WpnHRj=?ID!3V&$|onVk-4P)Qo>crD`J6(M;!KE>>X;HlQ|D zFKRFRh6?x(RHmZ&80d8#kNG$WwHX7b`)wH1j1N#CyVi}%@G@#96}RwF!g|cYe&;Z1 z#*quX8D(QQ^%CbSTt|H_Y7f1PX?O@V@Q0}9zg%c5#=gwOpl><_d(>a&p9arDXhx_I5~7aCMhx$m$W%Nv@hj0f9TSjw|$|WvI^gS D1FjgB diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po index 6a404c8..1589bd3 100644 --- a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po @@ -637,3 +637,11 @@ msgstr "" "corresponds to `playoutDoublingAdvantage` in KataGo, from black's " "perspective. When `automatic` is set, KaTrain will find a suitable value " "automatically." + +#. TODO - in AI settings +msgid "estimated strength" +msgstr "Estimated Strength" + +#. TODO - label in little popup for analyzing entire game +msgid "reanalyze max visits" +msgstr "Number of visits per move" diff --git a/katrain/popups.kv b/katrain/popups.kv index 97e73b7..589c672 100644 --- a/katrain/popups.kv +++ b/katrain/popups.kv @@ -450,6 +450,29 @@ on_press: root.update_config(True) +: + visits: visits + orientation: 'vertical' + BoxLayout: + size_hint: 1, 1.5 + orientation: 'horizontal' + DescriptionLabel: + text: i18n._('reanalyze max visits') + AnchorLayout: + LabelledIntInput: + id: visits + text: '2500' + AnchorLayout: + size_hint: 1, 1 + AutoSizedRoundedRectangleButton: + padding_x: 15 + size_hint: None,0.5 + text: i18n._("analysis:game") # re-use string + on_press: + root.katrain("analyze-extra", "game", visits=visits.input_value) + root.popup.dismiss() + + : fast: fast rewind: rewind diff --git a/tests/test_parser.py b/tests/test_parser.py index d84f77d..aa2986e 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -99,8 +99,13 @@ def test_ogs(): def test_gibo(): file = os.path.join(os.path.dirname(__file__), "data/test.gib") root = SGF.parse_file(file) - assert {'PW': ['wildsim1'], 'WR': ['2D'], 'PB': ['kim'], 'BR': ['2D'], 'RE': ['W+T'], 'KM': [6.5], 'DT': ['2020-06-14']} == root.properties + assert { + "PW": ["wildsim1"], + "WR": ["2D"], + "PB": ["kim"], + "BR": ["2D"], + "RE": ["W+T"], + "KM": [6.5], + "DT": ["2020-06-14"], + } == root.properties assert "pp" == root.children[0].get_property("B") - - - From 5527cab3a8d91daa05b0441e3fb556460614dcf7 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 28 Jun 2020 15:07:05 +0200 Subject: [PATCH 39/77] test --- tests/test_parser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_parser.py b/tests/test_parser.py index aa2986e..a07a89f 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -108,4 +108,4 @@ def test_gibo(): "KM": [6.5], "DT": ["2020-06-14"], } == root.properties - assert "pp" == root.children[0].get_property("B") + assert "pd" == root.children[0].get_property("B") From 237af03a586f7bec9751fb3462a7f4de346c092d Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 28 Jun 2020 19:23:39 +0200 Subject: [PATCH 40/77] placement fix/strings --- katrain/core/game.py | 5 ++--- katrain/gui.kv | 2 +- katrain/gui/badukpan.py | 5 +++-- .../i18n/locales/cn/LC_MESSAGES/katrain.mo | Bin 11123 -> 11126 bytes .../i18n/locales/cn/LC_MESSAGES/katrain.po | 2 +- .../i18n/locales/de/LC_MESSAGES/katrain.mo | Bin 12947 -> 12949 bytes .../i18n/locales/de/LC_MESSAGES/katrain.po | 4 ++-- .../i18n/locales/en/LC_MESSAGES/katrain.mo | Bin 12039 -> 12041 bytes .../i18n/locales/en/LC_MESSAGES/katrain.po | 4 ++-- .../i18n/locales/fr/LC_MESSAGES/katrain.mo | Bin 13557 -> 13559 bytes .../i18n/locales/fr/LC_MESSAGES/katrain.po | 4 ++-- .../i18n/locales/ko/LC_MESSAGES/katrain.mo | Bin 13789 -> 13788 bytes .../i18n/locales/ko/LC_MESSAGES/katrain.po | 2 +- .../i18n/locales/ru/LC_MESSAGES/katrain.mo | Bin 17154 -> 17156 bytes .../i18n/locales/ru/LC_MESSAGES/katrain.po | 4 ++-- katrain/img/off.png | Bin 5770 -> 0 bytes katrain/img/play.png | Bin 0 -> 5546 bytes 17 files changed, 16 insertions(+), 16 deletions(-) delete mode 100644 katrain/img/off.png create mode 100644 katrain/img/play.png diff --git a/katrain/core/game.py b/katrain/core/game.py index ef5c09f..77f6ff5 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -299,10 +299,9 @@ class Game: if mode == "extra": if kwargs.get("continuous", False): - visits = max(engine.config["fast_visits"], math.ceil(cn.analysis_visits_requested * 1.25)) + visits = max(engine.config["max_visits"], math.ceil(cn.analysis_visits_requested * 1.25)) else: - visits = cn.analysis_visits_requested + engine.config["fast_visits"] - + visits = cn.analysis_visits_requested + engine.config["max_visits"] self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS) cn.analyze(engine, visits=visits, priority=-1_000, time_limit=False) return diff --git a/katrain/gui.kv b/katrain/gui.kv index e65d14b..87b6a2b 100644 --- a/katrain/gui.kv +++ b/katrain/gui.kv @@ -731,7 +731,7 @@ on_action: root.open_game_analysis_popup() AnalysisMenuItem: text: i18n._("analysis:continuous") - icon: 'img/off.png' + icon: 'img/play.png' shortcut: 'space' on_action: root.katrain.toggle_continuous_analysis() AnalysisMenuItem: diff --git a/katrain/gui/badukpan.py b/katrain/gui/badukpan.py index f88953b..a9d575f 100644 --- a/katrain/gui/badukpan.py +++ b/katrain/gui/badukpan.py @@ -256,14 +256,15 @@ class BadukPanWidget(Widget): evalsize = 0 else: evalsize = min(1, max(0, realized_points_lost / points_lost)) - for m in node.move_with_placements: + placements = node.placements + for m in node.moves + placements: if has_stone.get(m.coords) and not drawn_stone.get(m.coords): # skip captures, last only for move_eval_on = show_dots_for.get(m.player) and (i < show_n_eval or full_eval_on) if move_eval_on and points_lost is not None: evalcol = self.eval_color(points_lost, show_dots_for_class) else: evalcol = None - inner = STONE_COLORS[m.opponent] if i == 0 else None + inner = STONE_COLORS[m.opponent] if i == 0 and not m in placements else None drawn_stone[m.coords] = m.player self.draw_stone( m.coords[0], diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo index 13fcb958bcd3abbbdff44a5cc3db627d59df13d2..f47d12d23d2ffb380d031e5d360ccc5aa9e2f289 100644 GIT binary patch delta 939 zcmXZaPe@cz6vy#ni)oZn3zV@!CdbK43z3L2S{VI>C>J){oN$zI3Q?mB>OlnU`m?B& z1Z{*?6|)Lj6zy7tL_ra?Xc3gPus=`{eSf|mJovnG?!D)pd*53s{VYA0S(&s&L{}lA z0S8g>5nPL7xEg2eRgd4oRpjqG{}}6upJN7Jdi<^9PmaIYzqYz&Boa1?0Lmli1YU5X^1N^{dT$6OA6FTfp%rI~i zRromS6P$E@()n}t3aX)-_Ab(MJj5)%M%DR%s=Mg;yG`mgr=2&VCgyAr)pM&c$^z8!UwJ*a&Cum)K)FzE$R4m5C7#Gwuvq(NMISa1lM^%&Gh zf+j+v5J3*<$Eb~8q7LvKS7L3}nornZcVU5n zqo~4ts84Xp`61`a_ByJeJN5z6bBtppzCzV`kE%Q8_?yiZcv0ue+wzG;+lcDC1GVrF zYNKPQlbmpT)!sy1{e9GiPf>NB;}U$0s`J)a2R#<$DL2?AnGR0<7LcH z^Z$6fg6g%z)u?;}>L%Mz|56v~puHHA^HqE6>-y4d$#8P+%1ByDhs$R#q@{smD4pzX H>a6?^yfR|Y diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po index d1f3a99..6af9d59 100644 --- a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po @@ -579,7 +579,7 @@ msgstr "Re-analyzing entire game using {visits} visits" #. TODO - Analyze every node in the game more deeply msgid "analysis:game" -msgstr "Re-analyze entire game" +msgstr "Deeper full game analysis" #. TODO - Keep improving current position analysis when there is nothing else #. to do diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo index d7b8517d2e1367875e5d69431a2874e50ba4e17b..0f8649a8162792eb25e8e9e27b0a58dfdb8edfd0 100644 GIT binary patch delta 982 zcmX}qPe@cz6vy#17MX9xKjToRk&f*IV#dQ32B!8F$4048t{(|7}uco*Y1k8HJvsQ=cY^|yG4`3J1VFR1umGZySE z{A59ienlVZyTgLoP=P!=idn41k!XD!+nG<{VJxBwSwtmxj7_+TDtH6W;5Ht?loyzF zFzEKonsFFAF^{cS#0Ff!tN0SnV(g^ZCG5jSyoXs_KqY*Qr|~nYa|cd^g*Rdia~GA* z3mB+?%h-#Pcnlv!^D?UP9dt3)6SlA&705#+?8k#Rgbt3N;@rZ$0#OHAKn`zDPzMX% zGth=_sJF0-3H*y{slzein8I3YLv8Fqo!}zY;V3F$9*^TZD$Wur?sHU!-k|pXLEc1Q z4i{3zLK5{znoy6V74;@MA}?S+^HJ1)MO2^?QmZW^O|p-ug8v~oEZ!Twh4YcuPzM`B zhx~S%L5zjz$Qjff&!Sqoj4J3ED)1`mWUo*i*+d1bpgOsQC-4V8#U#Ph`gK&iPpA%6 zw644T%|LG;buMgG7KfN$FYh|v(=~2xI#+lwH#JwPbPcv#@%K zW)6Nv4}V|+<7^r*jkVZ^H8_aPcpZ~Cjnx<+M{NQ1zj72`$7Ad_u@XO`@_+FxI1KzI z&_rM_`XPQUEGUgi`5VjM@K_-#DHeiCC?L={p(U9gO5a1~YX26o{$Jb}sf!0arG zOuJblj$j*3;3+I(9X`e(e2G1{kNw!wVV1&KyomFt3t!^}{D7KV#rd%CI;>*fg1XNO zSg3#j>_#6?;{9m<998*uba5XwLbo$ab<1ehif2fhhXrmgF7{@f~UVs7I1Qb=ZV@6Q?6DV;}o*)c=a8M2kqRwt@`D-k}QqkKAEZ-Qiof6nPER*e!Ip z-*PM}2;?IRsBb)j8tDqEpeLxrtEkRip(gSMm2e9+$!%=KU3`QIl4ps|`2&&S>lUg}YO;zB`$l_RCrC Gj&lG3C2nH? diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po index d13727a..f4d0c02 100644 --- a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po @@ -622,12 +622,12 @@ msgstr "Re-analyzing entire game using {visits} visits" #. TODO - Analyze every node in the game more deeply msgid "analysis:game" -msgstr "Re-analyze entire game" +msgstr "Deeper full game analysis" #. TODO - Keep improving current position analysis when there is nothing else #. to do msgid "analysis:continuous" -msgstr "Continuously analyze" +msgstr "Continuous analysis" #. TODO - ai which handles handicap games better msgid "ai:handicap" diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo index 139e259a87e26290dfe9bebecb02af1287a46ca4..a81cc380d364f8e748e10a3e264287885ad586f1 100644 GIT binary patch delta 982 zcmX}qPe@cz6vy$?ADcfqX+=#lMhv1en&ZSke@M(gN@&+YiXcV@eTpzjG|FU~M-)Vh z7GV*HZiJ#1QZ(cuH%U>@DvTCRn;=nIwQwPU3%@_T1jBspJ@3AA&pr1&$UV%x=?VCU z%8@BU{%Vj8Y8H943)Um#m}G$>_TmH1NWncI>4CoKC0j+*n+Q7?=7c% zGxoA|7l=2TH8MYiT5uhesEB9r9`dsV{?6eFssqQ#n#C-tqy3n{66ydiFoJI}gdb2H z`06vLXYdo#xPhv)@dQcn0&1Q^6*PfCoODj33Ve=g@jKjy?@=9FbkDz|I{X{CUE4$z z<~OwNv@C;4d>%vCjY@RI#jm06zRx`$K^0WOFh0g3IE^a2j5_fW>PmhgO}4+Nf}@<4 zf_+Od*i9gfRd^D$@o9`=2Wq1pq}w)%TK5dAamK}8p{{ZXb%1{^zNc-ckThz2JF0+g z46?snWw47tuQQLj%YIZ#pJD)~P#e#pPW%!hSa$J6R0qG{Vf==-an;Rx`5=^U9QDoI z!P;XC9x>3}%wP@9<0br9{u5q{hZ6bGd|{$ET%4>lb;d4w-c9dTVsN}rNDSpiy#G(j IndEYC3vD!S?f?J) delta 980 zcmXZaPe@cz6vy#nW%Fi6)BdQVNj#((b8@nOP$@ig3R!+h>J@4kD_J@?(u-^`>Br4y4TDZ;Sk7Fb88PrA>aR&~g4lwGxiz@grw&5)5y)Uhi z8GG5POQa5%?I%8pns5QN&~-e4caWbg@^=b*2`DQe?s)QMkUHO{;I5~_pmuns@s82)nc03U?byMg*< z#;~TD!2<@mn<=cq1w4yyN`Di7ni7R^KNtxLi!b8;SUVqE|y}jmDpH=0c^xW7;QD~{);3B zRUCAn3b~0ve1J;)7{fS$C74GAnnrS%m#BNosKBeJLVuwO_=Eh+4iDXLNf>hy`xCAg z@F@rN986&o7Ome=3D+@-|L_7f(B@^li|Tw9mEavJ@G9~%TRhZxH0d>zKp*=~)Ookd zqMAhyw%|P^hk1d`xPYqoCn})tjMrfbHR5hmpzEl>efIn|ma)H&n&=>&!W^E(xAxp! zW1&&5qXKQ9I@-ok40F32>#zo6sEJ%b72bzobWjOLun}{p{})jOd_Ya;8y>~KNIuv2 z&Uu}bpbDtAHlPwTp+3ScJdORRf}i6t%wrViQ4?CmBe;p`IFRxt6T^e-lh$?&k>7N& zIKYc*)?Vyje*-nr9BR~gyDy*$okMl_3KjT0>ZARPhw&@E#ue0Lo}Bjze}>vmN_}(% zdr(9L_=uPA%i`})Wh59$52dpcqnXhef4sN$s^bhhBaugA*=%GWJ?Q*@yZA6(2<`#S Cf^F~s delta 980 zcmXZa%}Z2K7{~D^X`1Hd`&-6HO)~1#cq`K)m7<_RL;}-=ix396)sRVqQAThoW^ZAO zAR3}Y5EuzAqEuVW{s4o7utbO!et+Eqm;1Tr%$etnvsv6IE{-K#qnWKAG;6?Q z-fSPHu^tDo882WpMp%P0n8p(B!566WAMg-w#(fAA3`>o z*yu$SGK49dKqbD4m3SL>VF?xJ5t756qRuU&035XMGH{-|zU) zh|k~w97l54Lu|)IRK?#=0V|Hg9d@He+>Z)$4i$LV?~hr1GKUc)wY*o`m!zWd6D zM!AX#^b6I|CRSm<=^AXo-Izm7U-MKX+pR%@APdu3%CRm5w9t_BDeSB== zhqK=Ec%1bhYNQS|>XKj2p$dJ1>hKvV@N3kieT%jD9?SR{HJM^>T=*T-`o7eqoAVoG zRDd^l65lQT2pTfM)NH0OU6`7^8_g&3gUuJC&@VI5^i1()l(|y49xYwYKTd4{Tvlvd diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po index 03336b8..99fde04 100644 --- a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po @@ -656,12 +656,12 @@ msgstr "Re-analyzing entire game using {visits} visits" #. TODO - Analyze every node in the game more deeply msgid "analysis:game" -msgstr "Re-analyze entire game" +msgstr "Deeper full game analysis" #. TODO - Keep improving current position analysis when there is nothing else #. to do msgid "analysis:continuous" -msgstr "Continuously analyze" +msgstr "Continuous analysis" #. TODO - ai which handles handicap games better msgid "ai:handicap" diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo index c3171596dfdd905fd534b947ddd23dc147ff7ab1..97a171b9cb5bd85d89898e47333fbad2fbc31162 100644 GIT binary patch delta 952 zcmXZaPe@cz6vy!cLOL^z%{2Z?Q%7i{GmTl97-G2+5k%6Z1S1CiBm5T>O}w@(bfN7g zkaW{Bkw9FztiXi}7m8&<7=bH6!4}c?*Bp2=pL@=|ocr#%@7u(|#K*znAYo>sO=gvt z!z8}-_!CwTZ(|&PqSl{d1BO%lFpVK>!#mjJ`DYjcpI9RiV`9Vo z;tTgZKEf*IPf-OUX|o!9-HH@6IZYskMJrsX3QR92Wo>?$YU$~MR5l&gwzkU4lx~m9-#Ev;ovr1_1*VSV9$kkNOrq zpzd@Fb;n;ZhP$ZwW30!&sFT&T6bhzLFZ=-2xyKmCaqPz&#&8?iH?Z#vRNw^l0vD)_ z?RKHHVblp~QCHQ5S~rclvgddo=dlYvqc#ZhCH7zq>HtsNDU1`p#3=jQJOi~Z?=IsI z@hWN~yIZi>EkzY9M|CdgamM3ztR~-$i#Uu4JVPBU)LNKNVDvhP3r{ delta 953 zcmXZaPe@cz6vy#12$u7loXPP|W{yUyacs&`OfalX89{-%3t|jI8wnaHm{OlE-6%xu zCWv&?E)s%h<+219Em~Ak385`q2@1BT@6S2#c%OUDy`1yzz3+Q|J^yCtP?$8cv9wtO zPGKuP_xK&w6R%+ce`77~VH*bRW>M_GD(t~?c-ixhFh)Gv#kQu?9y_>kFv$Gk6M19_T*NE5fk!cY+N>Y@Pz|P$#};|T@H1B9Pmj0VzaazdtSU=W zJc-)T4eZ8I)Q6PZ52&4fb$_86Y+(iNdVU{ub&)e>$FLD~Wu3?&+7Rk0!x;uD@C;S> z73yDjjk?nn)E%#49KWFEH!+3(P&-SVEf>t7KDZxsa6{3kk>icS<;bN6!-#mA$|Ptv*IKOI?S!Tu&DCj|9^+eF=*;M(W#xlG%!*&UP^x$7RcLYRg$t$rE!&rzW-o<%z;WyOpA7d6e>O}Iu|uAiWfypV@E*p3E1z_U1n75EIV;d>0@o}E_* zO_44Nk)Q}o$_94e53EKXtFB-_&fs$##S={6NQ2erA%@8R;YDm{6sf^3JcHAy8=6CH zY#B>2WfHUy?Bh+maNTO47u(5~kg3aeq$hFRuy%SMb%c-5jl*_*8udr7P>*a8%Ww;I z{Q}fL>g| zKHNpUTcx+scfyo=1ln;o>ZoFvhm%->FHm>5jCwh@QQynHZPkmAsf!PL@iFR#c5o06 z@G`bGTMnR$dIN3z|B@`qQ6k@))AIpA}HZNh@}J5XNNKJ#njKdhZVWr$kRwXnRt?T8}h$%hoaH( W=!7>m8IOC1BcsvNr>Q@Fqu@Uu;e3Js delta 982 zcmXZbK}Zx~6u|MPkja{ryX(rjm7}>TLbltsrj==?vMGX^OL&l4X$;BTg9@gS0}>KM zUN(fVix&k!EG0Y?bgZKwLZH$?f`T9~;=xP*w=<{T_ukC6^WOWueL0&oGn@6H_q62| zkx*Eq1Y7YwnplNv=))fvz<=n+ylWy?F^H8I$3lF89hk-<{D~es#vIIxh@3$;p2w1i zSeZnKgl=fSAjZ*+Q%-&kuM;n!7P{-?PcTI6;bAVepf=Qr=WqZka0F}d9X`Y@C$5TG zB0VG?MMWr5)-i^^uo`RFbqV`$2A^XRPjD26ZrFwHV+-*i7Gt!Iz1W3k(L!x#4z;jF zyo?|9K@*D|yp6tkyMPDSM*JR`x*Q-iN#RYq(+<=T_M#t6CvTyC=oRXbEnqorqVC_r zW^~`OCvL@A=tz^OBYcAaoJT#Yb@bp@EX8dM;E@v-P_>shg4%Hmbz{7v@1pbYiH^{77(!+d;<`d$ikQsYRI@*4l* zchtM}>8?G|->7%%2!j_`7|kNAr42J@hl9im8-HBg6|Uh4BauoBPfVKWd*Qdf9`lM5 U8fI#2Xv8$0CX(jHVc1*nACM|~?*IS* diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po index 1589bd3..22a574d 100644 --- a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po @@ -619,12 +619,12 @@ msgstr "Re-analyzing entire game using {visits} visits" #. TODO - Analyze every node in the game more deeply msgid "analysis:game" -msgstr "Re-analyze entire game" +msgstr "Deeper full game analysis" #. TODO - Keep improving current position analysis when there is nothing else #. to do msgid "analysis:continuous" -msgstr "Continuously analyze" +msgstr "Continuous analysis" #. TODO - ai which handles handicap games better msgid "ai:handicap" diff --git a/katrain/img/off.png b/katrain/img/off.png deleted file mode 100644 index c6b35b5dd2e607e451d61bd4936ba42bc1215b8d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5770 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 A00000 diff --git a/katrain/img/play.png b/katrain/img/play.png new file mode 100644 index 0000000000000000000000000000000000000000..622e7408a609e61a2701340af981c323331d18a9 GIT binary patch literal 5546 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 literal 0 HcmV?d00001 From 1fabb6fc96198c86c1a40588e7980d21666b24a3 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 28 Jun 2020 22:16:15 +0200 Subject: [PATCH 41/77] cb toggle --- katrain/core/ai.py | 7 ++++--- katrain/gui.kv | 8 ++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/katrain/core/ai.py b/katrain/core/ai.py index ab7abc4..37f62ce 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -63,11 +63,12 @@ def policy_weighted_move(policy_moves, lower_bound, weaken_fac): ] if weighted_coords: top = weighted_selection_without_replacement(weighted_coords, 1)[0] - ai_thoughts = f"Playing policy-weighted random move {top[2].gtp()} ({top[0]:.1%}) from {len(weighted_coords)} moves above lower_bound of {lower_bound:.1%}." + move = top[2] + ai_thoughts = f"Playing policy-weighted random move {move.gtp()} ({top[0]:.1%}) from {len(weighted_coords)} moves above lower_bound of {lower_bound:.1%}." else: - top = policy_moves[0] + move = policy_moves[0][1] ai_thoughts = f"Playing top policy move because no non-pass move > above lower_bound of {lower_bound:.1%}." - return top[2], ai_thoughts + return move, ai_thoughts def generate_influence_territory_weights(ai_mode, ai_settings, policy_grid, size): diff --git a/katrain/gui.kv b/katrain/gui.kv index 87b6a2b..cd0ae07 100644 --- a/katrain/gui.kv +++ b/katrain/gui.kv @@ -164,11 +164,6 @@ size: image.size font_size: root.min_size * 1.2 / (1 + 1 * len(self.text)) -: - color: WHITE - halign: 'center' - valign: 'middle' - size: self.texture_size # UI components @@ -673,7 +668,7 @@ size_hint: None, 0.7 pos_hint: {'center_y':0.5} checkbox: checkbox - Label: + ClickableLabel: id: label text: root.text halign: 'center' @@ -682,6 +677,7 @@ color: checkbox.selected_color if checkbox.active else checkbox.unselected_color size_hint_x: None width: self.texture_size[0] + 4 + on_left_press: checkbox.active = not checkbox.active MDCheckbox: id: checkbox size_hint: None,0.5 From 48e216fc1693e7220f90053ef8587c83bae5edf1 Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 28 Jun 2020 23:31:18 +0200 Subject: [PATCH 42/77] pixel fiddling --- katrain/__main__.py | 2 +- katrain/gui.kv | 17 ++++++++++++----- katrain/gui/controlspanel.py | 3 ++- katrain/gui/popups.py | 1 + 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/katrain/__main__.py b/katrain/__main__.py index 18a78c3..a35b66e 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -2,7 +2,7 @@ # first, logging level lower import os -os.environ["KCFG_KIVY_LOG_LEVEL"] = os.environ.get("KCFG_KIVY_LOG_LEVEL", "warning") +os.environ["KCFG_KIVY_LOG_LEVEL"] = os.environ.get("KCFG_KIVY_LOG_LEVEL", "trace") if "KIVY_AUDIO" not in os.environ: os.environ["KIVY_AUDIO"] = "sdl2" # seems to be most stable / some players hard crash diff --git a/katrain/gui.kv b/katrain/gui.kv index cd0ae07..6485d6c 100644 --- a/katrain/gui.kv +++ b/katrain/gui.kv @@ -53,7 +53,7 @@ Color: rgba: root.outline_color Line: - rounded_rectangle: (*self.pos,self.width,self.height,root.background_radius,root.background_radius,root.background_radius,root.background_radius,max(1,root.height)) + rounded_rectangle: (self.x + root.outline_width, self.y + root.outline_width, self.width - 2 * root.outline_width, self.height - 2*root.outline_width, root.background_radius,root.background_radius,root.background_radius,root.background_radius,max(1,root.height)) width: root.outline_width or 1 @@ -475,7 +475,7 @@ rgba: BOX_BACKGROUND_COLOR Line: width: 1 - points: (*self.pos,self.x+self.width,self.y) + points: (self.x,self.y+1,self.x+self.width,self.y+1) : @@ -512,6 +512,12 @@ id: timer_or_movetree size_hint_y: None height: root.timer_box_height + canvas.before: + Color: + rgba: RED + Line: + rectangle: (*self.pos,*self.size) + width: 1 BoxLayout: size_hint_x: 0.0001 if self.parent.mode != MODE_PLAY else 1 opacity: 0 if self.parent.mode != MODE_PLAY else 1 @@ -590,10 +596,11 @@ orientation: 'vertical' ScrollableLabel: id: status + error: False size_hint: 1, None height: min(self.parent.height*0.66,self.label.texture_size[1]) - outline_color: RED - background_color: DARKRED + outline_color: RED if self.error else WHITE + background_color: LIGHTER_BACKGROUND_COLOR # DARKRED ScrollableLabel: id: info detailed: False @@ -803,7 +810,7 @@ icon_size: self.height * 0.75, self.height * 0.75 -: +: background_color: BACKGROUND_COLOR font_size: self.height * 0.5 height: self.parent.item_height or 50 diff --git a/katrain/gui/controlspanel.py b/katrain/gui/controlspanel.py index 8905b56..ad69419 100644 --- a/katrain/gui/controlspanel.py +++ b/katrain/gui/controlspanel.py @@ -82,9 +82,10 @@ class ControlsPanel(BoxLayout): def set_status(self, msg, status_type, at_node=None): at_node = at_node or self.katrain and self.katrain.game and self.katrain.game.current_node - if at_node != self.status_state[2] or int(status_type) >= int(self.status_state[1]): + if at_node != self.status_state[2] or int(status_type) >= int(self.status_state[1]) or msg=="": self.status_state = (msg, status_type, at_node) self.status.text = msg + self.status.error = status_type==STATUS_ERROR Clock.schedule_once(self.update_evaluation, 0) # handles showing completed analysis and score graph diff --git a/katrain/gui/popups.py b/katrain/gui/popups.py index c37ed93..d2dc8fc 100644 --- a/katrain/gui/popups.py +++ b/katrain/gui/popups.py @@ -447,6 +447,7 @@ class ConfigPopup(QuickConfigGui): if detected_restart: def restart_engine(_dt): + self.katrain.controls.set_status("", STATUS_INFO) self.katrain.log(f"Restarting Engine after {detected_restart} settings change") self.katrain.controls.set_status(i18n._("restarting engine"), STATUS_INFO) From 44d927abdaa8f795d140ffc584bd49899bdd35bf Mon Sep 17 00:00:00 2001 From: Sander Land Date: Mon, 29 Jun 2020 16:26:27 +0200 Subject: [PATCH 43/77] forgot2committhepixels --- katrain/gui.kv | 54 +++++++++++++------------ katrain/gui/badukpan.py | 4 +- katrain/gui/controlspanel.py | 6 +-- katrain/gui/kivyutils.py | 15 ++++--- katrain/gui/popups.py | 4 +- katrain/gui/widgets/graph.py | 4 +- katrain/gui/widgets/selection_slider.py | 6 +-- 7 files changed, 45 insertions(+), 48 deletions(-) diff --git a/katrain/gui.kv b/katrain/gui.kv index 6485d6c..f6e8469 100644 --- a/katrain/gui.kv +++ b/katrain/gui.kv @@ -32,6 +32,8 @@ #:set CP_PADDING dp(6) #:set NOTES_FONT_SIZE dp(18) +#:set EPSILON 1e-9 + # for sizing help #