policy
This commit is contained in:
1 parent
8aaa85e73d
commit
c4ce996ef5
7 files changed
+64
-40
No files matched your search
@@ -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())
|
||||
|
||||
@@ -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
@@ -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"]),
|
||||
)
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in new issue
Block a user