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 67fba6b..13fcb95 100644 Binary files a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo differ 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 d996963..d7b8517 100644 Binary files a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo differ 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 d7e6040..139e259 100644 Binary files a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo differ 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 cef9e99..3d5b857 100644 Binary files a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po index eea8dd4..d7b03b5 100644 --- a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po @@ -627,3 +627,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/fr/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo index 6a4ecda..4e364b4 100644 Binary files a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po index f5388c0..03336b8 100644 --- a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po @@ -674,3 +674,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/ko/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo index ccde1e7..c317159 100644 Binary files a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo differ 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 51bb52b..7b84b28 100644 Binary files a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo differ 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") - - -