diff --git a/CONTRIBUTIONS.md b/CONTRIBUTIONS.md index 49f283b..ea2c6a4 100644 --- a/CONTRIBUTIONS.md +++ b/CONTRIBUTIONS.md @@ -47,7 +47,7 @@ Many thanks to these additional authors: * "Dontbtme" for detailed feedback and early testing of v1.0+. * "nowoowoo" for a fix to the parser for SGF files with extra line breaks. * "nimets123" for the timer sound effects. -* "JSinSeaward" for stone sounds. +* Jordan Seaward for the stone sound effects. * "fohristiwhirl" for the Gibo format parsing code. ## Translators diff --git a/INSTALL.md b/INSTALL.md index 7b484ad..58d943c 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -7,7 +7,7 @@ * [Quick install guide for Linux](#LinuxQuick) * [Troubleshooting and installation from sources](#LinuxSources) -## drawing Installation for MacOS users +## MacOs Installation for MacOS users ### Quick install guide @@ -25,11 +25,12 @@ These commands install [Homebrew](https://brew.sh), which simplifies installing ### Troubleshooting and Installation from sources -Installation from sources is essentially the same as for Linux, see [here](#LinuxSources). +Installation from sources is essentially the same as for Linux, see [here](#LinuxSources), + note that you will still need to install your own KataGo, using brew or otherwise. If you encounter SSL errors on downloading model files, you may need to follow [these](https://stackoverflow.com/questions/52805115/certificate-verify-failed-unable-to-get-local-issuer-certificate) instructions to fix your certificates. -## drawing Installation for Windows users +## Windows Installation for Windows users ### Quick install guide @@ -42,9 +43,9 @@ Simply download and run, everything is included. * Make sure you have a python installation, I will assume Anaconda (Python 3.7), available [here](https://www.anaconda.com/products/individual#download-section). * Open 'Anaconda prompt' from the start menu and navigate to where you extracted the zip file using the `cd ` command. * Execute the command `pip3 install .` -* Start the app by running `katrain` in the directory where you downloaded the scripts. +* Start the app by running `katrain` in the command prompt. -## Installation for Linux users +## Linux Installation for Linux users ### Quick install guide @@ -74,6 +75,7 @@ A binary for KataGo is included, but if you have compiled your own, press F8 to You can try to manually install dependencies to resolve some issues relating to missing dependencies, e.g. the binary 'wheel' is not provided, KataGo is not starting, or sounds are not working. +You can also follow these instructions if you don't want to install KaTrain, and just run it locally. First install the following packages, which are either required for building Kivy, or may help resolve missing dependencies for Kivy or KataGo. @@ -93,6 +95,7 @@ In case the sound is not working, or there is no available wheel for your OS or pip3 uninstall kivy pip3 install --no-binary kivy kivy==2.0.0rc2 ``` +You can now start KaTrain by running `python3 -m katrain` In case KataGo does not start, an alternative is to go [here](https://github.com/lightvector/KataGo) and compile KataGo yourself. @@ -114,7 +117,8 @@ The above devices were found on a 2019 MacBook Pro with both an on-motherboard g As you can see it scores about twice as high as the Intel UHD chip and KataGo has selected it as it's sole device. You can configure KataGo to use *both* the AMD and the Intel devices to get the best performance out of the system. -* Open the 'analysis_config.cfg' file in the KataGo folder. +* Open the 'analysis_config.cfg' file in the `katrain/KataGo` folder in your python packages, or local sources. + If you can't find it, turn on `debug_level=1` in general settings and look for the command that is used to start KataGo. * Search for `numNNServerThreadsPerModel` (~line 75), uncomment the line by deleting the # and set the value to 2. The line should read `numNNServerThreadsPerModel = 2`. * Search for `openclDeviceToUseThread` (~line 117), uncomment by deleting the # and set the values to the device ID numbers identified in the terminal. From the example above, we would want to use devices 1 and 2, for the Intel and AMD GPU's, but not device 0 (the CPU). In our case, the lines should read: @@ -122,7 +126,7 @@ As you can see it scores about twice as high as the Intel UHD chip and KataGo ha openclDeviceToUseThread0 = 1 openclDeviceToUseThread1 = 2 ``` -* Run `python3 -m katrain` and confirm that KataGo is now using both devices, by +* Run `katrain` and confirm that KataGo is now using both devices, by checking the output from the terminal, which should indicate two devices being used. For example: ``` Found 3 device(s) on platform 0 with type CPU or GPU or Accelerator diff --git a/katrain/__main__.py b/katrain/__main__.py index 61ec7c7..e41af20 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -199,6 +199,7 @@ class KaTrainGui(Screen, KaTrainBase): and not (teaching_undo and cn.auto_undo is None) ): # cn mismatch stops this if undo fired. avoid message loop here or fires repeatedly. self._do_ai_move(cn) + Clock.schedule_once(self.board_gui.play_stone_sound, 0) if len(self.engine.queries) == 0 and self.idle_analysis: self("analyze-extra", "extra", continuous=True) Clock.schedule_once(lambda _dt: self.update_gui(cn, redraw_board=redraw_board), -1) # trigger? diff --git a/katrain/core/ai.py b/katrain/core/ai.py index 37f62ce..c1d83a4 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -2,30 +2,30 @@ import heapq import math import random import time -from typing import Dict, List, Tuple, Optional +from typing import Dict, List, Optional, Tuple -from katrain.core.utils import var_to_grid from katrain.core.constants import ( - OUTPUT_INFO, - OUTPUT_DEBUG, - AI_STRATEGIES_POLICY, - AI_POLICY, - AI_WEIGHTED, - AI_STRATEGIES_PICK, - AI_JIGO, - AI_SCORELOSS, AI_DEFAULT, + AI_HANDICAP, AI_INFLUENCE, + AI_JIGO, AI_LOCAL, + AI_PICK, + AI_POLICY, + AI_RANK, + AI_SCORELOSS, + AI_STRATEGIES_PICK, + AI_STRATEGIES_POLICY, + AI_STRENGTH, AI_TENUKI, AI_TERRITORY, - AI_PICK, - AI_RANK, - AI_HANDICAP, + AI_WEIGHTED, + OUTPUT_DEBUG, OUTPUT_ERROR, - AI_STRENGTH, + OUTPUT_INFO, ) from katrain.core.game import Game, GameNode, Move +from katrain.core.utils import var_to_grid def ai_rank_estimation(strategy, settings) -> Tuple[int, bool]: @@ -200,7 +200,7 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, elif ai_mode in AI_STRATEGIES_PICK: if ai_mode != AI_RANK: - n_moves = int(ai_settings["pick_frac"] * len(legal_policy_moves) + ai_settings["pick_n"]) + n_moves = max(1, int(ai_settings["pick_frac"] * len(legal_policy_moves) + ai_settings["pick_n"])) else: orig_calib_avemodrank = 0.063015 + 0.7624 * board_squares / ( 10 ** (-0.05737 * ai_settings["kyu_rank"] + 1.9482) @@ -222,9 +222,8 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, ) - 0.01093 * ai_settings["kyu_rank"] ) * orig_calib_avemodrank - n_moves = int( - round(board_squares * norm_leg_moves / (1.31165 * (modified_calib_avemodrank + 1) - 0.082653)) - ) + n_moves = board_squares * norm_leg_moves / (1.31165 * (modified_calib_avemodrank + 1) - 0.082653) + n_moves = max(1, round(n_moves)) if ai_mode in [AI_INFLUENCE, AI_TERRITORY, AI_LOCAL, AI_TENUKI]: if cn.depth > ai_settings["endgame"] * board_squares: diff --git a/katrain/core/engine.py b/katrain/core/engine.py index d3a400c..8bb58c1 100644 --- a/katrain/core/engine.py +++ b/katrain/core/engine.py @@ -101,7 +101,7 @@ class KataGoEngine: self.shutdown(finish=False) self.start() - def check_alive(self, os_error='', exception_if_dead=False): + 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: @@ -140,7 +140,7 @@ class KataGoEngine: else: self.check_alive(exception_if_dead=True) except Exception as e: - self.katrain.log(f"Exception in reading stdout {e}" , OUTPUT_DEBUG) + self.katrain.log(f"Exception in reading stdout {e}", OUTPUT_DEBUG) return def _analysis_read_thread(self): @@ -150,7 +150,7 @@ class KataGoEngine: if not line: self.check_alive(exception_if_dead=True) except OSError as e: - self.check_alive(os_error=str(e),exception_if_dead=True) + self.check_alive(os_error=str(e), exception_if_dead=True) return if b"Uncaught exception" in line: @@ -203,7 +203,7 @@ class KataGoEngine: self.katago_process.stdin.write((json.dumps(query) + "\n").encode()) self.katago_process.stdin.flush() except OSError as e: - self.check_alive(os_error=str(e),exception_if_dead=True) + self.check_alive(os_error=str(e), exception_if_dead=True) return # do not raise, since there's nothing to catch it def request_analysis( diff --git a/katrain/gui/badukpan.py b/katrain/gui/badukpan.py index 6db3b62..d9d5f75 100644 --- a/katrain/gui/badukpan.py +++ b/katrain/gui/badukpan.py @@ -98,14 +98,17 @@ class BadukPanWidget(Widget): self.draw_hover_contents() self.last_mouse_pos = pos + def play_stone_sound(self, *_args): + if self.katrain.config("timer/sound"): + random.choice(self.stones_sounds).play() + 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.ghost_stone and ("button" not in touch.profile or touch.button == "left"): katrain("play", self.ghost_stone) - if self.katrain.config("timer/sound"): - random.choice(self.stones_sounds).play() + 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) diff --git a/katrain/gui/widgets/graph.py b/katrain/gui/widgets/graph.py index 00ff4e3..9f9df67 100644 --- a/katrain/gui/widgets/graph.py +++ b/katrain/gui/widgets/graph.py @@ -40,11 +40,11 @@ class Graph(Widget): def update_value(self, node): with self._lock: - changed = (self.redraw_on_highlight_change and self.highlighted_index != node.depth) + changed = self.redraw_on_highlight_change and self.highlighted_index != node.depth self.highlighted_index = index = node.depth self.nodes.extend([None] * max(0, index - (len(self.nodes) - 1))) - if self.nodes[index]!=node: - changed=True + if self.nodes[index] != node: + changed = True self.nodes[index] = node if index > 1 and node.parent: # sometimes there are gaps backfill, bfnode = index - 1, node.parent @@ -215,7 +215,7 @@ class RankGraph(Graph): ) else: rank_kyu = -4 - return min(RankGraph.RANK_CAP,1 - rank_kyu) # dan rank + return min(RankGraph.RANK_CAP, 1 - rank_kyu) # dan rank @staticmethod def calculate_ranks(segment_stats, num_intersec): @@ -275,7 +275,7 @@ class RankGraph(Graph): min_rank = math.floor(min(all_ranks)) max_rank = math.ceil(max(all_ranks)) - if max_rank==min_rank: + if max_rank == min_rank: min_rank -= 1 if (max_rank - min_rank) % 2 != 0: # make midpoint whole integer if abs(max_rank - max(all_ranks)) < abs(min(all_ranks) - min_rank) and max_rank < self.RANK_CAP: @@ -285,7 +285,7 @@ class RankGraph(Graph): rank_range = max_rank - min_rank self.ids.mid_marker.text = rank_label((max_rank + min_rank) / 2) - self.ids.top_marker.text = rank_label(max_rank) + ("+" if max_rank==self.RANK_CAP else "") + self.ids.top_marker.text = rank_label(max_rank) + ("+" if max_rank == self.RANK_CAP else "") self.ids.bottom_marker.text = rank_label(min_rank) graph_points = {} @@ -303,6 +303,7 @@ class RankGraph(Graph): self.black_rank_points = [] self.white_rank_points = [] + Builder.load_string( """ #:set GRAPH_CENTER_COLOR [0.5,0.5,0.5]