From d6be82ecba284a47cf9a80776dddeed1449e5b8c Mon Sep 17 00:00:00 2001 From: Sander Land Date: Sun, 28 Jun 2020 14:41:10 +0200 Subject: [PATCH] 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") - - -