analyze game popup
This commit is contained in:
1 parent
7a4a8e268b
commit
d6be82ecba
26 files changed
+147
-30
No files matched your search
@@ -34,7 +34,7 @@ for lang in locales:
|
||||
print("duplicate", entry.msgid, "in", lang)
|
||||
errors = True
|
||||
lang_to_strings[lang].add(entry.msgid)
|
||||
if todos[lang] and any('todo' in a for a in sys.argv):
|
||||
if todos[lang] and any("todo" in a for a in sys.argv):
|
||||
print(f"========== {lang} has {len(todos[lang])} TODO entries ========== ")
|
||||
for item in todos[lang]:
|
||||
print(item)
|
||||
@@ -73,16 +73,27 @@ for lang in locales:
|
||||
errors = True
|
||||
|
||||
for msgid, lang_entries in strings_to_langs.items():
|
||||
if lang in lang_entries and 'TODO' in lang_entries[lang].comment:
|
||||
if lang in lang_entries and "TODO" in lang_entries[lang].comment:
|
||||
if any(e.msgstr == lang_entries[lang].msgstr for l, e in lang_entries.items() if l != lang):
|
||||
todo_comment = f"TODO - {lang_entries[DEFAULT_LANG].comment}" if lang_entries[DEFAULT_LANG].comment else 'TODO' # update todo
|
||||
if lang_entries[lang].msgstr != lang_entries[DEFAULT_LANG].msgstr or lang_entries[lang].comment.replace('\n',' ')!=todo_comment:
|
||||
print([lang_entries[lang].msgstr, lang_entries[DEFAULT_LANG].msgstr, lang_entries[lang].comment, todo_comment])
|
||||
todo_comment = (
|
||||
f"TODO - {lang_entries[DEFAULT_LANG].comment}" if lang_entries[DEFAULT_LANG].comment else "TODO"
|
||||
) # update todo
|
||||
if (
|
||||
lang_entries[lang].msgstr != lang_entries[DEFAULT_LANG].msgstr
|
||||
or lang_entries[lang].comment.replace("\n", " ") != todo_comment
|
||||
):
|
||||
print(
|
||||
[
|
||||
lang_entries[lang].msgstr,
|
||||
lang_entries[DEFAULT_LANG].msgstr,
|
||||
lang_entries[lang].comment,
|
||||
todo_comment,
|
||||
]
|
||||
)
|
||||
lang_entries[lang].msgstr = lang_entries[DEFAULT_LANG].msgstr # update
|
||||
lang_entries[lang].comment = todo_comment
|
||||
print(f"{lang}/{msgid} todo entry updated")
|
||||
|
||||
|
||||
po[lang].save(pofile[lang])
|
||||
mofile = pofile[lang].replace(".po", ".mo")
|
||||
po[lang].save_as_mofile(mofile)
|
||||
|
||||
+1
-2
@@ -413,7 +413,6 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
"a": ("analyze-extra", "extra"),
|
||||
"s": ("analyze-extra", "equalize"),
|
||||
"d": ("analyze-extra", "sweep"),
|
||||
"f": ("analyze-extra", "game"),
|
||||
"p": ("play", None),
|
||||
"down": ("switch-branch", 1),
|
||||
"up": ("switch-branch", -1),
|
||||
@@ -497,7 +496,7 @@ class KaTrainApp(MDApp):
|
||||
Builder.load_file(kv_file)
|
||||
|
||||
Window.bind(on_request_close=self.on_request_close)
|
||||
Window.bind(on_dropfile=lambda win, file: self.gui.load_sgf_file(file.decode('utf8')))
|
||||
Window.bind(on_dropfile=lambda win, file: self.gui.load_sgf_file(file.decode("utf8")))
|
||||
|
||||
self.gui = KaTrainGui()
|
||||
Builder.load_file(popup_kv_file)
|
||||
|
||||
+4
-2
@@ -22,7 +22,8 @@ from katrain.core.constants import (
|
||||
AI_PICK,
|
||||
AI_RANK,
|
||||
AI_HANDICAP,
|
||||
OUTPUT_ERROR, AI_STRENGTH,
|
||||
OUTPUT_ERROR,
|
||||
AI_STRENGTH,
|
||||
)
|
||||
from katrain.core.game import Game, GameNode, Move
|
||||
|
||||
@@ -31,13 +32,14 @@ def ai_rank_estimation(strategy,settings) -> Tuple[int,bool]:
|
||||
if strategy in [AI_DEFAULT, AI_HANDICAP, AI_JIGO]:
|
||||
return 9, True
|
||||
if strategy == AI_RANK:
|
||||
return 1-settings['kyu_rank'], True
|
||||
return 1 - settings["kyu_rank"], True
|
||||
if strategy == AI_WEIGHTED:
|
||||
dan_rank = -4
|
||||
return dan_rank, True
|
||||
else:
|
||||
return AI_STRENGTH[strategy], False
|
||||
|
||||
|
||||
def weighted_selection_without_replacement(items: List[Tuple], pick_n: int) -> List[Tuple]:
|
||||
"""For a list of tuples where the second element is a weight, returns random items with those weights, without replacement."""
|
||||
elt = [(math.log(random.random()) / item[1], item) for item in items] # magic
|
||||
|
||||
@@ -58,7 +58,7 @@ AI_STRATEGIES_RECOMMENDED_ORDER = [
|
||||
AI_STRENGTH = { # dan ranks
|
||||
AI_DEFAULT: 9,
|
||||
AI_POLICY: 4,
|
||||
AI_JIGO: float('nan'),
|
||||
AI_JIGO: float("nan"),
|
||||
AI_SCORELOSS: -4,
|
||||
AI_WEIGHTED: -4,
|
||||
AI_PICK: -7,
|
||||
@@ -66,7 +66,7 @@ AI_STRENGTH = { # dan ranks
|
||||
AI_TENUKI: -7,
|
||||
AI_INFLUENCE: -7,
|
||||
AI_TERRITORY: -7,
|
||||
AI_RANK: float('nan')
|
||||
AI_RANK: float("nan"),
|
||||
}
|
||||
|
||||
AI_OPTION_VALUES = {
|
||||
|
||||
@@ -184,8 +184,6 @@ class Game:
|
||||
ix = (ordered_children.index(cn) + len(ordered_children) + direction) % len(ordered_children)
|
||||
self.set_current_node(ordered_children[ix])
|
||||
|
||||
|
||||
|
||||
@property
|
||||
def board_size(self):
|
||||
return self.root.board_size
|
||||
@@ -310,6 +308,9 @@ class Game:
|
||||
return
|
||||
if mode == "game":
|
||||
nodes = self.root.nodes_in_tree
|
||||
if "visits" in kwargs:
|
||||
visits = kwargs["visits"]
|
||||
else:
|
||||
min_visits = min(node.analysis_visits_requested for node in nodes)
|
||||
visits = min_visits + engine.config["max_visits"]
|
||||
for node in nodes:
|
||||
|
||||
@@ -331,7 +331,7 @@ class SGF:
|
||||
|
||||
@classmethod
|
||||
def parse_file(cls, filename, encoding=None) -> SGFNode:
|
||||
is_gib = filename.lower().endswith('.gib')
|
||||
is_gib = filename.lower().endswith(".gib")
|
||||
|
||||
"""Parse a file as SGF, encoding will be detected if not given."""
|
||||
with open(filename, "rb") as f:
|
||||
@@ -344,7 +344,7 @@ class SGF:
|
||||
else:
|
||||
encoding = "ISO-8859-1" # default
|
||||
else:
|
||||
encoding = 'utf8' # ?
|
||||
encoding = "utf8" # ?
|
||||
decoded = bin_contents.decode(encoding=encoding, errors="ignore")
|
||||
if is_gib:
|
||||
return cls.parse_gib(decoded)
|
||||
@@ -382,7 +382,6 @@ class SGF:
|
||||
raise ParseError(f"Parse Error: unexpected character at {self.contents[self.ix:self.ix+25]}")
|
||||
raise ParseError("Parse Error: expected ')' at end of input.")
|
||||
|
||||
|
||||
# GIB parser adapted from https://github.com/fohristiwhirl/gofish/
|
||||
@classmethod
|
||||
def parse_gib(cls, gib):
|
||||
|
||||
+3
-2
@@ -553,6 +553,7 @@
|
||||
graph.show_graphs(args[1])
|
||||
graph.hidden = not (args[1]['score'] or args[1]['winrate'])
|
||||
rank_graph.hidden = not args[1]['rank_est']
|
||||
rank_graph.trigger_calculate()
|
||||
ScoreGraph:
|
||||
id: graph
|
||||
opacity: 0 if self.hidden else 1
|
||||
@@ -726,8 +727,8 @@
|
||||
AnalysisMenuItem:
|
||||
text: i18n._("analysis:game")
|
||||
icon: 'img/analysis.png'
|
||||
shortcut: 'f'
|
||||
on_action: root.katrain("analyze-extra", "game")
|
||||
shortcut: ''
|
||||
on_action: root.open_game_analysis_popup()
|
||||
AnalysisMenuItem:
|
||||
text: i18n._("analysis:continuous")
|
||||
icon: 'img/off.png'
|
||||
|
||||
@@ -7,9 +7,11 @@ from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
from kivy.graphics.context_instructions import Color
|
||||
from kivy.graphics.vertex_instructions import Ellipse, Line, Rectangle
|
||||
from kivy.metrics import dp
|
||||
from kivy.properties import ListProperty, ObjectProperty, BooleanProperty
|
||||
from kivy.uix.dropdown import DropDown
|
||||
from kivy.uix.widget import Widget
|
||||
from kivymd.app import MDApp
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
|
||||
@@ -18,6 +20,7 @@ from katrain.core.game import Move
|
||||
from katrain.core.lang import i18n
|
||||
from katrain.core.utils import evaluation_class, var_to_grid
|
||||
from katrain.gui.kivyutils import draw_circle, draw_text, BackgroundMixin
|
||||
from katrain.gui.popups import I18NPopup, ReAnalyzeGamePopup
|
||||
from katrain.gui.style import *
|
||||
|
||||
|
||||
@@ -467,7 +470,11 @@ class BadukPanWidget(Widget):
|
||||
|
||||
|
||||
class AnalysisDropDown(DropDown):
|
||||
pass
|
||||
def open_game_analysis_popup(self, *_args):
|
||||
analysis_popup = I18NPopup(title_key="analysis:game", size=[dp(500), dp(300)], content=ReAnalyzeGamePopup())
|
||||
analysis_popup.content.popup = analysis_popup
|
||||
analysis_popup.content.katrain = MDApp.get_running_app().gui
|
||||
analysis_popup.open()
|
||||
|
||||
|
||||
class AnalysisControls(MDBoxLayout):
|
||||
|
||||
@@ -4,7 +4,7 @@ import re
|
||||
from typing import Any, Dict, List, Tuple, Union
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.properties import BooleanProperty, NumericProperty, StringProperty, ListProperty
|
||||
from kivy.properties import BooleanProperty, NumericProperty, StringProperty, ListProperty, ObjectProperty
|
||||
from kivy.metrics import dp
|
||||
from kivy.uix.anchorlayout import AnchorLayout
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
@@ -23,7 +23,8 @@ from katrain.core.constants import (
|
||||
OUTPUT_DEBUG,
|
||||
OUTPUT_ERROR,
|
||||
OUTPUT_INFO,
|
||||
AI_OPTION_VALUES, STATUS_INFO,
|
||||
AI_OPTION_VALUES,
|
||||
STATUS_INFO,
|
||||
)
|
||||
from katrain.core.engine import KataGoEngine
|
||||
from katrain.core.lang import i18n
|
||||
@@ -323,7 +324,7 @@ class ConfigAIPopup(QuickConfigGui):
|
||||
options = {k[len(prefix) :]: v for k, v in options.items() if k.startswith(prefix)}
|
||||
print(options)
|
||||
dan_rank, model_based = ai_rank_estimation(strategy, options)
|
||||
self.estimated_rank_label.text = ('' if model_based else '~') + RankGraph.rank_label(dan_rank)
|
||||
self.estimated_rank_label.text = ("" if model_based else "~") + RankGraph.rank_label(dan_rank)
|
||||
|
||||
def build_ai_options(self, *_args):
|
||||
strategy = self.ai_select.selected[1]
|
||||
@@ -472,3 +473,8 @@ class LoadSGFPopup(BoxLayout):
|
||||
]
|
||||
self.filesel.path = os.path.abspath(os.path.expanduser(app.gui.config("general/sgf_load")))
|
||||
self.filesel.select_string = i18n._("Load File")
|
||||
|
||||
|
||||
class ReAnalyzeGamePopup(BoxLayout):
|
||||
katrain = ObjectProperty(None)
|
||||
popup = ObjectProperty(None)
|
||||
@@ -5,6 +5,7 @@ from kivy.metrics import dp
|
||||
from kivy.properties import BooleanProperty, ListProperty, NumericProperty, Clock, StringProperty
|
||||
from kivymd.app import MDApp
|
||||
|
||||
from katrain.core.constants import OUTPUT_ERROR
|
||||
from katrain.core.lang import i18n
|
||||
from katrain.gui.kivyutils import BackgroundMixin
|
||||
import threading
|
||||
@@ -224,7 +225,7 @@ class RankGraph(Graph):
|
||||
return {pl: RankGraph.calculate_rank_for_player(segment_stats, num_intersec, pl) for pl in "BW"}
|
||||
|
||||
def rank_game(self):
|
||||
|
||||
try:
|
||||
nodes = self.nodes
|
||||
parent_policy_per_move = [node.parent.policy_ranking if node.parent else None for node in nodes]
|
||||
num_legal_moves = [
|
||||
@@ -232,7 +233,11 @@ class RankGraph(Graph):
|
||||
for policy_ranking in parent_policy_per_move
|
||||
]
|
||||
policy_stats = [
|
||||
[(num_mv, rank, value, mv.player) for rank, (value, mv) in enumerate(policy_ranking) if mv == move.move][0]
|
||||
[
|
||||
(num_mv, rank, value, mv.player)
|
||||
for rank, (value, mv) in enumerate(policy_ranking)
|
||||
if mv == move.move
|
||||
][0]
|
||||
if policy_ranking
|
||||
else (None, None, None, None)
|
||||
for move, policy_ranking, num_mv in zip(nodes, parent_policy_per_move, num_legal_moves)
|
||||
@@ -249,11 +254,17 @@ class RankGraph(Graph):
|
||||
if bounds[1] - bounds[0] > self.segment_length * 0.75:
|
||||
ranks[pl].append((segment_mid, rank))
|
||||
self.rank_by_player = ranks
|
||||
except Exception as e:
|
||||
MDApp.get_running_app().gui.log(f"Exception while calculating rank: {e}", OUTPUT_ERROR)
|
||||
self.redraw_trigger()
|
||||
|
||||
def update_value(self, node):
|
||||
super().update_value(node)
|
||||
self.calculate_trigger() # recalc here on trigger for speed
|
||||
self.trigger_calculate()
|
||||
|
||||
def trigger_calculate(self):
|
||||
if self.opacity != 0: # recalc here on trigger and only if visible for speed
|
||||
self.calculate_trigger()
|
||||
|
||||
def update_graph(self, *args):
|
||||
if self.rank_by_player:
|
||||
|
||||
Binary file not shown.
@@ -597,3 +597,11 @@ msgstr ""
|
||||
"corresponds to `playoutDoublingAdvantage` in KataGo, from black's "
|
||||
"perspective. When `automatic` is set, KaTrain will find a suitable value "
|
||||
"automatically."
|
||||
|
||||
#. TODO - in AI settings
|
||||
msgid "estimated strength"
|
||||
msgstr "Estimated Strength"
|
||||
|
||||
#. TODO - label in little popup for analyzing entire game
|
||||
msgid "reanalyze max visits"
|
||||
msgstr "Number of visits per move"
|
||||
Binary file not shown.
@@ -640,3 +640,11 @@ msgstr ""
|
||||
"corresponds to `playoutDoublingAdvantage` in KataGo, from black's "
|
||||
"perspective. When `automatic` is set, KaTrain will find a suitable value "
|
||||
"automatically."
|
||||
|
||||
#. TODO - in AI settings
|
||||
msgid "estimated strength"
|
||||
msgstr "Estimated Strength"
|
||||
|
||||
#. TODO - label in little popup for analyzing entire game
|
||||
msgid "reanalyze max visits"
|
||||
msgstr "Number of visits per move"
|
||||
Binary file not shown.
@@ -635,3 +635,7 @@ msgstr "{num} available model(s) found"
|
||||
#. error message on trying to download models when already done
|
||||
msgid "All models downloaded"
|
||||
msgstr "All available models already downloaded"
|
||||
|
||||
#. label in little popup for analyzing entire game
|
||||
msgid "reanalyze max visits"
|
||||
msgstr "Number of visits per move"
|
||||
Binary file not shown.
@@ -627,3 +627,11 @@ msgstr ""
|
||||
"corresponds to `playoutDoublingAdvantage` in KataGo, from black's "
|
||||
"perspective. When `automatic` is set, KaTrain will find a suitable value "
|
||||
"automatically."
|
||||
|
||||
#. TODO - in AI settings
|
||||
msgid "estimated strength"
|
||||
msgstr "Estimated Strength"
|
||||
|
||||
#. TODO - label in little popup for analyzing entire game
|
||||
msgid "reanalyze max visits"
|
||||
msgstr "Number of visits per move"
|
||||
Binary file not shown.
@@ -674,3 +674,11 @@ msgstr ""
|
||||
"corresponds to `playoutDoublingAdvantage` in KataGo, from black's "
|
||||
"perspective.When `automatic` is set, KaTrain will find a suitable value "
|
||||
"automatically."
|
||||
|
||||
#. TODO - in AI settings
|
||||
msgid "estimated strength"
|
||||
msgstr "Estimated Strength"
|
||||
|
||||
#. TODO - label in little popup for analyzing entire game
|
||||
msgid "reanalyze max visits"
|
||||
msgstr "Number of visits per move"
|
||||
Binary file not shown.
@@ -612,3 +612,11 @@ msgstr ""
|
||||
"corresponds to `playoutDoublingAdvantage` in KataGo, from black's "
|
||||
"perspective. When `automatic` is set, KaTrain will find a suitable value "
|
||||
"automatically."
|
||||
|
||||
#. TODO - in AI settings
|
||||
msgid "estimated strength"
|
||||
msgstr "Estimated Strength"
|
||||
|
||||
#. TODO - label in little popup for analyzing entire game
|
||||
msgid "reanalyze max visits"
|
||||
msgstr "Number of visits per move"
|
||||
Binary file not shown.
@@ -637,3 +637,11 @@ msgstr ""
|
||||
"corresponds to `playoutDoublingAdvantage` in KataGo, from black's "
|
||||
"perspective. When `automatic` is set, KaTrain will find a suitable value "
|
||||
"automatically."
|
||||
|
||||
#. TODO - in AI settings
|
||||
msgid "estimated strength"
|
||||
msgstr "Estimated Strength"
|
||||
|
||||
#. TODO - label in little popup for analyzing entire game
|
||||
msgid "reanalyze max visits"
|
||||
msgstr "Number of visits per move"
|
||||
@@ -450,6 +450,29 @@
|
||||
on_press: root.update_config(True)
|
||||
|
||||
|
||||
<ReAnalyzeGamePopup>:
|
||||
visits: visits
|
||||
orientation: 'vertical'
|
||||
BoxLayout:
|
||||
size_hint: 1, 1.5
|
||||
orientation: 'horizontal'
|
||||
DescriptionLabel:
|
||||
text: i18n._('reanalyze max visits')
|
||||
AnchorLayout:
|
||||
LabelledIntInput:
|
||||
id: visits
|
||||
text: '2500'
|
||||
AnchorLayout:
|
||||
size_hint: 1, 1
|
||||
AutoSizedRoundedRectangleButton:
|
||||
padding_x: 15
|
||||
size_hint: None,0.5
|
||||
text: i18n._("analysis:game") # re-use string
|
||||
on_press:
|
||||
root.katrain("analyze-extra", "game", visits=visits.input_value)
|
||||
root.popup.dismiss()
|
||||
|
||||
|
||||
<LoadSGFPopup>:
|
||||
fast: fast
|
||||
rewind: rewind
|
||||
|
||||
@@ -99,8 +99,13 @@ def test_ogs():
|
||||
def test_gibo():
|
||||
file = os.path.join(os.path.dirname(__file__), "data/test.gib")
|
||||
root = SGF.parse_file(file)
|
||||
assert {'PW': ['wildsim1'], 'WR': ['2D'], 'PB': ['kim'], 'BR': ['2D'], 'RE': ['W+T'], 'KM': [6.5], 'DT': ['2020-06-14']} == root.properties
|
||||
assert {
|
||||
"PW": ["wildsim1"],
|
||||
"WR": ["2D"],
|
||||
"PB": ["kim"],
|
||||
"BR": ["2D"],
|
||||
"RE": ["W+T"],
|
||||
"KM": [6.5],
|
||||
"DT": ["2020-06-14"],
|
||||
} == root.properties
|
||||
assert "pp" == root.children[0].get_property("B")
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user