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()
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)
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()
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
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()
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
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()
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
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)
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
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
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
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()
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()
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()
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)
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)
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
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()
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()
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()
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)
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
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])
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()
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
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")
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)
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