diff --git a/katrain/__main__.py b/katrain/__main__.py index ba081d9..7a5564e 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -351,7 +351,7 @@ class KaTrainGui(Screen, KaTrainBase): self.controls.timer.paused = True if not self.ai_settings_popup: self.ai_settings_popup = I18NPopup( - title_key="ai settings", size=[dp(600), dp(650)], content=ConfigAIPopup(self) + title_key="ai settings", size=[dp(750), dp(750)], content=ConfigAIPopup(self) ).__self__ self.ai_settings_popup.content.popup = self.ai_settings_popup self.ai_settings_popup.open() diff --git a/katrain/config.json b/katrain/config.json index 78094cb..5b7d1c9 100644 --- a/katrain/config.json +++ b/katrain/config.json @@ -92,7 +92,9 @@ "max_points_lost": 1.75, "settled_weight": 1.0, "opponent_fac": 0.5, - "min_visits": 3 + "min_visits": 3, + "attach_penalty": 1, + "tenuki_penalty": 0.5 }, "ai:p:weighted": { "weaken_fac": 1.25, diff --git a/katrain/core/ai.py b/katrain/core/ai.py index 3440d15..0183c12 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -18,7 +18,6 @@ from katrain.core.constants import ( AI_RANK, AI_SCORELOSS, AI_SCORELOSS_ELO, - AI_SIMPLE, AI_SIMPLE_OWNERSHIP, AI_STRATEGIES_PICK, AI_STRATEGIES_POLICY, @@ -205,13 +204,6 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, game.katrain.log(f"Error getting handicap-based move", OUTPUT_ERROR) ai_mode = AI_DEFAULT - if ai_mode == AI_SIMPLE: - simple_moves = ai_settings["simple_moves"] - simple_analysis = request_ai_analysis(game, cn, {"simpleMovesBias": simple_moves, "wideRootNoise": 0.10}) - if not simple_analysis: - game.katrain.log(f"Error getting simple-biased 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) @@ -335,18 +327,6 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, candidate_ai_moves = cn.candidate_moves if ai_mode == AI_HANDICAP: candidate_ai_moves = handicap_analysis["moveInfos"] - if ai_mode == AI_SIMPLE: - candidate_ai_moves = simple_analysis["moveInfos"] - for data in candidate_ai_moves: - print( - "{order} {move}: visits {visits} utility {util} utilityLcb {lcb}".format( - visits=data["visits"], - order=data["order"], - move=data["move"], - util=data["utility"], - lcb=data["utilityLcb"], - ) - ) top_cand = Move.from_gtp(candidate_ai_moves[0]["move"], player=cn.next_player) if top_cand.is_pass and ai_mode not in [ @@ -377,31 +357,54 @@ 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." elif ai_mode == AI_SIMPLE_OWNERSHIP: - - def settledness(d, player_fac): - return sum([abs(o) for o in d["ownership"] if player_fac * o > 0]) - + last_player_stones = {s.coords for s in game.stones if s.player == cn.player} next_player_sign = cn.player_sign(cn.next_player) + + def settledness(d, player_sign): + return sum([abs(o) for o in d["ownership"] if player_sign * o > 0]) + + def is_attachment(d): + return any( + (d.coords[0] + dx, d.coords[1] + dy) in last_player_stones + for dx in [-1, 0, 1] + for dy in [-1, 0, 1] + if abs(dx) + abs(dy) == 1 + ) + + def is_tenuki(d): + return not any( + not node + or not node.move + or node.move.is_pass + or max(abs(last_c - cand_c) for last_c, cand_c in zip(node.move.coords, d.coords)) < 5 + for node in [cn, cn.parent] + ) + moves_with_settledness = sorted( [ ( - Move.from_gtp(d["move"], player=cn.next_player), + move, settledness(d, next_player_sign), settledness(d, -next_player_sign), + is_attachment(move), + is_tenuki(move), d, ) for d in candidate_ai_moves if d["pointsLost"] < ai_settings["max_points_lost"] and "ownership" in d and (d["order"] < 5 or d["visits"] >= ai_settings.get("min_visits", 1)) + for move in [Move.from_gtp(d["move"], player=cn.next_player)] ], - key=lambda t: t[3]["pointsLost"] + key=lambda t: t[5]["pointsLost"] + + ai_settings["attach_penalty"] * t[3] + + ai_settings["tenuki_penalty"] * t[4] - ai_settings["settled_weight"] * (t[1] + ai_settings["opponent_fac"] * t[2]), ) if moves_with_settledness: cands = [ - f"{move.gtp()} ({d['pointsLost']:.1f} pt lost, {d['visits']} visits, {settled:.1f} settledness, {oppsettled:.1f} opponent settledness)" - for move, settled, oppsettled, d in moves_with_settledness[:5] + f"{move.gtp()} ({d['pointsLost']:.1f} pt lost, {d['visits']} visits, {settled:.1f} settledness, {oppsettled:.1f} opponent settledness{', attachment' if isattach else ''}{', tenuki' if istenuki else ''})" + for move, settled, oppsettled, isattach, istenuki, d in moves_with_settledness[:5] ] ai_thoughts += f"Simple ownership strategy. Top 5 Candidates {', '.join(cands)} " aimove = moves_with_settledness[0][0] @@ -411,14 +414,12 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, ) aimove = top_cand else: - if ai_mode not in [AI_DEFAULT, AI_HANDICAP, AI_SIMPLE]: + 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 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'])}" - elif ai_mode == AI_SIMPLE: - ai_thoughts += f"Simple moves strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move. " 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) diff --git a/katrain/core/constants.py b/katrain/core/constants.py index 540fde0..c31a41d 100644 --- a/katrain/core/constants.py +++ b/katrain/core/constants.py @@ -33,12 +33,11 @@ AI_TENUKI = "ai:p:tenuki" AI_INFLUENCE = "ai:p:influence" AI_TERRITORY = "ai:p:territory" AI_RANK = "ai:p:rank" -AI_SIMPLE = "ai:disabled" AI_SIMPLE_OWNERSHIP = "ai:simple" AI_CONFIG_DEFAULT = AI_RANK -AI_STRATEGIES_ENGINE = [AI_DEFAULT, AI_HANDICAP, AI_SIMPLE, 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 @@ -90,11 +89,13 @@ AI_OPTION_VALUES = { "automatic": "bool", "pda": [(x / 10, f"{'W' if x<0 else 'B'}+{abs(x/10):.1f}") for x in range(-30, 31)], "max_points_lost": [x / 10 for x in range(51)], - "settled_weight": [x / 4 for x in range(1, 17)], + "settled_weight": [x / 4 for x in range(0, 17)], "opponent_fac": [x / 10 for x in range(-20, 11)], - "min_visits": range(1,10) + "min_visits": range(1, 10), + "attach_penalty": [x / 10 for x in range(-10, 51)], + "tenuki_penalty": [x / 10 for x in range(-10, 51)], } -AI_KEY_PROPERTIES = {"kyu_rank", "strength", "weaken_fac", "pick_frac", "pick_n", "automatic"} +AI_KEY_PROPERTIES = {"kyu_rank", "strength", "weaken_fac", "pick_frac", "pick_n", "automatic","max_points_lost","min_visits"} CALIBRATED_RANK_ELO = [ diff --git a/katrain/core/game_node.py b/katrain/core/game_node.py index 0a73a3b..3e5a9c0 100644 --- a/katrain/core/game_node.py +++ b/katrain/core/game_node.py @@ -211,11 +211,12 @@ class GameNode(SGFNode): top_pv = self.analysis_ready and self.candidate_moves[0].get("pv") if top_pv: text += i18n._("Info:undo predicted PV").format(pv=f"{self.next_player}{' '.join(top_pv)}") + "\n" - if self.ai_thoughts and (sgf or details): - text += "\n" + i18n._("Info:AI thoughts").format(thoughts=self.ai_thoughts) else: text = i18n._("No analysis available") if sgf else i18n._("Analyzing move...") + if self.ai_thoughts and (sgf or details): + text += "\n" + i18n._("Info:AI thoughts").format(thoughts=self.ai_thoughts) + if "C" in self.properties: text += "\n[u]SGF Comments:[/u]\n" + "\n".join(self.properties["C"]) diff --git a/katrain/gui/popups.py b/katrain/gui/popups.py index f8808c0..0663a35 100644 --- a/katrain/gui/popups.py +++ b/katrain/gui/popups.py @@ -342,7 +342,7 @@ class ConfigAIPopup(QuickConfigGui): 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.25)) + 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": diff --git a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo index 36b6916..feb3a9a 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 91de790..dadb5a8 100644 --- a/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/cn/LC_MESSAGES/katrain.po @@ -652,3 +652,20 @@ msgstr "Think for at least {num} seconds before playing." #. TODO - in minutes msgid "main time" msgstr "Main time (min.)" + +#. TODO - TODO +msgid "aihelp:simple" +msgstr "" +"Plays moves that lead to simplifying the board state. Of moves that lose at " +"most 'max_points_lost' points and with at least 'min_visits', prefer moves " +"that lose fewer points, settle own territory more (with importance " +"settled_weight), settle opponent's territory more (importance settled_weight" +" * opponent fac), avoid tenuki (more than 5 spaces, importance " +"tenuki_penalty), and avoid attachments (importance attachment_penalty). " +"Weights can be negative to make it complicate the game instead, andwide root" +" noise (in general settings) can be used to further play according to the " +"style at the cost of strength. " + +#. TODO +msgid "ai:simple" +msgstr "Simple Style" diff --git a/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/de/LC_MESSAGES/katrain.mo index 34b36de..461ecc4 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 93dc91b..fbf587f 100644 --- a/katrain/i18n/locales/de/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/de/LC_MESSAGES/katrain.po @@ -696,8 +696,25 @@ msgstr "Minimal time use in byo-yomi (s)" #. TODO msgid "move too fast" -msgstr "Think for at least %d seconds before playing." +msgstr "Think for at least {num} seconds before playing." #. TODO - in minutes msgid "main time" msgstr "Main time (min.)" + +#. TODO - TODO +msgid "aihelp:simple" +msgstr "" +"Plays moves that lead to simplifying the board state. Of moves that lose at " +"most 'max_points_lost' points and with at least 'min_visits', prefer moves " +"that lose fewer points, settle own territory more (with importance " +"settled_weight), settle opponent's territory more (importance settled_weight" +" * opponent fac), avoid tenuki (more than 5 spaces, importance " +"tenuki_penalty), and avoid attachments (importance attachment_penalty). " +"Weights can be negative to make it complicate the game instead, andwide root" +" noise (in general settings) can be used to further play according to the " +"style at the cost of strength. " + +#. TODO +msgid "ai:simple" +msgstr "Simple Style" diff --git a/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/en/LC_MESSAGES/katrain.mo index 8ad3bbd..93f3a28 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 92f8f8f..ba01c0c 100644 --- a/katrain/i18n/locales/en/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/en/LC_MESSAGES/katrain.po @@ -571,6 +571,22 @@ msgstr "" "perspective. When `automatic` is set, KaTrain will find a suitable value " "automatically." +msgid "ai:simple" +msgstr "Simple Style" + +#. TODO - TODO +msgid "aihelp:simple" +msgstr "" +"Plays moves that lead to simplifying the board state. Of moves that lose at " +"most 'max_points_lost' points and with at least 'min_visits', prefer moves " +"that lose fewer points, settle own territory more (with importance " +"settled_weight), settle opponent's territory more (importance settled_weight" +" * opponent fac), avoid tenuki (more than 5 spaces, importance " +"tenuki_penalty), and avoid attachments (importance attachment_penalty). " +"Weights can be negative to make it complicate the game instead, andwide root" +" noise (in general settings) can be used to further play according to the " +"style at the cost of strength. " + 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 eebeda8..06b9cc1 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 d7f32a2..d062ad4 100644 --- a/katrain/i18n/locales/es/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/es/LC_MESSAGES/katrain.po @@ -709,3 +709,11 @@ msgstr "Think for at least {num} seconds before playing." #. TODO - in minutes msgid "main time" msgstr "Main time (min.)" + +#. TODO +msgid "aihelp:simple" +msgstr "Plays moves that lead to simplifying the board state." + +#. TODO +msgid "ai:simple" +msgstr "Simple Style" diff --git a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo index 09a9c96..28c2e2d 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 28f1c6c..5f682fa 100644 --- a/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/fr/LC_MESSAGES/katrain.po @@ -733,3 +733,20 @@ msgstr "Think for at least {num} seconds before playing." #. TODO - in minutes msgid "main time" msgstr "Main time (min.)" + +#. TODO - TODO +msgid "aihelp:simple" +msgstr "" +"Plays moves that lead to simplifying the board state. Of moves that lose at " +"most 'max_points_lost' points and with at least 'min_visits', prefer moves " +"that lose fewer points, settle own territory more (with importance " +"settled_weight), settle opponent's territory more (importance settled_weight" +" * opponent fac), avoid tenuki (more than 5 spaces, importance " +"tenuki_penalty), and avoid attachments (importance attachment_penalty). " +"Weights can be negative to make it complicate the game instead, andwide root" +" noise (in general settings) can be used to further play according to the " +"style at the cost of strength. " + +#. TODO +msgid "ai:simple" +msgstr "Simple Style" diff --git a/katrain/i18n/locales/jp/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/jp/LC_MESSAGES/katrain.mo index 206ae2d..2c3236d 100644 Binary files a/katrain/i18n/locales/jp/LC_MESSAGES/katrain.mo and b/katrain/i18n/locales/jp/LC_MESSAGES/katrain.mo differ diff --git a/katrain/i18n/locales/jp/LC_MESSAGES/katrain.po b/katrain/i18n/locales/jp/LC_MESSAGES/katrain.po index b4b9b4f..fa24839 100644 --- a/katrain/i18n/locales/jp/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/jp/LC_MESSAGES/katrain.po @@ -691,3 +691,20 @@ msgstr "Think for at least {num} seconds before playing." #. TODO - in minutes msgid "main time" msgstr "Main time (min.)" + +#. TODO - TODO +msgid "aihelp:simple" +msgstr "" +"Plays moves that lead to simplifying the board state. Of moves that lose at " +"most 'max_points_lost' points and with at least 'min_visits', prefer moves " +"that lose fewer points, settle own territory more (with importance " +"settled_weight), settle opponent's territory more (importance settled_weight" +" * opponent fac), avoid tenuki (more than 5 spaces, importance " +"tenuki_penalty), and avoid attachments (importance attachment_penalty). " +"Weights can be negative to make it complicate the game instead, andwide root" +" noise (in general settings) can be used to further play according to the " +"style at the cost of strength. " + +#. TODO +msgid "ai:simple" +msgstr "Simple Style" diff --git a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.mo index 6739fb1..379b5d6 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 0e89d0b..b043a7b 100644 --- a/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ko/LC_MESSAGES/katrain.po @@ -666,3 +666,17 @@ msgstr "Think for at least {num} seconds before playing." #. TODO - in minutes msgid "main time" msgstr "Main time (min.)" + +#. TODO +msgid "aihelp:simple" +msgstr "" +"Plays moves that lead to simplifying the board state. Of moves that lose at " +"most 'max_points_lost' points and with at least 'min_visits', prefer moves " +"that lose fewer points, settle own territory more (with importance " +"settled_weight), settle opponent's territory more (importance settled_weight" +" * opponent fac), and avoid attachments (importance attachment_penalty). " +"Weights can be negative to make it complicate the game instead. " + +#. TODO +msgid "ai:simple" +msgstr "Simple Style" diff --git a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.mo index b62d3c0..0ce6ed0 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 cc87c03..082bcff 100644 --- a/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po +++ b/katrain/i18n/locales/ru/LC_MESSAGES/katrain.po @@ -696,3 +696,20 @@ msgstr "Think for at least {num} seconds before playing." #. TODO - in minutes msgid "main time" msgstr "Main time (min.)" + +#. TODO - TODO +msgid "aihelp:simple" +msgstr "" +"Plays moves that lead to simplifying the board state. Of moves that lose at " +"most 'max_points_lost' points and with at least 'min_visits', prefer moves " +"that lose fewer points, settle own territory more (with importance " +"settled_weight), settle opponent's territory more (importance settled_weight" +" * opponent fac), avoid tenuki (more than 5 spaces, importance " +"tenuki_penalty), and avoid attachments (importance attachment_penalty). " +"Weights can be negative to make it complicate the game instead, andwide root" +" noise (in general settings) can be used to further play according to the " +"style at the cost of strength. " + +#. TODO +msgid "ai:simple" +msgstr "Simple Style" diff --git a/katrain/popups.kv b/katrain/popups.kv index 83c44b4..ad51904 100644 --- a/katrain/popups.kv +++ b/katrain/popups.kv @@ -469,7 +469,7 @@ textbox: textbox SelectionSlider: id: slider - size_hint: 2,1 + size_hint: 2.25,1 values: root.values on_change: textbox.text = str(slider.value) track_color: LIGHTGREY