Files
katrain-qt/gui/popups.py
T
2020-04-25 01:20:53 +02:00

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)