This commit is contained in:
Sander Land committed 2020-04-21 23:28:36 +02:00
1 parent 8171a6d5fd
commit 39556ad690
9 files changed
+64 -50

No files matched your search

+2 -2
View File
@@ -1,7 +1,7 @@
# additions
KataGoData
KataGo/KataGoData
old
experiments
.idea
gtp.log
*.zip
@@ -14,7 +14,7 @@ my
outdated_log.txt
# too big
models/b20*.gz
models/*.bin.gz
# standard python ignore
# Byte-compiled / optimized / DLL files
+9 -5
View File
@@ -1,14 +1,18 @@
[x] Player button to black/white with AI style spinner
[] Multi engine (detect all req. analysis in analysis[engine] fn)
[] Non square boards
[] Polish graph
[x Non square boards
[x] remove fast -> in settings?
[x] Polish graph
[] Multi engine (detect all req. analysis in analysis[engine] fn)
[] Graph is awkward when reviewing. Should we store nodes rather than values? & populate on new game.
[] Score instead of game end
[] Self-play tournaments?
[] List edit settings/object edit settings?
[] README
[] Release notes
[] remove button disable code?
[] BoxLayout padding/spacing use instead of fiddling?
+2 -1
View File
@@ -15,7 +15,8 @@
"init_size": 19,
"komi_19": 6.5,
"komi_13": 6.5,
"komi_9": 6.5
"komi_9": 6.5,
"sweep_visits_frac": 0.2
},
"trainer": {
"num_undo_prompts": [1,1,1,0.5,0],
+11 -9
View File
@@ -78,18 +78,24 @@ class KataGoEngine:
else:
self.katrain.log(f"Query result {analysis['id']} discarded -- recent new game?", OUTPUT_DEBUG)
def request_analysis(self, analysis_node: GameNode, callback: Callable, visits: int = None, priority: int = 0, ownership: Optional[bool] = None, next_move=None):
query_id = f"QUERY:{str(self.query_counter)}"
def send_query(self, query, callback):
self.query_counter += 1
if "id" not in query:
query["id"] = f"QUERY:{str(self.query_counter)}"
self.queries[query["id"]] = (callback, time.time())
if self.katago_process:
self.katrain.log(f"Sending query {query['id']}: {str(query)}", OUTPUT_DEBUG)
self.katago_process.stdin.write((json.dumps(query) + "\n").encode())
self.katago_process.stdin.flush()
def request_analysis(self, analysis_node: GameNode, callback: Callable, visits: int = None, priority: int = 0, ownership: Optional[bool] = None, next_move=None):
moves = [m for node in analysis_node.nodes_from_root for m in node.move_with_placements]
if next_move:
moves.append(next_move)
if ownership is None:
ownership = self.config["enable_ownership"] and not next_move
size_x, size_y = analysis_node.board_size
query = {
"id": query_id,
"rules": self.get_rules(analysis_node),
"priority": self.base_priority + priority,
"analyzeTurns": [len(moves)],
@@ -101,8 +107,4 @@ class KataGoEngine:
"includePolicy": not next_move,
"moves": [[m.player, m.gtp()] for m in moves],
}
self.queries[query_id] = (callback, time.time())
if self.katago_process:
self.katrain.log(f"Sending query {query_id}: {str(query)}", OUTPUT_DEBUG)
self.katago_process.stdin.write((json.dumps(query) + "\n").encode())
self.katago_process.stdin.flush()
self.send_query(query, callback)
+3 -3
View File
@@ -281,13 +281,13 @@ class Game:
elif mode == "sweep":
board_size_x, board_size_y = self.board_size
analyze_moves = [Move(coords=(x, y), player=cn.next_player) for x in range(board_size_x) for y in range(board_size_y) if (x, y) not in stones]
visits = self.engine.config["visits_fast"]
visits = self.engine.config["visits"] * self.config["sweep_visits_frac"]
self.katrain.controls.set_status(f"Refining analysis of entire board to {visits} visits")
priority = -1_000_000_000
else: # mode=='refine':
analyze_moves = [Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items()]
visits = max(d["visits"] for d in cn.analysis["moves"].values()) + self.engine.config["visits_fast"]
self.katrain.controls.set_status(f"Refining analysis of candidate moves to {visits} visits")
visits = max(d["visits"] for d in cn.analysis["moves"].values())
self.katrain.controls.set_status(f"Equalizing analysis of candidate moves to {visits} visits")
priority = -1_000
for move in analyze_moves:
cn.analyze(self.engine, priority, visits=visits, refine_move=move)
+7 -2
View File
@@ -75,12 +75,17 @@ class GameNode(SGFNode):
if self.parent and self.parent.analysis_ready:
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"
text += f"Predicted 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 sgf or hints:
text += f"Top policy move was {self.parent.policy_ranking[0][0].gtp()}\n"
policy_ranking = self.parent.policy_ranking
policy_ix = [ix+1 for (m,p),ix in zip(policy_ranking,range(len(policy_ranking))) if m==single_move]
if not policy_ix or policy_ix[0]!=1:
text += f"Top policy move was {policy_ranking[0][0].gtp()}\n"
if policy_ix:
text += f"Your move was #{policy_ix} according to NN policy\n"
if self.auto_undo:
text += "Move was automatically undone."
else:
+1 -1
View File
@@ -69,6 +69,6 @@ class Controls(BoxLayout):
self.show_evaluation_stats(current_node)
if current_node.score:
self.graph.update_value(current_node.depth, current_node.score)
self.graph.update_value(current_node)
self.info.text = info
+15 -19
View File
@@ -19,17 +19,6 @@ from kivy.uix.spinner import Spinner
from kivy.uix.textinput import TextInput
class CheckBoxHint(BoxLayout):
__events__ = ("on_active",)
@property
def active(self):
return self.checkbox.active
def on_active(self, *args):
pass
class DarkLabel(Label):
pass
@@ -173,7 +162,7 @@ class CensorableLabel(BoxLayout):
class ScoreGraph(Label):
values = ListProperty([])
nodes = ListProperty([])
line_points = ListProperty([])
dot_pos = ListProperty([0, 0])
highlighted_index = NumericProperty(None)
@@ -182,12 +171,18 @@ class ScoreGraph(Label):
super().__init__(**kwargs)
Clock.schedule_once(self.on_size, 0)
def clear(self):
self.values = []
def initialize_from_game(self, root):
self.nodes = [root]
node = root
while node.children:
node = node.children[0]
self.nodes.append(node)
self.highlighted_index = 0
def on_size(self, *args):
values = self.values
if values:
nodes = self.nodes
if nodes:
values = [n.score if n and n.score else 0 for n in nodes]
val_range = min(values or [0]), max(values or [0])
scale = math.ceil(max(3, max(-val_range[0], val_range[1]) * 1.05))
@@ -202,9 +197,10 @@ class ScoreGraph(Label):
self.highlighted_index = min(self.highlighted_index, len(values) - 1)
self.dot_pos = [c - self.highlight_size / 2 for c in line_points[self.highlighted_index]]
def update_value(self, index, value):
self.values.extend([0] * max(0, index - (len(self.values) - 1)))
self.values[index] = value or 0
def update_value(self, node):
index = node.depth
self.nodes.extend([None] * max(0, index - (len(self.nodes) - 1)))
self.nodes[index] = node
self.highlighted_index = index
self.on_size()
+14 -8
View File
@@ -19,6 +19,7 @@ from engine import KataGoEngine
from game import Game, IllegalMoveException, KaTrainSGF, Move
from gui import *
from gui.popups import ConfigPopup, NewGamePopup
from sgf_parser import ParseError
class KaTrainGui(BoxLayout):
@@ -50,7 +51,7 @@ class KaTrainGui(BoxLayout):
config_file = sys.argv[1] if len(sys.argv) > 1 else os.path.join(base_path, "config.json")
try:
self.log(f"Using config file {config_file}", OUTPUT_INFO)
self._config_store = JsonStore(config_file)
self._config_store = JsonStore(config_file,indent=4)
self._config = dict(self._config_store)
except Exception as e:
self.log(f"Failed to load config {config_file}: {e}", OUTPUT_ERROR)
@@ -125,7 +126,7 @@ class KaTrainGui(BoxLayout):
def _do_new_game(self, move_tree=None):
self.game = Game(self, self.engine, self.config("game"), move_tree=move_tree)
self.controls.select_mode("analyze" if move_tree and len(move_tree.nodes_in_tree) > 1 else "play")
self.controls.graph.clear()
self.controls.graph.initialize_from_game(self.game.root)
self.update_state(redraw_board=True) # TODO: just board here/redraw is in all anyway?
def _do_ai_move(self, node=None):
@@ -163,7 +164,12 @@ class KaTrainGui(BoxLayout):
def readfile(files, _mouse):
fileselect_popup.dismiss()
self._do_new_game(self, move_tree=KaTrainSGF.parse_file(files[0]))
try:
move_tree = KaTrainSGF.parse_file(files[0])
except ParseError as e:
self.log(f"Failed to load SGF. Parse Error: {e}", OUTPUT_ERROR)
return
self._do_new_game(move_tree=move_tree)
popup_contents.filesel.on_submit = readfile
fileselect_popup.open()
@@ -184,7 +190,7 @@ class KaTrainGui(BoxLayout):
for pl in Move.PLAYERS:
if not self.game.root.get_first(f"P{pl}"):
_, model_file = os.path.split(self.engine.config["model"])
self.game.root.properties[f"P{pl}"] = [f"KaTrain (KataGo {model_file})" if self.controls.ai_auto.active(pl) else "Player"]
self.game.root.properties[f"P{pl}"] = [f"KaTrain (KataGo {model_file})" if 'ai' in self.controls.player_mode(pl) else "Player"]
msg = self.game.write_sgf(self.config("files/sgf_save"))
self.log(msg, OUTPUT_INFO)
self.controls.set_status(msg)
@@ -194,10 +200,10 @@ class KaTrainGui(BoxLayout):
return # if in new game or load, don't allow keyboard shortcuts
shortcuts = {
"u": self.controls.eval.checkbox,
"i": self.controls.hints.checkbox,
"p": self.controls.policy.checkbox,
"o": self.controls.ownership.checkbox,
"u": self.controls.eval,
"i": self.controls.hints,
"p": self.controls.policy,
"o": self.controls.ownership,
"a": ("ai-move",),
"right": ("switch-branch", 1),
"left": ("switch-branch", -1),