This commit is contained in:
Sander Land committed 2020-04-19 01:00:05 +02:00
1 parent 8aaa85e73d
commit c4ce996ef5
7 files changed
+64 -40

No files matched your search

+1
View File
@@ -106,6 +106,7 @@ class KataGoEngine:
"boardXSize": analysis_node.board_size,
"boardYSize": analysis_node.board_size,
"includeOwnership": ownership,
"includePolicy": not refine_move,
"moves": [[m.player, m.gtp()] for m in moves],
}
self.queries[query_id] = (callback, time.time())
+1 -1
View File
@@ -40,7 +40,7 @@ class Game:
else:
self.board_size = board_size or config["init_size"]
self.komi = self.config.get(f"komi_{self.board_size}", 6.5)
self.root = GameNode(properties={"SZ": self.board_size, "KM": self.komi, "DT": self.game_id, **Game.DEFAULT_PROPERTIES})
self.root = GameNode(properties={**Game.DEFAULT_PROPERTIES, **{"SZ": self.board_size, "KM": self.komi, "DT": self.game_id}})
self.current_node = self.root
self._init_chains()
+29 -21
View File
@@ -10,8 +10,9 @@ class GameNode(SGFNode):
def __init__(self, parent=None, properties=None, move=None):
super().__init__(parent=parent, properties=properties, move=move)
self.analysis = None
self.analysis = {"moves": {}, "root": None}
self.ownership = None
self.policy = None
self.auto_undo = None # None = not analyzed. False: not undone (good move). True: undone (bad move)
self.move_number = 0
self.undo_threshold = random.random() # for fractional undos, store the random threshold in the move itself for consistency
@@ -31,29 +32,33 @@ class GameNode(SGFNode):
def analyze(self, engine, priority=0, visits=None, refine_move=None):
engine.request_analysis(self, lambda result: self.set_analysis(result, refine_move), priority=priority, visits=visits, refine_move=refine_move)
def set_analysis(self, analysis_blob, refine_move):
def update_move_analysis(self, move_analysis, move_gtp):
cur = self.analysis["moves"].get(move_gtp)
if cur is None:
self.analysis["moves"][move_gtp] = {"move": move_gtp, "order": 999, **move_analysis} # some default values for keys missing in rootInfo
elif cur["visits"] < move_analysis["visits"]:
cur.update(move_analysis)
def set_analysis(self, analysis_json, refine_move):
if refine_move:
gtp = refine_move.gtp()
for d in self.analysis:
if d["move"] == gtp:
if d["visits"] < analysis_blob["rootInfo"]["visits"]:
d.update(analysis_blob["rootInfo"])
return
self.analysis.append({"move": gtp, "order": 999, **analysis_blob["rootInfo"]})
else: # TODO root info / to dict?
self.analysis = analysis_blob["moveInfos"] # TODO: fix when rootInfos comes in
self.ownership = analysis_blob["ownership"]
self.update_move_analysis(analysis_json["rootInfo"], refine_move.gtp())
else:
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")
self.analysis["root"] = analysis_json["rootInfo"]
@property
def analysis_ready(self):
return self.analysis is not None
return self.analysis["root"] is not None
def format_score(self, score=None):
score = score or self.score
return f"{'B' if score >= 0 else 'W'}+{abs(score):.1f}"
def format_win_rate(self, win_rate=None):
win_rate = win_rate or self.analysis[0]["winrate"]
win_rate = win_rate or self.analysis["root"]["winrate"]
return f"{'B' if win_rate > 0.5 else 'W'} {max(win_rate,1-win_rate):.1%}"
def comment(self, sgf=False, eval=False, hints=False):
@@ -68,11 +73,14 @@ class GameNode(SGFNode):
if sgf:
text += f"Score: {self.format_score(score)}\n"
if self.parent and self.parent.analysis_ready:
if sgf or hints and self.parent.analysis[0]["move"] != single_move.gtp():
text += f"Top move was {self.parent.analysis[0]['move']} ({self.format_score(self.parent.analysis[0]['scoreLead'])})\n"
previous_top_move = self.parent.candidate_moves[0]
if sgf or hints and previous_top_move["move"] != single_move.gtp(): # TODO: when to include?
text += f"Top move was {previous_top_move['move']} ({self.format_score(previous_top_move['scoreLead'])})\n"
points_lost = self.points_lost
if sgf and points_lost > 0.5:
text += f"Estimated point loss: {points_lost:.1f}\n"
if self.auto_undo:
text += "Move was automatically undone."
else:
text = "No analysis available" if sgf else "Analyzing move..."
return text
@@ -87,7 +95,7 @@ class GameNode(SGFNode):
@property
def score(self):
return self.analysis[0]["scoreLead"] # TODO: update for rootInfo
return self.analysis["root"]["scoreLead"] # TODO: update for rootInfo
@staticmethod
def player_sign(player):
@@ -97,7 +105,7 @@ class GameNode(SGFNode):
def candidate_moves(self) -> List[Dict]:
if not self.analysis_ready:
return []
analysis = copy.copy(self.analysis) # not deep, so eval is saved, but avoids race conditions
for d in analysis:
d["pointsLost"] = self.player_sign(self.next_player) * (analysis[0]["scoreLead"] - d["scoreLead"]) # TODO: update for rootInfo
return analysis
return sorted(
[{"pointsLost": self.player_sign(self.next_player) * (self.analysis["root"]["scoreLead"] - d["scoreLead"]), **d} for d in self.analysis["moves"].values()],
key=lambda d: (d["order"], d["pointsLost"]),
)
+20
View File
@@ -174,6 +174,25 @@ class BadukPanWidget(Widget):
Rectangle(pos=(self.gridpos[x] - rsz / 2, self.gridpos[y] - rsz / 2), size=(rsz, rsz))
ix = ix + 1
policy = current_node.policy
if katrain.controls.policy.active and policy and not katrain.controls.ownership.active:
n_legal_moves = sum([p > 0 for p in policy])
avg_policy = 1.0 / n_legal_moves
best_move_policy = max(policy) # num legal moves scale?
rsz = self.grid_size * 0.2
ix = 0
for y in range(board_size-1,-1,-1):
for x in range(board_size):
if policy[ix] > 0:
policy_delta = best_move_policy - policy[ix]
polcol = self.eval_color(0.1 * policy_delta / avg_policy)
Color(*polcol)
Rectangle(pos=(self.gridpos[x] - rsz / 2, self.gridpos[y] - rsz / 2), size=(rsz, rsz))
else:
ix = ix + 1
policy_delta = best_move_policy - policy[ix]
katrain.controls.pass_btn.face_color = (*self.eval_color(100*policy_delta),1)
# children of current moves in undo / review
undo_coords = set()
alpha = self.ui_config["_child_alpha"]
@@ -201,6 +220,7 @@ class BadukPanWidget(Widget):
scale = 0.85
self.draw_stone(move.coords[0], move.coords[1], c, scale=scale)
# hover next move ghost stone
if self.ghost_stone:
self.draw_stone(*self.ghost_stone, (*stone_color[next_player], _ghost_alpha))
+2 -15
View File
@@ -1,17 +1,4 @@
import copy
import json
import os
import random
import re
import sys
import threading
import time
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.checkbox import CheckBox
from kivy.uix.filechooser import FileChooserListView
from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label
class Controls(BoxLayout):
@@ -43,8 +30,8 @@ class Controls(BoxLayout):
def unlock(self):
if self.ai_lock.active:
self.ai_lock.checkbox._do_press()
for el in [self.ai_lock.checkbox, self.hints.black, self.hints.white, self.ai_auto.black, self.ai_auto.white, self.auto_undo.black, self.auto_undo.white, self.ai_move]:
self.ai_lock.checkbox.trigger_action(duration=0)
for el in [self.ai_lock.checkbox, self.analyze_tab_button, self.ai_auto.white, self.ai_auto.black, self.ai_move]:
el.disabled = False
# handles showing completed analysis and triggered actions like auto undo and ai move
+6 -2
View File
@@ -276,6 +276,7 @@
score: score
hints: hints
ownership: ownership
policy: policy
eval: eval
ai_balance: ai_balance
ai_auto: ai_auto
@@ -283,6 +284,7 @@
ai_lock: ai_lock
ai_move: ai_move
auto_undo: auto_undo
pass_btn: pass_btn
katrain: self.parent
BoxLayout:
size_hint: 1,0.05
@@ -418,10 +420,10 @@
size_hint: 0.25, 1
text: 'lock\nAI'
id: ai_lock
on_active: self.checkbox.disabled = analyze_tab_button.disabled = ai_auto.white.checkbox = ai_auto.black.checkbox = ai_move.disabled = True
on_active: self.checkbox.disabled = analyze_tab_button.disabled = ai_auto.white = ai_auto.black = ai_move.disabled = True
LargeLabel:
text: 'free real estate'
size_hint: 1,0.5
size_hint: 1,0.2
CensorableLabel:
id: points_lost
size_hint: 1, 0.03
@@ -438,6 +440,7 @@
default_active: True
StyledButton:
text: 'Pass'
id: pass_btn
on_press: root.katrain("play",None)
TextInput:
id: info
@@ -477,6 +480,7 @@
BadukPanWidget:
id: board_gui
size_hint: 1 - controls.size_hint[0], 1
Controls:
id: controls
size_hint: max(0.25,(root.width - root.height)/root.width), 1
+5 -1
View File
@@ -222,8 +222,12 @@ class KaTrainGui(BoxLayout):
Clipboard.copy(self.game.root.sgf())
self.controls.set_status("Copied SGF to clipboard.")
elif keycode[1] == "v" and "ctrl" in modifiers:
clipboard = Clipboard.paste()
if not clipboard:
self.controls.set_status(f"Ctrl-V pressed but clipboard is empty.")
return
try:
move_tree = KaTrainSGF.parse(Clipboard.paste())
move_tree = KaTrainSGF.parse(clipboard)
except Exception as e:
self.controls.set_status(f"Failed to imported game from clipboard: {e}")
return