update komi/rules on the fly, etc

This commit is contained in:
Sander Land committed 2020-10-26 22:52:30 +01:00
1 parent ad00d2545c
commit 3a2e908669
24 files changed
+215 -52

No files matched your search

+1 -1
View File
@@ -131,7 +131,6 @@ class KaTrainGui(Screen, KaTrainBase):
def toggle_continuous_analysis(self):
if self.idle_analysis:
self.engine.terminate_continuous_query()
self.controls.set_status("", STATUS_INFO)
self.idle_analysis = not self.idle_analysis
self.update_state()
@@ -442,6 +441,7 @@ class KaTrainGui(Screen, KaTrainBase):
"s": ("analyze-extra", "equalize"),
"d": ("analyze-extra", "sweep"),
"f": ("analyze-extra", "alternative"),
"g": ("analyze-extra", "local"),
"p": ("play", None),
"down": ("switch-branch", 1),
"up": ("switch-branch", -1),
+1 -1
View File
@@ -17,7 +17,7 @@
"anim_pv_time": 0.5,
"debug_level": 0,
"lang": "en",
"version": "1.5.1"
"version": "1.6.0"
},
"timer": {
"byo_length": 30,
+26 -14
View File
@@ -13,6 +13,7 @@ from kivy.utils import platform
from katrain.core.constants import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_KATAGO_STDERR
from katrain.core.game_node import GameNode
from katrain.core.lang import i18n
from katrain.core.sgf_parser import Move
from katrain.core.utils import find_package_resource
@@ -34,7 +35,6 @@ class KataGoEngine:
def __init__(self, katrain, config):
self.katrain = katrain
self.queries = {} # outstanding query id -> start time and callback
self.continuous_query = None
self.config = config
self.query_counter = 0
self.katago_process = None
@@ -108,10 +108,9 @@ class KataGoEngine:
def on_new_game(self):
self.base_priority += 1
for query_id in self.queries:
for query_id in list(self.queries.keys()):
self.terminate_query(query_id)
self.queries = {}
self.continuous_query = None
def restart(self):
self.queries = {}
@@ -211,7 +210,7 @@ class KataGoEngine:
self.katrain.log(line, OUTPUT_EXTRA_DEBUG)
try:
if callback and not analysis.get("noResults", False):
callback(analysis)
callback(analysis, partial_result)
except Exception as e:
self.katrain.log(f"Error in engine callback for query {query_id}: {e}", OUTPUT_ERROR)
if getattr(self.katrain, "update_state", None): # easier mocking etc
@@ -220,16 +219,12 @@ class KataGoEngine:
self.katrain.log(f"Unexpected exception {e} while processing KataGo output {line}", OUTPUT_ERROR)
traceback.print_exc()
def send_query(self, query, callback, error_callback, next_move=None, report_during_search=False):
def send_query(self, query, callback, error_callback, next_move=None):
with self._lock:
self.query_counter += 1
if "id" not in query:
query["id"] = f"QUERY:{str(self.query_counter)}"
self.queries[query["id"]] = (callback, error_callback, time.time(), next_move)
if report_during_search:
query["reportDuringSearchEvery"] = 0.25
self.terminate_continuous_query()
self.continuous_query = query["id"]
if self.katago_process:
self.katrain.log(f"Sending query {query['id']}: {json.dumps(query)}", OUTPUT_DEBUG)
try:
@@ -238,9 +233,6 @@ class KataGoEngine:
except OSError as e:
self.check_alive(os_error=str(e), exception_if_dead=True)
def terminate_continuous_query(self):
self.terminate_query(self.continuous_query)
def terminate_query(self, query_id):
if query_id is not None:
self.send_query({"action": "terminate", "terminateId": query_id}, None, None)
@@ -254,6 +246,7 @@ class KataGoEngine:
analyze_fast: bool = False,
time_limit=True,
find_alternatives: bool = False,
find_local: bool = False,
priority: int = 0,
ownership: Optional[bool] = None,
next_move: Optional[GameNode] = None,
@@ -272,6 +265,8 @@ class KataGoEngine:
if analyze_fast and self.config.get("fast_visits"):
visits = self.config["fast_visits"]
size_x, size_y = analysis_node.board_size
if find_alternatives:
avoid = [
{
@@ -280,10 +275,26 @@ class KataGoEngine:
"untilDepth": 1,
}
]
elif find_local:
distance = 5
last_move = analysis_node.move
if last_move is None or last_move.is_pass:
return
avoid = [
{
"moves": [
Move((x, y)).gtp()
for x in range(0, size_x)
for y in range(0, size_y)
if max(abs(x - last_move.coords[0]), abs(y - last_move.coords[1])) > distance
],
"player": analysis_node.next_player,
"untilDepth": 1,
}
]
else:
avoid = []
size_x, size_y = analysis_node.board_size
settings = copy.copy(self.override_settings)
if time_limit:
settings["maxTime"] = self.config["max_time"]
@@ -296,7 +307,6 @@ class KataGoEngine:
"analyzeTurns": [len(moves)],
"maxVisits": visits,
"komi": analysis_node.komi,
"avoidMoves": avoid,
"boardXSize": size_x,
"boardYSize": size_y,
"includeOwnership": ownership and not next_move,
@@ -309,5 +319,7 @@ class KataGoEngine:
}
if report_every is not None:
query["reportDuringSearchEvery"] = report_every
if avoid:
query["avoidMoves"] = avoid
self.send_query(query, callback, error_callback, next_move)
analysis_node.analysis_visits_requested = max(analysis_node.analysis_visits_requested, visits)
+18 -13
View File
@@ -86,9 +86,8 @@ class Game:
def analyze_all_nodes(self, priority=0, analyze_fast=False):
for node in self.root.nodes_in_tree:
node.analyze(
self.engines[node.next_player], priority=priority, analyze_fast=analyze_fast #, report_every=None
)
node.clear_analysis()
node.analyze(self.engines[node.next_player], priority=priority, analyze_fast=analyze_fast)
# -- move tree functions --
def _calculate_groups(self):
@@ -325,11 +324,14 @@ class Game:
if mode == "extra":
if kwargs.get("continuous", False):
visits = 1_000_000_000
visits = min(
1_000_000_000, max(engine.config["max_visits"], math.ceil(cn.analysis_visits_requested * 1.25))
)
else:
visits = cn.analysis_visits_requested + engine.config["max_visits"]
self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS)
cn.analyze(engine, visits=visits, priority=-1_000, time_limit=False, report_during_search=True)
self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS)
cn.analyze(engine, visits=visits, priority=-1_000, time_limit=False)
return
if mode == "game":
nodes = self.root.nodes_in_tree
@@ -370,26 +372,29 @@ class Game:
visits = engine.config["fast_visits"]
self.katrain.controls.set_status(i18n._("sweep analysis").format(visits=visits), STATUS_ANALYSIS)
priority = -1_000_000_000
elif mode in ["equalize", "alternative"]:
if not cn.analysis_complete:
elif mode == "local": # also do a quick update on current candidates so it doesn't look too weird
self.katrain.controls.set_status(i18n._("local analysis"), STATUS_ANALYSIS)
cn.analyze(engine, priority=-500, time_limit=False, find_local="local")
return
elif mode in ["equalize", "alternative", "local"]:
if not cn.analysis_complete and mode != "local":
self.katrain.controls.set_status(i18n._("wait-before-equalize"), STATUS_INFO, self.current_node)
return
analyze_moves = [Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items()]
priority = -1_000
if mode == "alternative": # also do a quick update on current candidates so it doesn't look too weird
self.katrain.controls.set_status(i18n._("alternative analysis"), STATUS_ANALYSIS)
cn.analyze(engine, priority=-500, time_limit=False, find_alternatives=True)
cn.analyze(engine, priority=-500, time_limit=False, find_alternatives="alternative")
visits = engine.config["fast_visits"]
else:
else: # equalize
visits = max(d["visits"] for d in cn.analysis["moves"].values())
self.katrain.controls.set_status(i18n._("equalizing analysis").format(visits=visits), STATUS_ANALYSIS)
priority = -1_000
analyze_moves = [Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items()]
else:
raise ValueError("Invalid analysis mode")
for move in analyze_moves:
if cn.analysis["moves"].get(move.gtp(), {"visits": 0})["visits"] < visits:
cn.analyze(
engine, priority, visits=visits, refine_move=move, time_limit=False, report_every=None
engine, priority=priority, visits=visits, refine_move=move, time_limit=False, report_every=None
) # explicitly requested so take as long as you need
def analyze_undo(self, node):
+16 -9
View File
@@ -13,7 +13,6 @@ class GameNode(SGFNode):
def __init__(self, parent=None, properties=None, move=None):
super().__init__(parent=parent, properties=properties, move=move)
self.analysis = {"moves": {}, "root": None, "completed": False}
self.ownership = None
self.policy = None
self.auto_undo = None # None = not analyzed. False: not undone (good move). True: undone (bad move)
@@ -21,9 +20,13 @@ class GameNode(SGFNode):
self.note = ""
self.move_number = 0
self.time_used = 0
self.analysis_visits_requested = 0
self.undo_threshold = random.random() # for fractional undos
self.end_state = None
self.clear_analysis()
def clear_analysis(self):
self.analysis_visits_requested = 0
self.analysis = {"moves": {}, "root": None, "completed": False}
def sgf_properties(self, save_comments_player=None, save_comments_class=None, eval_thresholds=None):
properties = copy.copy(super().sgf_properties())
@@ -77,17 +80,21 @@ class GameNode(SGFNode):
refine_move=None,
analyze_fast=False,
find_alternatives=False,
find_local=False,
report_every=0.25,
):
engine.request_analysis(
self,
callback=lambda result, partial_result: self.set_analysis(result, refine_move, find_alternatives, partial_result),
callback=lambda result, partial_result: self.set_analysis(
result, refine_move, find_alternatives or find_local, partial_result
),
priority=priority,
visits=visits,
analyze_fast=analyze_fast,
time_limit=time_limit,
next_move=refine_move,
find_alternatives=find_alternatives,
find_local=find_local,
report_every=report_every,
)
@@ -105,7 +112,7 @@ class GameNode(SGFNode):
cur.update(move_analysis)
def set_analysis(
self, analysis_json: Dict, refine_move: Optional[Move], alternatives_mode: bool, partial_result: bool = False
self, analysis_json: Dict, refine_move: Optional[Move], additional_moves: bool, partial_result: bool = False
):
if refine_move:
pvtail = analysis_json["moveInfos"][0]["pv"] if analysis_json["moveInfos"] else []
@@ -113,17 +120,17 @@ class GameNode(SGFNode):
{"pv": [refine_move.gtp()] + pvtail, **analysis_json["rootInfo"]}, refine_move.gtp()
)
else:
if alternatives_mode:
if additional_moves:
for m in analysis_json["moveInfos"]:
m["order"] += 100 # offset for not making this top
if refine_move is None and not alternatives_mode:
del m["order"] # avoid changing order
if refine_move is None and not additional_moves:
for move_dict in self.analysis["moves"].values():
move_dict["order"] = 999 # old moves to end
for move_analysis in analysis_json["moveInfos"]:
self.update_move_analysis(move_analysis, move_analysis["move"])
self.ownership = analysis_json.get("ownership")
self.policy = analysis_json.get("policy")
if not alternatives_mode:
if not additional_moves:
self.analysis["root"] = analysis_json["rootInfo"]
if self.parent and self.move:
analysis_json["rootInfo"]["pv"] = [self.move.gtp()] + (
@@ -132,7 +139,7 @@ class GameNode(SGFNode):
self.parent.update_move_analysis(
analysis_json["rootInfo"], self.move.gtp()
) # update analysis in parent for consistency
is_normal_query = refine_move is None and not alternatives_mode
is_normal_query = refine_move is None and not additional_moves
self.analysis["completed"] = self.analysis["completed"] or (is_normal_query and not partial_result)
@property
+5
View File
@@ -749,6 +749,11 @@
icon: 'img/Alternative.png'
shortcut: 'f'
on_action: root.katrain("analyze-extra", "alternative")
AnalysisMenuItem:
text: i18n._("analysis:local")
icon: 'img/Alternative.png'
shortcut: 'g'
on_action: root.katrain("analyze-extra", "local")
AnalysisMenuItem:
text: i18n._("analysis:game")
icon: 'img/analysis.png'
+13
View File
@@ -260,6 +260,19 @@ class NewGamePopup(QuickConfigGui):
self.katrain.update_player(bw, **player_setup.player_type_dump)
self.katrain("new-game")
def update_game(self, save_to_file=True):
props = self.collect_properties(self)
root = self.katrain.game.root
changed = False
for k, v in [("RU", props["game/rules"]), ("KM", props["game/komi"])]:
current = root.get_property(k)
if current != v:
changed = True
self.katrain.game.root.set_property(k, v)
if changed:
self.katrain.engine.on_new_game()
self.katrain.game.analyze_all_nodes()
self.popup.dismiss()
def wrap_anchor(widget):
anchor = AnchorLayout()
Binary file not shown.
@@ -686,3 +686,17 @@ msgstr ""
#. TODO
msgid "ai:settle"
msgstr "Settle Style"
#. TODO - Analyze again but allow only nearby moves
msgid "analysis:local"
msgstr "Find local responses"
#. TODO - status message
msgid "local analysis"
msgstr "Searching for local responses"
#. TODO - change komi etc in current game
msgid "change current game"
msgstr ""
"Update komi/rules\n"
"in active game"
Binary file not shown.
@@ -735,3 +735,17 @@ msgstr ""
#. TODO
msgid "ai:settle"
msgstr "Settle Style"
#. TODO - Analyze again but allow only nearby moves
msgid "analysis:local"
msgstr "Find local responses"
#. TODO - status message
msgid "local analysis"
msgstr "Searching for local responses"
#. TODO - change komi etc in current game
msgid "change current game"
msgstr ""
"Update komi/rules\n"
"in active game"
Binary file not shown.
@@ -106,6 +106,10 @@ msgstr "Deeper full game analysis"
msgid "analysis:alternatives"
msgstr "Find alternative moves"
#. Analyze again but allow only nearby moves
msgid "analysis:local"
msgstr "Find local responses"
#. Keep improving current position analysis when there is nothing else to do
msgid "analysis:continuous"
msgstr "Toggle continuous analysis"
@@ -125,6 +129,10 @@ msgstr "Analyzing entire board using {visits} visits/move"
msgid "alternative analysis"
msgstr "Searching for alternative moves"
#. status message
msgid "local analysis"
msgstr "Searching for local responses"
#. status message
msgid "equalizing analysis"
msgstr "Equalizing analysis of candidate moves to {visits} visits"
@@ -362,6 +370,12 @@ msgstr "Use x:y (e.g. 19:9) for non-square size"
msgid "new game"
msgstr "Start New Game"
#. change komi etc in current game
msgid "change current game"
msgstr ""
"Update komi/rules\n"
"in active game"
# teacher settings from here
msgid "teacher settings"
msgstr "Edit Teacher Settings"
Binary file not shown.
@@ -734,3 +734,17 @@ msgstr ""
#. TODO
msgid "ai:settle"
msgstr "Settle Style"
#. TODO - Analyze again but allow only nearby moves
msgid "analysis:local"
msgstr "Find local responses"
#. TODO - status message
msgid "local analysis"
msgstr "Searching for local responses"
#. TODO - change komi etc in current game
msgid "change current game"
msgstr ""
"Update komi/rules\n"
"in active game"
Binary file not shown.
@@ -767,3 +767,17 @@ msgstr ""
#. TODO
msgid "ai:settle"
msgstr "Settle Stones Style"
#. TODO - Analyze again but allow only nearby moves
msgid "analysis:local"
msgstr "Find local responses"
#. TODO - status message
msgid "local analysis"
msgstr "Searching for local responses"
#. TODO - change komi etc in current game
msgid "change current game"
msgstr ""
"Update komi/rules\n"
"in active game"
Binary file not shown.
@@ -714,3 +714,17 @@ msgstr ""
msgid "ai:settle"
msgstr "堅実派"
#. TODO - Analyze again but allow only nearby moves
msgid "analysis:local"
msgstr "Find local responses"
#. TODO - status message
msgid "local analysis"
msgstr "Searching for local responses"
#. TODO - change komi etc in current game
msgid "change current game"
msgstr ""
"Update komi/rules\n"
"in active game"
Binary file not shown.
@@ -697,3 +697,17 @@ msgstr ""
#. TODO
msgid "ai:settle"
msgstr "Settle Style"
#. TODO - Analyze again but allow only nearby moves
msgid "analysis:local"
msgstr "Find local responses"
#. TODO - status message
msgid "local analysis"
msgstr "Searching for local responses"
#. TODO - change komi etc in current game
msgid "change current game"
msgstr ""
"Update komi/rules\n"
"in active game"
Binary file not shown.
@@ -730,3 +730,17 @@ msgstr ""
#. TODO
msgid "ai:settle"
msgstr "Settle Style"
#. TODO - Analyze again but allow only nearby moves
msgid "analysis:local"
msgstr "Find local responses"
#. TODO - status message
msgid "local analysis"
msgstr "Searching for local responses"
#. TODO - change komi etc in current game
msgid "change current game"
msgstr ""
"Update komi/rules\n"
"in active game"
+23 -14
View File
@@ -175,17 +175,22 @@
font_size: DESC_FONT_SIZE * 1.5
text: i18n._('engine settings')
BoxLayout:
size_hint: 1,6
size_hint: 1,5
orientation: 'horizontal'
GridLayout:
cols: 2
rows: 4
rows: 5
spacing: CP_SPACING
DescriptionLabel:
text: i18n._("general:sgf_save")
AnchorLayout:
LabelledPathInput:
input_property: "general/sgf_save"
DescriptionLabel:
text: i18n._('count down sound')
AnchorLayout:
LabelledCheckBox:
input_property: "timer/sound"
DescriptionLabel:
text: i18n._("general:anim_pv_time")
AnchorLayout:
@@ -199,11 +204,6 @@
input_property: "general/debug_level"
helper_text: i18n._("general:debug_level:hint")
helper_text_mode: "on_focus"
DescriptionLabel:
text: i18n._('count down sound')
AnchorLayout:
LabelledCheckBox:
input_property: "timer/sound"
SmallDescriptionLabel:
text: i18n._('config file path')
SmallDescriptionLabel:
@@ -455,13 +455,22 @@
input_property: 'game/clear_cache'
SmallDescriptionLabel:
text: i18n._('avoids replaying')
AnchorLayout:
size_hint: 1,1
AutoSizedRoundedRectangleButton:
padding_x: 15
size_hint: None,0.5
text: i18n._("new game")
on_press: root.update_config(True)
BoxLayout:
size_hint: 1,0.75
AnchorLayout:
size_hint: 0.5, 1
AutoSizedRoundedRectangleButton:
padding_x: 15
size_hint: None,0.5
_font_size: self.height*0.35
text: i18n._("change current game")
on_press: root.update_game()
AnchorLayout:
AutoSizedRoundedRectangleButton:
padding_x: 15
size_hint: None,0.66
text: i18n._("new game")
on_press: root.update_config(True)
<LabelledSelectionSlider>: