diff --git a/katrain/__main__.py b/katrain/__main__.py index 4e8cc8a..626a8a3 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -53,7 +53,7 @@ class KaTrainGui(BoxLayout): self._keyboard.bind(on_key_down=self._on_keyboard_down) def log(self, message, level=OUTPUT_INFO): - if level == OUTPUT_KATAGO_STDERR and 'ERROR' not in self.controls.status.text: + if level == OUTPUT_KATAGO_STDERR and "ERROR" not in self.controls.status.text: if "starting" in message.lower(): self.controls.set_status(f"KataGo engine starting...") if message.startswith("Tuning"): diff --git a/katrain/core/common.py b/katrain/core/common.py index 8cc39a4..747bf86 100644 --- a/katrain/core/common.py +++ b/katrain/core/common.py @@ -45,7 +45,7 @@ def find_package_resource(path): path_obj = pkg_resources.path(".".join(parts[:-1]), parts[-1]).__enter__() resource_scopes.append(path_obj) return str(path_obj) - except ModuleNotFoundError as e: + except (ModuleNotFoundError, FileNotFoundError) as e: print(f"File {path} not found, installation possibly broken") return "FILENOTFOUND" else: diff --git a/katrain/core/engine.py b/katrain/core/engine.py index bac748d..4538d93 100644 --- a/katrain/core/engine.py +++ b/katrain/core/engine.py @@ -37,7 +37,7 @@ class KataGoEngine: executable = "katago" modelfile, configfile = find_package_resource(config["model"]), find_package_resource(config["config"]) - self.command = f"{find_package_resource(executable)} analysis -model {modelfile} -config {configfile} -analysis-threads {config['threads']}" + self.command = f'{find_package_resource(executable)} analysis -model "{modelfile}" -config "{configfile}" -analysis-threads {config["threads"]}' if not sys.platform.startswith("win"): self.command = shlex.split(self.command) self.queries = {} # outstanding query id -> start time and callback @@ -55,6 +55,7 @@ class KataGoEngine: 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) + print(self.katago_process, self.katago_process.poll()) except (FileNotFoundError, PermissionError, OSError) as e: if self.config["katago"].strip(): self.katrain.log( @@ -111,6 +112,7 @@ class KataGoEngine: raise EngineDiedException(f"Engine died unexpectedly without sending output, possibly due to out of memory: {e}") if b"Uncaught exception" in line: self.katrain.log(f"KataGo Engine Failed: {line.decode()}", OUTPUT_ERROR) + return if not line: continue analysis = json.loads(line) diff --git a/katrain/core/game.py b/katrain/core/game.py index 133722f..7b7b9b8 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -136,6 +136,7 @@ class Game: cn = self.current_node # avoid race conditions for _ in range(n_times): if not cn.is_root: + cn.parent.set_favourite_child(cn) cn = cn.parent self.set_current_node(cn) @@ -143,7 +144,7 @@ class Game: cn = self.current_node # avoid race conditions for _ in range(n_times): if cn.children: - cn = cn.children[-1] + cn = cn.favourite_child self.set_current_node(cn) def switch_branch(self, direction): diff --git a/katrain/core/game_node.py b/katrain/core/game_node.py index a949b6a..c046134 100644 --- a/katrain/core/game_node.py +++ b/katrain/core/game_node.py @@ -18,6 +18,7 @@ class GameNode(SGFNode): self.ai_thoughts = "" self.move_number = 0 self.undo_threshold = random.random() # for fractional undos, store the random threshold in the move itself for consistency + self._favourite_child = None def sgf_properties(self, save_comments_player=None, save_comments_class=None, eval_thresholds=None): properties = copy.copy(super().sgf_properties()) @@ -48,6 +49,16 @@ class GameNode(SGFNode): def order_children(children): return sorted(children, key=lambda c: 0.5 if c.auto_undo is None else int(c.auto_undo)) # analyzed/not undone main, non-teach second, undone last + def set_favourite_child(self, c): + self._favourite_child = c + + @property + def favourite_child(self) -> Optional["GameNode"]: + if self._favourite_child: + return self._favourite_child + elif self.children: + return self.children[0] + # various analysis functions def analyze(self, engine, priority=0, visits=None, time_limit=True, refine_move=None, analyze_fast=False): engine.request_analysis( diff --git a/katrain/gui/badukpan.py b/katrain/gui/badukpan.py index bc05e70..b4e81b7 100644 --- a/katrain/gui/badukpan.py +++ b/katrain/gui/badukpan.py @@ -334,46 +334,46 @@ class BadukPanWidget(Widget): if self.ghost_stone: self.draw_stone(*self.ghost_stone, (*stone_color[next_player], ghost_alpha)) + animating_pv = self.animating_pv + if animating_pv: + pv, node, start_time, _ = animating_pv + delay = self.ui_config.get("anim_pv_time", 0.5) + up_to_move = (time.time() - start_time) / delay + self.draw_pv(pv, node, up_to_move) + def animate_pv(self, _dt): - animating_pv = self.animating_pv - if not animating_pv: - return - pv, node, start_time, _ = animating_pv - delay = self.ui_config.get("anim_pv_time", 0.5) - up_to_move = (time.time() - start_time) / delay - self.draw_hover_contents() - self.draw_pv(pv, node, up_to_move) + if self.animating_pv: + self.draw_hover_contents() def draw_pv(self, pv, node, up_to_move): katrain = self.katrain next_last_player = [node.next_player, node.player] stone_color = self.ui_config["stones"] cn = katrain.game.current_node - with self.canvas.after: - if node != cn and node.parent != cn: - hide_node = cn - while hide_node and hide_node.move and hide_node != node: - if not hide_node.move.is_pass: - self.draw_stone(*hide_node.move.coords, [0.85, 0.68, 0.40, 0.8]) # board coloured dot - hide_node = hide_node.parent - for i, gtpmove in enumerate(pv): - if i > up_to_move: - return - move_player = next_last_player[i % 2] - opp_player = next_last_player[1 - i % 2] - coords = Move.from_gtp(gtpmove).coords - if coords is None: # tee-hee - sizefac = katrain.board_controls.pass_btn.size[1] / 2 / self.stone_size - board_coords = [ - katrain.board_controls.pass_btn.pos[0] + katrain.board_controls.pass_btn.size[0] + self.stone_size * sizefac, - katrain.board_controls.pass_btn.pos[1] + katrain.board_controls.pass_btn.size[1] / 2, - ] - else: - board_coords = (self.gridpos_x[coords[0]], self.gridpos_y[coords[1]]) + if node != cn and node.parent != cn: + hide_node = cn + while hide_node and hide_node.move and hide_node != node: + if not hide_node.move.is_pass: + self.draw_stone(*hide_node.move.coords, [0.85, 0.68, 0.40, 0.8]) # board coloured dot + hide_node = hide_node.parent + for i, gtpmove in enumerate(pv): + if i > up_to_move: + return + move_player = next_last_player[i % 2] + opp_player = next_last_player[1 - i % 2] + coords = Move.from_gtp(gtpmove).coords + if coords is None: # tee-hee + sizefac = katrain.board_controls.pass_btn.size[1] / 2 / self.stone_size + board_coords = [ + katrain.board_controls.pass_btn.pos[0] + katrain.board_controls.pass_btn.size[0] + self.stone_size * sizefac, + katrain.board_controls.pass_btn.pos[1] + katrain.board_controls.pass_btn.size[1] / 2, + ] + else: + board_coords = (self.gridpos_x[coords[0]], self.gridpos_y[coords[1]]) - draw_circle(board_coords, self.stone_size, stone_color[move_player]) - Color(*stone_color[opp_player]) - draw_text(pos=board_coords, text=str(i + 1), font_size=self.grid_size / 1.45) + draw_circle(board_coords, self.stone_size, stone_color[move_player]) + Color(*stone_color[opp_player]) + draw_text(pos=board_coords, text=str(i + 1), font_size=self.grid_size / 1.45) def set_animating_pv(self, pv, node): if node is not None and (not self.animating_pv or not (self.animating_pv[0] == pv and self.animating_pv[1] == node)): diff --git a/katrain/gui/kivyutils.py b/katrain/gui/kivyutils.py index ba84a09..fa06a43 100644 --- a/katrain/gui/kivyutils.py +++ b/katrain/gui/kivyutils.py @@ -278,7 +278,7 @@ class ScoreGraph(Label): self.nodes = [root] node = root while node.children: - node = node.children[0] + node = node.favourite_child self.nodes.append(node) self.highlighted_index = 0 diff --git a/setup.py b/setup.py index 3160900..e3aed67 100644 --- a/setup.py +++ b/setup.py @@ -36,7 +36,13 @@ setup( author_email="sander.land@gmail.com", url="https://github.com/sanderland/katrain", license="MIT", - install_requires=["kivy", "kivy_deps.glew;platform_system=='Windows'", "kivy_deps.sdl2;platform_system=='Windows'", "kivy_deps.gstreamer;platform_system=='Windows'","importlib_resources ;python_version<'3.7'",], + install_requires=[ + "kivy", + "kivy_deps.glew;platform_system=='Windows'", + "kivy_deps.sdl2;platform_system=='Windows'", + "kivy_deps.gstreamer;platform_system=='Windows'", + "importlib_resources ;python_version<'3.7'", + ], python_requires=">=3.6, <3.8", entry_points={"console_scripts": ["katrain=katrain.__main__:run_app"]}, classifiers=["Development Status :: 4 - Beta", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3"],