diff --git a/katrain/config.json b/katrain/config.json index 1a85486..e643f7e 100644 --- a/katrain/config.json +++ b/katrain/config.json @@ -17,7 +17,7 @@ "anim_pv_time": 0.5, "debug_level": 0, "lang": "en", - "version": "1.6.0" + "version": "1.7.0" }, "timer": { "byo_length": 30, @@ -69,7 +69,8 @@ ], "low_visits": 25, "eval_off_show_last": 3, - "text_point_loss": true, + "top_moves_show": "delta_score", + "top_moves_show_secondary": "visits", "eval_show_ai": true, "lock_ai": false }, diff --git a/katrain/core/constants.py b/katrain/core/constants.py index b40cbd6..7b061eb 100644 --- a/katrain/core/constants.py +++ b/katrain/core/constants.py @@ -1,6 +1,6 @@ -VERSION = "1.6.0" +VERSION = "1.7.0" HOMEPAGE = "https://github.com/sanderland/katrain" -CONFIG_MIN_VERSION = "1.6.0" # keep config files from this version +CONFIG_MIN_VERSION = "1.7.0" # keep config files from this version OUTPUT_ERROR = -1 OUTPUT_KATAGO_STDERR = -0.5 @@ -220,3 +220,13 @@ AI_PICK_ELO_GRID = [ [514.0, 670.0, 870.0, 1128.0, 1305.0, 1550.0, 1700.0, 1700.0], ], ] + + +TOP_MOVE_DELTA_SCORE = "delta score" +TOP_MOVE_SCORE = "score" +TOP_MOVE_DELTA_WINRATE = "delta winrate" +TOP_MOVE_WINRATE = "winrate" +TOP_MOVE_VISITS = "visits" +TOP_MOVE_NOTHING = "" + +TOP_MOVE_OPTIONS = [TOP_MOVE_DELTA_SCORE, TOP_MOVE_SCORE, TOP_MOVE_DELTA_WINRATE, TOP_MOVE_WINRATE, TOP_MOVE_VISITS, TOP_MOVE_NOTHING] diff --git a/katrain/core/game_node.py b/katrain/core/game_node.py index de7bc60..ca6257a 100644 --- a/katrain/core/game_node.py +++ b/katrain/core/game_node.py @@ -276,6 +276,7 @@ class GameNode(SGFNode): { **self.analysis["root"], "pointsLost": 0, + "winrateLost": 0, "order": 0, "move": top_polmove.gtp(), "pv": [top_polmove.gtp()], @@ -283,10 +284,15 @@ class GameNode(SGFNode): ] # single visit -> go by policy/root root_score = self.analysis["root"]["scoreLead"] + root_winrate = self.analysis["root"]["winrate"] move_dicts = list(self.analysis["moves"].values()) # prevent incoming analysis from causing crash return sorted( [ - {"pointsLost": self.player_sign(self.next_player) * (root_score - d["scoreLead"]), **d} + { + "pointsLost": self.player_sign(self.next_player) * (root_score - d["scoreLead"]), + "winrateLost": self.player_sign(self.next_player) * (root_winrate - d["winrate"]), + **d, + } for d in move_dicts ], key=lambda d: (d["order"], d["pointsLost"]), diff --git a/katrain/core/utils.py b/katrain/core/utils.py index 368270e..f053127 100644 --- a/katrain/core/utils.py +++ b/katrain/core/utils.py @@ -53,3 +53,14 @@ def find_package_resource(path, silent_errors=False): return os.path.join(PATHS["PACKAGE"], path.replace("katrain\\", "katrain/").replace("katrain/", "")) else: return os.path.abspath(os.path.expanduser(path)) # absolute path + + +def format_visits(n): + if n < 1000: + return str(n) + if n < 1e6: + return f"{n/1000:.1f}k" + if n < 1e6: + return f"{n/1000:.0f}k" + return f"{n/1e6:.0f}k" + diff --git a/katrain/gui/badukpan.py b/katrain/gui/badukpan.py index 0c921f2..92fc40c 100644 --- a/katrain/gui/badukpan.py +++ b/katrain/gui/badukpan.py @@ -17,10 +17,22 @@ from kivymd.app import MDApp from kivymd.uix.boxlayout import MDBoxLayout from kivymd.uix.floatlayout import MDFloatLayout -from katrain.core.constants import MODE_PLAY, OUTPUT_DEBUG, STATUS_TEACHING, OUTPUT_EXTRA_DEBUG +from katrain.core.constants import ( + MODE_PLAY, + OUTPUT_DEBUG, + STATUS_TEACHING, + OUTPUT_EXTRA_DEBUG, + TOP_MOVE_OPTIONS, + TOP_MOVE_NOTHING, + TOP_MOVE_DELTA_SCORE, + TOP_MOVE_SCORE, + TOP_MOVE_WINRATE, + TOP_MOVE_DELTA_WINRATE, + TOP_MOVE_VISITS, +) from katrain.core.game import Move from katrain.core.lang import i18n -from katrain.core.utils import evaluation_class, var_to_grid +from katrain.core.utils import evaluation_class, var_to_grid, format_visits from katrain.gui.kivyutils import BackgroundMixin, draw_circle, draw_text from katrain.gui.popups import I18NPopup, ReAnalyzeGamePopup from katrain.gui.style import * @@ -446,6 +458,14 @@ class BadukPanWidget(Widget): top_move_coords = None if hint_moves: low_visits_threshold = katrain.config("trainer/low_visits", 25) + top_moves_show = [ + opt + for opt in [ + katrain.config("trainer/top_moves_show"), + katrain.config("trainer/top_moves_show_secondary"), + ] + if opt in TOP_MOVE_OPTIONS and opt != TOP_MOVE_NOTHING + ] for move_dict in hint_moves: move = Move.from_gtp(move_dict["move"]) if move.coords is not None: @@ -471,18 +491,34 @@ class BadukPanWidget(Widget): size=(2 * evalsize, 2 * evalsize), source="img/topmove.png", ) - if self.trainer_config["text_point_loss"] and text_on: - if -0.05 < move_dict["pointsLost"] < 0.05: - ptloss_text = "0.0" + if text_on and top_moves_show: + keys = {"size": self.grid_size / 3, "smallsize": self.grid_size / 3.5} + if len(top_moves_show) == 1: + fmt = "[size={size:.0f}]{" + top_moves_show[0] + "}[/size]" else: - ptloss_text = f"{-move_dict['pointsLost']:+.1f}" - sizefac = 1 + fmt = ( + "[size={size:.0f}]{" + + top_moves_show[0] + + "}[/size]\n[size={smallsize:.0f}]{" + + top_moves_show[1] + + "}[/size]" + ) + + + keys[TOP_MOVE_DELTA_SCORE] = ( + "0.0" if -0.05 < move_dict["pointsLost"] < 0.05 else f"{-move_dict['pointsLost']:+.1f}" + ) + keys[TOP_MOVE_SCORE] = move_dict["scoreLead"] + keys[TOP_MOVE_WINRATE] = f"{move_dict['winrate']*100:.1f}" + keys[TOP_MOVE_DELTA_WINRATE] = f"{-move_dict['winrateLost']:+.1%}" + keys[TOP_MOVE_VISITS] = format_visits(move_dict["visits"]) + print(fmt,keys) Color(*BLACK) draw_text( pos=(self.gridpos_x[move.coords[0]], self.gridpos_y[move.coords[1]]), - text=ptloss_text, - font_size=self.grid_size * sizefac / 2.5, + text=fmt.format(**keys), font_name="Roboto", + markup=True, line_height=0.8, halign='center' ) if engine_best_move: diff --git a/katrain/gui/kivyutils.py b/katrain/gui/kivyutils.py index 703ac55..35f641c 100644 --- a/katrain/gui/kivyutils.py +++ b/katrain/gui/kivyutils.py @@ -1,5 +1,7 @@ from kivy.clock import Clock from kivy.core.text import Label as CoreLabel +from kivy.core.text.markup import MarkupLabel as CoreMarkupLabel + from kivy.core.window import Window from kivy.graphics import * from kivy.properties import ( @@ -219,6 +221,13 @@ class KeyValueSpinner(Spinner): self.build_values() self.bind(size=self.update_dropdown_props, pos=self.update_dropdown_props, value_refs=self.build_values) + @property + def input_value(self): + try: + return self.value_refs[self.selected_index] + except: + return "" + @property def selected(self): try: @@ -601,8 +610,9 @@ class ScrollableLabel(ScrollView, BackgroundMixin): pass -def draw_text(pos, text, font_name=None, **kw): - label = CoreLabel(text=text, bold=True, font_name=font_name or i18n.font_name, **kw) # +def draw_text(pos, text, font_name=None, markup=False, **kw): + label_cls = CoreMarkupLabel if markup else CoreLabel + label = label_cls(text=text, bold=True, font_name=font_name or i18n.font_name, **kw) label.refresh() Rectangle( texture=label.texture, diff --git a/katrain/popups.kv b/katrain/popups.kv index 59fd232..1d14b7e 100644 --- a/katrain/popups.kv +++ b/katrain/popups.kv @@ -16,6 +16,8 @@ #:import LIGHTGREY katrain.gui.style.LIGHTGREY #:import TEXT_COLOR katrain.gui.style.TEXT_COLOR +#:import TOP_MOVE_OPTIONS katrain.core.constants.TOP_MOVE_OPTIONS + # MDTextField: # hint_text: "Helper text on focus" @@ -295,17 +297,34 @@ id: themes_spinner DescriptionLabel: font_size: DESC_FONT_SIZE * 0.8 - text: i18n._("show last n dots") - AnchorLayout: - LabelledIntInput: - size_hint_x: 0.15 - input_property: "trainer/eval_off_show_last" + text: i18n._("analysis:topmoves").replace('\n',' ') + BoxLayout: + orientation: 'horizontal' + LabelledSpinner: + id: top_moves_show + value_refs: TOP_MOVE_OPTIONS + input_property: "trainer/top_moves_show" + Label: + size_hint: 0.15, 1 + text: '&' + LabelledSpinner: + id: top_moves_show_secondary + value_refs: TOP_MOVE_OPTIONS + input_property: "trainer/top_moves_show_secondary" DescriptionLabel: font_size: DESC_FONT_SIZE * 0.8 text: i18n._("show point loss as text") - AnchorLayout: - LabelledCheckBox: - input_property: "trainer/text_point_loss" + BoxLayout: + Label: + text: '>' + size_hint: 0.1, 1 + AnchorLayout: + LabelledIntInput: + input_property: "trainer/low_visits" + Label: + size_hint: 0.1, 1 + text: i18n._('visits') + Label: DescriptionLabel: font_size: DESC_FONT_SIZE * 0.8 text: i18n._("show ai dots") diff --git a/setup.py b/setup.py index 2e114ee..9cdf005 100644 --- a/setup.py +++ b/setup.py @@ -57,10 +57,11 @@ setup( python_requires=">=3.6, <4", entry_points={"console_scripts": ["katrain=katrain.__main__:run_app"]}, classifiers=[ - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3", + "Topic :: Games/Entertainment :: Board Games", ], packages=packages, package_data=package_data,