Merge pull request #223 from sanderland/v1.5.1

update 1.6
This commit is contained in:
sanderland authored and GitHub committed 2020-10-26 16:02:01 +01:00
commit 44134abe48
31 files changed
+423 -112

No files matched your search

+2
View File
@@ -16,6 +16,8 @@ tmp.pickle
my
logs
callgrind.*
cpp/out
.vs
# debug
outdated_log.txt
+3 -2
View File
@@ -177,9 +177,10 @@ In addition to shortcuts mentioned above and those shown in the main menu:
* **[~]** or **[ ` ]** or **[m]**: Cycles through more minimalistic UI modes.
* **[p]**: Pass
* **[b]**: Pause/Resume timer
* **[arrow left]** or **[z]**: Undo move. Hold alt for 10 moves at a time, or ctrl to skip to the start.
* **[arrow right]** or **[x]**: Redo move. Hold alt for 10 moves at a time, or ctrl to skip to the start.
* **[arrow left]** or **[z]**: Undo move. Hold alt for 5 moves at a time, or ctrl to skip to the start.
* **[arrow right]** or **[x]**: Redo move. Hold alt for 5 moves at a time, or ctrl to skip to the start.
* **[arrow up/down]** Switch branch, as would be expected from the move tree.
* **[home/end]** Go to the beginning/end of the game.
* **[scroll up]**: Undo move. Only works when hovering the cursor over the board.
* **[scroll down]**: Redo move. Only works when hovering the cursor over the board.
* **[click on a move]**: See detailed statistics for a previous move, along with expected variation that was best instead of this move.
Binary file not shown.
Binary file not shown.
+20 -15
View File
@@ -194,14 +194,14 @@ class KaTrainGui(Screen, KaTrainBase):
teaching_undo = cn.player and last_player.being_taught and cn.parent
if (
teaching_undo
and cn.analysis_ready
and cn.parent.analysis_ready
and cn.analysis_complete
and cn.parent.analysis_complete
and not cn.children
and not self.game.end_result
):
self.game.analyze_undo(cn) # not via message loop
if (
cn.analysis_ready
cn.analysis_complete
and next_player.ai
and not cn.children
and not self.game.end_result
@@ -352,7 +352,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()
@@ -468,7 +468,8 @@ class KaTrainGui(Screen, KaTrainBase):
return
else:
return
ctrl_pressed = "ctrl" in modifiers
alt_pressed = "alt" in modifiers
shortcuts = self.shortcuts
if keycode[1] == "tab":
self.play_mode.switch_ui_mode()
@@ -476,26 +477,30 @@ class KaTrainGui(Screen, KaTrainBase):
self.nav_drawer.set_state("toggle")
elif keycode[1] == "spacebar":
self.toggle_continuous_analysis()
elif keycode[1] == "b" and "ctrl" not in modifiers:
elif keycode[1] == "b" and ctrl_pressed:
self.controls.timer.paused = not self.controls.timer.paused
elif keycode[1] in ["`", "~", "m"] and "ctrl" not in modifiers:
elif keycode[1] in ["`", "~", "m"] and ctrl_pressed:
self.zen = (self.zen + 1) % 3
elif keycode[1] in ["left", "z"]:
self("undo", 1 + ("alt" in modifiers) * 9 + ("ctrl" in modifiers) * 999)
self("undo", 1 + alt_pressed * 4 + (ctrl_pressed and not alt_pressed) * 999)
elif keycode[1] in ["right", "x"]:
self("redo", 1 + ("alt" in modifiers) * 9 + ("ctrl" in modifiers) * 999)
elif keycode[1] == "n" and "ctrl" in modifiers:
self("redo", 1 + alt_pressed * 4 + (ctrl_pressed and not alt_pressed) * 999)
elif keycode[1] == "home":
self("undo", 999)
elif keycode[1] == "end":
self("redo", 999)
elif keycode[1] == "n" and ctrl_pressed:
self("new-game-popup")
elif keycode[1] == "l" and "ctrl" in modifiers:
elif keycode[1] == "l" and ctrl_pressed:
self("analyze-sgf-popup")
elif keycode[1] == "s" and "ctrl" in modifiers:
elif keycode[1] == "s" and ctrl_pressed:
self("output-sgf")
elif keycode[1] == "c" and "ctrl" in modifiers:
elif keycode[1] == "c" and ctrl_pressed:
Clipboard.copy(self.game.root.sgf())
self.controls.set_status(i18n._("Copied SGF to clipboard."), STATUS_INFO)
elif keycode[1] == "v" and "ctrl" in modifiers:
elif keycode[1] == "v" and ctrl_pressed:
self.load_sgf_from_clipboard()
elif keycode[1] in shortcuts.keys() and "ctrl" not in modifiers:
elif keycode[1] in shortcuts.keys() and not ctrl_pressed:
shortcut = shortcuts[keycode[1]]
if isinstance(shortcut, Widget):
shortcut.trigger_action(duration=0)
+11 -1
View File
@@ -92,7 +92,17 @@
"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:settle": {
"max_points_lost": 1.75,
"settled_weight": 1.0,
"opponent_fac": 0.5,
"min_visits": 3,
"attach_penalty": 1,
"tenuki_penalty": 0.5
},
"ai:p:weighted": {
"weaken_fac": 1.25,
+63 -43
View File
@@ -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,
@@ -33,6 +32,7 @@ from katrain.core.constants import (
OUTPUT_DEBUG,
OUTPUT_ERROR,
OUTPUT_INFO,
AI_SETTLE_STONES,
)
from katrain.core.game import Game, GameNode, Move
from katrain.core.utils import var_to_grid
@@ -164,9 +164,10 @@ def request_ai_analysis(game: Game, cn: GameNode, extra_settings: Dict) -> Optio
error = False
analysis = None
def set_analysis(a):
def set_analysis(a, partial_result):
nonlocal analysis
analysis = a
if not partial_result:
analysis = a
def set_error(a):
nonlocal error
@@ -205,14 +206,7 @@ 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:
while not cn.analysis_complete:
time.sleep(0.01)
game.engines[cn.next_player].check_alive(exception_if_dead=True)
@@ -335,18 +329,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 [
@@ -376,49 +358,87 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move,
topmove = weighted_selection_without_replacement(moves, 1)[0]
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])
elif ai_mode in [AI_SIMPLE_OWNERSHIP, AI_SETTLE_STONES]:
stones_with_player = {(*s.coords, s.player) for s in game.stones}
next_player_sign = cn.player_sign(cn.next_player)
if ai_mode == AI_SIMPLE_OWNERSHIP:
def settledness(d, player_sign, player):
return sum([abs(o) for o in d["ownership"] if player_sign * o > 0])
else:
board_size_x, board_size_y = game.board_size
def settledness(d, player_sign, player):
ownership_grid = var_to_grid(d["ownership"], (board_size_x, board_size_y))
return sum(
[abs(ownership_grid[s.coords[0]][s.coords[1]]) for s in game.stones if s.player == player]
)
def is_attachment(move):
if move.is_pass:
return False
attach_opponent_stones = sum(
(move.coords[0] + dx, move.coords[1] + dy, cn.player) in stones_with_player
for dx in [-1, 0, 1]
for dy in [-1, 0, 1]
if abs(dx) + abs(dy) == 1
)
nearby_own_stones = sum(
(move.coords[0] + dx, move.coords[1] + dy, cn.next_player) in stones_with_player
for dx in [-2, 0, 1, 2]
for dy in [-2 - 1, 0, 1, 2]
if abs(dx) + abs(dy) <= 2 # allows clamps/jumps
)
return attach_opponent_stones >= 1 and nearby_own_stones == 0
def is_tenuki(d):
return not d.is_pass and 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),
settledness(d, next_player_sign),
settledness(d, -next_player_sign),
move,
settledness(d, next_player_sign, cn.next_player),
settledness(d, -next_player_sign, cn.player),
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))
and (d["order"] <= 1 or d["visits"] >= ai_settings.get("min_visits", 1))
for move in [Move.from_gtp(d["move"], player=cn.next_player)]
if not (move.is_pass and d["pointsLost"] > 0.75)
],
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)} "
ai_thoughts += f"{ai_mode} strategy. Top 5 Candidates {', '.join(cands)} "
aimove = moves_with_settledness[0][0]
else:
game.katrain.log(
"No moves found - are you using an older KataGo with no per-move ownership info?", OUTPUT_ERROR
)
aimove = top_cand
raise (Exception("No moves found - are you using an older KataGo with no per-move ownership info?"))
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)
+21 -8
View File
@@ -1,6 +1,6 @@
VERSION = "1.5.1"
VERSION = "1.6.0"
HOMEPAGE = "https://github.com/sanderland/katrain"
CONFIG_MIN_VERSION = "1.5.1" # keep config files from this version
CONFIG_MIN_VERSION = "1.6.0" # keep config files from this version
OUTPUT_ERROR = -1
OUTPUT_KATAGO_STDERR = -0.5
@@ -33,20 +33,21 @@ 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_SETTLE_STONES = "ai:settle"
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_SIMPLE_OWNERSHIP, AI_SETTLE_STONES, 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_SIMPLE_OWNERSHIP,
AI_HANDICAP,
AI_SIMPLE_OWNERSHIP,
AI_SETTLE_STONES,
AI_SCORELOSS,
AI_POLICY,
AI_WEIGHTED,
@@ -70,7 +71,8 @@ AI_STRENGTH = { # dan ranks, backup if model is missing. TODO: remove some?
AI_INFLUENCE: -7,
AI_TERRITORY: -7,
AI_RANK: float("nan"),
AI_SIMPLE_OWNERSHIP: 3,
AI_SIMPLE_OWNERSHIP: 2,
AI_SETTLE_STONES: 2,
}
AI_OPTION_VALUES = {
@@ -90,11 +92,22 @@ 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),
"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",
"max_points_lost",
"min_visits",
}
AI_KEY_PROPERTIES = {"kyu_rank", "strength", "weaken_fac", "pick_frac", "pick_n", "automatic"}
CALIBRATED_RANK_ELO = [
+8 -4
View File
@@ -200,11 +200,13 @@ class KataGoEngine:
elif "warning" in analysis:
self.katrain.log(f"{analysis} received from KataGo", OUTPUT_DEBUG)
else:
if not analysis.get("isDuringSearch", False):
partial_result = analysis.get("isDuringSearch", False)
if not partial_result:
del self.queries[query_id]
time_taken = time.time() - start_time
self.katrain.log(
f"[{time_taken:.1f}][{query_id}] KataGo Analysis Received: {analysis.keys()}", OUTPUT_DEBUG,
f"[{time_taken:.1f}][{query_id}][{'....' if partial_result else 'done'}] KataGo Analysis Received: {analysis.keys()}",
OUTPUT_DEBUG,
)
self.katrain.log(line, OUTPUT_EXTRA_DEBUG)
try:
@@ -256,7 +258,7 @@ class KataGoEngine:
ownership: Optional[bool] = None,
next_move: Optional[GameNode] = None,
extra_settings: Optional[Dict] = None,
report_during_search: bool = False,
report_every: Optional[float] = None,
):
nodes = analysis_node.nodes_from_root
moves = [m for node in nodes for m in node.moves]
@@ -305,5 +307,7 @@ class KataGoEngine:
"moves": [[m.player, m.gtp()] for m in moves],
"overrideSettings": {**settings, **(extra_settings or {})},
}
self.send_query(query, callback, error_callback, next_move, report_during_search)
if report_every is not None:
query["reportDuringSearchEvery"] = report_every
self.send_query(query, callback, error_callback, next_move)
analysis_node.analysis_visits_requested = max(analysis_node.analysis_visits_requested, visits)
+8 -6
View File
@@ -86,7 +86,9 @@ 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)
node.analyze(
self.engines[node.next_player], priority=priority, analyze_fast=analyze_fast #, report_every=None
)
# -- move tree functions --
def _calculate_groups(self):
@@ -337,13 +339,13 @@ class Game:
min_visits = min(node.analysis_visits_requested for node in nodes)
visits = min_visits + engine.config["max_visits"]
for node in nodes:
node.analyze(engine, visits=visits, priority=-1_000_000, time_limit=False)
node.analyze(engine, visits=visits, priority=-1_000_000, time_limit=False, report_every=None)
self.katrain.controls.set_status(i18n._("game re-analysis").format(visits=visits), STATUS_ANALYSIS)
return
elif mode == "sweep":
board_size_x, board_size_y = self.board_size
if cn.analysis_ready:
if cn.analysis_exists:
policy_grid = (
var_to_grid(self.current_node.policy, size=(board_size_x, board_size_y))
if self.current_node.policy
@@ -369,7 +371,7 @@ class Game:
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_ready:
if not cn.analysis_complete:
self.katrain.controls.set_status(i18n._("wait-before-equalize"), STATUS_INFO, self.current_node)
return
@@ -387,13 +389,13 @@ class Game:
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
engine, 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):
train_config = self.katrain.config("trainer")
move = node.move
if node != self.current_node or node.auto_undo is not None or not node.analysis_ready or not move:
if node != self.current_node or node.auto_undo is not None or not node.analysis_complete or not move:
return
points_lost = node.points_lost
thresholds = train_config["eval_thresholds"]
+29 -20
View File
@@ -13,7 +13,7 @@ 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}
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)
@@ -34,8 +34,8 @@ class GameNode(SGFNode):
show_class = False
if (
self.parent
and self.parent.analysis_ready
and self.analysis_ready
and self.parent.analysis_exists
and self.analysis_exists
and (note or ((save_comments_player or {}).get(self.player, False) and show_class))
):
candidate_moves = self.parent.candidate_moves
@@ -77,18 +77,18 @@ class GameNode(SGFNode):
refine_move=None,
analyze_fast=False,
find_alternatives=False,
report_during_search=False,
report_every=0.25,
):
engine.request_analysis(
self,
lambda result: self.set_analysis(result, refine_move, find_alternatives),
lambda result, partial_result: self.set_analysis(result, refine_move, find_alternatives, partial_result),
priority=priority,
visits=visits,
analyze_fast=analyze_fast,
time_limit=time_limit,
next_move=refine_move,
find_alternatives=find_alternatives,
report_during_search=report_during_search,
report_every=report_every,
)
def update_move_analysis(self, move_analysis, move_gtp):
@@ -104,7 +104,9 @@ class GameNode(SGFNode):
if cur["visits"] < move_analysis["visits"]:
cur.update(move_analysis)
def set_analysis(self, analysis_json: Dict, refine_move: Optional[Move], alternatives_mode: bool):
def set_analysis(
self, analysis_json: Dict, refine_move: Optional[Move], alternatives_mode: bool, partial_result: bool = False
):
if refine_move:
pvtail = analysis_json["moveInfos"][0]["pv"] if analysis_json["moveInfos"] else []
self.update_move_analysis(
@@ -113,7 +115,7 @@ class GameNode(SGFNode):
else:
if alternatives_mode:
for m in analysis_json["moveInfos"]:
m["order"] += 10 # offset for not making this top
m["order"] += 100 # offset for not making this top
if refine_move is None and not alternatives_mode:
for move_dict in self.analysis["moves"].values():
move_dict["order"] = 999 # old moves to end
@@ -130,14 +132,20 @@ 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
self.analysis["completed"] = self.analysis["completed"] or (is_normal_query and not partial_result)
@property
def analysis_ready(self):
def analysis_exists(self):
return self.analysis["root"] is not None
@property
def analysis_complete(self):
return self.analysis["completed"] and self.analysis["root"] is not None
@property
def score(self) -> Optional[float]:
if self.analysis_ready:
if self.analysis_exists:
return self.analysis["root"].get("scoreLead")
def format_score(self, score=None):
@@ -147,7 +155,7 @@ class GameNode(SGFNode):
@property
def winrate(self) -> Optional[float]:
if self.analysis_ready:
if self.analysis_exists:
return self.analysis["root"].get("winrate")
def format_winrate(self, win_rate=None):
@@ -178,12 +186,12 @@ class GameNode(SGFNode):
return ""
text = i18n._("move").format(number=self.depth) + f": {single_move.player} {single_move.gtp()}\n"
if self.analysis_ready:
if self.analysis_exists:
score = self.score
if sgf:
text += i18n._("Info:score").format(score=self.format_score(score)) + "\n"
text += i18n._("Info:winrate").format(winrate=self.format_winrate()) + "\n"
if self.parent and self.parent.analysis_ready:
if self.parent and self.parent.analysis_exists:
previous_top_move = self.parent.candidate_moves[0]
if sgf or details:
if previous_top_move["move"] != single_move.gtp():
@@ -210,14 +218,15 @@ class GameNode(SGFNode):
text += policy_best_msg.format(move=pol_move, probability=pol_prob) + "\n"
if self.auto_undo and sgf:
text += i18n._("Info:teaching undo") + "\n"
top_pv = self.analysis_ready and self.candidate_moves[0].get("pv")
top_pv = self.analysis_exists 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"])
@@ -226,7 +235,7 @@ class GameNode(SGFNode):
@property
def points_lost(self) -> Optional[float]:
single_move = self.move
if single_move and self.parent and self.analysis_ready and self.parent.analysis_ready:
if single_move and self.parent and self.analysis_exists and self.parent.analysis_exists:
parent_score = self.parent.score
score = self.score
return self.player_sign(single_move.player) * (parent_score - score)
@@ -238,8 +247,8 @@ class GameNode(SGFNode):
single_move
and self.parent
and self.parent.parent
and self.analysis_ready
and self.parent.parent.analysis_ready
and self.analysis_exists
and self.parent.parent.analysis_exists
):
parent_parent_score = self.parent.parent.score
score = self.score
@@ -251,7 +260,7 @@ class GameNode(SGFNode):
@property
def candidate_moves(self) -> List[Dict]:
if not self.analysis_ready:
if not self.analysis_exists:
return []
if not self.analysis["moves"]:
polmoves = self.policy_ranking
+2 -2
View File
@@ -140,7 +140,7 @@ class BadukPanWidget(Widget):
katrain.log(f"\nRoot Stats:\n{nodes_here[-1].analysis['root']}", OUTPUT_DEBUG)
katrain.controls.info.text = nodes_here[-1].comment(sgf=True)
katrain.controls.active_comment_node = nodes_here[-1]
if nodes_here[-1].parent.analysis_ready:
if nodes_here[-1].parent.analysis_exists:
self.set_animating_pv(nodes_here[-1].parent.candidate_moves[0]["pv"], nodes_here[-1].parent)
self.ghost_stone = None
@@ -499,7 +499,7 @@ class BadukPanWidget(Widget):
for child_node in current_node.children:
move = child_node.move
if move and move.coords is not None:
if child_node.analysis_ready:
if child_node.analysis_exists:
self.active_pv_moves.append(
(move.coords, [move.gtp()] + child_node.candidate_moves[0]["pv"], current_node)
)
+2 -2
View File
@@ -123,7 +123,7 @@ class ControlsPanel(BoxLayout):
self.active_comment_node = current_node.children[-1]
elif current_node.parent:
self.active_comment_node = current_node.parent
elif both_players_are_robots and not current_node.analysis_ready and current_node.parent:
elif both_players_are_robots and not current_node.analysis_exists and current_node.parent:
self.active_comment_node = current_node.parent
lock_ai = katrain.config("trainer/lock_ai") and katrain.play_analyze_mode == MODE_PLAY
@@ -134,7 +134,7 @@ class ControlsPanel(BoxLayout):
teach=katrain.players_info[self.active_comment_node.player].being_taught, details=details
)
if self.active_comment_node.analysis_ready:
if self.active_comment_node.analysis_exists:
self.stats.score = self.active_comment_node.format_score() or ""
self.stats.winrate = self.active_comment_node.format_winrate() or ""
self.stats.points_lost = self.active_comment_node.points_lost
+1 -1
View File
@@ -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":
Binary file not shown.
@@ -652,3 +652,37 @@ 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), 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, and wide "
"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"
#. TODO
msgid "aihelp:settle"
msgstr ""
"Plays moves that lead to clarity on stones' status, favouring solid moves "
"rather than complex sabaki. Of moves that lose at most 'max_points_lost' "
"points and with at least 'min_visits', prefer moves that lose fewer points, "
"settle own stones more (with importance settled_weight), settle opponent's "
"stones 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, and wide root noise (in general settings) can be used to further "
"play according to the style at the cost of strength. "
#. TODO
msgid "ai:settle"
msgstr "Settle Style"
Binary file not shown.
+35 -1
View File
@@ -696,8 +696,42 @@ 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
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, and wide "
"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"
#. TODO
msgid "aihelp:settle"
msgstr ""
"Plays moves that lead to clarity on stones' status, favouring solid moves "
"rather than complex sabaki. Of moves that lose at most 'max_points_lost' "
"points and with at least 'min_visits', prefer moves that lose fewer points, "
"settle own stones more (with importance settled_weight), settle opponent's "
"stones 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, and wide root noise (in general settings) can be used to further "
"play according to the style at the cost of strength. "
#. TODO
msgid "ai:settle"
msgstr "Settle Style"
Binary file not shown.
@@ -571,6 +571,36 @@ msgstr ""
"perspective. When `automatic` is set, KaTrain will find a suitable value "
"automatically."
msgid "ai:simple"
msgstr "Simple Style"
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, and wide "
"root noise (in general settings) can be used to further play according to "
"the style at the cost of strength. "
msgid "ai:settle"
msgstr "Settle Style"
msgid "aihelp:settle"
msgstr ""
"Plays moves that lead to clarity on stones' status, favouring solid moves "
"rather than complex sabaki. Of moves that lose at most 'max_points_lost' "
"points and with at least 'min_visits', prefer moves that lose fewer points, "
"settle own stones more (with importance settled_weight), settle opponent's "
"stones 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, and wide 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"
Binary file not shown.
@@ -709,3 +709,28 @@ 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"
#. TODO
msgid "aihelp:settle"
msgstr ""
"Plays moves that lead to clarity on stones' status, favouring solid moves "
"rather than complex sabaki. Of moves that lose at most 'max_points_lost' "
"points and with at least 'min_visits', prefer moves that lose fewer points, "
"settle own stones more (with importance settled_weight), settle opponent's "
"stones 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, and wide root noise (in general settings) can be used to further "
"play according to the style at the cost of strength. "
#. TODO
msgid "ai:settle"
msgstr "Settle Style"
Binary file not shown.
@@ -733,3 +733,37 @@ 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), 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, and wide "
"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"
#. TODO
msgid "aihelp:settle"
msgstr ""
"Plays moves that lead to clarity on stones' status, favouring solid moves "
"rather than complex sabaki. Of moves that lose at most 'max_points_lost' "
"points and with at least 'min_visits', prefer moves that lose fewer points, "
"settle own stones more (with importance settled_weight), settle opponent's "
"stones 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, and wide root noise (in general settings) can be used to further "
"play according to the style at the cost of strength. "
#. TODO
msgid "ai:settle"
msgstr "Settle Stones Style"
Binary file not shown.
+29 -6
View File
@@ -680,14 +680,37 @@ msgstr ""
msgid "alternative analysis"
msgstr "他の候補手を分析中"
#. TODO - ignore moves if timer is on and less than this is used
#. ignore moves if timer is on and less than this is used
msgid "minimal time use"
msgstr "Minimal time use in byo-yomi (s)"
msgstr "早打ち禁止秒数"
#. TODO
msgid "move too fast"
msgstr "Think for at least {num} seconds before playing."
msgstr "早打ちすぎです。{num}秒は考えてください。"
#. TODO - in minutes
#. in minutes
msgid "main time"
msgstr "Main time (min.)"
msgstr "持ち時間(分)"
msgid "aihelp:simple"
msgstr ""
"盤面の単純化をめざして, 次の基準で手を選びます (括弧内は重視度): 損する目数が少ない, 自分の確定地が多い (settled_weight), "
"相手も確定地が多い (settled_weight × opponent fac), 相手が打った手の近辺…縦横とも3間以内 "
"(tenuki_penalty), 相手の石に接触しない (attachment_penalty). ただし, 損が大きすぎたり "
"(max_points_lost目以上) 探索手数が少なすぎたり (min_visits手未満) する候補手は除きます. 重視度を負にすれば, "
"逆に盤面の複雑化をめざします. また, 一般設定のwide root noiseを大きくすれば, 強さは犠牲になりますが, "
"より前述の基準に忠実な打ち方をします."
msgid "ai:simple"
msgstr "簡明派"
msgid "aihelp:settle"
msgstr ""
"複雑なサバキよりも, 石の生死をはっきりさせる堅実な手を好みます. 具体的には, 次の基準で手を選びます (括弧内は重視度): 損する目数が少ない, "
"自分の生死が明確 (settled_weight), 相手の生死も明確 (settled_weight × opponent fac), "
"相手が打った手の近辺…縦横とも3間以内 (tenuki_penalty), 相手の石に接触しない (attachment_penalty). ただし, "
"損が大きすぎたり (max_points_lost目以上) 探索手数が少なすぎたり (min_visits手未満) する候補手は除きます. "
"重視度を負にすれば, 逆に盤面の複雑化をめざします. また, 一般設定のwide root noiseを大きくすれば, 強さは犠牲になりますが, "
"より前述の基準に忠実な打ち方をします."
msgid "ai:settle"
msgstr "堅実派"
Binary file not shown.
@@ -666,3 +666,34 @@ 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"
#. TODO
msgid "aihelp:settle"
msgstr ""
"Plays moves that lead to clarity on stones' status, favouring solid moves "
"rather than complex sabaki. Of moves that lose at most 'max_points_lost' "
"points and with at least 'min_visits', prefer moves that lose fewer points, "
"settle own stones more (with importance settled_weight), settle opponent's "
"stones 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, and wide root noise (in general settings) can be used to further "
"play according to the style at the cost of strength. "
#. TODO
msgid "ai:settle"
msgstr "Settle Style"
Binary file not shown.
@@ -696,3 +696,37 @@ 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), 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, and wide "
"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"
#. TODO
msgid "aihelp:settle"
msgstr ""
"Plays moves that lead to clarity on stones' status, favouring solid moves "
"rather than complex sabaki. Of moves that lose at most 'max_points_lost' "
"points and with at least 'min_visits', prefer moves that lose fewer points, "
"settle own stones more (with importance settled_weight), settle opponent's "
"stones 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, and wide root noise (in general settings) can be used to further "
"play according to the style at the cost of strength. "
#. TODO
msgid "ai:settle"
msgstr "Settle Style"
+1 -1
View File
@@ -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