ui fiddling
This commit is contained in:
1 parent
3ea068ccd0
commit
d14f077e71
16 files changed
+434
-294
No files matched your search
@@ -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
@@ -4,8 +4,7 @@
|
||||
"model": " models/b15-1.3.2.txt.gz",
|
||||
"config": "KataGo/analysis_config.cfg",
|
||||
"threads": 8,
|
||||
"visits": 2000,
|
||||
"visits_fast": 500,
|
||||
"visits": 500,
|
||||
"enable_ownership": true
|
||||
},
|
||||
"files": {
|
||||
@@ -19,13 +18,12 @@
|
||||
"komi_9": 6.5
|
||||
},
|
||||
"trainer": {
|
||||
"lock_undos": true,
|
||||
"num_undo_prompts": [1,1,1,0.5,0],
|
||||
"eval_thresholds": [12,6,3,1.5,0.5],
|
||||
"balance_play_target_score": 2,
|
||||
"balance_play_randomize_score": 1,
|
||||
"balance_play_max_lost": 5,
|
||||
"balance_play_min_visits": 20,
|
||||
"balance_target_score": 2,
|
||||
"balance_randomize_score": 1,
|
||||
"balance_max_lost": 5,
|
||||
"balance_min_visits": 20,
|
||||
"eval_off_show_last": 3
|
||||
},
|
||||
"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]],
|
||||
"board_color": [0.85, 0.68, 0.40],
|
||||
"line_color": [0,0,0],
|
||||
"policy_color": [0,0.2,0.8],
|
||||
"policy_color": [0,0.2,0.8]
|
||||
},
|
||||
"debug": {
|
||||
"level": 2
|
||||
|
||||
@@ -28,7 +28,6 @@ class KataGoEngine:
|
||||
self.command = shlex.split(self.command)
|
||||
self.queries = {} # outstanding query id -> start time and callback
|
||||
self.config = config
|
||||
self.visits = [config["visits"], config["visits_fast"]]
|
||||
self.query_counter = 0
|
||||
self.katago_process = None
|
||||
self.base_priority = 0
|
||||
@@ -79,34 +78,27 @@ 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, faster: bool = False, visits: int = None, priority: int = 0, ownership: Optional[bool] = None, refine_move=None
|
||||
):
|
||||
fast = self.katrain.controls.ai_fast.active
|
||||
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)}"
|
||||
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]
|
||||
if refine_move:
|
||||
moves.append(refine_move)
|
||||
if next_move:
|
||||
moves.append(next_move)
|
||||
|
||||
if ownership is None:
|
||||
ownership = self.config["enable_ownership"] and not refine_move
|
||||
ownership = self.config["enable_ownership"] and not next_move
|
||||
|
||||
query = {
|
||||
"id": query_id,
|
||||
"rules": self.get_rules(analysis_node),
|
||||
"priority": self.base_priority + priority,
|
||||
"analyzeTurns": [len(moves)],
|
||||
"maxVisits": visits,
|
||||
"maxVisits": visits or self.config["visits"],
|
||||
"komi": analysis_node.komi,
|
||||
"boardXSize": analysis_node.board_size,
|
||||
"boardYSize": analysis_node.board_size,
|
||||
"includeOwnership": ownership,
|
||||
"includePolicy": not refine_move,
|
||||
"includePolicy": not next_move,
|
||||
"moves": [[m.player, m.gtp()] for m in moves],
|
||||
}
|
||||
self.queries[query_id] = (callback, time.time())
|
||||
|
||||
@@ -7,7 +7,7 @@ from typing import List
|
||||
|
||||
from kivy.clock import Clock
|
||||
|
||||
from constants import OUTPUT_DEBUG
|
||||
from constants import OUTPUT_DEBUG, OUTPUT_INFO
|
||||
from game_node import GameNode
|
||||
from sgf_parser import SGF, Move
|
||||
|
||||
@@ -202,27 +202,29 @@ class Game:
|
||||
time.sleep(0.05)
|
||||
# select move
|
||||
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:
|
||||
policy_moves = cn.policy_ranking
|
||||
self.katrain.log(f"Top 5 policy moves are: {policy_moves[:5]}", OUTPUT_DEBUG)
|
||||
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
|
||||
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
|
||||
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
|
||||
sel_moves = [ # top move, or anything not too bad, or anything that makes you still ahead
|
||||
move
|
||||
for i, move in enumerate(candidate_ai_moves)
|
||||
if i == 0
|
||||
or move["visits"] >= train_settings["balance_play_min_visits"]
|
||||
or move["visits"] >= train_settings["balance_min_visits"]
|
||||
and (
|
||||
move["pointsLost"] < train_settings["balance_play_randomize_score"]
|
||||
or move["pointsLost"] < train_settings["balance_play_max_lost"]
|
||||
and sign * move["scoreLead"] > train_settings["balance_play_target_score"]
|
||||
move["pointsLost"] < train_settings["balance_randomize_score"]
|
||||
or move["pointsLost"] < train_settings["balance_max_lost"]
|
||||
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:
|
||||
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)
|
||||
self.play(aimove)
|
||||
|
||||
|
||||
+3
-3
@@ -30,7 +30,7 @@ class GameNode(SGFNode):
|
||||
|
||||
# various analysis functions
|
||||
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):
|
||||
cur = self.analysis["moves"].get(move_gtp)
|
||||
@@ -120,7 +120,7 @@ class GameNode(SGFNode):
|
||||
moves = []
|
||||
for y in range(self.board_size - 1, -1, -1):
|
||||
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
|
||||
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])
|
||||
+1
-13
@@ -1,16 +1,4 @@
|
||||
from gui.badukpan import BadukPanWidget, BadukPanControls
|
||||
from gui.controls import Controls
|
||||
from gui.kivyutils import (
|
||||
BWCheckBoxHint,
|
||||
CensorableLabel,
|
||||
CheckBoxHint,
|
||||
LabelledFloatInput,
|
||||
LabelledIntInput,
|
||||
StyledButton,
|
||||
DarkLabel,
|
||||
BaseCircleWithText,
|
||||
LabelledSpinner,
|
||||
LabelledCheckBox,
|
||||
LabelledTextInput,
|
||||
)
|
||||
from gui.kivyutils import *
|
||||
from gui.popups import LoadSGFPopup
|
||||
+16
-13
@@ -59,9 +59,8 @@ class BadukPanWidget(Widget):
|
||||
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"\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.show_evaluation_stats(nodes_here[-1])
|
||||
katrain.controls.info.text = nodes_here[-1].comment(sgf=True)
|
||||
katrain.controls.show_evaluation_stats(nodes_here[-1])
|
||||
|
||||
self.ghost_stone = None
|
||||
self.draw_board_contents() # remove ghost
|
||||
@@ -148,7 +147,8 @@ class BadukPanWidget(Widget):
|
||||
# stones
|
||||
current_node = katrain.game.current_node
|
||||
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 = {}
|
||||
drawn_stone = {}
|
||||
for m in katrain.game.stones:
|
||||
@@ -161,7 +161,7 @@ class BadukPanWidget(Widget):
|
||||
evalsize = 1
|
||||
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
|
||||
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
|
||||
inner = stone_color[m.opponent] if i == 0 else None
|
||||
drawn_stone[m.coords] = m.player
|
||||
@@ -181,7 +181,7 @@ class BadukPanWidget(Widget):
|
||||
ix = ix + 1
|
||||
|
||||
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
|
||||
pass_btn = katrain.board_controls.pass_btn
|
||||
pass_btn.canvas.after.clear()
|
||||
@@ -192,7 +192,10 @@ class BadukPanWidget(Widget):
|
||||
for x in range(board_size):
|
||||
if policy[ix] > 0:
|
||||
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)
|
||||
ix = ix + 1
|
||||
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)
|
||||
|
||||
# hints
|
||||
if katrain.controls.hints.active(next_player):
|
||||
if katrain.controls.hints.active and not game_ended:
|
||||
hint_moves = current_node.candidate_moves
|
||||
for i, d in enumerate(hint_moves):
|
||||
move = Move.from_gtp(d["move"])
|
||||
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:
|
||||
c[3] += self.ui_config["top_move_x_alpha"]
|
||||
elif d["visits"] < 0.1 * hint_moves[0]["visits"]: # TODO: config?
|
||||
alpha += self.ui_config["top_move_x_alpha"]
|
||||
elif d["visits"] < 0.1 * hint_moves[0]["visits"]: # TODO: config?
|
||||
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
|
||||
if self.ghost_stone:
|
||||
@@ -231,7 +234,7 @@ class BadukPanWidget(Widget):
|
||||
# pass circle
|
||||
passed = len(nodes) > 1 and current_node.is_pass
|
||||
if passed:
|
||||
if katrain.game.game_ended:
|
||||
if game_ended:
|
||||
text = "game\nend"
|
||||
else:
|
||||
text = "pass"
|
||||
|
||||
+9
-24
@@ -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.vertex_instructions import Line, SmoothLine
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
|
||||
|
||||
class Controls(BoxLayout):
|
||||
@@ -41,11 +41,8 @@ class Controls(BoxLayout):
|
||||
def player_mode(self, player):
|
||||
return self.player_mode_groups[player].value
|
||||
|
||||
def unlock(self):
|
||||
if self.ai_lock.active:
|
||||
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 ai_mode(self, player):
|
||||
return self.ai_mode_groups[player].text.lower()
|
||||
|
||||
def on_size(self, *args):
|
||||
self.update_evaluation()
|
||||
@@ -63,26 +60,14 @@ class Controls(BoxLayout):
|
||||
|
||||
if current_node:
|
||||
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:
|
||||
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:
|
||||
self.show_evaluation_stats(current_node)
|
||||
|
||||
game_node = katrain.game.current_node
|
||||
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.graph.update_value(current_node.depth, current_node.score)
|
||||
|
||||
self.info.text = info
|
||||
+55
-24
@@ -10,6 +10,7 @@ import re
|
||||
|
||||
from kivy.uix.button import Button
|
||||
from kivy.uix.checkbox import CheckBox
|
||||
from kivy.uix.dropdown import DropDown
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.spinner import Spinner
|
||||
@@ -32,13 +33,21 @@ class DarkLabel(Label):
|
||||
|
||||
|
||||
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):
|
||||
value = StringProperty("")
|
||||
|
||||
|
||||
class StyledSpinner(Spinner):
|
||||
pass
|
||||
|
||||
|
||||
class ToggleButtonContainer(GridLayout):
|
||||
__events__ = ("on_selection",)
|
||||
|
||||
@@ -47,8 +56,7 @@ class ToggleButtonContainer(GridLayout):
|
||||
selected = StringProperty("")
|
||||
group = StringProperty(None)
|
||||
autosize = BooleanProperty(True)
|
||||
button_class = ObjectProperty(StyledToggleButton)
|
||||
margin = ListProperty([1, 1, 0, 0])
|
||||
margin = ListProperty([0, 0, 1, 1]) # TODO outer vs inner margin?
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -66,20 +74,19 @@ class ToggleButtonContainer(GridLayout):
|
||||
if len(self.labels) < len(self.options):
|
||||
self.labels += self.options[len(self.labels) + 1 :]
|
||||
|
||||
def state_handler(btn,*args):
|
||||
def state_handler(btn, *args):
|
||||
self.dispatch("on_selection")
|
||||
btn.state = 'down' # no toggle
|
||||
btn.state = "down" # no toggle
|
||||
|
||||
for i, opt in enumerate(self.options):
|
||||
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,
|
||||
margin=self.margin,on_press=state_handler))
|
||||
self.add_widget(StyledToggleButton(group=self.group, text=self.labels[i], value=opt, state=state, margin=self.margin, on_press=state_handler,))
|
||||
Clock.schedule_once(self._size, 0)
|
||||
|
||||
def _size(self, _dt):
|
||||
if self.autosize:
|
||||
for tb in self.children:
|
||||
tb.size_hint = (tb.texture_size[0] + 3, 1)
|
||||
tb.size_hint = (tb.texture_size[0] + 25, 1)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
@@ -96,6 +103,7 @@ class BaseCircleWithText(DarkLabel):
|
||||
|
||||
class LabelledTextInput(TextInput):
|
||||
input_property = StringProperty("")
|
||||
multiline = BooleanProperty(False)
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
@@ -115,7 +123,7 @@ class LabelledCheckBox(CheckBox):
|
||||
return bool(self.active)
|
||||
|
||||
|
||||
class LabelledSpinner(Spinner):
|
||||
class LabelledSpinner(StyledSpinner):
|
||||
input_property = StringProperty("")
|
||||
|
||||
@property
|
||||
@@ -156,30 +164,53 @@ class LabelledIntInput(LabelledTextInput):
|
||||
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):
|
||||
@property
|
||||
def text(self):
|
||||
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):
|
||||
label = CoreLabel(text=text, bold=True, **kw)
|
||||
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):
|
||||
|
||||
+31
-14
@@ -1,3 +1,5 @@
|
||||
from collections import defaultdict
|
||||
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
import os
|
||||
from constants import OUTPUT_DEBUG, OUTPUT_ERROR
|
||||
@@ -76,13 +78,15 @@ class ConfigPopup(QuickConfigGui):
|
||||
else:
|
||||
return LabelledTextInput
|
||||
|
||||
def __init__(self, katrain, popup, config):
|
||||
def __init__(self, katrain, popup, config, ignore_cats):
|
||||
self.config = config
|
||||
self.orientation = "vertical"
|
||||
super().__init__(katrain, popup)
|
||||
cols = [BoxLayout(orientation="vertical"), BoxLayout(orientation="vertical")]
|
||||
props_in_col = [0, 0]
|
||||
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
|
||||
cat = GridLayout(cols=2, rows=len(d) + 1, size_hint=(1, len(d) + 1))
|
||||
cat.add_widget(Label(text=""))
|
||||
@@ -101,22 +105,27 @@ class ConfigPopup(QuickConfigGui):
|
||||
col_container.add_widget(cols[0])
|
||||
col_container.add_widget(cols[1])
|
||||
self.add_widget(col_container)
|
||||
self.save_button = StyledButton(text="Apply Settings", on_press=lambda _: self.update_config(), size_hint=(1, 0.05))
|
||||
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.add_widget(self.save_button)
|
||||
self.info_label = Label()
|
||||
self.apply_button = StyledButton(text="Apply Settings", on_press=lambda _: self.update_config())
|
||||
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):
|
||||
updated_cat = []
|
||||
updated_cat = defaultdict(list)
|
||||
try:
|
||||
for k, v in self.collect_properties(self).items():
|
||||
k1, k2 = k.split("/")
|
||||
if self.config[k1][k2] != v:
|
||||
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.popup.dismiss()
|
||||
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)
|
||||
return
|
||||
|
||||
@@ -124,10 +133,18 @@ class ConfigPopup(QuickConfigGui):
|
||||
for cat in updated_cat:
|
||||
self.katrain.save_config(cat, **self.config[cat])
|
||||
|
||||
if "engine" in updated_cat:
|
||||
self.katrain.log("Restarting Engine after settings change")
|
||||
old_engine = self.katrain.engine
|
||||
self.katrain.engine = KataGoEngine(self.katrain, self.config["engine"])
|
||||
self.katrain.game.engine = self.katrain.engine
|
||||
old_engine.shutdown(finish=True)
|
||||
self.katrain.update_state(redraw_board=True)
|
||||
engine_restart = False
|
||||
for cat, updates in updated_cat.items():
|
||||
if "engine" in cat: # TODO: multi engine support
|
||||
if "visits" in updates:
|
||||
self.katrain.engine.visits = self.config[cat]["visits"]
|
||||
if set(updates) != {"visits"}:
|
||||
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)
|
||||
@@ -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 |
@@ -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 |
@@ -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
@@ -1,16 +1,14 @@
|
||||
#:kivy 1.11.0
|
||||
#: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>:
|
||||
text_color: 0.95,0.95,0.95,1
|
||||
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
|
||||
font_size: self.size[1] * 0.45
|
||||
radius: (self.size[1]/7,)
|
||||
# dont set these
|
||||
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])
|
||||
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>:
|
||||
|
||||
<ToggleButtonContainer>:
|
||||
|
||||
<StyledTabButton@StyledToggleButton>:
|
||||
bold: True
|
||||
font_size: self.size[1] * 0.45
|
||||
radius: (self.size[1]/3,self.size[1]/3,0,0)
|
||||
|
||||
|
||||
|
||||
<ToggleButtonContainer>:
|
||||
|
||||
|
||||
<StyledIconButton@StyledButton>:
|
||||
button_color: 0.71, 0.78, 0.81, 1
|
||||
button_color: ICON_BUTTON_COLOR
|
||||
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)
|
||||
#EffectWidget: effects: [ew.FXAAEffect()]
|
||||
#EffectWidget: effects: [ew.FXAAEffect()]
|
||||
Image:
|
||||
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]
|
||||
@@ -56,7 +62,7 @@
|
||||
background_color: (0,0,0,0)
|
||||
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: 'missing.png'
|
||||
icon: ''
|
||||
Image:
|
||||
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]
|
||||
@@ -98,30 +104,6 @@
|
||||
on_active: root.dispatch('on_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>:
|
||||
orientation: 'horizontal'
|
||||
text: ''
|
||||
@@ -185,6 +167,7 @@
|
||||
size_hint: 0.05, 1
|
||||
StyledButton:
|
||||
text: 'Pass'
|
||||
font_size: 0.35*self.size[1]
|
||||
id: pass_btn
|
||||
on_press: root.katrain("play",None)
|
||||
size_hint: 0.1, 1
|
||||
@@ -233,7 +216,16 @@
|
||||
size_hint: 0.1, 1
|
||||
on_press: root.katrain('redo',999)
|
||||
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>:
|
||||
@@ -248,13 +240,8 @@
|
||||
ownership: ownership
|
||||
policy: policy
|
||||
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}
|
||||
ai_mode_groups: {'B':B_AI_mode,'W':W_AI_mode}
|
||||
graph: graph
|
||||
katrain: self.parent
|
||||
canvas.before:
|
||||
@@ -299,31 +286,24 @@
|
||||
id: analyze_tab
|
||||
opacity: 0
|
||||
orientation: 'vertical'
|
||||
Label:
|
||||
size_hint: 1,0.02
|
||||
GridLayout:
|
||||
cols: 6
|
||||
rows: 1
|
||||
size_hint: 1, 0.35
|
||||
size_hint: 1, 0.5
|
||||
Label:
|
||||
size_hint: 0.01, 1
|
||||
BoxLayout:
|
||||
orientation: 'vertical'
|
||||
size_hint: 0.1, 0.5
|
||||
Label:
|
||||
size_hint: 1, 0.2
|
||||
BlackCircleWithText:
|
||||
text: 'B'
|
||||
WhiteCircleWithText:
|
||||
text: 'W'
|
||||
BWCheckBoxHint:
|
||||
size_hint: 0.05, 1
|
||||
CheckBoxHint:
|
||||
size_hint: 0.2, 0.5
|
||||
id: eval
|
||||
text: 'all eval'
|
||||
text: 'show\nall dots'
|
||||
default_active: False
|
||||
on_active: root.katrain.update_state()
|
||||
BWCheckBoxHint:
|
||||
CheckBoxHint:
|
||||
size_hint: 0.2, 0.5
|
||||
id: hints
|
||||
text: 'top move'
|
||||
text: 'show\ntop move'
|
||||
on_active: root.katrain.update_state()
|
||||
CheckBoxHint:
|
||||
size_hint: 0.2, 0.5
|
||||
@@ -335,10 +315,11 @@
|
||||
id: policy
|
||||
text: 'show\npolicy'
|
||||
on_active: root.katrain.update_state()
|
||||
LargeLabel:
|
||||
Label:
|
||||
size_hint: 0.05, 1
|
||||
ScoreGraph:
|
||||
id: graph
|
||||
text: 'graph?'
|
||||
size_hint: 1,0.75
|
||||
size_hint: 1,0.5
|
||||
CensorableLabel:
|
||||
id: win_rate
|
||||
size_hint: 1, 0.15
|
||||
@@ -354,45 +335,47 @@
|
||||
size_hint: 1,1
|
||||
opacity: 1
|
||||
id: play_tab
|
||||
Label:
|
||||
size_hint: 1,0.02
|
||||
GridLayout:
|
||||
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
|
||||
size_hint: 1,0.125
|
||||
BlackCircleWithText:
|
||||
text: 'B'
|
||||
size_hint: 0.1, 1
|
||||
ToggleButtonContainer:
|
||||
size_hint: 0.9, 1
|
||||
size_hint: 0.6, 1
|
||||
id: B_player_mode
|
||||
options: ['human','human+undo','ai','ai+balance','ai+policy']
|
||||
labels: ['Human', ' AI\nAssist','AI',' AI\nBalance',' AI\nPolicy']
|
||||
options: ['human','human+undo','ai']
|
||||
labels: PLAYER_MODES
|
||||
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:
|
||||
text: 'W'
|
||||
size_hint: 0.1, 1
|
||||
ToggleButtonContainer:
|
||||
size_hint: 0.9, 1
|
||||
size_hint: 0.6, 1
|
||||
id: W_player_mode
|
||||
options: ['human','human+undo','ai','ai+balance','ai+policy']
|
||||
labels: ['Human', ' AI\nAssist','AI',' AI\nBalance',' AI\nPolicy']
|
||||
options: ['human','human+undo','ai']
|
||||
labels: PLAYER_MODES
|
||||
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:
|
||||
text: 'free real estate'
|
||||
size_hint: 1,0.2
|
||||
size_hint: 1,0.25
|
||||
CensorableLabel:
|
||||
id: points_lost
|
||||
size_hint: 1, 0.03
|
||||
@@ -402,39 +385,6 @@
|
||||
id: info
|
||||
size_hint: 1, 0.25
|
||||
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:
|
||||
orientation: 'horizontal'
|
||||
size_hint: 1, None
|
||||
|
||||
+28
-43
@@ -8,17 +8,17 @@ from kivy.app import App
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.clipboard import Clipboard
|
||||
from kivy.core.window import Window
|
||||
from kivy.lang import Builder
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
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 engine import KataGoEngine
|
||||
from game import Game, IllegalMoveException, KaTrainSGF, Move
|
||||
from gui import *
|
||||
from gui.popups import NewGamePopup, ConfigPopup
|
||||
|
||||
from kivy.lang import Builder
|
||||
from gui.popups import ConfigPopup, NewGamePopup
|
||||
|
||||
|
||||
class KaTrainGui(BoxLayout):
|
||||
@@ -27,12 +27,12 @@ class KaTrainGui(BoxLayout):
|
||||
def __init__(self, **kwargs):
|
||||
super(KaTrainGui, self).__init__(**kwargs)
|
||||
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.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._keyboard = Window.request_keyboard(None, self, "")
|
||||
@@ -124,7 +124,6 @@ class KaTrainGui(BoxLayout):
|
||||
|
||||
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.controls.unlock()
|
||||
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?
|
||||
|
||||
@@ -134,11 +133,6 @@ class KaTrainGui(BoxLayout):
|
||||
self.update_state()
|
||||
|
||||
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.update_state()
|
||||
|
||||
@@ -181,7 +175,7 @@ class KaTrainGui(BoxLayout):
|
||||
|
||||
def _do_config_popup(self):
|
||||
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.open()
|
||||
|
||||
@@ -198,37 +192,28 @@ class KaTrainGui(BoxLayout):
|
||||
if isinstance(App.get_running_app().root_window.children[0], Popup):
|
||||
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)
|
||||
elif keycode[1] == "down":
|
||||
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:
|
||||
self("new-game-popup")
|
||||
elif keycode[1] == "l" and "ctrl" in modifiers:
|
||||
@@ -238,7 +223,7 @@ class KaTrainGui(BoxLayout):
|
||||
elif keycode[1] == "c" and "ctrl" in modifiers:
|
||||
Clipboard.copy(self.game.root.sgf())
|
||||
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()
|
||||
if not clipboard:
|
||||
self.controls.set_status(f"Ctrl-V pressed but clipboard is empty.")
|
||||
|
||||
+8
-8
@@ -17,18 +17,18 @@ class Move:
|
||||
PLAYERS = "BW"
|
||||
SGF_COORD = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ".lower()) + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
|
||||
@staticmethod
|
||||
def from_gtp(gtp_coords, player="B"):
|
||||
@classmethod
|
||||
def from_gtp(cls, gtp_coords, player="B"):
|
||||
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)
|
||||
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
|
||||
def from_sgf(sgf_coords, board_size, player="B"):
|
||||
@classmethod
|
||||
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
|
||||
return Move(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=None, 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"):
|
||||
self.player = player
|
||||
|
||||
Reference in new issue
Block a user