159 lines
6.5 KiB
Python
159 lines
6.5 KiB
Python
from collections import defaultdict
|
|
|
|
from kivy.clock import Clock
|
|
from kivy.uix.boxlayout import BoxLayout
|
|
from kivy.uix.gridlayout import GridLayout
|
|
from kivy.uix.label import Label
|
|
|
|
from common import OUTPUT_DEBUG, OUTPUT_ERROR
|
|
from engine import KataGoEngine
|
|
from game import Game, GameNode
|
|
from gui.kivyutils import LabelledCheckBox, LabelledFloatInput, LabelledIntInput, LabelledObjectInputArea, LabelledSpinner, LabelledTextInput, ScaledLightLabel, StyledButton
|
|
|
|
|
|
class InputParseError(Exception):
|
|
pass
|
|
|
|
|
|
class QuickConfigGui(BoxLayout):
|
|
def __init__(self, katrain, popup, initial_values=None):
|
|
super().__init__()
|
|
self.katrain = katrain
|
|
self.popup = popup
|
|
if initial_values:
|
|
self.set_properties(self, initial_values)
|
|
|
|
def collect_properties(self, widget):
|
|
if isinstance(widget, (LabelledTextInput, LabelledSpinner)):
|
|
try:
|
|
ret = {widget.input_property: widget.input_value}
|
|
except Exception as e:
|
|
raise InputParseError(f"Could not parse value for {widget.input_property} ({widget.__class__}): {e}")
|
|
else:
|
|
ret = {}
|
|
for c in widget.children:
|
|
for k, v in self.collect_properties(c).items():
|
|
ret[k] = v
|
|
return ret
|
|
|
|
def set_properties(self, widget, properties):
|
|
if isinstance(widget, (LabelledTextInput, LabelledSpinner)):
|
|
key = widget.input_property
|
|
if key in properties:
|
|
widget.text = str(properties[key])
|
|
for c in widget.children:
|
|
self.set_properties(c, properties)
|
|
|
|
|
|
class LoadSGFPopup(BoxLayout):
|
|
pass
|
|
|
|
|
|
class NewGamePopup(QuickConfigGui):
|
|
def __init__(self, katrain, popup, properties, **kwargs):
|
|
properties["RU"] = KataGoEngine.get_rules(katrain.game.root)
|
|
super().__init__(katrain, popup, properties)
|
|
self.rules_spinner.values = list(set(self.katrain.engine.RULESETS.values()))
|
|
self.rules_spinner.text = properties["RU"]
|
|
|
|
def new_game(self):
|
|
properties = self.collect_properties(self)
|
|
self.katrain.log(f"New game settings: {properties}", OUTPUT_DEBUG)
|
|
new_root = GameNode(properties={**Game.DEFAULT_PROPERTIES, **properties})
|
|
x, y = new_root.board_size
|
|
if x > 52 or y > 52:
|
|
self.info.text = "Board size too big, should be at most 52"
|
|
return
|
|
self.katrain("new-game", new_root)
|
|
self.popup.dismiss()
|
|
|
|
|
|
class ConfigPopup(QuickConfigGui):
|
|
@staticmethod
|
|
def type_to_widget_class(value):
|
|
if isinstance(value, float):
|
|
return LabelledFloatInput
|
|
elif isinstance(value, bool):
|
|
return LabelledCheckBox
|
|
elif isinstance(value, int):
|
|
return LabelledIntInput
|
|
if isinstance(value, dict):
|
|
return LabelledObjectInputArea
|
|
else:
|
|
return LabelledTextInput
|
|
|
|
def __init__(self, katrain, popup, config, ignore_cats):
|
|
self.config = config
|
|
self.ignore_cats = ignore_cats
|
|
self.orientation = "vertical"
|
|
super().__init__(katrain, popup)
|
|
Clock.schedule_once(self._build, 0)
|
|
|
|
def _build(self, _):
|
|
cols = [BoxLayout(orientation="vertical"), BoxLayout(orientation="vertical")]
|
|
props_in_col = [0, 0]
|
|
for k1, all_d in sorted(self.config.items(), key=lambda tup: -len(tup[1])): # sort to make greedy bin packing work better
|
|
if k1 in self.ignore_cats:
|
|
continue
|
|
d = {k: v for k, v in all_d.items() if isinstance(v, (int, float, str, bool))} # no lists . dict could be supported but hard to scale
|
|
cat = GridLayout(cols=2, rows=len(d) + 1, size_hint=(1, len(d) + 1))
|
|
cat.add_widget(Label(text=""))
|
|
cat.add_widget(ScaledLightLabel(text=f"{k1} settings", bold=True))
|
|
for k2, v in d.items():
|
|
cat.add_widget(ScaledLightLabel(text=f"{k2}:"))
|
|
cat.add_widget(self.type_to_widget_class(v)(text=str(v), input_property=f"{k1}/{k2}"))
|
|
if props_in_col[0] <= props_in_col[1]:
|
|
cols[0].add_widget(cat)
|
|
props_in_col[0] += len(d)
|
|
else:
|
|
cols[1].add_widget(cat)
|
|
props_in_col[1] += len(d)
|
|
|
|
col_container = BoxLayout(size_hint=(1, 0.9))
|
|
col_container.add_widget(cols[0])
|
|
col_container.add_widget(cols[1])
|
|
self.add_widget(col_container)
|
|
self.info_label = Label()
|
|
self.apply_button = StyledButton(text="Apply", on_press=lambda _: self.update_config())
|
|
self.save_button = StyledButton(text="Apply and Save", on_press=lambda _: self.update_config(save_to_file=True))
|
|
btn_container = BoxLayout(orientation="horizontal", size_hint=(1, 0.1), spacing=1, padding=1)
|
|
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 = 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[k1].append(k2)
|
|
self.config[k1][k2] = v
|
|
self.popup.dismiss()
|
|
except InputParseError as e:
|
|
self.info_label.text = str(e)
|
|
self.katrain.log(e, OUTPUT_ERROR)
|
|
return
|
|
|
|
if save_to_file:
|
|
self.katrain.save_config()
|
|
|
|
engine_updates = updated_cat["engine"]
|
|
if "visits" in engine_updates:
|
|
self.katrain.engine.visits = engine_updates["visits"]
|
|
if {key for key in engine_updates if key not in {"max_visits", "max_time"}}:
|
|
self.katrain.log(f"Restarting Engine after {engine_updates} settings change")
|
|
self.katrain.controls.set_status(f"Restarting Engine after {engine_updates} settings change")
|
|
old_engine = self.katrain.engine
|
|
new_engine = KataGoEngine(self.katrain, self.config["engine"])
|
|
self.katrain.engine = {"B": new_engine, "W": new_engine}
|
|
self.katrain.game.engine = new_engine
|
|
if getattr(old_engine, "katago_process"):
|
|
old_engine.shutdown(finish=True)
|
|
else:
|
|
self.katrain.game.analyze_all_nodes() # old engine was broken, so make sure we redo any failures
|
|
|
|
self.katrain.update_state(redraw_board=True)
|