diff --git a/katrain/__main__.py b/katrain/__main__.py index f833d5a..76961c8 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -3,7 +3,8 @@ import os os.environ["KCFG_KIVY_LOG_LEVEL"] = os.environ.get("KCFG_KIVY_LOG_LEVEL", "warning") -os.environ["KIVY_AUDIO"] = "ffpyplayer" # force working audio +if "KIVY_AUDIO" not in os.environ: + os.environ["KIVY_AUDIO"] = "sdl2" # seems to be most stable / some players hard crash # next, icon from katrain.core.utils import find_package_resource, PATHS @@ -161,7 +162,10 @@ class KaTrainGui(Screen, KaTrainBase): # update move tree self.controls.move_tree.current_node = self.game.current_node - def update_state( + def update_state(self, redraw_board=False): # redirect to message queue thread + self("update_state", redraw_board=redraw_board) + + def _do_update_state( self, redraw_board=False ): # is called after every message and on receiving analyses and config changes # AI and Trainer/auto-undo handlers @@ -214,7 +218,8 @@ class KaTrainGui(Screen, KaTrainBase): continue fn = getattr(self, f"_do_{msg.replace('-','_')}") fn(*args, **kwargs) - self.update_state() + 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() diff --git a/katrain/config.json b/katrain/config.json index 8b111d4..7b18d4f 100644 --- a/katrain/config.json +++ b/katrain/config.json @@ -69,6 +69,10 @@ }, "ai": { "ai:default": {}, + "ai:handicap": { + "automatic": true, + "pda": 0 + }, "ai:jigo": { "target_score": 0.5 }, diff --git a/katrain/core/ai.py b/katrain/core/ai.py index 3e8bb92..187741e 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -21,6 +21,8 @@ from katrain.core.constants import ( AI_TERRITORY, AI_PICK, AI_RANK, + AI_HANDICAP, + OUTPUT_ERROR, ) from katrain.core.game import Game, GameNode, Move @@ -93,8 +95,51 @@ def generate_local_tenuki_weights(ai_mode, ai_settings, policy_grid, cn, size): return weighted_coords, ai_thoughts +def request_ai_analysis(game: Game, cn: GameNode, extra_settings: Dict) -> Dict: + error = False + analysis = None + + def set_analysis(a): + nonlocal analysis + analysis = a + + def set_error(a): + nonlocal error + game.katrain.log("Error in PDA-based analysis", a) + error = True + + engine = game.engines[cn.player] + engine.request_analysis( + cn, + callback=set_analysis, + error_callback=set_error, + priority=1_000, + ownership=False, + extra_settings=extra_settings, + ) + while not (error or analysis): + time.sleep(0.01) + engine.check_alive(exception_if_dead=True) + return analysis + + def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, GameNode]: cn = game.current_node + + if ai_mode == AI_HANDICAP: + pda = ai_settings["pda"] + if ai_settings["automatic"]: + n_handicaps = len(game.root.get_list_property("AB", [])) + MOVE_VALUE = 14 # could be rules dependent + b_stones_advantage = max(n_handicaps - 1, 0) - (cn.komi - MOVE_VALUE / 2) / MOVE_VALUE + pda = min(3, max(-3, -b_stones_advantage * (3 / 8))) # max PDA at 8 stone adv, normal 9 stone game is 8.46 + handicap_analysis = request_ai_analysis( + game, cn, {"playoutDoublingAdvantage": pda, "playoutDoublingAdvantagePla": "BLACK"} + ) + if not handicap_analysis: + game.katrain.log(f"Error getting handicap-based move", OUTPUT_ERROR) + ai_mode = AI_DEFAULT + while not cn.analysis_ready: time.sleep(0.01) game.engines[cn.next_player].check_alive(exception_if_dead=True) @@ -188,6 +233,9 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, raise ValueError(f"Unknown Policy-based AI mode {ai_mode}") else: # Engine based move candidate_ai_moves = cn.candidate_moves + if ai_mode == AI_HANDICAP: + candidate_ai_moves = handicap_analysis["moveInfos"] + top_cand = Move.from_gtp(candidate_ai_moves[0]["move"], player=cn.next_player) if top_cand.is_pass: # don't play suicidal to balance score - pass when it's best aimove = top_cand @@ -214,11 +262,14 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, aimove = topmove[2] ai_thoughts += f"ScoreLoss strategy found {len(candidate_ai_moves)} candidate moves (best {top_cand.gtp()}) and chose {aimove.gtp()} (weight {topmove[1]:.3f}, point loss {topmove[0]:.1f}) based on score weights." else: - if ai_mode != AI_DEFAULT: + if ai_mode not in [AI_DEFAULT, AI_HANDICAP]: game.katrain.log(f"Unknown AI mode {ai_mode} or policy missing, using default.", OUTPUT_INFO) ai_thoughts += f"Strategy {ai_mode} not found or unexpected fallback." aimove = top_cand - ai_thoughts += f"Default strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move" + if ai_mode == AI_HANDICAP: + ai_thoughts += f"Handicap strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move. PDA based score {cn.format_score(handicap_analysis['rootInfo']['scoreLead'])} and win rate {cn.format_winrate(handicap_analysis['rootInfo']['winrate'])}" + else: + ai_thoughts += f"Default strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move" game.katrain.log(f"AI thoughts: {ai_thoughts}", OUTPUT_DEBUG) played_node = game.play(aimove) played_node.ai_thoughts = ai_thoughts diff --git a/katrain/core/constants.py b/katrain/core/constants.py index 1e5c0fe..f90a925 100644 --- a/katrain/core/constants.py +++ b/katrain/core/constants.py @@ -1,6 +1,6 @@ VERSION = "1.3.0" HOMEPAGE = "https://github.com/sanderland/katrain" -CONFIG_MIN_VERSION = "1.2.0" # keep config files from this version +CONFIG_MIN_VERSION = "1.3.0" # keep config files from this version OUTPUT_ERROR = -1 OUTPUT_KATAGO_STDERR = -0.5 @@ -17,6 +17,7 @@ GAME_TYPES = [PLAYING_NORMAL, PLAYING_TEACHING] MODE_PLAY, MODE_ANALYZE = "play", "analyze" AI_DEFAULT = "ai:default" +AI_HANDICAP = "ai:handicap" AI_SCORELOSS = "ai:scoreloss" AI_WEIGHTED = "ai:p:weighted" AI_JIGO = "ai:jigo" @@ -30,13 +31,14 @@ AI_RANK = "ai:p:rank" AI_CONFIG_DEFAULT = AI_SCORELOSS -AI_STRATEGIES_ENGINE = [AI_DEFAULT, AI_SCORELOSS, AI_JIGO] +AI_STRATEGIES_ENGINE = [AI_DEFAULT, AI_HANDICAP, AI_SCORELOSS, AI_JIGO] AI_STRATEGIES_PICK = [AI_PICK, AI_LOCAL, AI_TENUKI, AI_INFLUENCE, AI_TERRITORY, AI_RANK] AI_STRATEGIES_POLICY = [AI_WEIGHTED, AI_POLICY] + AI_STRATEGIES_PICK AI_STRATEGIES = AI_STRATEGIES_ENGINE + AI_STRATEGIES_POLICY AI_STRATEGIES_RECOMMENDED_ORDER = [ AI_DEFAULT, AI_RANK, + AI_HANDICAP, AI_SCORELOSS, AI_POLICY, AI_WEIGHTED, @@ -49,20 +51,6 @@ AI_STRATEGIES_RECOMMENDED_ORDER = [ ] -AI_STRENGTH = { # not used - AI_DEFAULT: "9d", - AI_POLICY: "4d", - AI_JIGO: "?d", - AI_SCORELOSS: "5k", - AI_WEIGHTED: "5k", - AI_PICK: "8k", - AI_LOCAL: "5k", - AI_TENUKI: "8k", - AI_INFLUENCE: "8k", - AI_TERRITORY: "5k", - AI_RANK: "15k - 3d", -} - AI_OPTION_VALUES = { "kyu_rank": [(k, f"{k}[strength:kyu]") for k in range(15, 0, -1)] + [(k, f"{1-k}[strength:dan]") for k in range(0, -3, -1)], @@ -77,4 +65,6 @@ AI_OPTION_VALUES = { "stddev": [x / 2 for x in range(21)], "line_weight": range(0, 11), "threshold": [2, 2.5, 3, 3.5, 4, 4.5], + "automatic": "bool", + "pda": [(x / 10, f"{'W' if x<0 else 'B'}+{abs(x/10):.1f}") for x in range(-30, 31)], } diff --git a/katrain/core/engine.py b/katrain/core/engine.py index b0c81fc..69065be 100644 --- a/katrain/core/engine.py +++ b/katrain/core/engine.py @@ -5,7 +5,7 @@ import subprocess import threading import time import traceback -from typing import Callable, Optional +from typing import Callable, Optional, Dict from katrain.core.constants import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_KATAGO_STDERR from katrain.core.game_node import GameNode @@ -206,9 +206,11 @@ class KataGoEngine: time_limit=True, priority: int = 0, ownership: Optional[bool] = None, - next_move=None, + next_move: Optional[GameNode] = None, + extra_settings: Optional[Dict] = None, ): - moves = [m for node in analysis_node.nodes_from_root for m in node.move_with_placements] + moves = [m for node in analysis_node.nodes_from_root for m in node.moves] + initial_stones = analysis_node.root.placements if next_move: moves.append(next_move) if ownership is None: @@ -233,10 +235,11 @@ class KataGoEngine: "komi": analysis_node.komi, "boardXSize": size_x, "boardYSize": size_y, - "includeOwnership": ownership, + "includeOwnership": ownership and not next_move, "includePolicy": not next_move, + "initialStones": [[m.player, m.gtp()] for m in initial_stones], "moves": [[m.player, m.gtp()] for m in moves], - "overrideSettings": settings, + "overrideSettings": {**settings, **(extra_settings or {})}, } self.send_query(query, callback, error_callback, next_move) - analysis_node.analysis_visits_requested = max(analysis_node.analysis_visits_requested,visits) \ No newline at end of file + analysis_node.analysis_visits_requested = max(analysis_node.analysis_visits_requested, visits) diff --git a/katrain/gui/popups.py b/katrain/gui/popups.py index 23d3ee0..5a7b618 100644 --- a/katrain/gui/popups.py +++ b/katrain/gui/popups.py @@ -322,12 +322,16 @@ class ConfigAIPopup(QuickConfigGui): self.options_grid.add_widget(DescriptionLabel(text=k, size_hint_x=0.25)) if k in AI_OPTION_VALUES: values = AI_OPTION_VALUES[k] - 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}") - widget.set_value(v) + if values == "bool": + widget = LabelledCheckBox(input_property=f"ai/{strategy}/{k}") + widget.active = v + 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}") + widget.set_value(v) self.options_grid.add_widget(wrap_anchor(widget)) else: self.options_grid.add_widget( diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo index 8376848..78189a9 100644 Binary files a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po index eaa954a..fc88241 100644 --- a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po @@ -584,3 +584,15 @@ msgstr "Re-analyze entire game" #. TODO msgid "analysis:continuous" msgstr "Continuously analyze" + +#. TODO +msgid "ai:handicap" +msgstr "KataGo Handicap" + +#. TODO +msgid "aihelp:handicap" +msgstr "" +"KataGo optimized for playing higher handicap games.The `pda` setting " +"corresponds to `playoutDoublingAdvantage` in KataGo, from black's " +"perspective.When `automatic` is set, KaTrain will find a suitable value " +"automatically." diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo index bcfc2c6..cc9fac0 100644 Binary files a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po index cbab7b4..c7548c4 100644 --- a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po @@ -627,3 +627,15 @@ msgstr "Re-analyze entire game" #. TODO msgid "analysis:continuous" msgstr "Continuously analyze" + +#. TODO +msgid "ai:handicap" +msgstr "KataGo Handicap" + +#. TODO +msgid "aihelp:handicap" +msgstr "" +"KataGo optimized for playing higher handicap games.The `pda` setting " +"corresponds to `playoutDoublingAdvantage` in KataGo, from black's " +"perspective.When `automatic` is set, KaTrain will find a suitable value " +"automatically." diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo index c6ca5ca..8090a20 100644 Binary files a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po index 518a233..76c75cb 100644 --- a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po @@ -518,6 +518,16 @@ msgstr "" "in the general settings `engine` section and engine configuration file. No " "options are available here." +msgid "ai:handicap" +msgstr "KataGo Handicap" + +msgid "aihelp:handicap" +msgstr "" +"KataGo optimized for playing higher handicap games.The `pda` setting " +"corresponds to `playoutDoublingAdvantage` in KataGo, from black's " +"perspective. When `automatic` is set, KaTrain will find a suitable value " +"automatically." + msgid "ai:jigo" msgstr "KataJigo" diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo index 74e83df..aad4b02 100644 Binary files a/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/es/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po index 0ee6641..50d8e35 100644 --- a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po @@ -622,3 +622,15 @@ msgstr "Re-analyze entire game" #. TODO msgid "analysis:continuous" msgstr "Continuously analyze" + +#. TODO +msgid "ai:handicap" +msgstr "KataGo Handicap" + +#. TODO +msgid "aihelp:handicap" +msgstr "" +"KataGo optimized for playing higher handicap games.The `pda` setting " +"corresponds to `playoutDoublingAdvantage` in KataGo, from black's " +"perspective.When `automatic` is set, KaTrain will find a suitable value " +"automatically." diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo index 145fba7..6a4ecda 100644 Binary files a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po index 7e66bcf..162db90 100644 --- a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po @@ -661,3 +661,15 @@ msgstr "Re-analyze entire game" #. TODO msgid "analysis:continuous" msgstr "Continuously analyze" + +#. TODO +msgid "ai:handicap" +msgstr "KataGo Handicap" + +#. TODO +msgid "aihelp:handicap" +msgstr "" +"KataGo optimized for playing higher handicap games.The `pda` setting " +"corresponds to `playoutDoublingAdvantage` in KataGo, from black's " +"perspective.When `automatic` is set, KaTrain will find a suitable value " +"automatically." diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo index 3459326..06e6b88 100644 Binary files a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po index 9ce8469..5f1eedd 100644 --- a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po @@ -599,3 +599,15 @@ msgstr "Re-analyze entire game" #. TODO msgid "analysis:continuous" msgstr "Continuously analyze" + +#. TODO +msgid "ai:handicap" +msgstr "KataGo Handicap" + +#. TODO +msgid "aihelp:handicap" +msgstr "" +"KataGo optimized for playing higher handicap games.The `pda` setting " +"corresponds to `playoutDoublingAdvantage` in KataGo, from black's " +"perspective.When `automatic` is set, KaTrain will find a suitable value " +"automatically." diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo index 70212a2..1229229 100644 Binary files a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po index 5f6d215..b285349 100644 --- a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po @@ -624,3 +624,15 @@ msgstr "Re-analyze entire game" #. TODO msgid "analysis:continuous" msgstr "Continuously analyze" + +#. TODO +msgid "ai:handicap" +msgstr "KataGo Handicap" + +#. TODO +msgid "aihelp:handicap" +msgstr "" +"KataGo optimized for playing higher handicap games.The `pda` setting " +"corresponds to `playoutDoublingAdvantage` in KataGo, from black's " +"perspective.When `automatic` is set, KaTrain will find a suitable value " +"automatically." diff --git a/setup.py b/setup.py index 0519510..fb31b19 100644 --- a/setup.py +++ b/setup.py @@ -46,8 +46,7 @@ setup( "kivy_deps.glew;platform_system=='Windows'", "kivy_deps.sdl2;platform_system=='Windows'", "kivy_deps.gstreamer;platform_system=='Windows'", - "kivy==2.0.0rc2", # rc3 failing on mac - "ffpyplayer>=4.3.1", + "kivy==2.0.0rc2", # rc3 failing on mac "kivymd>=0.104.1", "screeninfo;platform_system!='Darwin'", # for screen resolution, has problems on macos ],