diff --git a/README.md b/README.md index 810438a..72f7c44 100644 --- a/README.md +++ b/README.md @@ -97,30 +97,11 @@ In addition to these, there are: ### Configuration -Configuration is stored in `config.json`. Most settings are available to edit in the program, but some are not. +Configuration is stored in `config.json`. Most settings are now available to edit in the program, but + some cosmetic options are now. You can use `python katrain.py your_config_file.json` to use another config file instead. -#### The settings panel - - - -The `trainer` block has the following options to tweak for engine assisted play and reviewing: - -* `eval_off_show_last`: when the `eval` checkbox is off for a player, show coloured dots on the last this many moves regardless. -* `undo_eval_threshold`, `undo_point_threshold`: prompt player to undo if move is worse than this in terms of points AND evaluation. - -The following options are relevant for the `balance score` AI play mode. - -* `balance_play_target_score`: indicates how many points the AI aims to win by when using 'balance score'. -* `balance_play_randomize_eval`: when not needing to balance score, the AI will pick a random move which is at least this good as long as it stays ahead. -* `balance_play_min_eval`: when needing to balance score, the AI will pick a move which is at least this good. -* `balance_play_min_visits`: never pick a move with fewer playouts than this. - -The cfg file has additional configuration for KataGo, which are documented there. - -#### Configuring feedback - -* `num_undo_prompts`: automatically undo bad moves when `undo` is on at most this many times. Can be a fraction like 0.5 for 50% chance of being granted an undo on a bad move. +If you ever need to reset to the original settings, simply re-download the `config.json` file in this repository. ## FAQ diff --git a/TODO b/TODO index f6bab6d..7f82598 100644 --- a/TODO +++ b/TODO @@ -33,12 +33,13 @@ Low priority [] more teaching / groups in danger? hard [] When creating a new game, the 9 buttons on the right side aren't all that useful. Maybe the 9, 13 and 19 ones make sense since these three board sizes are the traditionally used ones, but why 2, 4 and 9 stones buttons? Why 0.5, 6.5 and -40pts komi buttons? [] Score instead of game end +[] Plus, explanatory bubbles would help to clarify labels like "Debug" (are there bugs in the matrix? :p), "enable_ownership" (which didn't seem to prevent or allow visualizing territory ownership on the board, as that was already covered by the "owner" checkbox anyway... so I guess I missed something), etc. +-- possible right-click? Wont do for now [] dual engine support -- easily possible but has weird effects on win rate etc [/] The UI would be directly understood if there were bubbles with a short explanation popping up when hovering above buttons and labels, e.g "Performing additional analysis to 1502 visits" which appears currently in the comments window after pushing the Extra button would also make a perfect explanatory bubble when hovering over said button too. + --- partially solved by config with help for some -bug/suggestion reports --- Plus, explanatory bubbles would help to clarify labels like "Debug" (are there bugs in the matrix? :p), "enable_ownership" (which didn't seem to prevent or allow visualizing territory ownership on the board, as that was already covered by the "owner" checkbox anyway... so I guess I missed something), etc. diff --git a/game.py b/game.py index 401dd23..8314b27 100644 --- a/game.py +++ b/game.py @@ -43,8 +43,7 @@ class Game: self.komi = self.config.get("init_komi", {}).get(str(board_size), 6.5) self.root = GameNode(properties={**Game.DEFAULT_PROPERTIES, **{"SZ": board_size, "KM": self.komi, "DT": self.game_id}}) - self.current_node = self.root - self._init_chains() + self.set_current_node(self.root) threading.Thread(target=lambda: self.analyze_all_nodes(-1_000_000, analyze_fast=analyze_fast), daemon=True).start() # return faster, but bypass Kivy Clock def analyze_all_nodes(self, priority=0, analyze_fast=False): @@ -52,7 +51,7 @@ class Game: node.analyze(self.engines[node.next_player], priority=priority, analyze_fast=analyze_fast) # -- move tree functions -- - def _init_chains(self): + def _calculate_groups(self): board_size_x, board_size_y = self.board_size self.board = [[-1 for _x in range(board_size_x)] for _y in range(board_size_y)] # type: List[List[int]] # board pos -> chain id self.chains = [] # type: List[List[Move]] # chain id -> chain @@ -121,7 +120,7 @@ class Game: try: self._validate_move_and_update_chains(move, ignore_ko) except IllegalMoveException: - self._init_chains() + self._calculate_groups() raise played_node = self.current_node.play(move) self.current_node = played_node @@ -129,28 +128,29 @@ class Game: played_node.analyze(self.engines[played_node.next_player]) return played_node + def set_current_node(self, node): + self.current_node = node + self._calculate_groups() + def undo(self, n_times=1): cn = self.current_node # avoid race conditions for _ in range(n_times): if not cn.is_root: cn = cn.parent - self.current_node = cn - self._init_chains() + self.set_current_node(cn) def redo(self, n_times=1): cn = self.current_node # avoid race conditions for _ in range(n_times): if cn.children: cn = cn.children[-1] - self.current_node = cn - self._init_chains() + self.set_current_node(cn) def switch_branch(self, direction): cn = self.current_node # avoid race conditions if cn.parent and len(cn.parent.children) > 1: ix = cn.parent.children.index(cn) - self.current_node = cn.parent.children[(ix + direction) % len(cn.parent.children)] - self._init_chains() + self.set_current_node(cn.parent.children[(ix + direction) % len(cn.parent.children)]) def place_handicap_stones(self, n_handicaps): board_size_x, board_size_y = self.board_size diff --git a/gui/badukpan.py b/gui/badukpan.py index a18cf72..6de3805 100644 --- a/gui/badukpan.py +++ b/gui/badukpan.py @@ -71,23 +71,25 @@ class BadukPanWidget(Widget): self.draw_hover_contents() def on_touch_up(self, touch): - if touch.button != "left": - return - - if not self.gridpos_x: + if touch.button != "left" or not self.gridpos_x: return katrain = self.katrain - if self.ghost_stone: + if self.ghost_stone and touch.button == "left": katrain("play", self.ghost_stone) - else: + 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.single_move and node.single_move.coords == (xp, yp)] if nodes_here and max(yd, xd) < self.grid_size / 2: # load old comment - katrain.log(f"\nAnalysis:\n{nodes_here[-1].analysis}", OUTPUT_DEBUG) - katrain.log(f"\nParent Analysis:\n{nodes_here[-1].parent.analysis}", OUTPUT_DEBUG) - katrain.controls.info.text = nodes_here[-1].comment(sgf=True) + if touch.is_double_tap: # navigate to move + katrain.game.set_current_node(nodes_here[-1]) + self.draw_board_contents() + else: # load comments + katrain.log(f"\nAnalysis:\n{nodes_here[-1].analysis}", OUTPUT_DEBUG) + katrain.log(f"\nParent Analysis:\n{nodes_here[-1].parent.analysis}", OUTPUT_DEBUG) + katrain.controls.info.text = nodes_here[-1].comment(sgf=True) + katrain.controls.info.text = nodes_here[-1].comment(sgf=True) self.ghost_stone = None self.draw_hover_contents() # remove ghost @@ -191,7 +193,6 @@ class BadukPanWidget(Widget): realized_points_lost = None for i, node in enumerate(nodes[::-1]): # reverse order! points_lost = node.points_lost - print("node", node.single_move, "pl", points_lost, "rpl", realized_points_lost) evalsize = 1 if points_lost and realized_points_lost: if points_lost <= 0.5 and realized_points_lost <= 1.5: diff --git a/katrain.py b/katrain.py index 37515de..cab27a7 100644 --- a/katrain.py +++ b/katrain.py @@ -228,6 +228,13 @@ class KaTrainGui(BoxLayout): self.log(msg, OUTPUT_INFO) self.controls.set_status(msg) + def on_touch_up(self, touch): + if touch.button == "scrollup": + self("redo") + elif touch.button == "scrolldown": + self("undo") + return super().on_touch_up(touch) + def _on_keyboard_down(self, keyboard, keycode, text, modifiers): if isinstance(App.get_running_app().root_window.children[0], Popup): return # if in new game or load, don't allow keyboard shortcuts