Exemple #1
0
    def __init__(self, katrain, config):
        self.katrain = katrain
        self.queries = {}  # outstanding query id -> start time and callback
        self.config = config
        self.query_counter = 0
        self.katago_process = None
        self.base_priority = 0
        self.override_settings = {
            "reportAnalysisWinratesAs": "BLACK"
        }  # force these settings
        self.analysis_thread = None
        self.stderr_thread = None
        self.write_stdin_thread = None
        self.shell = False
        self.write_queue = queue.Queue()

        exe = config.get("katago", "").strip()
        if config.get("altcommand", ""):
            self.command = config["altcommand"]
            self.shell = True
        else:
            if not exe:
                if platform == "win":
                    exe = "katrain/KataGo/katago.exe"
                elif platform == "linux":
                    exe = "katrain/KataGo/katago"
                else:  # e.g. MacOS after brewing
                    exe = "katago"

            model = find_package_resource(config["model"])
            cfg = find_package_resource(config["config"])
            if exe.startswith("katrain"):
                exe = find_package_resource(exe)

            exepath, exename = os.path.split(exe)
            if exepath and not os.path.isfile(exe):
                self.katrain.log(
                    i18n._("Kata exe not found").format(exe=exe), OUTPUT_ERROR)
                return  # don't start
            elif not exepath and not any(
                    os.path.isfile(os.path.join(path, exe))
                    for path in os.environ.get("PATH", "").split(os.pathsep)):
                self.katrain.log(
                    i18n._("Kata exe not found in path").format(exe=exe),
                    OUTPUT_ERROR)
                return  # don't start
            elif not os.path.isfile(model):
                self.katrain.log(
                    i18n._("Kata model not found").format(model=model),
                    OUTPUT_ERROR)
                return  # don't start
            elif not os.path.isfile(cfg):
                self.katrain.log(
                    i18n._("Kata config not found").format(config=cfg),
                    OUTPUT_ERROR)
                return  # don't start
            self.command = shlex.split(
                f'"{exe}" analysis -model "{model}" -config "{cfg}" -analysis-threads {config["threads"]}'
            )
        self.start()
Exemple #2
0
 def build_ai_options(self, *_args):
     strategy = self.ai_select.selected[1]
     mode_settings = self.katrain.config(f"ai/{strategy}")
     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] not in AI_KEY_PROPERTIES, kv[0])):
         self.options_grid.add_widget(DescriptionLabel(text=k, size_hint_x=0.275))
         if k in AI_OPTION_VALUES:
             values = AI_OPTION_VALUES[k]
             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]
                 else:  # just numbers
                     fixed_values = [(v, str(v)) for v in values]
                 widget = LabelledSelectionSlider(
                     values=fixed_values, input_property=f"ai/{strategy}/{k}", key_option=(k in AI_KEY_PROPERTIES)
                 )
                 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(
                 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(size_hint_x=None))
     Clock.schedule_once(self.estimate_rank_from_options)
Exemple #3
0
    def start(self):
        try:
            self.katrain.log(f"Starting KataGo with {self.command}",
                             OUTPUT_DEBUG)

            self.katago_process = subprocess.Popen(self.command,
                                                   stdin=subprocess.PIPE,
                                                   stdout=subprocess.PIPE,
                                                   stderr=subprocess.PIPE,
                                                   shell=True)
        except (FileNotFoundError, PermissionError, OSError) as e:
            if not self.config["katago"].strip():
                self.katrain.log(
                    i18n._("Starting default Kata failed").format(
                        command=self.command, error=e),
                    OUTPUT_ERROR,
                )
            else:
                self.katrain.log(
                    i18n._("Starting Kata failed").format(command=self.command,
                                                          error=e),
                    OUTPUT_ERROR,
                )
        self.analysis_thread = threading.Thread(
            target=self._analysis_read_thread, daemon=True).start()
        self.stderr_thread = threading.Thread(target=self._read_stderr_thread,
                                              daemon=True).start()
Exemple #4
0
    def get_engine_path(self, exe):
        if not exe:
            if kivy_platform == "win":
                exe = "katrain/KataGo/katago.exe"
            elif kivy_platform == "linux":
                exe = "katrain/KataGo/katago"
            else:
                exe = find_package_resource("katrain/KataGo/katago-osx")  # github actions built
                if not os.path.isfile(exe) or "arm64" in platform.version().lower():
                    exe = "katago"  # e.g. MacOS after brewing
        if exe.startswith("katrain"):
            exe = find_package_resource(exe)
        exepath, exename = os.path.split(exe)

        if exepath and not os.path.isfile(exe):
            self.katrain.log(i18n._("Kata exe not found").format(exe=exe), OUTPUT_ERROR)
            return None
        elif not exepath:
            paths = os.getenv("PATH", ".").split(os.pathsep) + ["/opt/homebrew/bin/"]
            exe_with_paths = [os.path.join(path, exe) for path in paths if os.path.isfile(os.path.join(path, exe))]
            if not exe_with_paths:
                self.katrain.log(i18n._("Kata exe not found in path").format(exe=exe), OUTPUT_ERROR)
                return None
            exe = exe_with_paths[0]
        return exe
Exemple #5
0
    def __init__(self, katrain, config):
        super().__init__(katrain, config)

        self.queries = {}  # outstanding query id -> start time and callback
        self.query_counter = 0
        self.katago_process = None
        self.base_priority = 0
        self.override_settings = {"reportAnalysisWinratesAs": "BLACK"}  # force these settings
        self.analysis_thread = None
        self.stderr_thread = None
        self.write_stdin_thread = None
        self.shell = False
        self.write_queue = queue.Queue()
        self.thread_lock = threading.Lock()
        exe = config.get("katago", "").strip()
        if config.get("altcommand", ""):
            self.command = config["altcommand"]
            self.shell = True
        else:
            model = find_package_resource(config["model"])
            cfg = find_package_resource(config["config"])
            exe = self.get_engine_path(config["katago"])
            if not exe:
                return
            if not os.path.isfile(model):
                self.katrain.log(i18n._("Kata model not found").format(model=model), OUTPUT_ERROR)
                return  # don't start
            if not os.path.isfile(cfg):
                self.katrain.log(i18n._("Kata config not found").format(config=cfg), OUTPUT_ERROR)
                return  # don't start
            self.command = shlex.split(
                f'"{exe}" analysis -model "{model}" -config "{cfg}" -analysis-threads {config["threads"]} -override-config "homeDataDir={os.path.expanduser(DATA_FOLDER)}"'
            )
        self.start()
Exemple #6
0
 def check_alive(self, os_error="", exception_if_dead=False):
     ok = self.katago_process and self.katago_process.poll() is None
     if not ok and exception_if_dead:
         if self.katago_process:
             os_error += f"status {self.katago_process and self.katago_process.poll()}"
             died_msg = i18n._("Engine died unexpectedly").format(error=os_error)
             self.katrain.log(died_msg, OUTPUT_ERROR)
             self.katago_process = None
         else:
             died_msg = i18n._("Engine died unexpectedly").format(error=os_error)
         raise EngineDiedException(died_msg)
     return ok
Exemple #7
0
    def __init__(self, katrain, config, override_command=None):
        self.katrain = katrain
        self.queries = {}  # outstanding query id -> start time and callback
        self.config = config
        self.query_counter = 0
        self.katago_process = None
        self.base_priority = 0
        self.override_settings = {}  # mainly for bot scripts to hook into
        self._lock = threading.Lock()
        self.analysis_thread = None
        self.stderr_thread = None

        if override_command:
            self.command = override_command
        else:
            exe = config["katago"].strip()
            if not exe:
                if platform == "win":
                    exe = "katrain/KataGo/katago.exe"
                elif platform == "linux":
                    exe = "katrain/KataGo/katago"
                else:  # e.g. MacOS after brewing
                    exe = "katago"

            model = find_package_resource(config["model"])
            cfg = find_package_resource(config["config"])
            if exe.startswith("katrain"):
                exe = find_package_resource(exe)

            exepath, exename = os.path.split(exe)
            if exepath and not os.path.isfile(exe):
                self.katrain.log(
                    i18n._("Kata exe not found").format(exe=exe), OUTPUT_ERROR)
                return  # don't start
            elif not exepath and not any(
                    os.path.isfile(os.path.join(path, exe))
                    for path in os.environ.get("PATH", "").split(os.pathsep)):
                self.katrain.log(
                    i18n._("Kata exe not found in path").format(exe=exe),
                    OUTPUT_ERROR)
                return  # don't start
            elif not os.path.isfile(model):
                self.katrain.log(
                    i18n._("Kata model not found").format(model=model),
                    OUTPUT_ERROR)
                return  # don't start
            elif not os.path.isfile(cfg):
                self.katrain.log(
                    i18n._("Kata config not found").format(config=cfg),
                    OUTPUT_ERROR)
                return  # don't start
            self.command = f'"{exe}" analysis -model "{model}" -config "{cfg}" -analysis-threads {config["threads"]}'
        self.start()
Exemple #8
0
    def check_katas(self, *args):
        def find_description(path):
            file = os.path.split(path)[1].replace(".exe", "")
            file_to_desc = {
                re.match(r".*/([^/]+)", kg)[1].replace(".zip", ""): desc
                for _, kgs in self.KATAGOS.items() for desc, kg in kgs.items()
            }
            if file in file_to_desc:
                return f"{file_to_desc[file]}  -  {path}"
            else:
                return path

        done = set()
        kata_files = []
        for path in self.katago_paths + [self.katago_path.text]:
            path = path.rstrip("/\\")
            if path.startswith("katrain"):
                path = path.replace("katrain", PATHS["PACKAGE"].rstrip("/\\"),
                                    1)
            path = os.path.expanduser(path)
            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)
            files = [
                f.replace("/", os.path.sep).replace(PATHS["PACKAGE"],
                                                    "katrain")
                for ftype in ["katago*"]
                for f in glob.glob(slashpath + "/" + ftype)
                if os.path.isfile(f) and not f.endswith(".zip")
            ]
            if files and path not in self.paths:
                self.paths.append(
                    path)  # persistent on paths with models found
            kata_files += files

        kata_files = sorted(
            [(path, find_description(path)) for path in kata_files],
            key=lambda f: ("bs29" in f[0]) * 0.1 - (f[0] != f[1]),
        )
        katas_available_msg = i18n._("katago binaries available").format(
            num=len(kata_files))
        self.katago_files.values = [
            katas_available_msg,
            i18n._("default katago option")
        ] + [desc for path, desc in kata_files]
        self.katago_files.value_keys = ["", ""] + [
            path for path, desc in kata_files
        ]
        self.katago_files.text = katas_available_msg
Exemple #9
0
 def set_insert_mode(self, mode):
     if mode == "toggle":
         mode = not self.insert_mode
     if mode == self.insert_mode:
         return
     self.insert_mode = mode
     if mode:
         children = self.current_node.ordered_children
         if not children:
             self.insert_mode = False
         else:
             self.insert_after = self.current_node.ordered_children[0]
             self.katrain.controls.set_status(
                 i18n._("starting insert mode"), STATUS_INFO)
     else:
         copy_from_node = self.insert_after
         copy_to_node = self.current_node
         num_copied = 0
         if copy_to_node != self.insert_after.parent:
             above_insertion_root = self.insert_after.parent.nodes_from_root
             already_inserted_moves = [
                 n.move for n in copy_to_node.nodes_from_root
                 if n not in above_insertion_root and n.move
             ]
             try:
                 while True:
                     if copy_from_node.move not in already_inserted_moves:
                         for m in copy_from_node.move_with_placements:
                             self._validate_move_and_update_chains(m, True)
                         # this inserts
                         copy_to_node = GameNode(
                             parent=copy_to_node,
                             properties=copy.deepcopy(
                                 copy_from_node.properties))
                         num_copied += 1
                     if not copy_from_node.children:
                         break
                     copy_from_node = copy_from_node.ordered_children[0]
             except IllegalMoveException:
                 pass  # illegal move = stop
             self._calculate_groups()  # recalculate groups
             self.katrain.controls.set_status(
                 i18n._("ending insert mode").format(num_copied=num_copied),
                 STATUS_INFO)
             self.analyze_all_nodes(analyze_fast=True,
                                    even_if_present=False)
         else:
             self.katrain.controls.set_status("", STATUS_INFO)
     self.katrain.controls.move_tree.insert_node = self.insert_after if self.insert_mode else None
     self.katrain.controls.move_tree.redraw()
     self.katrain.update_state(redraw_board=True)
Exemple #10
0
    def analyze_extra(self, mode):
        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"]
            self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits))
            cn.analyze(engine, visits=visits, priority=-1_000, time_limit=False)
            return
        elif mode == "sweep":
            board_size_x, board_size_y = self.board_size
            if cn.analysis_ready:
                policy_grid = (
                    var_to_grid(self.current_node.policy, size=(board_size_x, board_size_y))
                    if self.current_node.policy
                    else None
                )
                analyze_moves = sorted(
                    [
                        Move(coords=(x, y), player=cn.next_player)
                        for x in range(board_size_x)
                        for y in range(board_size_y)
                        if (policy_grid is None and (x, y) not in stones) or policy_grid[y][x] >= 0
                    ],
                    key=lambda mv: -policy_grid[mv.coords[1]][mv.coords[0]],
                )
            else:
                analyze_moves = [
                    Move(coords=(x, y), player=cn.next_player)
                    for x in range(board_size_x)
                    for y in range(board_size_y)
                    if (x, y) not in stones
                ]
            visits = engine.config["fast_visits"]
            self.katrain.controls.set_status(i18n._("sweep analysis").format(visits=visits))
            priority = -1_000_000_000
        else:  # mode=='equalize':
            if not cn.analysis_ready:
                self.katrain.controls.set_status(i18n._("wait-before-equalize"), 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))
            priority = -1_000
        for move in analyze_moves:
            cn.analyze(
                engine, priority, visits=visits, refine_move=move, time_limit=False
            )  # explicitly requested so take as long as you need
Exemple #11
0
 def check_alive(self, os_error="", exception_if_dead=False, maybe_open_recovery=False):
     ok = self.katago_process and self.katago_process.poll() is None
     if not ok and exception_if_dead:
         if self.katago_process:
             code = self.katago_process and self.katago_process.poll()
             if code == 3221225781:
                 died_msg = i18n._("Engine missing DLL")
             else:
                 died_msg = i18n._("Engine died unexpectedly").format(error=f"{os_error} status {code}")
             if code != 1:  # deliberate exit
                 self.on_error(died_msg, code, allow_popup=maybe_open_recovery)
             self.katago_process = None  # return from threads
         else:
             self.katrain.log(i18n._("Engine died unexpectedly").format(error=os_error), OUTPUT_DEBUG)
     return ok
 def check_alive(self, os_error="", maybe_open_help=False):
     ok = self.katago_process and self.katago_process.poll() is None
     if not ok:
         if self.katago_process:
             code = self.katago_process and self.katago_process.poll()
             if code == 3221225781:
                 died_msg = i18n._("Engine missing DLL")
             else:
                 os_error += f"status {code}"
                 died_msg = i18n._("Engine died unexpectedly").format(
                     error=os_error)
             if code != 1 and not self.server_error:  # deliberate exit, already showed message?
                 self.katrain.log(died_msg, OUTPUT_ERROR)
             self.katago_process = None
     return ok
Exemple #13
0
 def check_models(self, *args):  # WIP
     done = set()
     model_files = []
     for path in self.paths + [self.model_path.text]:
         path = path.rstrip("/\\")
         if path.startswith("katrain"):
             path = path.replace("katrain", PATHS["PACKAGE"].rstrip("/\\"),
                                 1)
         path = os.path.expanduser(path)
         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)
         files = [
             f.replace("/", os.path.sep).replace(PATHS["PACKAGE"],
                                                 "katrain")
             for ftype in ["*.bin.gz", "*.txt.gz"]
             for f in glob.glob(slashpath + "/" + ftype)
         ]
         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
Exemple #14
0
 def start(self):
     with self.thread_lock:
         self.write_queue = queue.Queue()
         try:
             self.katrain.log(f"Starting KataGo with {self.command}", OUTPUT_DEBUG)
             startupinfo = None
             if hasattr(subprocess, "STARTUPINFO"):
                 startupinfo = subprocess.STARTUPINFO()
                 startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW  # stop command box popups on win/pyinstaller
             self.katago_process = subprocess.Popen(
                 self.command,
                 startupinfo=startupinfo,
                 stdin=subprocess.PIPE,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.PIPE,
                 shell=self.shell,
             )
         except (FileNotFoundError, PermissionError, OSError) as e:
             self.on_error(i18n._("Starting Kata failed").format(command=self.command, error=e), code="c")
             return  # don't start
         self.analysis_thread = threading.Thread(target=self._analysis_read_thread, daemon=True)
         self.stderr_thread = threading.Thread(target=self._read_stderr_thread, daemon=True)
         self.write_stdin_thread = threading.Thread(target=self._write_stdin_thread, daemon=True)
         self.analysis_thread.start()
         self.stderr_thread.start()
         self.write_stdin_thread.start()
Exemple #15
0
 def _message_loop_thread(self):
     while True:
         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:
                 self.log(
                     f"Message skipped as it is outdated (current game is {self.game.game_id}", OUTPUT_EXTRA_DEBUG
                 )
                 continue
             msg = msg.replace("-", "_")
             if self.contributing:
                 if msg not in [
                     "katago_contribute",
                     "redo",
                     "undo",
                     "update_state",
                     "save_game",
                     "find_mistake",
                 ]:
                     self.controls.set_status(
                         i18n._("gui-locked").format(action=msg), STATUS_INFO, check_level=False
                     )
                     continue
             fn = getattr(self, f"_do_{msg}")
             fn(*args, **kwargs)
             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()
Exemple #16
0
    def build_and_set_properties(self, *_args):
        theme = self.katrain.config("trainer/theme")
        undos = self.katrain.config("trainer/num_undo_prompts")
        thresholds = self.katrain.config("trainer/eval_thresholds")
        savesgfs = self.katrain.config("trainer/save_feedback")
        show_dots = self.katrain.config("trainer/show_dots")

        self.themes_spinner.value_refs = list(Theme.EVAL_COLORS.keys())
        self.options_grid.clear_widgets()

        for k in ["dot color", "point loss threshold", "num undos", "show dots", "save dots"]:
            self.options_grid.add_widget(DescriptionLabel(text=i18n._(k), font_name=i18n.font_name, font_size=dp(17)))

        for i, (color, threshold, undo, show_dot, savesgf) in enumerate(
            zip(Theme.EVAL_COLORS[theme], thresholds, undos, show_dots, savesgfs)
        ):
            self.add_option_widgets(
                [
                    BackgroundMixin(background_color=color, size_hint=[0.9, 0.9]),
                    LabelledFloatInput(text=str(threshold), input_property=f"trainer/eval_thresholds::{i}"),
                    LabelledFloatInput(text=str(undo), input_property=f"trainer/num_undo_prompts::{i}"),
                    LabelledCheckBox(text=str(show_dot), input_property=f"trainer/show_dots::{i}"),
                    LabelledCheckBox(text=str(savesgf), input_property=f"trainer/save_feedback::{i}"),
                ]
            )
        super().build_and_set_properties()
Exemple #17
0
    def write_sgf(
        self, path: str, trainer_config: Optional[Dict] = None,
    ):
        if trainer_config is None:
            trainer_config = self.katrain.config("trainer")
        save_feedback = trainer_config["save_feedback"]
        eval_thresholds = trainer_config["eval_thresholds"]

        def player_name(player_info):
            return f"{i18n._(player_info.player_type)} ({i18n._(player_info.player_subtype)})"

        player_names = {
            bw: re.sub(
                r"['<>:\"/\\|?*]", "", self.root.get_property("P" + bw) or player_name(self.katrain.players_info[bw])
            )
            for bw in "BW"
        }
        game_name = f"katrain_{player_names['B']} vs {player_names['W']} {self.game_id}"
        file_name = os.path.abspath(os.path.join(path, f"{game_name}.sgf"))
        os.makedirs(os.path.dirname(file_name), exist_ok=True)

        show_dots_for = {
            bw: trainer_config.get("eval_show_ai", True) or self.katrain.players_info[bw].human for bw in "BW"
        }
        sgf = self.root.sgf(
            save_comments_player=show_dots_for, save_comments_class=save_feedback, eval_thresholds=eval_thresholds
        )
        with open(file_name, "w", encoding="utf-8") as f:
            f.write(sgf)
        return i18n._("sgf written").format(file_name=file_name)
Exemple #18
0
    def load_sgf_from_clipboard(self):
        clipboard = Clipboard.paste()
        if not clipboard:
            self.controls.set_status("Ctrl-V pressed but clipboard is empty.",
                                     STATUS_INFO)
            return

        url_match = re.match(r"(?P<url>https?://[^\s]+)", clipboard)
        if url_match:
            self.log("Recognized url: " + url_match.group(), OUTPUT_INFO)
            http = urllib3.PoolManager()
            response = http.request("GET", url_match.group())
            clipboard = response.data.decode("utf-8")

        try:
            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]), STATUS_INFO)
            return
        move_tree.nodes_in_tree[-1].analyze(
            self.engine,
            analyze_fast=False)  # speed up result for looking at end of game
        self._do_new_game(move_tree=move_tree, analyze_fast=True)
        self("redo", 9999)
        self.log("Imported game from clipboard.", OUTPUT_INFO)
Exemple #19
0
 def sgf_properties(self,
                    save_comments_player=None,
                    save_comments_class=None,
                    eval_thresholds=None,
                    save_analysis=False):
     properties = copy.copy(super().sgf_properties())
     note = self.note.strip()
     if save_analysis and self.analysis_complete:
         try:
             properties["KT"] = analysis_dumps(self.analysis)
         except Exception as e:
             print(f"Error in saving analysis: {e}")
     if self.points_lost and save_comments_class is not None and eval_thresholds is not None:
         show_class = save_comments_class[evaluation_class(
             self.points_lost, eval_thresholds)]
     else:
         show_class = False
     comments = properties.get("C", [])
     if (self.parent and self.parent.analysis_exists
             and self.analysis_exists and
         (note or ((save_comments_player or {}).get(self.player, False)
                   and show_class))):
         candidate_moves = self.parent.candidate_moves
         top_x = Move.from_gtp(candidate_moves[0]["move"]).sgf(
             self.board_size)
         best_sq = [
             Move.from_gtp(d["move"]).sgf(self.board_size)
             for d in candidate_moves[1:] if d["pointsLost"] <= 0.5
         ]
         if best_sq and "SQ" not in properties:
             properties["SQ"] = best_sq
         if top_x and "MA" not in properties:
             properties["MA"] = [top_x]
         comments.append(
             self.comment(sgf=True, interactive=False) +
             SGF_INTERNAL_COMMENTS_MARKER)
     if self.is_root:
         comments = [
             i18n._("SGF start message") + SGF_INTERNAL_COMMENTS_MARKER +
             "\n",
             *comments,
             f"\nSGF generated by {PROGRAM_NAME} {VERSION}{SGF_INTERNAL_COMMENTS_MARKER}\n",
         ]
         properties["CA"] = ["UTF-8"]
         properties["AP"] = [f"{PROGRAM_NAME}:{VERSION}"]
     if self.shortcut_from:
         properties["KTSF"] = [id(self.shortcut_from)]
     elif "KTSF" in properties:
         del properties["KTSF"]
     if self.shortcuts_to:
         properties["KTSID"] = [id(self)]
     elif "KTSID" in properties:
         del properties["KTSID"]
     if note:
         comments.append(f"{self.note}")
     if comments:
         properties["C"] = [SGF_SEPARATOR_MARKER.join(comments)]
     elif "C" in properties:
         del properties["C"]
     return properties
Exemple #20
0
    def set_current_node(self, node):
        if self.insert_mode:
            self.katrain.controls.set_status(i18n._("finish inserting before navigating"), STATUS_ERROR)
            return

        self.current_node = node
        self._calculate_groups()
Exemple #21
0
 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])
     self.option_buttons = []
     option_labels = self.option_labels or [
         i18n._(f"tab:{opt}") for opt in self.options
     ]
     for ix, (lbl, opt_col, active) in enumerate(
             zip(option_labels, self.option_colors, self.option_active)):
         button = CollapsablePanelTab(
             text=lbl,
             font_name=i18n.font_name,
             active_outline_color=opt_col,
             height=self.options_height,
             state="down" if active else "normal",
         )
         self.option_buttons.append(button)
         button.bind(state=lambda *_args, _ix=ix: self.trigger_select(_ix))
     self.open_close_button = TransparentIconButton(  # <<  / >> collapse button
         icon=self.open_close_icon(),
         icon_size=[0.5 * self.options_height, 0.5 * self.options_height],
         width=0.75 * self.options_height,
         size_hint_x=None,
         on_press=lambda *_args: self.set_state("toggle"),
     )
     self.bind(state=lambda *_args: self.open_close_button.setter("icon")
               (None, self.open_close_icon()))
     self.build()
Exemple #22
0
    def analyze_undo(self, node):
        train_config = self.katrain.config("trainer")
        move = node.move
        if node != self.current_node or node.auto_undo is not None or not node.analysis_complete or not move:
            return
        points_lost = node.points_lost
        thresholds = train_config["eval_thresholds"]
        num_undo_prompts = train_config["num_undo_prompts"]
        i = 0
        while i < len(thresholds) and points_lost < thresholds[i]:
            i += 1
        num_undos = num_undo_prompts[i] if i < len(num_undo_prompts) else 0
        if num_undos == 0:
            undo = False
        elif num_undos < 1:  # probability
            undo = int(node.undo_threshold < num_undos) and len(
                node.parent.children) == 1
        else:
            undo = len(node.parent.children) <= num_undos

        node.auto_undo = undo
        if undo:
            self.undo(1)
            self.katrain.controls.set_status(
                i18n._("teaching undo message").format(
                    move=move.gtp(), points_lost=points_lost), STATUS_TEACHING)
            self.katrain.update_state()
Exemple #23
0
 def write_sgf(self, filename: str, trainer_config: Optional[Dict] = None):
     if trainer_config is None:
         trainer_config = self.katrain.config("trainer", {})
     save_feedback = trainer_config.get("save_feedback", False)
     eval_thresholds = trainer_config["eval_thresholds"]
     save_analysis = trainer_config.get("save_analysis", False)
     save_marks = trainer_config.get("save_marks", False)
     self.update_root_properties()
     show_dots_for = {
         bw: trainer_config.get("eval_show_ai", True)
         or self.katrain.players_info[bw].human
         for bw in "BW"
     }
     sgf = self.root.sgf(
         save_comments_player=show_dots_for,
         save_comments_class=save_feedback,
         eval_thresholds=eval_thresholds,
         save_analysis=save_analysis,
         save_marks=save_marks,
     )
     self.sgf_filename = filename
     os.makedirs(os.path.dirname(filename), exist_ok=True)
     with open(filename, "w", encoding="utf-8") as f:
         f.write(sgf)
     return i18n._("sgf written").format(file_name=filename)
Exemple #24
0
    def build(self, *args, **kwargs):
        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(
                    text=i18n._(self.closed_label), font_name=i18n.font_name, halign="right", height=self.options_height
                )
            )
        self.header.add_widget(self.open_close_button)

        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
Exemple #25
0
 def action(self, item):
     katrain = MDApp.get_running_app().gui
     shortcuts = katrain.shortcuts
     for text, shortcut in zip(self.ANALYSIS_OPTIONS,
                               self.ANALYSIS_SHORTCUTS):
         if item.text.startswith(i18n._(text)):
             katrain(*shortcuts[shortcut])
Exemple #26
0
 def start(self):
     try:
         self.katrain.log(f"Starting Distributed KataGo with {self.command}", OUTPUT_INFO)
         startupinfo = None
         if hasattr(subprocess, "STARTUPINFO"):
             startupinfo = subprocess.STARTUPINFO()
             startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW  # stop command box popups on win/pyinstaller
         self.katago_process = subprocess.Popen(
             self.command,
             stdout=subprocess.PIPE,
             stderr=subprocess.PIPE,
             stdin=subprocess.PIPE,
             startupinfo=startupinfo,
             shell=self.shell,
         )
     except (FileNotFoundError, PermissionError, OSError) as e:
         self.katrain.log(
             i18n._("Starting Kata failed").format(command=self.command, error=e),
             OUTPUT_ERROR,
         )
         return  # don't start
     self.stdout_thread = threading.Thread(target=self._read_stdout_thread, daemon=True)
     self.stderr_thread = threading.Thread(target=self._read_stderr_thread, daemon=True)
     self.stdout_thread.start()
     self.stderr_thread.start()
Exemple #27
0
 def check_alive(self, os_error="", exception_if_dead=False):
     ok = self.katago_process and self.katago_process.poll() is None
     if not ok and exception_if_dead:
         if self.katago_process:
             code = self.katago_process and self.katago_process.poll()
             if code == 3221225781:
                 died_msg = i18n._("Engine missing DLL")
             else:
                 os_error += f"status {code}"
                 died_msg = i18n._("Engine died unexpectedly").format(error=os_error)
             if code != 1:  # deliberate exit, already showed message?
                 self.katrain.log(died_msg, OUTPUT_ERROR)
             self.katago_process = None
         else:
             died_msg = i18n._("Engine died unexpectedly").format(error=os_error)
         raise EngineDiedException(died_msg)
     return ok
Exemple #28
0
 def __init__(self, **kwargs):
     super().__init__(**kwargs)
     app = MDApp.get_running_app()
     self.filesel.favorites = [
         (os.path.abspath(app.gui.config("general/sgf_load")), "Last Load Dir"),
         (os.path.abspath(app.gui.config("general/sgf_save")), "Last Save Dir"),
     ]
     self.filesel.path = os.path.abspath(os.path.expanduser(app.gui.config("general/sgf_load")))
     self.filesel.select_string = i18n._("Load File")
Exemple #29
0
 def load_sgf_file(self, file, fast=False, rewind=False):
     try:
         move_tree = KaTrainSGF.parse_file(file)
     except ParseError as e:
         self.log(i18n._("Failed to load SGF").format(error=e), OUTPUT_ERROR)
         return
     self._do_new_game(move_tree=move_tree, analyze_fast=fast)
     if not rewind:
         self.game.redo(999)
Exemple #30
0
    def on_touch_up(self, touch):
        if ("button" in touch.profile and touch.button != "left") or not self.gridpos_x:
            return
        katrain = self.katrain
        if self.selecting_region_of_interest:
            if len(self.region_of_interest) == 4:
                self.katrain.game.set_region_of_interest(self.region_of_interest)
                self.region_of_interest = []
                self.selecting_region_of_interest = False

        elif self.ghost_stone and ("button" not in touch.profile or touch.button == "left"):
            game = self.katrain and self.katrain.game
            current_node = game and self.katrain.game.current_node
            if (
                current_node
                and not current_node.children
                and not self.katrain.next_player_info.ai
                and not self.katrain.controls.timer.paused
                and self.katrain.play_analyze_mode == MODE_PLAY
                and self.katrain.config("timer/main_time", 0) * 60 - game.main_time_used <= 0
                and current_node.time_used < self.katrain.config("timer/minimal_use", 0)
            ):
                self.katrain.controls.set_status(
                    i18n._("move too fast").format(num=self.katrain.config("timer/minimal_use", 0)), STATUS_TEACHING
                )
            else:
                katrain("play", self.ghost_stone)
                self.play_stone_sound()
        elif not self.ghost_stone:
            xd, xp = self._find_closest(touch.x, self.gridpos_x)
            yd, yp = self._find_closest(touch.y, self.gridpos_y)

            nodes_here = [
                node for node in katrain.game.current_node.nodes_from_root if node.move and node.move.coords == (xp, yp)
            ]
            if nodes_here and max(yd, xd) < self.grid_size / 2:  # load old comment
                if touch.is_double_tap:  # navigate to move
                    katrain.game.set_current_node(nodes_here[-1].parent)
                    katrain.update_state()
                else:  # load comments & pv
                    katrain.log(
                        f"\nAnalysis:\n{json_truncate_arrays(nodes_here[-1].analysis,lim=5)}", OUTPUT_EXTRA_DEBUG
                    )
                    katrain.log(
                        f"\nParent Analysis:\n{json_truncate_arrays(nodes_here[-1].parent.analysis,lim=5)}",
                        OUTPUT_EXTRA_DEBUG,
                    )
                    katrain.log(
                        f"\nRoot Stats:\n{json_truncate_arrays(nodes_here[-1].analysis['root'],lim=5)}", OUTPUT_DEBUG
                    )
                    katrain.controls.info.text = nodes_here[-1].comment(sgf=True)
                    katrain.controls.active_comment_node = nodes_here[-1]
                    if nodes_here[-1].parent.analysis_exists:
                        self.set_animating_pv(nodes_here[-1].parent.candidate_moves[0]["pv"], nodes_here[-1].parent)

        self.ghost_stone = None
        self.redraw_hover_contents_trigger()  # remove ghost