ui fiddling

This commit is contained in:
Sander Land committed 2020-04-21 16:54:55 +02:00
1 parent 3ea068ccd0
commit d14f077e71
16 files changed
+434 -294

No files matched your search

+14
View File
@@ -0,0 +1,14 @@
[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] remove fast -> in settings?
[] List edit settings/object edit settings?
[] README
[] Release notes
[] remove button disable code?
+6 -8
View File
@@ -4,8 +4,7 @@
"model": " models/b15-1.3.2.txt.gz", "model": " models/b15-1.3.2.txt.gz",
"config": "KataGo/analysis_config.cfg", "config": "KataGo/analysis_config.cfg",
"threads": 8, "threads": 8,
"visits": 2000, "visits": 500,
"visits_fast": 500,
"enable_ownership": true "enable_ownership": true
}, },
"files": { "files": {
@@ -19,13 +18,12 @@
"komi_9": 6.5 "komi_9": 6.5
}, },
"trainer": { "trainer": {
"lock_undos": true,
"num_undo_prompts": [1,1,1,0.5,0], "num_undo_prompts": [1,1,1,0.5,0],
"eval_thresholds": [12,6,3,1.5,0.5], "eval_thresholds": [12,6,3,1.5,0.5],
"balance_play_target_score": 2, "balance_target_score": 2,
"balance_play_randomize_score": 1, "balance_randomize_score": 1,
"balance_play_max_lost": 5, "balance_max_lost": 5,
"balance_play_min_visits": 20, "balance_min_visits": 20,
"eval_off_show_last": 3 "eval_off_show_last": 3
}, },
"board_ui": { "board_ui": {
@@ -41,7 +39,7 @@
"eval_colors": [[0.537, 0.129, 0.42], [1, 0, 0], [1,0.5,0], [0.88, 0.70, 0.30], [0.67, 0.95, 0.18], [0.117, 0.588, 0]], "eval_colors": [[0.537, 0.129, 0.42], [1, 0, 0], [1,0.5,0], [0.88, 0.70, 0.30], [0.67, 0.95, 0.18], [0.117, 0.588, 0]],
"board_color": [0.85, 0.68, 0.40], "board_color": [0.85, 0.68, 0.40],
"line_color": [0,0,0], "line_color": [0,0,0],
"policy_color": [0,0.2,0.8], "policy_color": [0,0.2,0.8]
}, },
"debug": { "debug": {
"level": 2 "level": 2
+6 -14
View File
@@ -28,7 +28,6 @@ class KataGoEngine:
self.command = shlex.split(self.command) self.command = shlex.split(self.command)
self.queries = {} # outstanding query id -> start time and callback self.queries = {} # outstanding query id -> start time and callback
self.config = config self.config = config
self.visits = [config["visits"], config["visits_fast"]]
self.query_counter = 0 self.query_counter = 0
self.katago_process = None self.katago_process = None
self.base_priority = 0 self.base_priority = 0
@@ -79,34 +78,27 @@ class KataGoEngine:
else: else:
self.katrain.log(f"Query result {analysis['id']} discarded -- recent new game?", OUTPUT_DEBUG) self.katrain.log(f"Query result {analysis['id']} discarded -- recent new game?", OUTPUT_DEBUG)
def request_analysis( def request_analysis(self, analysis_node: GameNode, callback: Callable, visits: int = None, priority: int = 0, ownership: Optional[bool] = None, next_move=None):
self, analysis_node: GameNode, callback: Callable, faster: bool = False, visits: int = None, priority: int = 0, ownership: Optional[bool] = None, refine_move=None
):
fast = self.katrain.controls.ai_fast.active
query_id = f"QUERY:{str(self.query_counter)}" query_id = f"QUERY:{str(self.query_counter)}"
self.query_counter += 1 self.query_counter += 1
if not visits:
visits = self.config["visits_fast" if fast else "visits"]
if faster:
visits /= 5
moves = [m for node in analysis_node.nodes_from_root for m in node.move_with_placements] moves = [m for node in analysis_node.nodes_from_root for m in node.move_with_placements]
if refine_move: if next_move:
moves.append(refine_move) moves.append(next_move)
if ownership is None: if ownership is None:
ownership = self.config["enable_ownership"] and not refine_move ownership = self.config["enable_ownership"] and not next_move
query = { query = {
"id": query_id, "id": query_id,
"rules": self.get_rules(analysis_node), "rules": self.get_rules(analysis_node),
"priority": self.base_priority + priority, "priority": self.base_priority + priority,
"analyzeTurns": [len(moves)], "analyzeTurns": [len(moves)],
"maxVisits": visits, "maxVisits": visits or self.config["visits"],
"komi": analysis_node.komi, "komi": analysis_node.komi,
"boardXSize": analysis_node.board_size, "boardXSize": analysis_node.board_size,
"boardYSize": analysis_node.board_size, "boardYSize": analysis_node.board_size,
"includeOwnership": ownership, "includeOwnership": ownership,
"includePolicy": not refine_move, "includePolicy": not next_move,
"moves": [[m.player, m.gtp()] for m in moves], "moves": [[m.player, m.gtp()] for m in moves],
} }
self.queries[query_id] = (callback, time.time()) self.queries[query_id] = (callback, time.time())
+12 -10
View File
@@ -7,7 +7,7 @@ from typing import List
from kivy.clock import Clock from kivy.clock import Clock
from constants import OUTPUT_DEBUG from constants import OUTPUT_DEBUG, OUTPUT_INFO
from game_node import GameNode from game_node import GameNode
from sgf_parser import SGF, Move from sgf_parser import SGF, Move
@@ -202,27 +202,29 @@ class Game:
time.sleep(0.05) time.sleep(0.05)
# select move # select move
candidate_ai_moves = cn.candidate_moves candidate_ai_moves = cn.candidate_moves
ai_mode = self.katrain.controls.player_mode(cn.next_player) ai_mode = self.katrain.controls.ai_mode(cn.next_player)
if "policy" in ai_mode and cn.policy: if "policy" in ai_mode and cn.policy:
policy_moves = cn.policy_ranking policy_moves = cn.policy_ranking
self.katrain.log(f"Top 5 policy moves are: {policy_moves[:5]}", OUTPUT_DEBUG) self.katrain.log(f"Top 5 policy moves are: {policy_moves[:5]}", OUTPUT_DEBUG)
aimove = policy_moves[0][0] aimove = policy_moves[0][0]
elif "balance" in ai_mode and candidate_ai_moves[0]['move'] != "pass": # don't play suicidal to balance score - pass when it's best elif "balance" in ai_mode and candidate_ai_moves[0]["move"] != "pass": # don't play suicidal to balance score - pass when it's best
sign = cn.player_sign(cn.next_player) # TODO check sign = cn.player_sign(cn.next_player) # TODO check
sel_moves = [ # top move, or anything not too bad, or anything that makes you still ahead sel_moves = [ # top move, or anything not too bad, or anything that makes you still ahead
move move
for i, move in enumerate(candidate_ai_moves) for i, move in enumerate(candidate_ai_moves)
if i == 0 if i == 0
or move["visits"] >= train_settings["balance_play_min_visits"] or move["visits"] >= train_settings["balance_min_visits"]
and ( and (
move["pointsLost"] < train_settings["balance_play_randomize_score"] move["pointsLost"] < train_settings["balance_randomize_score"]
or move["pointsLost"] < train_settings["balance_play_max_lost"] or move["pointsLost"] < train_settings["balance_max_lost"]
and sign * move["scoreLead"] > train_settings["balance_play_target_score"] and sign * move["scoreLead"] > train_settings["balance_target_score"]
) )
] ]
aimove = Move.from_gtp(random.choice(sel_moves)["move"], player=cn.next_player) # TODO: could be weighted towards worse aimove = Move.from_gtp(random.choice(sel_moves)["move"], player=cn.next_player) # TODO: could be weighted towards worse
else: else:
if "default" not in ai_mode:
self.katrain.log(f"Unknown AI mode {ai_mode}, using default.", OUTPUT_INFO)
aimove = Move.from_gtp(candidate_ai_moves[0]["move"], player=cn.next_player) aimove = Move.from_gtp(candidate_ai_moves[0]["move"], player=cn.next_player)
self.play(aimove) self.play(aimove)
+3 -3
View File
@@ -30,7 +30,7 @@ class GameNode(SGFNode):
# various analysis functions # various analysis functions
def analyze(self, engine, priority=0, visits=None, refine_move=None): 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) engine.request_analysis(self, lambda result: self.set_analysis(result, refine_move), priority=priority, visits=visits, next_move=refine_move)
def update_move_analysis(self, move_analysis, move_gtp): def update_move_analysis(self, move_analysis, move_gtp):
cur = self.analysis["moves"].get(move_gtp) cur = self.analysis["moves"].get(move_gtp)
@@ -120,7 +120,7 @@ class GameNode(SGFNode):
moves = [] moves = []
for y in range(self.board_size - 1, -1, -1): for y in range(self.board_size - 1, -1, -1):
for x in range(self.board_size): for x in range(self.board_size):
moves.append((Move((x, y),player=self.next_player), self.policy[ix])) moves.append((Move((x, y), player=self.next_player), self.policy[ix]))
ix += 1 ix += 1
moves.append((Move(None,player=self.next_player), self.policy[ix])) moves.append((Move(None, player=self.next_player), self.policy[ix]))
return sorted(moves, key=lambda mp: -mp[1]) return sorted(moves, key=lambda mp: -mp[1])
+1 -13
View File
@@ -1,16 +1,4 @@
from gui.badukpan import BadukPanWidget, BadukPanControls from gui.badukpan import BadukPanWidget, BadukPanControls
from gui.controls import Controls from gui.controls import Controls
from gui.kivyutils import ( from gui.kivyutils import *
BWCheckBoxHint,
CensorableLabel,
CheckBoxHint,
LabelledFloatInput,
LabelledIntInput,
StyledButton,
DarkLabel,
BaseCircleWithText,
LabelledSpinner,
LabelledCheckBox,
LabelledTextInput,
)
from gui.popups import LoadSGFPopup from gui.popups import LoadSGFPopup
+16 -13
View File
@@ -59,9 +59,8 @@ class BadukPanWidget(Widget):
if nodes_here and max(yd, xd) < self.grid_size / 2: # load old comment if nodes_here and max(yd, xd) < self.grid_size / 2: # load old comment
katrain.log(f"\nAnalysis:\n{nodes_here[-1].analysis}", OUTPUT_DEBUG) katrain.log(f"\nAnalysis:\n{nodes_here[-1].analysis}", OUTPUT_DEBUG)
katrain.log(f"\nParent Analysis:\n{nodes_here[-1].parent.analysis}", OUTPUT_DEBUG) katrain.log(f"\nParent Analysis:\n{nodes_here[-1].parent.analysis}", OUTPUT_DEBUG)
if not katrain.controls.ai_lock.active: katrain.controls.info.text = nodes_here[-1].comment(sgf=True)
katrain.controls.info.text = nodes_here[-1].comment(sgf=True) katrain.controls.show_evaluation_stats(nodes_here[-1])
katrain.controls.show_evaluation_stats(nodes_here[-1])
self.ghost_stone = None self.ghost_stone = None
self.draw_board_contents() # remove ghost self.draw_board_contents() # remove ghost
@@ -148,7 +147,8 @@ class BadukPanWidget(Widget):
# stones # stones
current_node = katrain.game.current_node current_node = katrain.game.current_node
next_player = katrain.game.next_player next_player = katrain.game.next_player
full_eval_on = katrain.controls.eval.active_map game_ended = katrain.game.game_ended
full_eval_on = katrain.controls.eval.active
has_stone = {} has_stone = {}
drawn_stone = {} drawn_stone = {}
for m in katrain.game.stones: for m in katrain.game.stones:
@@ -161,7 +161,7 @@ class BadukPanWidget(Widget):
evalsize = 1 evalsize = 1
for m in node.move_with_placements: for m in node.move_with_placements:
if has_stone.get(m.coords) and not drawn_stone.get(m.coords): # skip captures, last only for if has_stone.get(m.coords) and not drawn_stone.get(m.coords): # skip captures, last only for
move_eval_on = full_eval_on[m.player] or i < show_n_eval move_eval_on = full_eval_on or i < show_n_eval
evalcol = self.eval_color(points_lost) if move_eval_on and points_lost is not None else None evalcol = self.eval_color(points_lost) if move_eval_on and points_lost is not None else None
inner = stone_color[m.opponent] if i == 0 else None inner = stone_color[m.opponent] if i == 0 else None
drawn_stone[m.coords] = m.player drawn_stone[m.coords] = m.player
@@ -181,7 +181,7 @@ class BadukPanWidget(Widget):
ix = ix + 1 ix = ix + 1
policy = current_node.policy policy = current_node.policy
if not policy and current_node.parent and current_node.parent.policy and 'ai' in katrain.controls.player_mode('B') and 'ai' in katrain.controls.player_mode('W'): if not policy and current_node.parent and current_node.parent.policy and "ai" in katrain.controls.player_mode("B") and "ai" in katrain.controls.player_mode("W"):
policy = current_node.parent.policy # in the case of AI self-play we allow the policy to be one step out of date policy = current_node.parent.policy # in the case of AI self-play we allow the policy to be one step out of date
pass_btn = katrain.board_controls.pass_btn pass_btn = katrain.board_controls.pass_btn
pass_btn.canvas.after.clear() pass_btn.canvas.after.clear()
@@ -192,7 +192,10 @@ class BadukPanWidget(Widget):
for x in range(board_size): for x in range(board_size):
if policy[ix] > 0: if policy[ix] > 0:
polsize = math.sqrt(policy[ix]) polsize = math.sqrt(policy[ix])
policy_circle_color = (*self.ui_config["policy_color"], self.ui_config["ghost_alpha"] + self.ui_config["top_move_x_alpha"] * (policy[ix] == best_move_policy)) policy_circle_color = (
*self.ui_config["policy_color"],
self.ui_config["ghost_alpha"] + self.ui_config["top_move_x_alpha"] * (policy[ix] == best_move_policy),
)
self.draw_stone(x, y, policy_circle_color, scale=polsize) self.draw_stone(x, y, policy_circle_color, scale=polsize)
ix = ix + 1 ix = ix + 1
polsize = math.sqrt(policy[ix]) polsize = math.sqrt(policy[ix])
@@ -212,17 +215,17 @@ class BadukPanWidget(Widget):
self.draw_stone(m.coords[0], m.coords[1], (*stone_color[m.player][:3], alpha), None, None, evalcol, evalscale=scale, scale=scale) self.draw_stone(m.coords[0], m.coords[1], (*stone_color[m.player][:3], alpha), None, None, evalcol, evalscale=scale, scale=scale)
# hints # hints
if katrain.controls.hints.active(next_player): if katrain.controls.hints.active and not game_ended:
hint_moves = current_node.candidate_moves hint_moves = current_node.candidate_moves
for i, d in enumerate(hint_moves): for i, d in enumerate(hint_moves):
move = Move.from_gtp(d["move"]) move = Move.from_gtp(d["move"])
if move.coords is not None and move.coords not in undo_coords: if move.coords is not None and move.coords not in undo_coords:
alpha, scale = self.ui_config["ghost_alpha"], 1.0 alpha, scale = self.ui_config["ghost_alpha"], 1.0
if i == 0: if i == 0:
c[3] += self.ui_config["top_move_x_alpha"] alpha += self.ui_config["top_move_x_alpha"]
elif d["visits"] < 0.1 * hint_moves[0]["visits"]: # TODO: config? elif d["visits"] < 0.1 * hint_moves[0]["visits"]: # TODO: config?
scale = 0.8 scale = 0.8
self.draw_stone(move.coords[0], move.coords[1], [*self.eval_color(d["pointsLost"]),alpha], scale=scale) self.draw_stone(move.coords[0], move.coords[1], [*self.eval_color(d["pointsLost"]), alpha], scale=scale)
# hover next move ghost stone # hover next move ghost stone
if self.ghost_stone: if self.ghost_stone:
@@ -231,7 +234,7 @@ class BadukPanWidget(Widget):
# pass circle # pass circle
passed = len(nodes) > 1 and current_node.is_pass passed = len(nodes) > 1 and current_node.is_pass
if passed: if passed:
if katrain.game.game_ended: if game_ended:
text = "game\nend" text = "game\nend"
else: else:
text = "pass" text = "pass"
+9 -24
View File
@@ -1,6 +1,6 @@
from kivy.graphics.vertex_instructions import SmoothLine, Line
from kivy.uix.boxlayout import BoxLayout
from kivy.graphics.context_instructions import Color from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Line, SmoothLine
from kivy.uix.boxlayout import BoxLayout
class Controls(BoxLayout): class Controls(BoxLayout):
@@ -41,11 +41,8 @@ class Controls(BoxLayout):
def player_mode(self, player): def player_mode(self, player):
return self.player_mode_groups[player].value return self.player_mode_groups[player].value
def unlock(self): def ai_mode(self, player):
if self.ai_lock.active: return self.ai_mode_groups[player].text.lower()
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
def on_size(self, *args): def on_size(self, *args):
self.update_evaluation() self.update_evaluation()
@@ -63,26 +60,14 @@ class Controls(BoxLayout):
if current_node: if current_node:
move = current_node.single_move move = current_node.single_move
current_player_is_human_or_both_robots = not current_node.player or not self.ai_auto.active(current_node.player) or self.ai_auto.active(current_node.next_player) current_player_is_human_or_both_robots = (
not current_node.player or "ai" not in self.player_mode(current_node.player) or "ai" in self.player_mode(current_node.next_player)
)
if current_player_is_human_or_both_robots and not current_node.is_root and move: if current_player_is_human_or_both_robots and not current_node.is_root and move:
info += current_node.comment(eval=True, hints=self.hints.active(move.player)) info += current_node.comment(eval=True, hints=self.hints.active)
if current_player_is_human_or_both_robots: if current_player_is_human_or_both_robots:
self.show_evaluation_stats(current_node) self.show_evaluation_stats(current_node)
game_node = katrain.game.current_node self.graph.update_value(current_node.depth, current_node.score)
scores = [n.score for n in game_node.nodes_from_root]
# TODO: like redo, what is the node to redo / should we append? cache?
self.graph.canvas.clear()
with self.graph.canvas:
pt = []
nnscores = [s for s in scores if s is not None] + [-5, 5]
scale = max(max(*nnscores), -min(*nnscores)) * 1.05
xscale = self.graph.width * 0.9 / max(len(scores), 20)
ls = 0
for i, s in enumerate(scores):
ls = s or ls
pt.extend([self.graph.pos[0] + 0.05 * self.graph.width + i * xscale, self.graph.pos[1] + self.graph.height / 2 * (1 + ls / scale)])
Color(0, 0, 0)
Line(points=pt, width=1.0) # just set points?
self.info.text = info self.info.text = info
+55 -24
View File
@@ -10,6 +10,7 @@ import re
from kivy.uix.button import Button from kivy.uix.button import Button
from kivy.uix.checkbox import CheckBox from kivy.uix.checkbox import CheckBox
from kivy.uix.dropdown import DropDown
from kivy.uix.gridlayout import GridLayout from kivy.uix.gridlayout import GridLayout
from kivy.uix.label import Label from kivy.uix.label import Label
from kivy.uix.spinner import Spinner from kivy.uix.spinner import Spinner
@@ -32,13 +33,21 @@ class DarkLabel(Label):
class StyledButton(Button): class StyledButton(Button):
pass text_color = ListProperty([0.95, 0.95, 0.95, 1]) # TODO defaults as in ..
button_color = ListProperty([0.21, 0.28, 0.31, 1])
button_color_down = ListProperty([0.105, 0.14, 0.155, 1])
margin = ListProperty([1, 1, 1, 1]) # margin left bottom right top
radius = ListProperty((0,))
class StyledToggleButton(StyledButton, ToggleButtonBehavior): class StyledToggleButton(StyledButton, ToggleButtonBehavior):
value = StringProperty("") value = StringProperty("")
class StyledSpinner(Spinner):
pass
class ToggleButtonContainer(GridLayout): class ToggleButtonContainer(GridLayout):
__events__ = ("on_selection",) __events__ = ("on_selection",)
@@ -47,8 +56,7 @@ class ToggleButtonContainer(GridLayout):
selected = StringProperty("") selected = StringProperty("")
group = StringProperty(None) group = StringProperty(None)
autosize = BooleanProperty(True) autosize = BooleanProperty(True)
button_class = ObjectProperty(StyledToggleButton) margin = ListProperty([0, 0, 1, 1]) # TODO outer vs inner margin?
margin = ListProperty([1, 1, 0, 0])
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
@@ -66,20 +74,19 @@ class ToggleButtonContainer(GridLayout):
if len(self.labels) < len(self.options): if len(self.labels) < len(self.options):
self.labels += self.options[len(self.labels) + 1 :] self.labels += self.options[len(self.labels) + 1 :]
def state_handler(btn,*args): def state_handler(btn, *args):
self.dispatch("on_selection") self.dispatch("on_selection")
btn.state = 'down' # no toggle btn.state = "down" # no toggle
for i, opt in enumerate(self.options): for i, opt in enumerate(self.options):
state = "down" if opt == self.selected else "normal" state = "down" if opt == self.selected else "normal"
self.add_widget(self.button_class(group=self.group, text=self.labels[i], value=opt, state=state, self.add_widget(StyledToggleButton(group=self.group, text=self.labels[i], value=opt, state=state, margin=self.margin, on_press=state_handler,))
margin=self.margin,on_press=state_handler))
Clock.schedule_once(self._size, 0) Clock.schedule_once(self._size, 0)
def _size(self, _dt): def _size(self, _dt):
if self.autosize: if self.autosize:
for tb in self.children: for tb in self.children:
tb.size_hint = (tb.texture_size[0] + 3, 1) tb.size_hint = (tb.texture_size[0] + 25, 1)
@property @property
def value(self): def value(self):
@@ -96,6 +103,7 @@ class BaseCircleWithText(DarkLabel):
class LabelledTextInput(TextInput): class LabelledTextInput(TextInput):
input_property = StringProperty("") input_property = StringProperty("")
multiline = BooleanProperty(False)
@property @property
def input_value(self): def input_value(self):
@@ -115,7 +123,7 @@ class LabelledCheckBox(CheckBox):
return bool(self.active) return bool(self.active)
class LabelledSpinner(Spinner): class LabelledSpinner(StyledSpinner):
input_property = StringProperty("") input_property = StringProperty("")
@property @property
@@ -156,30 +164,53 @@ class LabelledIntInput(LabelledTextInput):
return int(self.text) return int(self.text)
class BWCheckBoxHint(BoxLayout):
__events__ = ("on_active",)
@property
def active_map(self):
return {"B": self.black.active, "W": self.white.active}
def active(self, player):
return self.active_map[player]
def on_active(self, *args):
pass
class CensorableLabel(BoxLayout): class CensorableLabel(BoxLayout):
@property @property
def text(self): def text(self):
return self.value.text return self.value.text
class ScoreGraph(Label):
values = ListProperty([])
line_points = ListProperty([])
line = ObjectProperty(None)
dot = ObjectProperty(None)
highlighted_index = NumericProperty(0)
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.canvas:
Color(0, 0, 0)
self.line = Line(points=self.line_points, width=1.0)
Color(0.21, 0.28, 0.31)
self.dot = Ellipse(pos=self.pos, size=(2, 2))
def clear_values(self):
self.values = []
def on_size(self, *args):
if self.values:
val_range = min(self.values), max(self.values)
scale = max(5, max(-val_range[0], val_range[1]) * 1.05)
xscale = self.width * 0.9 / max(len(self.values), 20)
self.line_points = [[self.pos[0] + 0.05 * self.width + i * xscale, self.pos[1] + self.height / 2 * (1 + val / scale)] for i, val in enumerate(self.values)]
self.line.points = sum(self.line_points, [])
if self.highlighted_index:
self.dot.pos = [c - 1 for c in self.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
self.highlighted_index = index
self.on_size()
def draw_text(pos, text, **kw): def draw_text(pos, text, **kw):
label = CoreLabel(text=text, bold=True, **kw) label = CoreLabel(text=text, bold=True, **kw)
label.refresh() label.refresh()
Rectangle(texture=label.texture, pos=(pos[0] - label.texture.size[0] / 2, pos[1] - label.texture.size[1] / 2), size=label.texture.size) Rectangle(
texture=label.texture, pos=(pos[0] - label.texture.size[0] / 2, pos[1] - label.texture.size[1] / 2), size=label.texture.size,
)
def draw_circle(pos, r, col): def draw_circle(pos, r, col):
+31 -14
View File
@@ -1,3 +1,5 @@
from collections import defaultdict
from kivy.uix.boxlayout import BoxLayout from kivy.uix.boxlayout import BoxLayout
import os import os
from constants import OUTPUT_DEBUG, OUTPUT_ERROR from constants import OUTPUT_DEBUG, OUTPUT_ERROR
@@ -76,13 +78,15 @@ class ConfigPopup(QuickConfigGui):
else: else:
return LabelledTextInput return LabelledTextInput
def __init__(self, katrain, popup, config): def __init__(self, katrain, popup, config, ignore_cats):
self.config = config self.config = config
self.orientation = "vertical" self.orientation = "vertical"
super().__init__(katrain, popup) super().__init__(katrain, popup)
cols = [BoxLayout(orientation="vertical"), BoxLayout(orientation="vertical")] cols = [BoxLayout(orientation="vertical"), BoxLayout(orientation="vertical")]
props_in_col = [0, 0] props_in_col = [0, 0]
for k1, all_d in config.items(): for k1, all_d in config.items():
if k1 in ignore_cats:
continue
d = {k: v for k, v in all_d.items() if isinstance(v, (int, float, str, bool))} # no complex objects d = {k: v for k, v in all_d.items() if isinstance(v, (int, float, str, bool))} # no complex objects
cat = GridLayout(cols=2, rows=len(d) + 1, size_hint=(1, len(d) + 1)) cat = GridLayout(cols=2, rows=len(d) + 1, size_hint=(1, len(d) + 1))
cat.add_widget(Label(text="")) cat.add_widget(Label(text=""))
@@ -101,22 +105,27 @@ class ConfigPopup(QuickConfigGui):
col_container.add_widget(cols[0]) col_container.add_widget(cols[0])
col_container.add_widget(cols[1]) col_container.add_widget(cols[1])
self.add_widget(col_container) self.add_widget(col_container)
self.save_button = StyledButton(text="Apply Settings", on_press=lambda _: self.update_config(), size_hint=(1, 0.05)) self.info_label = Label()
self.save_button = StyledButton(text="Apply and Save Settings", on_press=lambda _: self.update_config(save_to_file=True), size_hint=(1, 0.05)) self.apply_button = StyledButton(text="Apply Settings", on_press=lambda _: self.update_config())
self.add_widget(self.save_button) self.save_button = StyledButton(text="Apply and Save Settings", on_press=lambda _: self.update_config(save_to_file=True))
btn_container = BoxLayout(orientation="horizontal", size_hint=(1, 0.05))
btn_container.add_widget(self.info_label)
btn_container.add_widget(self.apply_button)
btn_container.add_widget(self.save_button)
self.add_widget(btn_container)
def update_config(self, save_to_file=False): def update_config(self, save_to_file=False):
updated_cat = [] updated_cat = defaultdict(list)
try: try:
for k, v in self.collect_properties(self).items(): for k, v in self.collect_properties(self).items():
k1, k2 = k.split("/") k1, k2 = k.split("/")
if self.config[k1][k2] != v: if self.config[k1][k2] != v:
self.katrain.log(f"Updating setting {k} = {v}", OUTPUT_DEBUG) self.katrain.log(f"Updating setting {k} = {v}", OUTPUT_DEBUG)
updated_cat.append(k1) updated_cat[k1].append(k2)
self.config[k1][k2] = v self.config[k1][k2] = v
self.popup.dismiss() self.popup.dismiss()
except InputParseError as e: except InputParseError as e:
self.save_button.text = str(e) # TODO: nicer error self.info_label.text = str(e)
self.katrain.log(e, OUTPUT_ERROR) self.katrain.log(e, OUTPUT_ERROR)
return return
@@ -124,10 +133,18 @@ class ConfigPopup(QuickConfigGui):
for cat in updated_cat: for cat in updated_cat:
self.katrain.save_config(cat, **self.config[cat]) self.katrain.save_config(cat, **self.config[cat])
if "engine" in updated_cat: engine_restart = False
self.katrain.log("Restarting Engine after settings change") for cat, updates in updated_cat.items():
old_engine = self.katrain.engine if "engine" in cat: # TODO: multi engine support
self.katrain.engine = KataGoEngine(self.katrain, self.config["engine"]) if "visits" in updates:
self.katrain.game.engine = self.katrain.engine self.katrain.engine.visits = self.config[cat]["visits"]
old_engine.shutdown(finish=True) if set(updates) != {"visits"}:
self.katrain.update_state(redraw_board=True) self.katrain.log(f"Restarting Engine {cat} after {updates} settings change")
old_engine = self.katrain.engine
self.katrain.engine = KataGoEngine(self.katrain, self.config[cat])
self.katrain.game.engine = self.katrain.engine
old_engine.shutdown(finish=True)
engine_restart = True
if engine_restart:
self.katrain.update_state(redraw_board=True)
+1
View File
@@ -0,0 +1 @@
<svg id="_x31_px" enable-background="new 0 0 24 24" height="512" viewBox="0 0 24 24" width="512" xmlns="http://www.w3.org/2000/svg"><path d="m21 24h-18c-1.654 0-3-1.346-3-3s1.346-3 3-3h18c1.654 0 3 1.346 3 3s-1.346 3-3 3zm-18-5c-1.103 0-2 .897-2 2s.897 2 2 2h18c1.103 0 2-.897 2-2s-.897-2-2-2z"/><path d="m12 16c-.128 0-.256-.049-.354-.146l-5.5-5.5c-.142-.144-.185-.358-.108-.545s.26-.309.462-.309h3v-1.5c0-4.41 3.587-7.998 7.997-8h.003c.276 0 .5.224.5.5s-.224.5-.5.5c-1.654 0-3 1.346-3 3v5.5h3c.202 0 .385.122.462.309s.034.402-.108.545l-5.5 5.5c-.098.097-.226.146-.354.146zm-4.293-5.5 4.293 4.293 4.293-4.293h-2.293c-.276 0-.5-.224-.5-.5v-6c0-.78.225-1.508.611-2.124-2.151 1.195-3.611 3.493-3.611 6.124v2c0 .276-.224.5-.5.5z"/></svg>

After

Width:  |  Height:  |  Size: 734 B

+114
View File
@@ -0,0 +1,114 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
<g>
<g>
<g>
<path d="M262.785,317.207c-1.913-4.032-6.736-5.752-10.769-3.837c-6.562,3.114-13.442,5.688-20.448,7.648
c-2.931,0.82-5.152,3.215-5.748,6.2l-8.552,42.758c-0.844,4.222-4.582,7.287-8.888,7.287h-23.329
c-4.306,0-8.043-3.064-8.888-7.287l-8.552-42.758c-0.597-2.985-2.819-5.38-5.748-6.2c-9.898-2.77-19.45-6.728-28.391-11.764
c-2.653-1.494-5.92-1.371-8.452,0.317l-36.286,24.19c-3.582,2.388-8.392,1.911-11.438-1.133L60.8,316.131
c-3.045-3.045-3.52-7.855-1.133-11.438l24.19-36.286c1.689-2.533,1.812-5.799,0.317-8.452
c-5.037-8.942-8.995-18.495-11.764-28.391c-0.82-2.931-3.215-5.152-6.2-5.748l-42.758-8.552
c-4.222-0.844-7.287-4.582-7.287-8.888v-23.329c0-4.306,3.064-8.043,7.287-8.888l42.758-8.552c2.985-0.597,5.38-2.819,6.2-5.748
c2.77-9.898,6.728-19.45,11.764-28.391c1.494-2.652,1.371-5.919-0.317-8.452L59.669,88.734
c-2.388-3.583-1.912-8.392,1.133-11.438l16.496-16.496c3.045-3.045,7.856-3.52,11.438-1.133l36.286,24.19
c2.532,1.689,5.8,1.811,8.452,0.317c8.941-5.036,18.493-8.994,28.391-11.764c2.931-0.82,5.152-3.215,5.748-6.2l8.552-42.758
c0.844-4.222,4.582-7.287,8.888-7.287h23.329c4.306,0,8.043,3.064,8.888,7.287l8.552,42.758c0.597,2.985,2.819,5.38,5.748,6.2
c9.898,2.77,19.45,6.728,28.391,11.764c2.653,1.494,5.92,1.371,8.452-0.317l36.286-24.19c3.582-2.388,8.391-1.911,11.438,1.133
l16.496,16.496c3.045,3.045,3.52,7.855,1.133,11.438l-24.19,36.286c-1.689,2.533-1.812,5.799-0.317,8.452
c5.036,8.941,8.994,18.493,11.764,28.391c0.82,2.931,3.215,5.152,6.2,5.748l42.758,8.552c4.222,0.844,7.287,4.582,7.287,8.888
v23.329c0,4.306-3.064,8.043-7.287,8.888l-42.758,8.552c-2.985,0.597-5.38,2.819-6.2,5.75c-2.009,7.18-4.66,14.223-7.88,20.934
c-1.932,4.025-0.234,8.854,3.791,10.785c4.025,1.933,8.855,0.234,10.785-3.791c2.906-6.057,5.401-12.354,7.442-18.777
l37.991-7.598c11.749-2.352,20.279-12.758,20.279-24.744v-23.329c0-11.986-8.53-22.391-20.284-24.743l-37.99-7.598
c-2.492-7.844-5.65-15.47-9.434-22.769l21.492-32.239c6.648-9.973,5.323-23.363-3.153-31.839l-16.496-16.496
c-8.477-8.476-21.867-9.8-31.839-3.153l-32.239,21.492c-7.299-3.783-14.925-6.942-22.769-9.434l-7.598-37.99
C230.772,8.53,220.366,0,208.38,0h-23.329c-11.986,0-22.391,8.53-24.743,20.284l-7.598,37.99
c-7.844,2.492-15.47,5.65-22.769,9.434L97.704,46.216c-9.972-6.648-23.363-5.324-31.839,3.153L49.369,65.865
c-8.475,8.476-9.801,21.865-3.153,31.839l21.492,32.239c-3.783,7.299-6.942,14.924-9.434,22.769l-37.991,7.598
C8.53,162.66,0,173.066,0,185.052v23.329c0,11.986,8.53,22.391,20.284,24.743l37.99,7.598c2.491,7.843,5.65,15.469,9.434,22.769
L46.216,295.73c-6.648,9.973-5.323,23.363,3.153,31.839l16.496,16.496c8.476,8.476,21.866,9.8,31.839,3.153l32.239-21.492
c7.299,3.783,14.924,6.942,22.769,9.434l7.598,37.992c2.35,11.75,12.756,20.281,24.742,20.281h23.329
c11.986,0,22.391-8.53,24.743-20.284l7.598-37.99c6.229-1.979,12.343-4.386,18.228-7.18
C262.983,326.063,264.701,321.241,262.785,317.207z"/>
<path d="M266.739,241.636c1.18,0.606,2.44,0.893,3.682,0.893c2.936,0,5.769-1.605,7.2-4.397
c6.52-12.715,9.967-27.036,9.967-41.415c0-50.107-40.765-90.873-90.873-90.873c-23.838,0-46.364,9.162-63.43,25.801
c-3.197,3.116-3.262,8.235-0.146,11.431c3.116,3.196,8.234,3.263,11.431,0.146c14.029-13.676,32.547-21.209,52.143-21.209
c41.192,0,74.705,33.512,74.705,74.705c0,11.994-2.754,23.446-8.185,34.038C261.197,234.726,262.766,239.598,266.739,241.636z"/>
<path d="M121.043,162.125c-4.223-1.453-8.821,0.792-10.274,5.014c-3.267,9.498-4.924,19.45-4.924,29.577
c0,50.108,40.766,90.873,90.873,90.873c19.271,0,37.683-5.956,53.247-17.227c3.616-2.619,4.425-7.674,1.807-11.29
c-2.618-3.616-7.674-4.425-11.29-1.807c-12.788,9.259-27.921,14.154-43.765,14.154c-41.193,0-74.705-33.513-74.705-74.705
c0-8.334,1.36-16.517,4.044-24.318C127.508,168.175,125.264,163.576,121.043,162.125z"/>
<path d="M496.442,353.316l-23.377-4.675c-1.424-4.237-3.139-8.374-5.127-12.373l13.226-19.838
c5.099-7.65,4.083-17.92-2.418-24.42l-10.84-10.84c-6.501-6.5-16.771-7.517-24.42-2.418l-19.838,13.226
c-3.999-1.988-8.136-3.703-12.373-5.126l-4.675-23.377c-1.803-9.015-9.784-15.558-18.977-15.558h-15.331
c-9.192,0-17.174,6.543-18.977,15.558l-4.675,23.377c-4.237,1.424-8.374,3.139-12.373,5.126l-19.838-13.226
c-7.65-5.098-17.92-4.083-24.42,2.418l-10.84,10.84c-6.501,6.501-7.517,16.771-2.418,24.42l13.226,19.838
c-2.734,5.498-4.949,11.256-6.605,17.177c-1.203,4.3,1.308,8.76,5.607,9.964c4.3,1.203,8.76-1.307,9.964-5.607
c1.76-6.292,4.276-12.362,7.476-18.043c1.494-2.653,1.371-5.92-0.317-8.452l-15.897-23.845c-0.839-1.259-0.672-2.949,0.398-4.019
l10.84-10.84c1.07-1.069,2.76-1.235,4.019-0.398l23.845,15.897c2.532,1.689,5.799,1.811,8.452,0.317
c5.682-3.2,11.751-5.716,18.043-7.476c2.931-0.82,5.152-3.215,5.748-6.2l5.619-28.097c0.296-1.483,1.61-2.56,3.123-2.56h15.331
c1.512,0,2.826,1.077,3.123,2.56l5.619,28.099c0.597,2.985,2.819,5.38,5.748,6.2c6.292,1.76,12.362,4.276,18.043,7.476
c2.652,1.494,5.92,1.371,8.452-0.317l23.845-15.897c1.259-0.839,2.949-0.673,4.019,0.398l10.84,10.84
c1.069,1.07,1.236,2.76,0.398,4.019l-15.897,23.845c-1.689,2.533-1.812,5.799-0.317,8.452c3.2,5.682,5.716,11.751,7.476,18.043
c0.82,2.931,3.215,5.152,6.2,5.748l28.097,5.619c1.483,0.296,2.56,1.61,2.56,3.123v15.331c0,1.512-1.077,2.826-2.56,3.123
l-28.099,5.619c-2.985,0.597-5.38,2.819-6.2,5.748c-1.76,6.292-4.276,12.362-7.476,18.043c-1.494,2.653-1.371,5.92,0.317,8.452
l15.897,23.845c0.839,1.259,0.672,2.949-0.398,4.019l-10.84,10.84c-1.069,1.07-2.759,1.236-4.019,0.398l-23.845-15.897
c-2.532-1.688-5.8-1.811-8.452-0.317c-5.682,3.2-11.751,5.716-18.043,7.476c-2.931,0.82-5.152,3.215-5.748,6.2l-5.619,28.097
c-0.296,1.483-1.61,2.56-3.123,2.56h-15.331c-1.512,0-2.826-1.077-3.123-2.56l-5.619-28.099c-0.597-2.985-2.819-5.38-5.748-6.2
c-6.292-1.76-12.362-4.276-18.043-7.476c-2.653-1.494-5.921-1.371-8.452,0.317l-23.845,15.897
c-1.259,0.839-2.949,0.673-4.019-0.398l-10.84-10.84c-1.069-1.07-1.236-2.76-0.398-4.019l15.897-23.845
c1.689-2.533,1.812-5.799,0.317-8.452c-3.2-5.682-5.716-11.751-7.476-18.043c-0.82-2.931-3.215-5.152-6.2-5.748l-28.097-5.619
c-1.483-0.296-2.56-1.61-2.56-3.123v-15.331c0-1.512,1.077-2.826,2.56-3.123l1.72-0.344c4.378-0.876,7.218-5.135,6.341-9.513
c-0.876-4.378-5.13-7.213-9.514-6.341l-1.72,0.344c-9.013,1.804-15.556,9.785-15.556,18.978v15.331
c0,9.192,6.543,17.174,15.558,18.977l23.377,4.675c1.424,4.237,3.139,8.374,5.126,12.373l-13.226,19.838
c-5.1,7.65-4.083,17.92,2.418,24.42l10.84,10.84c6.501,6.501,16.771,7.517,24.42,2.418l19.838-13.226
c3.999,1.988,8.136,3.703,12.373,5.126l4.675,23.377C355.119,505.457,363.1,512,372.293,512h15.331
c9.192,0,17.174-6.543,18.977-15.558l4.675-23.377c4.237-1.424,8.374-3.139,12.373-5.127l19.838,13.226
c7.65,5.099,17.92,4.083,24.42-2.418l10.84-10.84c6.501-6.501,7.517-16.771,2.418-24.42l-13.226-19.838
c1.988-3.999,3.703-8.136,5.126-12.373l23.377-4.675c9.014-1.803,15.557-9.784,15.557-18.977v-15.331
C512,363.1,505.457,355.119,496.442,353.316z"/>
<path d="M379.958,350.855c16.048,0,29.103,13.055,29.103,29.103c0,4.465,3.62,8.084,8.084,8.084s8.084-3.62,8.084-8.084
c0-24.963-20.309-45.272-45.272-45.272s-45.272,20.309-45.272,45.272s20.309,45.272,45.272,45.272
c10.111,0,19.675-3.261,27.659-9.429c3.533-2.73,4.184-7.807,1.455-11.341c-2.73-3.533-7.808-4.185-11.341-1.455
c-5.128,3.961-11.274,6.056-17.773,6.056c-16.048,0-29.103-13.055-29.103-29.103C350.855,363.909,363.91,350.855,379.958,350.855
z"/>
<path d="M245.969,196.716c0-27.158-22.096-49.253-49.253-49.253c-27.158,0-49.253,22.096-49.253,49.253
c0,27.158,22.095,49.253,49.253,49.253S245.969,223.873,245.969,196.716z M163.631,196.716c0-18.242,14.843-33.085,33.085-33.085
c18.242,0,33.085,14.843,33.085,33.085c0,18.242-14.842,33.085-33.085,33.085C178.472,229.801,163.631,214.958,163.631,196.716z"
/>
</g>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.3 KiB

+60
View File
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 434.393 434.393" style="enable-background:new 0 0 434.393 434.393;" xml:space="preserve">
<path id="XMLID_290_" d="M40.452,400.164c-11.329-7.896-18.093-20.774-18.093-34.45v-54.976c0-5.973,4.222-10.977,9.838-12.185
c0.159-0.027,0.316-0.058,0.474-0.093c0.699-0.122,1.417-0.186,2.15-0.186h135.434c1.081,0,2.13,0.139,3.132,0.398
c0.19,0.062,0.383,0.116,0.577,0.165c5.066,1.582,8.754,6.318,8.754,11.899v46.084c0,4.971,4.029,9,9,9s9-4.029,9-9v-46.084
c0-10.015-4.857-18.916-12.34-24.472c0.77-2.426,1.156-4.912,1.156-7.448c0-22.916-32.49-40.548-76.934-42.654v-12.366
c0-4.971-4.029-9-9-9s-9,4.029-9,9v12.366c-44.442,2.106-76.933,19.738-76.933,42.654c0,2.112,0.269,4.189,0.802,6.228
c-8.478,5.414-14.11,14.908-14.11,25.692v54.976c0,19.56,9.646,37.959,25.802,49.218c1.567,1.093,3.361,1.617,5.138,1.617
c2.844,0,5.643-1.345,7.392-3.855C45.532,408.615,44.53,403.006,40.452,400.164z M103.601,253.953
c41.47,0,67.934,14.727,67.934,24.865c0,0.482-0.061,0.975-0.181,1.477c-0.365-0.013-0.73-0.02-1.099-0.02H35.843
c-0.116-0.494-0.175-0.98-0.175-1.457C35.668,268.68,62.132,253.953,103.601,253.953z M421.033,0H73.212c-4.971,0-9,4.029-9,9
v86.822c-0.001,0.045-0.001,0.09-0.001,0.135s0,0.09,0.001,0.135v86.685c-0.001,0.045-0.001,0.09-0.001,0.135s0,0.09,0.001,0.135
v24.798c0,4.971,4.029,9,9,9s9-4.029,9-9v-15.933h68.956v17.829c0,4.971,4.029,9,9,9s9-4.029,9-9v-17.829h68.955v68.954h-19.844
c-4.971,0-9,4.029-9,9s4.029,9,9,9h19.844v68.957h-9.817c-4.971,0-9,4.029-9,9s4.029,9,9,9h192.728c4.971,0,9-4.029,9-9V9
C430.033,4.029,426.004,0,421.033,0z M151.168,173.911H82.212v-68.954h68.956V173.911z M151.168,86.957H82.212V18h68.956V86.957z
M238.123,173.911h-68.955v-68.954h68.955V173.911z M238.123,86.957h-68.955V18h68.955V86.957z M325.075,347.822h-68.952v-68.957
h68.952V347.822z M325.075,260.865h-68.952v-68.954h68.952V260.865z M325.075,173.911h-68.952v-68.954h68.952V173.911z
M325.075,86.957h-68.952V18h68.952V86.957z M412.033,347.822h-68.958v-68.957h68.958V347.822z M412.033,260.865h-68.958v-68.954
h68.958V260.865z M412.033,173.911h-68.958v-68.954h68.958V173.911z M412.033,86.957h-68.958V18h68.958V86.957z M187.046,378.297
c-16.59,0-30.726,4.368-39.351,11.333c-1.008-0.814-2.092-1.593-3.245-2.333c7.684-4.929,12.245-11.557,12.245-19.047
c0-15.99-20.787-28.048-48.352-28.048c-27.563,0-48.35,12.058-48.35,28.048c0,7.49,4.562,14.118,12.244,19.047
c-7.683,4.93-12.244,11.558-12.244,19.048c0,15.99,20.786,28.048,48.35,28.048c16.591,0,30.726-4.368,39.352-11.333
c8.625,6.965,22.761,11.333,39.351,11.333c27.564,0,48.351-12.058,48.351-28.048S214.61,378.297,187.046,378.297z M108.344,416.393
c-19.586,0-30.35-7.547-30.35-10.048s10.764-10.048,30.35-10.048c19.587,0,30.352,7.547,30.352,10.048
S127.931,416.393,108.344,416.393z M108.344,378.297c-19.586,0-30.35-7.546-30.35-10.047s10.764-10.048,30.35-10.048
c19.587,0,30.352,7.547,30.352,10.048S127.931,378.297,108.344,378.297z M187.046,416.393c-19.587,0-30.351-7.547-30.351-10.048
s10.764-10.048,30.351-10.048s30.351,7.547,30.351,10.048S206.633,416.393,187.046,416.393z"/>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

+70 -120
View File
@@ -1,16 +1,14 @@
#:kivy 1.11.0 #:kivy 1.11.0
#:import ew kivy.uix.effectwidget #:import ew kivy.uix.effectwidget
# import utils?
# margin left bottom right top #:set AI_MODES ['Default','Balance','Policy']
#:set PLAYER_MODES ['Human', 'Teach','AI:']
#:set BUTTON_COLOR [0.21, 0.28, 0.31, 1]
#:set ICON_BUTTON_COLOR [0.71, 0.78, 0.81, 1]
<StyledButton>: <StyledButton>:
text_color: 0.95,0.95,0.95,1 font_size: self.size[1] * 0.45
button_color: 0.21, 0.28, 0.31, 1
button_color_down: (*[c/2 for c in self.button_color[:3]],1)
margin: (1,1,1,1)
bold: False
font_size: self.size[1] * 0.35
radius: (self.size[1]/7,) radius: (self.size[1]/7,)
# dont set these # dont set these
disabled_mask: (0.5,0.5,0.5,1) if self.disabled else (1,1,1,1) disabled_mask: (0.5,0.5,0.5,1) if self.disabled else (1,1,1,1)
@@ -26,26 +24,34 @@
pos: (self.pos[0]+self.margin[0],self.pos[1]+self.margin[1]) pos: (self.pos[0]+self.margin[0],self.pos[1]+self.margin[1])
radius: root.radius or (0.0,) radius: root.radius or (0.0,)
<StyledSpinnerOption@SpinnerOption>:
font_size: self.size[1] * 0.45
background_color: BUTTON_COLOR
background_normal: ''
color: (1,1,1,1)
<StyledSpinner>:
text: self.values[0] if self.values else ''
font_size: self.size[1] * 0.45
sync_height: True
background_color: [*[c*255/88 for c in BUTTON_COLOR[:3]], 1] # compensate for texture
option_cls: 'StyledSpinnerOption'
<StyledToggleButton>: <StyledToggleButton>:
<ToggleButtonContainer>:
<StyledTabButton@StyledToggleButton>: <StyledTabButton@StyledToggleButton>:
bold: True bold: True
font_size: self.size[1] * 0.45
radius: (self.size[1]/3,self.size[1]/3,0,0) radius: (self.size[1]/3,self.size[1]/3,0,0)
<ToggleButtonContainer>:
<StyledIconButton@StyledButton>: <StyledIconButton@StyledButton>:
button_color: 0.71, 0.78, 0.81, 1 button_color: ICON_BUTTON_COLOR
icon_margin: 0.15 * self.size[0] icon_margin: 0.15 * self.size[0]
icon: 'missing.png' icon: ''
icon_size: min(self.size[0]-self.margin[0]-self.margin[2]-2*self.icon_margin,self.size[1]-self.margin[1]-self.margin[3]-2*self.icon_margin) icon_size: min(self.size[0]-self.margin[0]-self.margin[2]-2*self.icon_margin,self.size[1]-self.margin[1]-self.margin[3]-2*self.icon_margin)
#EffectWidget: effects: [ew.FXAAEffect()] #EffectWidget: effects: [ew.FXAAEffect()]
Image: Image:
size: (root.icon_size or 0,root.icon_size or 0) # square size: (root.icon_size or 0,root.icon_size or 0) # square
pos: [root.pos[i] + (root.size[i] - root.icon_size)/2 for i in [0,1]] if root.icon_size else [0,0] pos: [root.pos[i] + (root.size[i] - root.icon_size)/2 for i in [0,1]] if root.icon_size else [0,0]
@@ -56,7 +62,7 @@
background_color: (0,0,0,0) background_color: (0,0,0,0)
margin: (1,1,1,1) margin: (1,1,1,1)
icon_size: min(self.size[0]-self.margin[0]-self.margin[2],self.size[1]-self.margin[1]-self.margin[3]) icon_size: min(self.size[0]-self.margin[0]-self.margin[2],self.size[1]-self.margin[1]-self.margin[3])
icon: 'missing.png' icon: ''
Image: Image:
size: (root.icon_size or 0,root.icon_size or 0) # square size: (root.icon_size or 0,root.icon_size or 0) # square
pos: [root.pos[i] + (root.size[i] - root.icon_size)/2 for i in [0,1]] if root.icon_size else [0,0] pos: [root.pos[i] + (root.size[i] - root.icon_size)/2 for i in [0,1]] if root.icon_size else [0,0]
@@ -98,30 +104,6 @@
on_active: root.dispatch('on_active') on_active: root.dispatch('on_active')
active: root.default_active active: root.default_active
<BWCheckBoxHint>:
black: black
white: white
label: label
orientation: 'vertical'
text: ''
default_active: False
CheckBoxHintLabel:
size_hint: 1,0.2
id: label
text: root.text
font_size: self.height
on_press: if not white.disabled: white._do_press(); black._do_press()
CheckBox:
size_hint: 1,0.4
id: black
active: root.default_active
on_active: root.dispatch('on_active')
CheckBox:
size_hint: 1,0.4
id: white
active: root.default_active
on_active: root.dispatch('on_active')
<CensorableLabel>: <CensorableLabel>:
orientation: 'horizontal' orientation: 'horizontal'
text: '' text: ''
@@ -185,6 +167,7 @@
size_hint: 0.05, 1 size_hint: 0.05, 1
StyledButton: StyledButton:
text: 'Pass' text: 'Pass'
font_size: 0.35*self.size[1]
id: pass_btn id: pass_btn
on_press: root.katrain("play",None) on_press: root.katrain("play",None)
size_hint: 0.1, 1 size_hint: 0.1, 1
@@ -233,7 +216,16 @@
size_hint: 0.1, 1 size_hint: 0.1, 1
on_press: root.katrain('redo',999) on_press: root.katrain('redo',999)
Label: Label:
size_hint: 0.25, 1 size_hint: 0.07, 1
StyledButton:
text: 'AI Move'
font_size: 0.35*self.size[1]
id: pass_btn
on_press: root.katrain("ai-move",None)
size_hint: 0.14, 1
margin: (0, self.size[1]/5, 0, self.size[1]/5)
Label:
size_hint: 0.04, 1
<Controls>: <Controls>:
@@ -248,13 +240,8 @@
ownership: ownership ownership: ownership
policy: policy policy: policy
eval: eval eval: eval
ai_balance: ai_balance
ai_auto: ai_auto
ai_fast: ai_fast
ai_lock: ai_lock
ai_move: ai_move
auto_undo: auto_undo
player_mode_groups: {'B':B_player_mode,'W':W_player_mode} player_mode_groups: {'B':B_player_mode,'W':W_player_mode}
ai_mode_groups: {'B':B_AI_mode,'W':W_AI_mode}
graph: graph graph: graph
katrain: self.parent katrain: self.parent
canvas.before: canvas.before:
@@ -299,31 +286,24 @@
id: analyze_tab id: analyze_tab
opacity: 0 opacity: 0
orientation: 'vertical' orientation: 'vertical'
Label:
size_hint: 1,0.02
GridLayout: GridLayout:
cols: 6 cols: 6
rows: 1 rows: 1
size_hint: 1, 0.35 size_hint: 1, 0.5
Label: Label:
size_hint: 0.01, 1 size_hint: 0.05, 1
BoxLayout: CheckBoxHint:
orientation: 'vertical'
size_hint: 0.1, 0.5
Label:
size_hint: 1, 0.2
BlackCircleWithText:
text: 'B'
WhiteCircleWithText:
text: 'W'
BWCheckBoxHint:
size_hint: 0.2, 0.5 size_hint: 0.2, 0.5
id: eval id: eval
text: 'all eval' text: 'show\nall dots'
default_active: False default_active: False
on_active: root.katrain.update_state() on_active: root.katrain.update_state()
BWCheckBoxHint: CheckBoxHint:
size_hint: 0.2, 0.5 size_hint: 0.2, 0.5
id: hints id: hints
text: 'top move' text: 'show\ntop move'
on_active: root.katrain.update_state() on_active: root.katrain.update_state()
CheckBoxHint: CheckBoxHint:
size_hint: 0.2, 0.5 size_hint: 0.2, 0.5
@@ -335,10 +315,11 @@
id: policy id: policy
text: 'show\npolicy' text: 'show\npolicy'
on_active: root.katrain.update_state() on_active: root.katrain.update_state()
LargeLabel: Label:
size_hint: 0.05, 1
ScoreGraph:
id: graph id: graph
text: 'graph?' size_hint: 1,0.5
size_hint: 1,0.75
CensorableLabel: CensorableLabel:
id: win_rate id: win_rate
size_hint: 1, 0.15 size_hint: 1, 0.15
@@ -354,45 +335,47 @@
size_hint: 1,1 size_hint: 1,1
opacity: 1 opacity: 1
id: play_tab id: play_tab
Label:
size_hint: 1,0.02
GridLayout: GridLayout:
cols: 4 cols: 4
rows: 1
size_hint: 1, 0.1
StyledButton:
id: ai_move
size_hint: 0.5, 0.5
text: ' AI\nMove'
on_press: root.katrain("ai_move")
CheckBoxHint:
size_hint: 0.25, 1
text: 'lock\nAI'
id: ai_lock
on_active: self.checkbox.disabled = analyze_tab_button.disabled = ai_auto.white = ai_auto.black = ai_move.disabled = True
GridLayout:
cols: 2
rows: 2 rows: 2
size_hint: 1,0.125 size_hint: 1,0.125
BlackCircleWithText: BlackCircleWithText:
text: 'B' text: 'B'
size_hint: 0.1, 1 size_hint: 0.1, 1
ToggleButtonContainer: ToggleButtonContainer:
size_hint: 0.9, 1 size_hint: 0.6, 1
id: B_player_mode id: B_player_mode
options: ['human','human+undo','ai','ai+balance','ai+policy'] options: ['human','human+undo','ai']
labels: ['Human', ' AI\nAssist','AI',' AI\nBalance',' AI\nPolicy'] labels: PLAYER_MODES
on_selection: root.katrain.update_state() on_selection: root.katrain.update_state()
StyledSpinner:
id: B_AI_mode
size_hint: 0.3, 1
values: AI_MODES
Label:
size_hint: None,1
width: 3
WhiteCircleWithText: WhiteCircleWithText:
text: 'W' text: 'W'
size_hint: 0.1, 1 size_hint: 0.1, 1
ToggleButtonContainer: ToggleButtonContainer:
size_hint: 0.9, 1 size_hint: 0.6, 1
id: W_player_mode id: W_player_mode
options: ['human','human+undo','ai','ai+balance','ai+policy'] options: ['human','human+undo','ai']
labels: ['Human', ' AI\nAssist','AI',' AI\nBalance',' AI\nPolicy'] labels: PLAYER_MODES
on_selection: root.katrain.update_state() on_selection: root.katrain.update_state()
StyledSpinner:
id: W_AI_mode
size_hint: 0.3, 1
values: AI_MODES
Label:
size_hint: None,1
width: 3
LargeLabel: LargeLabel:
text: 'free real estate' text: 'free real estate'
size_hint: 1,0.2 size_hint: 1,0.25
CensorableLabel: CensorableLabel:
id: points_lost id: points_lost
size_hint: 1, 0.03 size_hint: 1, 0.03
@@ -402,39 +385,6 @@
id: info id: info
size_hint: 1, 0.25 size_hint: 1, 0.25
valign: 'middle' valign: 'middle'
GridLayout:
cols: 5
rows: 1
size_hint: 1, 0.1
BoxLayout:
orientation: 'vertical'
size_hint: 0.1, 0.5
Label:
size_hint: 1, 0.2
BlackCircleWithText:
text: 'B'
WhiteCircleWithText:
text: 'W'
BWCheckBoxHint:
text: 'ai'
id: ai_auto
default_active: False
size_hint: 0.25, 1
BWCheckBoxHint:
size_hint: 0.2, 0.5
id: auto_undo
text: 'undo'
on_active: root.katrain.update_state()
CheckBoxHint:
size_hint: 0.25, 1
text: 'balance\nscore'
id: ai_balance
default_active: False
CheckBoxHint:
size_hint: 0.25, 1
text: 'fast'
id: ai_fast
default_active: True
BoxLayout: BoxLayout:
orientation: 'horizontal' orientation: 'horizontal'
size_hint: 1, None size_hint: 1, None
+28 -43
View File
@@ -8,17 +8,17 @@ from kivy.app import App
from kivy.clock import Clock from kivy.clock import Clock
from kivy.core.clipboard import Clipboard from kivy.core.clipboard import Clipboard
from kivy.core.window import Window from kivy.core.window import Window
from kivy.lang import Builder
from kivy.storage.jsonstore import JsonStore from kivy.storage.jsonstore import JsonStore
from kivy.uix.boxlayout import BoxLayout from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup from kivy.uix.popup import Popup
from kivy.uix.widget import Widget
from constants import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_INFO from constants import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_INFO
from engine import KataGoEngine from engine import KataGoEngine
from game import Game, IllegalMoveException, KaTrainSGF, Move from game import Game, IllegalMoveException, KaTrainSGF, Move
from gui import * from gui import *
from gui.popups import NewGamePopup, ConfigPopup from gui.popups import ConfigPopup, NewGamePopup
from kivy.lang import Builder
class KaTrainGui(BoxLayout): class KaTrainGui(BoxLayout):
@@ -27,12 +27,12 @@ class KaTrainGui(BoxLayout):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super(KaTrainGui, self).__init__(**kwargs) super(KaTrainGui, self).__init__(**kwargs)
self.debug_level = 0 self.debug_level = 0
self._load_config()
self.debug_level = self.config("debug/level", OUTPUT_INFO)
self.logger = lambda message, level=OUTPUT_INFO: self.log(message, level)
self.engine = None self.engine = None
self.game = None self.game = None
self.logger = lambda message, level=OUTPUT_INFO: self.log(message, level)
self._load_config()
self.debug_level = self.config("debug/level", OUTPUT_INFO)
self.message_queue = Queue() self.message_queue = Queue()
self._keyboard = Window.request_keyboard(None, self, "") self._keyboard = Window.request_keyboard(None, self, "")
@@ -124,7 +124,6 @@ class KaTrainGui(BoxLayout):
def _do_new_game(self, board_size=None, move_tree=None): def _do_new_game(self, board_size=None, move_tree=None):
self.game = Game(self, self.engine, self.config("game"), board_size=board_size, move_tree=move_tree) self.game = Game(self, self.engine, self.config("game"), board_size=board_size, move_tree=move_tree)
self.controls.unlock()
self.controls.select_mode("analyze" if move_tree and len(move_tree.nodes_in_tree) > 1 else "play") self.controls.select_mode("analyze" if move_tree and len(move_tree.nodes_in_tree) > 1 else "play")
self.update_state(redraw_board=True) # TODO: just board here/redraw is in all anyway? self.update_state(redraw_board=True) # TODO: just board here/redraw is in all anyway?
@@ -134,11 +133,6 @@ class KaTrainGui(BoxLayout):
self.update_state() self.update_state()
def _do_undo(self, n_times=1): def _do_undo(self, n_times=1):
if self.controls.ai_lock.active and self.contols.auto_undo.active(self.game.current_node.player) and self.config("trainer/lock_undos"):
self.controls.set_status(
f"Can't undo manually when Automatic Undo and Lock AI are both set. (Change the `lock_undos` setting to false to allow this regardless)", self.game.current_node
)
return
self.game.undo(n_times) self.game.undo(n_times)
self.update_state() self.update_state()
@@ -181,7 +175,7 @@ class KaTrainGui(BoxLayout):
def _do_config_popup(self): def _do_config_popup(self):
config_popup = Popup(title="Edit Settings", size_hint=(0.9, 0.9)) config_popup = Popup(title="Edit Settings", size_hint=(0.9, 0.9))
popup_contents = ConfigPopup(self, config_popup, dict(self._config)) popup_contents = ConfigPopup(self, config_popup, dict(self._config), ignore_cats=("board_ui"))
config_popup.add_widget(popup_contents) config_popup.add_widget(popup_contents)
config_popup.open() config_popup.open()
@@ -198,37 +192,28 @@ class KaTrainGui(BoxLayout):
if isinstance(App.get_running_app().root_window.children[0], Popup): if isinstance(App.get_running_app().root_window.children[0], Popup):
return # if in new game or load, don't allow keyboard shortcuts return # if in new game or load, don't allow keyboard shortcuts
if keycode[1] == "up": shortcuts = {
"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),
"z": ("analyze-extra", "sweep"),
"x": ("analyze-extra", "extra"),
"c": ("analyze-extra", "refine"),
}
if keycode[1] in shortcuts.keys():
shortcut = shortcuts[keycode[1]]
if isinstance(shortcut, Widget):
shortcut.trigger_action(duration=0)
else:
self(*shortcut)
elif keycode[1] == "up":
self("undo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999) self("undo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999)
elif keycode[1] == "down": elif keycode[1] == "down":
self("redo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999) self("redo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999)
elif keycode[1] == "right":
self("switch-branch", 1)
elif keycode[1] == "left":
self("switch-branch", -1)
elif keycode[1] == "s":
self("analyze-extra", "sweep")
elif keycode[1] == "x":
self("analyze-extra", "extra")
elif keycode[1] == "r":
self("analyze-extra", "refine")
elif keycode[1] == "a":
if not self.controls.ai_thinking:
self.controls.ai_move.trigger_action(duration=0)
elif keycode[1] == "p": # TODO: clean repetitive shortcuts
self.controls.policy.label.trigger_action(duration=0)
elif keycode[1] == "f":
self.controls.ai_fast.label.trigger_action(duration=0)
elif keycode[1] == "h":
self.controls.hints.label.trigger_action(duration=0)
elif keycode[1] == "e":
self.controls.eval.label.trigger_action(duration=0)
elif keycode[1] == "u":
self.controls.auto_undo.label.trigger_action(duration=0)
elif keycode[1] == "b":
self.controls.ai_balance.label.trigger_action(duration=0)
elif keycode[1] == "o":
self.controls.ownership.label.trigger_action(duration=0)
elif keycode[1] == "n" and "ctrl" in modifiers: elif keycode[1] == "n" and "ctrl" in modifiers:
self("new-game-popup") self("new-game-popup")
elif keycode[1] == "l" and "ctrl" in modifiers: elif keycode[1] == "l" and "ctrl" in modifiers:
@@ -238,7 +223,7 @@ class KaTrainGui(BoxLayout):
elif keycode[1] == "c" and "ctrl" in modifiers: elif keycode[1] == "c" and "ctrl" in modifiers:
Clipboard.copy(self.game.root.sgf()) Clipboard.copy(self.game.root.sgf())
self.controls.set_status("Copied SGF to clipboard.") self.controls.set_status("Copied SGF to clipboard.")
elif keycode[1] == "v" and "ctrl" in modifiers: elif keycode[1] == "v" and "ctrl" in modifiers: # TODO: refactor
clipboard = Clipboard.paste() clipboard = Clipboard.paste()
if not clipboard: if not clipboard:
self.controls.set_status(f"Ctrl-V pressed but clipboard is empty.") self.controls.set_status(f"Ctrl-V pressed but clipboard is empty.")
+8 -8
View File
@@ -17,18 +17,18 @@ class Move:
PLAYERS = "BW" PLAYERS = "BW"
SGF_COORD = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ".lower()) + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ") SGF_COORD = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ".lower()) + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
@staticmethod @classmethod
def from_gtp(gtp_coords, player="B"): def from_gtp(cls, gtp_coords, player="B"):
if "pass" in gtp_coords: if "pass" in gtp_coords:
return Move(coords=None, player=player) return cls(coords=None, player=player)
match = re.match(r"([A-Z]+)(\d+)", gtp_coords) match = re.match(r"([A-Z]+)(\d+)", gtp_coords)
return Move(coords=(Move.GTP_COORD.index(match[1]), int(match[2]) - 1), player=player) return cls(coords=(Move.GTP_COORD.index(match[1]), int(match[2]) - 1), player=player)
@staticmethod @classmethod
def from_sgf(sgf_coords, board_size, player="B"): def from_sgf(cls, sgf_coords, board_size, player="B"):
if sgf_coords == "" or Move.SGF_COORD.index(sgf_coords[0]) == board_size: # some servers use [tt] for pass if sgf_coords == "" or Move.SGF_COORD.index(sgf_coords[0]) == board_size: # some servers use [tt] for pass
return Move(coords=None, player=player) return cls(coords=None, player=player)
return Move(coords=(Move.SGF_COORD.index(sgf_coords[0]), board_size - Move.SGF_COORD.index(sgf_coords[1]) - 1), player=player) return cls(coords=(Move.SGF_COORD.index(sgf_coords[0]), board_size - Move.SGF_COORD.index(sgf_coords[1]) - 1), player=player)
def __init__(self, coords: Optional[Tuple[int, int]] = None, player: str = "B"): def __init__(self, coords: Optional[Tuple[int, int]] = None, player: str = "B"):
self.player = player self.player = player