new game and timer popups
This commit is contained in:
1 parent
e0bfa3c416
commit
d70515235a
29 files changed
+853
-506
No files matched your search
+1
-1
@@ -74,7 +74,7 @@ As you can see it scores about twice as high as the Intel UHD chip and KataGo ha
|
||||
openclDeviceToUseThread0 = 1
|
||||
openclDeviceToUseThread1 = 2
|
||||
```
|
||||
* Run `python3 katrain.py` and confirm that KataGo is now using both devices, by
|
||||
* Run `python3 -m katrain` and confirm that KataGo is now using both devices, by
|
||||
checking the output from the terminal, which should indicate two devices being used. For example:
|
||||
```
|
||||
Found 3 device(s) on platform 0 with type CPU or GPU or Accelerator
|
||||
|
||||
@@ -125,19 +125,12 @@ In addition to shortcuts mentioned above, there are:
|
||||
* **[scroll down]**: Redo move. Only works when hovering the cursor over the board.
|
||||
* **[click on a move]**: See detailed statistics for a previous move, along with expected variation that was best instead of this move.
|
||||
* **[double-click on a move]**: Navigate directly to that point in the game.
|
||||
* **[Ctrl-v]**: Load SGF from clipboard and do a 'fast' analysis of the game (with a high priority normal analysis for the last move).
|
||||
* **[Ctrl-c]**: Save SGF to clipboard.
|
||||
* **[Ctrl-l]**: Load SGF from file and do a normal analysis.
|
||||
* **[Ctrl-s]**: Save SGF with automated review to file.
|
||||
* **[Ctrl-n]**: Load SGF from clipboard
|
||||
* **[F5]**: Open settings
|
||||
|
||||
## Configuration
|
||||
|
||||
Configuration is stored in `config.json`. Most settings are now available to edit in the program, but some advanced options are not.
|
||||
You can use `python katrain.py your_config_file.json` to use another config file instead.
|
||||
|
||||
If you ever need to reset to the original settings, simply re-download the `config.json` file in this repository.
|
||||
* **[Ctrl-V]**: Load SGF from clipboard and do a 'fast' analysis of the game (with a high priority normal analysis for the last move).
|
||||
* **[Ctrl-C]**: Save SGF to clipboard.
|
||||
* **[Ctrl-L]**: Load SGF from file and do a normal analysis.
|
||||
* **[Ctrl-S]**: Save SGF with automated review to file.
|
||||
* **[Ctrl-N]**: Load SGF from clipboard
|
||||
* **[F5]**: Open general settings
|
||||
|
||||
### Settings Panel
|
||||
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ class MenuOpener(BoxLayout):
|
||||
{"icon": icon, "text": text, "right_content_cls": AnalysisDropdownMenuRightContent(text=shortcut)}
|
||||
for icon, text, shortcut in zip(ANALYSIS_ICONS, ANALYSIS_OPTIONS, ANALYSIS_SHORTCUTS)
|
||||
]
|
||||
self.analysis_menu = AnalysisDropdownMenu(caller=self.analysis_button, items=menu_items, width_mult=6, use_icon_item=False, callback=lambda _: print(_))
|
||||
self.analysis_menu = AnalysisDropdownMenu(caller=self.analysis_button, items=menu_items, width_mult=6, use_icon_item=False, callback=lambda _: print(_),)
|
||||
|
||||
def open_analysis_menu(self):
|
||||
self.analysis_menu.open()
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ class MenuOpener(BoxLayout):
|
||||
{"icon": icon, "text": text, "right_content_cls": AnalysisDropdownMenuRightContent(text=shortcut)}
|
||||
for icon, text, shortcut in zip(ANALYSIS_ICONS, ANALYSIS_OPTIONS, ANALYSIS_SHORTCUTS)
|
||||
]
|
||||
self.analysis_menu = AnalysisDropdownMenu(caller=self.analysis_button, items=menu_items, width_mult=6, use_icon_item=False, callback=lambda _: print(_))
|
||||
self.analysis_menu = AnalysisDropdownMenu(caller=self.analysis_button, items=menu_items, width_mult=6, use_icon_item=False, callback=lambda _: print(_),)
|
||||
|
||||
# self.analysis_menu.create_menu_items()
|
||||
# for item in self.analysis_menu.menu.ids.box.children:
|
||||
|
||||
+32
-59
@@ -6,6 +6,7 @@ Config.set("input", "mouse", "mouse,multitouch_on_demand") # isort:skip # no r
|
||||
Config.set("graphics", "width", 1400)
|
||||
Config.set("graphics", "height", 1000)
|
||||
|
||||
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
@@ -36,8 +37,9 @@ from katrain.core.utils import (
|
||||
MODE_PLAY,
|
||||
switch_lang,
|
||||
)
|
||||
from katrain.gui.popups import ConfigTeacherPopup, ConfigTimerPopup
|
||||
from katrain.gui.popups import ConfigTeacherPopup, ConfigTimerPopup, I18NPopup
|
||||
from katrain.gui.ai_settings import ConfigAIPopupContents
|
||||
from katrain.core.settings_and_logging import KaTrainSettings
|
||||
from katrain.core.engine import KataGoEngine
|
||||
from katrain.core.game import Game, IllegalMoveException, KaTrainSGF
|
||||
from katrain.core.sgf_parser import Move, ParseError
|
||||
@@ -47,19 +49,17 @@ from katrain.gui.graph import ScoreGraph
|
||||
from katrain.gui.kivyutils import *
|
||||
from katrain.gui.popups import ConfigPopup, LoadSGFPopup, NewGamePopup
|
||||
from katrain.gui.style import ENGINE_BUSY_COL, ENGINE_DOWN_COL, ENGINE_READY_COL
|
||||
from katrain.gui.style import *
|
||||
|
||||
__version__ = "1.1.0"
|
||||
|
||||
|
||||
class KaTrainGui(Screen):
|
||||
class KaTrainGui(Screen, KaTrainSettings):
|
||||
"""Top level class responsible for tying everything together"""
|
||||
|
||||
zen = BooleanProperty(False)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super(KaTrainGui, self).__init__(**kwargs)
|
||||
self.debug_level = 0
|
||||
super().__init__(**kwargs)
|
||||
self.engine = None
|
||||
self.game = None
|
||||
|
||||
@@ -70,16 +70,13 @@ class KaTrainGui(Screen):
|
||||
self.teacher_settings_popup = None
|
||||
self.timer_settings_popup = None
|
||||
|
||||
self.logger = lambda message, level=OUTPUT_INFO: self.log(message, level)
|
||||
self.config_file = self._load_config()
|
||||
|
||||
self.debug_level = self.config("debug/level", OUTPUT_INFO)
|
||||
self.message_queue = Queue()
|
||||
|
||||
self._keyboard = Window.request_keyboard(None, self, "")
|
||||
self._keyboard.bind(on_key_down=self._on_keyboard_down)
|
||||
|
||||
def log(self, message, level=OUTPUT_INFO):
|
||||
super().log(message, level)
|
||||
if level == OUTPUT_KATAGO_STDERR and "ERROR" not in self.controls.status_label.text:
|
||||
if "starting" in message.lower():
|
||||
self.controls.set_status(f"KataGo engine starting...")
|
||||
@@ -92,34 +89,6 @@ class KaTrainGui(Screen):
|
||||
self.controls.set_status(f"KataGo engine ready.")
|
||||
if level == OUTPUT_ERROR or (level == OUTPUT_KATAGO_STDERR and "error" in message.lower()):
|
||||
self.controls.set_status(f"ERROR: {message}")
|
||||
print(f"ERROR: {message}")
|
||||
elif self.debug_level >= level:
|
||||
print(message)
|
||||
|
||||
def _load_config(self):
|
||||
config_file = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else find_package_resource("katrain/config.json"))
|
||||
try:
|
||||
self.log(f"Using config file {config_file}", OUTPUT_INFO)
|
||||
self._config_store = JsonStore(config_file, indent=4)
|
||||
self._config = dict(self._config_store)
|
||||
return config_file
|
||||
except Exception as e:
|
||||
self.log(f"Failed to load config {config_file}: {e}", OUTPUT_ERROR)
|
||||
sys.exit(1)
|
||||
|
||||
def save_config(self):
|
||||
for k, v in self._config.items():
|
||||
self._config_store.put(k, **v)
|
||||
|
||||
def config(self, setting, default=None):
|
||||
try:
|
||||
if "/" in setting:
|
||||
cat, key = setting.split("/")
|
||||
return self._config[cat].get(key, default)
|
||||
else:
|
||||
return self._config[setting]
|
||||
except KeyError:
|
||||
self.log(f"Missing configuration option {setting}", OUTPUT_ERROR)
|
||||
|
||||
@property
|
||||
def play_analyze_mode(self):
|
||||
@@ -141,15 +110,15 @@ class KaTrainGui(Screen):
|
||||
# AI and Trainer/auto-undo handlers
|
||||
cn = self.game.current_node
|
||||
if self.play_analyze_mode == MODE_PLAY:
|
||||
auto_undo = cn.player and "undo" in self.controls.player_mode(cn.player)
|
||||
if auto_undo and cn.analysis_ready and cn.parent and cn.parent.analysis_ready and not cn.children and not self.game.ended:
|
||||
self.game.analyze_undo(cn, self.config("trainer")) # not via message loop
|
||||
teaching_undo = cn.player and self.game.players[cn.player].being_taught
|
||||
if teaching_undo and cn.analysis_ready and cn.parent and cn.parent.analysis_ready and not cn.children and not self.game.ended:
|
||||
self.game.analyze_undo(cn) # not via message loop
|
||||
if (
|
||||
cn.analysis_ready
|
||||
and "ai" in self.controls.player_mode(cn.next_player).lower()
|
||||
and not cn.children
|
||||
and not self.game.ended
|
||||
and not (auto_undo and cn.auto_undo is None)
|
||||
and not (teaching_undo and cn.auto_undo is None)
|
||||
):
|
||||
self._do_ai_move(cn) # cn mismatch stops this if undo fired. avoid message loop here or fires repeatedly.
|
||||
|
||||
@@ -213,7 +182,7 @@ class KaTrainGui(Screen):
|
||||
def _do_new_game(self, move_tree=None, analyze_fast=False):
|
||||
self.board_gui.animating_pv = None
|
||||
self.engine.on_new_game() # clear queries
|
||||
self.game = Game(self, self.engine, self.config("game"), move_tree=move_tree, analyze_fast=analyze_fast)
|
||||
self.game = Game(self, self.engine, move_tree=move_tree, analyze_fast=analyze_fast)
|
||||
self.controls.graph.initialize_from_game(self.game.root)
|
||||
self.controls.periods_used = {"B": 0, "W": 0}
|
||||
self.update_state(redraw_board=True)
|
||||
@@ -252,6 +221,22 @@ class KaTrainGui(Screen):
|
||||
def _do_analyze_extra(self, mode):
|
||||
self.game.analyze_extra(mode)
|
||||
|
||||
def _do_new_game_popup(self):
|
||||
self.controls.timer.paused = True
|
||||
if not self.new_game_popup:
|
||||
self.new_game_popup = I18NPopup(title_key="New Game title", size_hint=(0.5, 0.6), content=NewGamePopup(self)).__self__
|
||||
self.new_game_popup.content.popup = self.new_game_popup
|
||||
self.new_game_popup.open()
|
||||
|
||||
def _do_timer_popup(self):
|
||||
self.controls.timer.paused = True
|
||||
if not self.timer_settings_popup:
|
||||
self.timer_settings_popup = I18NPopup(title_key="timer settings", size=[450, 450], content=ConfigTimerPopup(self)).__self__
|
||||
self.timer_settings_popup.content.popup = self.timer_settings_popup
|
||||
self.timer_settings_popup.open()
|
||||
|
||||
# todo pop
|
||||
|
||||
def _do_analyze_sgf_popup(self):
|
||||
if not self.fileselect_popup:
|
||||
self.fileselect_popup = Popup(title="Double Click SGF file to analyze", size_hint=(0.8, 0.8)).__self__
|
||||
@@ -273,14 +258,6 @@ class KaTrainGui(Screen):
|
||||
popup_contents.filesel.on_submit = readfile
|
||||
self.fileselect_popup.open()
|
||||
|
||||
def _do_new_game_popup(self):
|
||||
self.controls.timer.paused = True
|
||||
if not self.new_game_popup:
|
||||
self.new_game_popup = Popup(title="New Game", size_hint=(0.5, 0.6)).__self__
|
||||
popup_contents = NewGamePopup(self, self.new_game_popup, {k: v[0] for k, v in self.game.root.properties.items() if len(v) == 1})
|
||||
self.new_game_popup.add_widget(popup_contents)
|
||||
self.new_game_popup.open()
|
||||
|
||||
def _do_config_popup(self):
|
||||
self.controls.timer.paused = True
|
||||
if not self.config_popup:
|
||||
@@ -299,17 +276,10 @@ class KaTrainGui(Screen):
|
||||
def _do_teacher_popup(self):
|
||||
self.controls.timer.paused = True
|
||||
if not self.teacher_settings_popup:
|
||||
self.teacher_settings_popup = Popup(title="Edit Teacher Settings", size_hint=(0.7, 0.8)).__self__
|
||||
self.teacher_settings_popup = I18NPopup(title_key="Edit Teacher Settings", size_hint=(0.7, 0.8)).__self__
|
||||
self.teacher_settings_popup.add_widget(ConfigTeacherPopup(self, self.teacher_settings_popup))
|
||||
self.teacher_settings_popup.open()
|
||||
|
||||
def _do_timer_popup(self):
|
||||
self.controls.timer.paused = True
|
||||
if not self.timer_settings_popup:
|
||||
self.timer_settings_popup = Popup(title="Edit Timer Settings", size_hint=(0.4, 0.4)).__self__
|
||||
self.timer_settings_popup.add_widget(ConfigTimerPopup(self, self.timer_settings_popup))
|
||||
self.timer_settings_popup.open()
|
||||
|
||||
def _do_output_sgf(self):
|
||||
for pl in Move.PLAYERS:
|
||||
if not self.game.root.get_property(f"P{pl}"):
|
||||
@@ -444,13 +414,16 @@ class KaTrainApp(MDApp):
|
||||
|
||||
def run_app():
|
||||
kv_file = find_package_resource("katrain/gui.kv")
|
||||
popup_kv_file = find_package_resource("katrain/popups.kv")
|
||||
resource_add_path(os.path.split(kv_file)[0])
|
||||
Builder.load_file(kv_file)
|
||||
Builder.load_file(popup_kv_file)
|
||||
app = KaTrainApp()
|
||||
signal.signal(signal.SIGINT, app.signal_handler)
|
||||
try:
|
||||
app.run()
|
||||
except Exception:
|
||||
except Exception as e:
|
||||
print(e)
|
||||
app.on_request_close()
|
||||
raise
|
||||
|
||||
|
||||
+20
-19
@@ -13,24 +13,22 @@
|
||||
"_enable_ownership": true
|
||||
},
|
||||
"timer": {
|
||||
"byo_length": 30,
|
||||
"byo_num": 5
|
||||
"byo_length": 30,
|
||||
"byo_periods": 5
|
||||
},
|
||||
"sgf": {
|
||||
"general": {
|
||||
"sgf_load": "~/Downloads",
|
||||
"sgf_save": "./sgfout",
|
||||
"save_feedback": [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
]
|
||||
"anim_pv_time": 0.5,
|
||||
"debug_level": 3,
|
||||
"language": "en"
|
||||
},
|
||||
"game": {
|
||||
"init_size": 19,
|
||||
"init_komi": 6.5
|
||||
"size": "13",
|
||||
"komi": 0.5,
|
||||
"handicap": 2,
|
||||
"rules": "chinese",
|
||||
"clear_cache": true
|
||||
},
|
||||
"trainer": {
|
||||
"num_undo_prompts": [
|
||||
@@ -49,10 +47,17 @@
|
||||
0.5,
|
||||
0
|
||||
],
|
||||
"save_feedback": [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true
|
||||
],
|
||||
"eval_off_show_last": 3,
|
||||
"eval_show_ai": true,
|
||||
"lock_ai": false,
|
||||
"anim_pv_time": 0.5
|
||||
"lock_ai": false
|
||||
},
|
||||
"ai": {
|
||||
"Default": {
|
||||
@@ -133,9 +138,5 @@
|
||||
"_help_left": "Samples `pick_n + pick_frac * <number of legal moves>` and plays the best one, biased to below the `threshold` line.",
|
||||
"_help_right": "Increase `line_weight` to penalize moves closer to the center more. Stops strategy after the 'endgame' fraction of the board is filled."
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"level": 1,
|
||||
"_hint_level": "0: Little console output.\n1: Some extra output\nfor error reporting."
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ class KataGoEngine:
|
||||
"""Starts and communicates with the KataGO analysis engine"""
|
||||
|
||||
# TODO: we don't support suicide in game.py, so no "tt": "tromp-taylor", "nz": "new-zealand"
|
||||
RULESETS = {"jp": "japanese", "cn": "chinese", "ko": "korean", "aga": "aga"}
|
||||
RULESETS.update({v: v for v in RULESETS.values()})
|
||||
RULESETS_ABBR = [("jp", "japanese"), ("cn", "chinese"), ("ko", "korean"), ("aga", "aga")]
|
||||
RULESETS = {fromkey: name for abbr, name in RULESETS_ABBR for fromkey in [abbr, name]}
|
||||
|
||||
@staticmethod
|
||||
def get_rules(node):
|
||||
|
||||
+12
-6
@@ -49,15 +49,14 @@ class Player:
|
||||
class Game:
|
||||
"""Represents a game of go, including an implementation of capture rules."""
|
||||
|
||||
DEFAULT_PROPERTIES = {"GM": 1, "FF": 4, "RU": "JP", "AP": "KaTrain:https://github.com/sanderland/katrain"}
|
||||
DEFAULT_PROPERTIES = {"GM": 1, "FF": 4, "AP": "KaTrain:https://github.com/sanderland/katrain"}
|
||||
|
||||
def __init__(self, katrain, engine: Union[Dict, KataGoEngine], config: Dict, move_tree: GameNode = None, players: Dict = None, analyze_fast=False):
|
||||
def __init__(self, katrain, engine: Union[Dict, KataGoEngine], move_tree: GameNode = None, players: Dict = None, analyze_fast=False):
|
||||
self.katrain = katrain
|
||||
if not isinstance(engine, Dict):
|
||||
engine = {"B": engine, "W": engine}
|
||||
self.players = players or {"B": Player("B"), "W": Player("W")}
|
||||
self.engines = engine
|
||||
self.config = config
|
||||
self.game_id = datetime.strftime(datetime.now(), "%Y-%m-%d %H %M %S")
|
||||
|
||||
if move_tree:
|
||||
@@ -67,12 +66,18 @@ class Game:
|
||||
if handicap and not self.root.placements:
|
||||
self.place_handicap_stones(handicap)
|
||||
else:
|
||||
board_size = config.get("init_size", 19)
|
||||
self.komi = self.config.get("init_komi", 6.5)
|
||||
board_size = katrain.config("game/size")
|
||||
self.komi = katrain.config("game/komi")
|
||||
self.root = GameNode(properties={**Game.DEFAULT_PROPERTIES, **{"SZ": board_size, "KM": self.komi, "DT": self.game_id}})
|
||||
handicap = katrain.config("game/handicap")
|
||||
if handicap:
|
||||
self.place_handicap_stones(handicap)
|
||||
self.root.set_property("PW", str(self.players["W"]))
|
||||
self.root.set_property("PB", str(self.players["B"]))
|
||||
|
||||
if not self.root.get_property("RU"):
|
||||
self.root.set_property("RU", katrain.config("game/rules"))
|
||||
|
||||
self.set_current_node(self.root)
|
||||
threading.Thread(target=lambda: self.analyze_all_nodes(-1_000_000, analyze_fast=analyze_fast), daemon=True).start() # return faster, but bypass Kivy Clock
|
||||
|
||||
@@ -324,7 +329,8 @@ class Game:
|
||||
for move in analyze_moves:
|
||||
cn.analyze(engine, priority, visits=visits, refine_move=move, time_limit=False) # explicitly requested so take as long as you need
|
||||
|
||||
def analyze_undo(self, node, train_config):
|
||||
def analyze_undo(self, node):
|
||||
train_config = self.katrain.config("trainer")
|
||||
move = node.move
|
||||
if node != self.current_node or node.auto_undo is not None or not node.analysis_ready or not move:
|
||||
return
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import Dict, List, Optional, Tuple
|
||||
|
||||
from katrain.core.utils import evaluation_class, var_to_grid, i18n
|
||||
from katrain.core.sgf_parser import Move, SGFNode
|
||||
from katrain.gui.style import PV_COLOR
|
||||
from katrain.gui.style import INFO_PV_COLOR
|
||||
|
||||
|
||||
class GameNode(SGFNode):
|
||||
@@ -117,7 +117,7 @@ class GameNode(SGFNode):
|
||||
def make_pv(self, player, pv, interactive):
|
||||
pvtext = f"{player}{' '.join(pv)}"
|
||||
if interactive:
|
||||
pvtext = f"[u][ref={pvtext}][color={PV_COLOR}]{pvtext}[/color][/ref][/u]"
|
||||
pvtext = f"[u][ref={pvtext}][color={INFO_PV_COLOR}]{pvtext}[/color][/ref][/u]"
|
||||
return pvtext
|
||||
|
||||
def comment(self, sgf=False, teach=False, hints=False, interactive=True):
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
from kivy import Config
|
||||
from kivy.storage.jsonstore import JsonStore
|
||||
import os, sys
|
||||
from katrain.core.utils import (
|
||||
OUTPUT_ERROR,
|
||||
OUTPUT_INFO,
|
||||
OUTPUT_KATAGO_STDERR,
|
||||
find_package_resource,
|
||||
OUTPUT_DEBUG,
|
||||
OUTPUT_EXTRA_DEBUG,
|
||||
)
|
||||
|
||||
|
||||
class KaTrainSettings:
|
||||
CONFIG_FILE = "katrain/config.json"
|
||||
|
||||
"""Settings and logging functionality, so other classes who need a katrain instance can be used without a GUI"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.debug_level = 0
|
||||
self.logger = lambda message, level=OUTPUT_INFO: self.log(message, level)
|
||||
self.config_file = self._load_config()
|
||||
self.debug_level = self.config("general/debug_level", OUTPUT_INFO)
|
||||
|
||||
Config.set("kivy", "log_level", "error")
|
||||
if self.debug_level >= OUTPUT_DEBUG:
|
||||
Config.set("kivy", "log_enable", 1)
|
||||
Config.set("kivy", "log_level", "warning")
|
||||
|
||||
# if self.debug_level >= OUTPUT_EXTRA_DEBUG:
|
||||
# Config.set("kivy", "log_level", "trace")
|
||||
|
||||
def log(self, message, level=OUTPUT_INFO):
|
||||
if level == OUTPUT_ERROR:
|
||||
print(f"ERROR: {message}", sys.stderr)
|
||||
elif self.debug_level >= level:
|
||||
print(message)
|
||||
|
||||
def _load_config(self):
|
||||
config_file = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else find_package_resource(self.CONFIG_FILE))
|
||||
try:
|
||||
self.log(f"Using config file {config_file}", OUTPUT_INFO)
|
||||
self._config_store = JsonStore(config_file, indent=4)
|
||||
self._config = dict(self._config_store)
|
||||
return config_file
|
||||
except Exception as e:
|
||||
self.log(f"Failed to load config {config_file}: {e}", OUTPUT_ERROR)
|
||||
sys.exit(1)
|
||||
|
||||
def save_config(self):
|
||||
for k, v in self._config.items():
|
||||
self._config_store.put(k, **v)
|
||||
|
||||
def config(self, setting, default=None):
|
||||
try:
|
||||
if "/" in setting:
|
||||
cat, key = setting.split("/")
|
||||
return self._config[cat].get(key, default)
|
||||
else:
|
||||
return self._config[setting]
|
||||
except KeyError:
|
||||
self.log(f"Missing configuration option {setting}", OUTPUT_ERROR)
|
||||
@@ -67,8 +67,6 @@ class Lang(Observable):
|
||||
self.switch_lang(lang)
|
||||
|
||||
def _(self, text):
|
||||
if text == "":
|
||||
return "" # just for font change
|
||||
return self.ugettext(text)
|
||||
|
||||
def fbind(self, name, func, *args):
|
||||
|
||||
+57
-227
@@ -1,6 +1,5 @@
|
||||
#:kivy 1.11.0
|
||||
#:import i18n __main__.i18n
|
||||
#:import find_package_resource __main__.find_package_resource
|
||||
#:import i18n katrain.core.utils.i18n
|
||||
|
||||
#:set BUTTON_COLOR [0.23, 0.30, 0.35, 1]
|
||||
#:set TOOLTIP_COLOR [0.43, 0.50, 0.65, 1]
|
||||
@@ -9,12 +8,22 @@
|
||||
#:set GRAPH_DOT_COLOR [0.85, 0.3, 0.3,1]
|
||||
|
||||
#-- NEW
|
||||
#:import PLAYER_TYPES __main__.PLAYER_TYPES
|
||||
#:import RED __main__.RED
|
||||
#:import WHITE __main__.WHITE
|
||||
|
||||
# TODO: refactor
|
||||
|
||||
#:set CP_SPACING 6
|
||||
#:set CP_SMALL_SPACING 3
|
||||
#:set CP_PADDING 6
|
||||
|
||||
#:import PLAYER_TYPES katrain.core.constants.PLAYER_TYPES
|
||||
#:import RED katrain.gui.style.RED
|
||||
#:import WHITE katrain.gui.style.WHITE
|
||||
#:import BACKGROUND_COLOR katrain.gui.style.BACKGROUND_COLOR
|
||||
#:import BOX_BACKGROUND_COLOR katrain.gui.style.BOX_BACKGROUND_COLOR
|
||||
#:import TEXT_COLOR katrain.gui.style.TEXT_COLOR
|
||||
|
||||
|
||||
#:set LIGHTGREY [0.7,0.7,0.7,1]
|
||||
|
||||
#:set BLACK [0.05,0.05,0.05,1]
|
||||
#:set GREY [0.5,0.5,0.5,1]
|
||||
#:set GREEN [0.1,0.8,0.1,1]
|
||||
@@ -27,12 +36,13 @@
|
||||
#:set WINRATE_COLOR GREEN
|
||||
|
||||
#:set BUTTON_INACTIVE_COLOR LIGHTGREY
|
||||
#:set BACKGROUND_COLOR [36/255,48/255,62/255,1]
|
||||
#:set BOX_BACKGROUND_COLOR [46/255,65/255,88/255,1]
|
||||
#:set TEXT_COLOR WHITE
|
||||
|
||||
|
||||
#:set NOTES_FONT_SIZE dp(18)
|
||||
|
||||
|
||||
|
||||
# for sizing help
|
||||
<Label>:
|
||||
canvas.before:
|
||||
Color:
|
||||
@@ -57,29 +67,12 @@
|
||||
width: root.outline_width or 1
|
||||
|
||||
|
||||
<ToolTipLabel>: # TODO
|
||||
size_hint: None, None
|
||||
size: self.texture_size
|
||||
tooltip_font_size: dp(14)
|
||||
padding: (2,2)
|
||||
halign: 'center'
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: TOOLTIP_COLOR
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
Color:
|
||||
rgba: BUTTON_COLOR
|
||||
Line:
|
||||
width: 1.1
|
||||
rectangle: (*self.pos,*self.size)
|
||||
|
||||
# new widgets
|
||||
|
||||
<SizedButton>:
|
||||
ripple_duration_in_slow: 0.6
|
||||
text_color: WHITE
|
||||
text_color: TEXT_COLOR
|
||||
background_color: BOX_BACKGROUND_COLOR
|
||||
label: label
|
||||
text_size: root.size
|
||||
@@ -139,7 +132,6 @@
|
||||
source: root.icon
|
||||
mipmap: True
|
||||
|
||||
|
||||
<StyledSpinnerOption@SpinnerOption>:
|
||||
background_color: BACKGROUND_COLOR
|
||||
background_normal: ''
|
||||
@@ -160,55 +152,47 @@
|
||||
option_cls: 'StyledSpinnerOption'
|
||||
|
||||
|
||||
|
||||
# -- old widgets
|
||||
<CircleWithText>:
|
||||
min_size: min(self.height,self.width)
|
||||
Image:
|
||||
id: image
|
||||
pos: (root.pos[0] + (root.width - root.min_size)/2,root.pos[1] + (root.height - root.min_size)/2)
|
||||
size: (root.min_size,root.min_size)
|
||||
source: 'img/white.png' if root.player=='W' else 'img/black.png'
|
||||
mipmap: True
|
||||
Label:
|
||||
id: lbl
|
||||
text: root.text
|
||||
text_size: None, root.min_size
|
||||
bold: True
|
||||
valign: 'center'
|
||||
color: BLACK if root.player=='W' else WHITE
|
||||
pos: (image.x, image.y)
|
||||
size: image.size
|
||||
font_size: root.min_size * 1.2 / (1 + 1 * len(self.text))
|
||||
|
||||
|
||||
# -- old widgets TODO: remove
|
||||
|
||||
<StyledButton>:
|
||||
color: WHITE
|
||||
font_size: self.height * 0.45
|
||||
radius: (self.height/7,)
|
||||
button_color: BUTTON_COLOR
|
||||
button_color_down: [0.105, 0.14, 0.155, 1]
|
||||
button_color_highlight: [(a+b)/2 for a,b in zip(self.button_color,self.button_color_down)]
|
||||
background_color: 0,0,0,0
|
||||
background_normal: ''
|
||||
<ToolTipLabel>: # TODO update/remove
|
||||
size_hint: None, None
|
||||
size: self.texture_size
|
||||
tooltip_font_size: dp(14)
|
||||
padding: (2,2)
|
||||
halign: 'center'
|
||||
canvas.before:
|
||||
Color:
|
||||
rgba: self.button_color if root.state=='normal' else self.button_color_down
|
||||
RoundedRectangle:
|
||||
size: self.size[0],self.height
|
||||
pos: (self.pos[0],self.pos[1])
|
||||
radius: root.radius
|
||||
rgba: TOOLTIP_COLOR
|
||||
Rectangle:
|
||||
size: self.size
|
||||
pos: self.pos
|
||||
Color:
|
||||
rgba: BUTTON_COLOR
|
||||
Line:
|
||||
width: 1.1
|
||||
rectangle: (*self.pos,*self.size)
|
||||
|
||||
|
||||
<StyledToggleButton>:
|
||||
max_lines: 1
|
||||
|
||||
<ToggleButtonContainer>:
|
||||
|
||||
<LabelledTextInput>:
|
||||
padding: 3, 0.27*self.height
|
||||
font_size: 0.4*self.height
|
||||
|
||||
<LabelledIntInput>:
|
||||
font_size: 0.5*self.height
|
||||
padding: 7, 0.2*self.height
|
||||
|
||||
<LabelledFloatInput>:
|
||||
font_size: 0.5*self.height
|
||||
padding: 7, 0.2*self.height
|
||||
|
||||
<LabelledTextArea>
|
||||
font_size: 0.2*self.height
|
||||
|
||||
<CheckBox>:
|
||||
color: BLACK
|
||||
|
||||
<LabelledCheckBox>:
|
||||
color: WHITE
|
||||
|
||||
|
||||
<StyledIconButton@StyledButton>:
|
||||
button_color: ICON_BUTTON_COLOR
|
||||
@@ -247,25 +231,6 @@
|
||||
size: self.texture_size
|
||||
|
||||
|
||||
<CircleWithText>:
|
||||
min_size: min(self.height,self.width)
|
||||
Image:
|
||||
id: image
|
||||
pos: (root.pos[0] + (root.width - root.min_size)/2,root.pos[1] + (root.height - root.min_size)/2)
|
||||
size: (root.min_size,root.min_size)
|
||||
source: 'img/white.png' if root.player=='W' else 'img/black.png'
|
||||
mipmap: True
|
||||
Label:
|
||||
id: lbl
|
||||
text: root.text
|
||||
text_size: None, root.min_size
|
||||
bold: True
|
||||
valign: 'center'
|
||||
color: BLACK if root.player=='W' else WHITE
|
||||
pos: (image.x, image.y)
|
||||
size: image.size
|
||||
font_size: root.min_size * 1.2 / (1 + 1 * len(self.text))
|
||||
|
||||
# UI components
|
||||
|
||||
<BadukPanWidget>:
|
||||
@@ -477,8 +442,8 @@
|
||||
PauseButton:
|
||||
pos_hint: {'center_y':0.5}
|
||||
id: pause
|
||||
state: 'down' if root.paused else 'normal'
|
||||
on_press: root.paused = not root.paused
|
||||
active: root.paused
|
||||
on_left_press: root.paused = not root.paused
|
||||
|
||||
|
||||
#:set GRAPH_CENTER_COLOR GREY
|
||||
@@ -627,7 +592,7 @@
|
||||
markup: root.markup
|
||||
padding: 5, 5
|
||||
font_size: NOTES_FONT_SIZE
|
||||
color: WHITE
|
||||
color: TEXT_COLOR
|
||||
size_hint_y: None
|
||||
height: self.texture_size[1]
|
||||
text_size: self.width, None
|
||||
@@ -641,10 +606,6 @@
|
||||
#:set STATS_BOX_HEIGHT 0.27
|
||||
#:set INFO_BOX_HEIGHT 1
|
||||
|
||||
#:set CP_SPACING 6
|
||||
#:set CP_SMALL_SPACING 3
|
||||
#:set CP_PADDING 6
|
||||
|
||||
|
||||
<CollapsablePanelHeader>:
|
||||
canvas.after:
|
||||
@@ -1083,134 +1044,3 @@
|
||||
|
||||
|
||||
|
||||
<LoadSGFPopup>:
|
||||
fast: fast
|
||||
rewind: rewind
|
||||
filesel: filesel
|
||||
orientation: 'vertical'
|
||||
Label:
|
||||
text: root.filesel.path
|
||||
size_hint: 1,0.05
|
||||
halign: 'left'
|
||||
FileChooserListView:
|
||||
id: filesel
|
||||
multiselect: False
|
||||
filters: ["*.sgf"]
|
||||
path: "."
|
||||
size_hint: 1,0.88
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
size_hint: 1,0.07
|
||||
canvas.before:
|
||||
Color:
|
||||
rgb: 0.4, 0.4, 0.4
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
Label:
|
||||
text: "Analyze Extra Fast"
|
||||
size_hint: 0.45,1
|
||||
halign: 'right'
|
||||
CheckBox:
|
||||
id: fast
|
||||
color: (0.95, 0.95, 0.95)
|
||||
size_hint: 0.05,1
|
||||
Label:
|
||||
text: "Rewind to Start"
|
||||
halign: 'right'
|
||||
size_hint: 0.45,1
|
||||
CheckBox:
|
||||
id: rewind
|
||||
active: True
|
||||
color: (0.95, 0.95, 0.95)
|
||||
size_hint: 0.05,1
|
||||
|
||||
<QuickInputButton@StyledButton>
|
||||
on_left_press: self.parent.parent.children[self.parent.parent.children.index(self.parent)+1].text = self.text # why +1 instead of -1?
|
||||
|
||||
<NewGamePopup>:
|
||||
orientation: 'vertical'
|
||||
rules_spinner: rules_spinner
|
||||
info: info
|
||||
restart: restart
|
||||
GridLayout:
|
||||
size_hint: 1, 0.8
|
||||
rows: 6
|
||||
cols: 3
|
||||
spacing: 2
|
||||
padding: 2
|
||||
ScaledLightLabel:
|
||||
text: "Size"
|
||||
LabelledTextInput:
|
||||
text: '19'
|
||||
font_size: 0.5*self.height
|
||||
padding: 7, 0.2*self.height
|
||||
input_property: 'SZ'
|
||||
BoxLayout:
|
||||
spacing: 1
|
||||
QuickInputButton:
|
||||
text: '9'
|
||||
QuickInputButton:
|
||||
text: '13'
|
||||
QuickInputButton:
|
||||
text: '19'
|
||||
ScaledLightLabel:
|
||||
text: "Handicap"
|
||||
LabelledIntInput:
|
||||
text: '0'
|
||||
input_property: 'HA'
|
||||
BoxLayout:
|
||||
spacing: 1
|
||||
QuickInputButton:
|
||||
text: '0'
|
||||
on_left_press: if not self.last_touch or self.last_touch.button=="left": km.text='6.5'
|
||||
QuickInputButton:
|
||||
text: '2'
|
||||
on_left_press: if not self.last_touch or self.last_touch.button=="left": km.text='0.5'
|
||||
QuickInputButton:
|
||||
text: '9'
|
||||
on_left_press: if not self.last_touch or self.last_touch.button=="left": km.text='0.5'
|
||||
ScaledLightLabel:
|
||||
text: "Komi"
|
||||
LabelledFloatInput:
|
||||
text: '6.5'
|
||||
id: km
|
||||
input_property: 'KM'
|
||||
BoxLayout:
|
||||
spacing: 1
|
||||
QuickInputButton:
|
||||
text: '0.5'
|
||||
QuickInputButton:
|
||||
text: '5.5'
|
||||
QuickInputButton:
|
||||
text: '6.5'
|
||||
ScaledLightLabel:
|
||||
text: "Ruleset"
|
||||
LabelledSpinner:
|
||||
input_property: 'RU'
|
||||
id: rules_spinner
|
||||
Label:
|
||||
ScaledLightLabel:
|
||||
text: 'Clear cache'
|
||||
CheckBox:
|
||||
color: WHITE
|
||||
id: restart
|
||||
ScaledLightLabel:
|
||||
font_size: 0.35*self.height
|
||||
text: 'avoids replaying\nidentical games'
|
||||
LightHelpLabel:
|
||||
text: "Use x:y (e.g. 19:9) to play on a non-square board."
|
||||
size_hint: 1,2
|
||||
text_size: self.width-6, None
|
||||
font_size: self.height/6
|
||||
id: info
|
||||
LightHelpLabel:
|
||||
text: "Note that handicaps above 9 are not supported on non-square boards."
|
||||
font_size: self.height/6
|
||||
text_size: self.width-6, None
|
||||
id: info
|
||||
StyledButton:
|
||||
halign: 'center'
|
||||
text: 'Start\nGame'
|
||||
font_size: 0.3 * self.height
|
||||
on_left_press: if not self.last_touch or self.last_touch.button=="left": root.new_game()
|
||||
@@ -254,7 +254,7 @@ class BadukPanWidget(Widget):
|
||||
self.draw_stone(m.coords[0], m.coords[1], stone_color[m.player], outline_color[m.player], inner, evalcol, evalsize)
|
||||
realized_points_lost = node.parent_realized_points_lost
|
||||
|
||||
if katrain.game.current_node.is_root and katrain.config("debug/level") >= 3: # secret ;)
|
||||
if katrain.game.current_node.is_root and katrain.debug_level >= 3: # secret ;)
|
||||
for y in range(0, board_size_y):
|
||||
evalcol = self.eval_color(16 * y / board_size_y)
|
||||
self.draw_stone(0, y, stone_color["B"], outline_color["B"], None, evalcol, y / (board_size_y - 1))
|
||||
|
||||
@@ -95,7 +95,7 @@ class ControlsPanel(BoxLayout):
|
||||
now = time.time()
|
||||
self.last_timer_update = (current_node, now)
|
||||
byo_len = max(1, self.katrain.config("timer/byo_length"))
|
||||
byo_num = max(1, self.katrain.config("timer/byo_num"))
|
||||
byo_num = max(1, self.katrain.config("timer/byo_periods"))
|
||||
player = self.katrain.game.next_player
|
||||
ai = player.ai
|
||||
if not self.timer.paused and not ai:
|
||||
|
||||
@@ -54,9 +54,9 @@ class ScoreGraph(BackgroundMixin):
|
||||
|
||||
xscale = self.width / max(len(score_values) - 1, 15)
|
||||
available_height = self.height
|
||||
score_line_points = [[self.pos[0] + i * xscale, self.pos[1] + self.height / 2 + available_height / 2 * (val / self.score_scale)] for i, val in enumerate(score_values)]
|
||||
score_line_points = [[self.pos[0] + i * xscale, self.pos[1] + self.height / 2 + available_height / 2 * (val / self.score_scale),] for i, val in enumerate(score_values)]
|
||||
winrate_line_points = [
|
||||
[self.pos[0] + i * xscale, self.pos[1] + self.height / 2 + available_height / 2 * (val / self.winrate_scale)] for i, val in enumerate(winrate_values)
|
||||
[self.pos[0] + i * xscale, self.pos[1] + self.height / 2 + available_height / 2 * (val / self.winrate_scale),] for i, val in enumerate(winrate_values)
|
||||
]
|
||||
self.score_points = sum(score_line_points, [])
|
||||
self.winrate_points = sum(winrate_line_points, [])
|
||||
|
||||
+14
-79
@@ -5,7 +5,14 @@ import re
|
||||
from kivy.clock import Clock
|
||||
from kivy.core.window import Window
|
||||
from kivy.graphics import *
|
||||
from kivy.properties import BooleanProperty, ListProperty, NumericProperty, StringProperty, OptionProperty, ObjectProperty
|
||||
from kivy.properties import (
|
||||
BooleanProperty,
|
||||
ListProperty,
|
||||
NumericProperty,
|
||||
StringProperty,
|
||||
OptionProperty,
|
||||
ObjectProperty,
|
||||
)
|
||||
from kivy.uix.behaviors import ToggleButtonBehavior, ButtonBehavior
|
||||
from kivy.uix.button import Button
|
||||
from kivy.uix.checkbox import CheckBox
|
||||
@@ -13,7 +20,7 @@ from kivy.uix.filechooser import FileChooserLayout, FileChooserListLayout
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.scrollview import ScrollView
|
||||
from kivy.uix.spinner import Spinner, SpinnerOption
|
||||
from kivy.uix.spinner import Spinner
|
||||
from kivy.uix.textinput import TextInput
|
||||
from kivy.uix.widget import Widget
|
||||
|
||||
@@ -132,9 +139,6 @@ class PauseButton(CircularRippleBehavior, LeftButtonBehavior, Widget):
|
||||
line_color = ListProperty([0.5, 0.5, 0.5, 1])
|
||||
min_size = NumericProperty(100)
|
||||
|
||||
def on_left_press(self):
|
||||
self.active = not self.active
|
||||
|
||||
|
||||
# -- basic styles
|
||||
class LightLabel(Label):
|
||||
@@ -332,7 +336,7 @@ class CollapsablePanel(MDBoxLayout):
|
||||
self.option_buttons = []
|
||||
option_labels = self.option_labels or [i18n._(f"tab:{opt}") for opt in self.options]
|
||||
for lbl, opt_col, active in zip(option_labels, self.option_colors, self.option_default_active):
|
||||
button = CollapsablePanelTab(text=lbl, active_outline_color=opt_col, height=self.options_height, on_press=self.trigger_select, state="down" if active else "normal")
|
||||
button = CollapsablePanelTab(text=lbl, active_outline_color=opt_col, height=self.options_height, on_press=self.trigger_select, state="down" if active else "normal",)
|
||||
self.option_buttons.append(button)
|
||||
self.open_close_button = TransparentIconButton( # << / >> collapse button
|
||||
icon=self.open_close_icon(),
|
||||
@@ -515,7 +519,7 @@ class ToggleButtonContainer(GridLayout):
|
||||
for i, opt in enumerate(self.options):
|
||||
state = "down" if opt == self.selected else "normal"
|
||||
tooltip = self.tooltips[i] if self.tooltips else None
|
||||
self.add_widget(StyledToggleButton(group=self.group, text=self.labels[i], value=opt, state=state, on_press=state_handler, tooltip_text=tooltip))
|
||||
self.add_widget(StyledToggleButton(group=self.group, text=self.labels[i], value=opt, state=state, on_press=state_handler, tooltip_text=tooltip,))
|
||||
Clock.schedule_once(self._size, 0)
|
||||
|
||||
def _size(self, _dt):
|
||||
@@ -532,81 +536,12 @@ class ToggleButtonContainer(GridLayout):
|
||||
return self.options[0]
|
||||
|
||||
|
||||
class LabelledTextInput(TextInput):
|
||||
input_property = StringProperty("")
|
||||
multiline = BooleanProperty(False)
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
return self.text
|
||||
|
||||
|
||||
class LabelledObjectInputArea(LabelledTextInput):
|
||||
multiline = BooleanProperty(True)
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
return json.loads(self.text.replace("'", '"').replace("True", "true").replace("False", "false"))
|
||||
|
||||
|
||||
class LabelledCheckBox(CheckBox):
|
||||
input_property = StringProperty("")
|
||||
|
||||
def __init__(self, text=None, **kwargs):
|
||||
if text is not None:
|
||||
kwargs["active"] = text.lower() == "true"
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
return bool(self.active)
|
||||
|
||||
|
||||
class LabelledSpinner(StyledSpinner):
|
||||
input_property = StringProperty("")
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
return self.text
|
||||
|
||||
|
||||
class LabelledFloatInput(LabelledTextInput):
|
||||
signed = BooleanProperty(True)
|
||||
pat = re.compile("[^0-9-]")
|
||||
|
||||
def insert_text(self, substring, from_undo=False):
|
||||
pat = self.pat
|
||||
if "." in self.text:
|
||||
s = re.sub(pat, "", substring)
|
||||
else:
|
||||
s = ".".join([re.sub(pat, "", s) for s in substring.split(".", 1)])
|
||||
r = super().insert_text(s, from_undo=from_undo)
|
||||
if not self.signed and "-" in self.text:
|
||||
self.text = self.text.replace("-", "")
|
||||
elif self.text and "-" in self.text[1:]:
|
||||
self.text = self.text[0] + self.text[1:].replace("-", "")
|
||||
return r
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
return float(self.text)
|
||||
|
||||
|
||||
class LabelledIntInput(LabelledTextInput):
|
||||
pat = re.compile("[^0-9]")
|
||||
|
||||
def insert_text(self, substring, from_undo=False):
|
||||
return super().insert_text(re.sub(self.pat, "", substring), from_undo=from_undo)
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
return int(self.text)
|
||||
|
||||
|
||||
def draw_text(pos, text, font_name=None, **kw):
|
||||
label = CoreLabel(text=text, bold=True, font_name=font_name or i18n.font_name, **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):
|
||||
|
||||
+146
-84
@@ -1,29 +1,90 @@
|
||||
from collections import defaultdict
|
||||
from typing import Dict, List, DefaultDict, Tuple
|
||||
import re
|
||||
|
||||
from kivy.clock import Clock
|
||||
from kivy.properties import StringProperty, BooleanProperty
|
||||
from kivy.uix.boxlayout import BoxLayout
|
||||
from kivy.uix.gridlayout import GridLayout
|
||||
from kivy.uix.label import Label
|
||||
from kivy.uix.popup import Popup
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.selectioncontrol import MDCheckbox
|
||||
from kivymd.uix.textfield import MDTextField
|
||||
|
||||
from katrain.core.utils import OUTPUT_DEBUG, OUTPUT_ERROR
|
||||
from katrain.core.engine import KataGoEngine
|
||||
from katrain.core.game import Game, GameNode
|
||||
from katrain.gui.kivyutils import (
|
||||
LabelledCheckBox,
|
||||
LabelledFloatInput,
|
||||
LabelledIntInput,
|
||||
LabelledObjectInputArea,
|
||||
LabelledSpinner,
|
||||
LabelledTextInput,
|
||||
LightHelpLabel,
|
||||
ScaledLightLabel,
|
||||
StyledButton,
|
||||
StyledSpinner,
|
||||
BackgroundMixin,
|
||||
)
|
||||
from katrain.gui.kivyutils import StyledSpinner
|
||||
from katrain.gui.style import DEFAULT_FONT
|
||||
|
||||
|
||||
class I18NPopup(Popup):
|
||||
title_key = StringProperty("")
|
||||
font_name = StringProperty(DEFAULT_FONT)
|
||||
|
||||
|
||||
class LabelledTextInput(MDTextField):
|
||||
input_property = StringProperty("")
|
||||
multiline = BooleanProperty(False)
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
return self.text
|
||||
|
||||
|
||||
class LabelledCheckBox(MDCheckbox):
|
||||
input_property = StringProperty("")
|
||||
|
||||
def __init__(self, text=None, **kwargs):
|
||||
if text is not None:
|
||||
kwargs["active"] = text.lower() == "true"
|
||||
super().__init__(**kwargs)
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
return bool(self.active)
|
||||
|
||||
|
||||
class LabelledSpinner(StyledSpinner):
|
||||
input_property = StringProperty("")
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
return self.selected[1] # ref value
|
||||
|
||||
|
||||
class LabelledFloatInput(LabelledTextInput):
|
||||
signed = BooleanProperty(True)
|
||||
pat = re.compile("[^0-9-]")
|
||||
|
||||
def insert_text(self, substring, from_undo=False):
|
||||
pat = self.pat
|
||||
if "." in self.text:
|
||||
s = re.sub(pat, "", substring)
|
||||
else:
|
||||
s = ".".join([re.sub(pat, "", s) for s in substring.split(".", 1)])
|
||||
r = super().insert_text(s, from_undo=from_undo)
|
||||
if not self.signed and "-" in self.text:
|
||||
self.text = self.text.replace("-", "")
|
||||
elif self.text and "-" in self.text[1:]:
|
||||
self.text = self.text[0] + self.text[1:].replace("-", "")
|
||||
return r
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
return float(self.text)
|
||||
|
||||
|
||||
class LabelledIntInput(LabelledTextInput):
|
||||
pat = re.compile("[^0-9]")
|
||||
|
||||
def insert_text(self, substring, from_undo=False):
|
||||
return super().insert_text(re.sub(self.pat, "", substring), from_undo=from_undo)
|
||||
|
||||
@property
|
||||
def input_value(self):
|
||||
return int(self.text)
|
||||
|
||||
|
||||
class InputParseError(Exception):
|
||||
@@ -31,33 +92,18 @@ class InputParseError(Exception):
|
||||
|
||||
|
||||
class QuickConfigGui(MDBoxLayout):
|
||||
def __init__(self, katrain, popup: Popup, initial_values: Dict = None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
def __init__(self, katrain):
|
||||
super().__init__()
|
||||
self.katrain = katrain
|
||||
self.popup = popup
|
||||
self.orientation = "vertical"
|
||||
if initial_values:
|
||||
self.set_properties(self, initial_values)
|
||||
|
||||
@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
|
||||
self.popup = None
|
||||
Clock.schedule_once(lambda _dt: self.set_properties(self))
|
||||
|
||||
def collect_properties(self, widget):
|
||||
if isinstance(widget, (LabelledTextInput, LabelledSpinner, LabelledCheckBox)):
|
||||
if isinstance(widget, (LabelledTextInput, LabelledSpinner, LabelledCheckBox)) and getattr(widget, "input_property", None):
|
||||
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}")
|
||||
raise InputParseError(f"Could not parse value for {widget.input_property} ({widget.__class__}): {e}") # TODO : on widget!
|
||||
else:
|
||||
ret = {}
|
||||
for c in widget.children:
|
||||
@@ -65,39 +111,77 @@ class QuickConfigGui(MDBoxLayout):
|
||||
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])
|
||||
def get_setting(self, key):
|
||||
keys = key.split("/")
|
||||
config = self.katrain._config
|
||||
for k in keys[:-1]:
|
||||
if k not in config:
|
||||
config[k] = {}
|
||||
config = config[k]
|
||||
if keys[-1] not in config:
|
||||
config[keys[-1]] = ""
|
||||
self.katrain.log(f"Configuration setting {repr(key)} was missing, created it, but this likely indicates a broken config file.", OUTPUT_ERROR)
|
||||
return config[keys[-1]], config, keys[-1]
|
||||
|
||||
def set_properties(self, widget):
|
||||
if isinstance(widget, (LabelledTextInput, LabelledSpinner, LabelledCheckBox)) and getattr(widget, "input_property", None):
|
||||
value = self.get_setting(widget.input_property)[0]
|
||||
if isinstance(widget, LabelledCheckBox):
|
||||
widget.active = value is True
|
||||
elif isinstance(widget, LabelledSpinner):
|
||||
selected = 0
|
||||
try:
|
||||
selected = widget.value_refs.index(value)
|
||||
except:
|
||||
pass
|
||||
widget.text = widget.values[selected]
|
||||
else:
|
||||
widget.text = str(value)
|
||||
for c in widget.children:
|
||||
self.set_properties(c, properties)
|
||||
self.set_properties(c)
|
||||
|
||||
def update_config(self, save_to_file=True):
|
||||
updated = set()
|
||||
for multikey, value in self.collect_properties(self).items():
|
||||
old_value, conf, key = self.get_setting(multikey)
|
||||
if value != old_value:
|
||||
self.katrain.log(f"Updating setting {multikey} = {value}", OUTPUT_DEBUG)
|
||||
conf[key] = value # reference straight back to katrain._config
|
||||
updated.add(multikey)
|
||||
if save_to_file:
|
||||
self.katrain.save_config()
|
||||
if updated:
|
||||
self.katrain.update_state()
|
||||
if self.popup:
|
||||
self.popup.dismiss()
|
||||
return updated
|
||||
|
||||
|
||||
class LoadSGFPopup(BoxLayout):
|
||||
pass
|
||||
class ConfigTimerPopup(QuickConfigGui):
|
||||
def __init__(self, katrain):
|
||||
super().__init__(katrain)
|
||||
|
||||
def update_config(self, save_to_file=True):
|
||||
super().update_config(save_to_file=save_to_file)
|
||||
for p in self.katrain.game.players.values():
|
||||
p.periods_used = 0
|
||||
self.katrain.controls.timer.paused = True
|
||||
self.katrain.game.current_node.time_used = 0
|
||||
self.katrain.update_state()
|
||||
|
||||
|
||||
class NewGamePopup(QuickConfigGui):
|
||||
def __init__(self, katrain, popup: Popup, properties: Dict, **kwargs):
|
||||
properties["RU"] = KataGoEngine.get_rules(katrain.game.root)
|
||||
super().__init__(katrain, popup, properties, **kwargs)
|
||||
self.rules_spinner.values = list(set(self.katrain.engine.RULESETS.values()))
|
||||
self.rules_spinner.text = properties["RU"]
|
||||
def __init__(self, katrain):
|
||||
super().__init__(katrain)
|
||||
self.rules_spinner.value_refs = [name for abbr, name in katrain.engine.RULESETS_ABBR]
|
||||
|
||||
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
|
||||
def update_config(self, save_to_file=True):
|
||||
updated = super().update_config(save_to_file=save_to_file)
|
||||
self.katrain.log(f"New game settings: {self.katrain.config('game')}", OUTPUT_DEBUG)
|
||||
if self.restart.active:
|
||||
self.katrain.log("Restarting Engine")
|
||||
self.katrain.log("Restarting Engine", OUTPUT_DEBUG)
|
||||
self.katrain.engine.restart()
|
||||
self.katrain("new-game", new_root)
|
||||
self.popup.dismiss()
|
||||
self.katrain("new-game")
|
||||
|
||||
|
||||
class ConfigPopup(QuickConfigGui):
|
||||
@@ -193,6 +277,10 @@ class ConfigPopup(QuickConfigGui):
|
||||
self.katrain.update_state(redraw_board=True)
|
||||
|
||||
|
||||
class LoadSGFPopup(BoxLayout):
|
||||
pass
|
||||
|
||||
|
||||
class ConfigTeacherPopup(QuickConfigGui):
|
||||
def __init__(self, katrain, popup, **kwargs):
|
||||
self.settings = katrain.config("trainer")
|
||||
@@ -273,29 +361,3 @@ class ConfigTeacherPopup(QuickConfigGui):
|
||||
return
|
||||
self.katrain.update_state()
|
||||
self.popup.dismiss()
|
||||
|
||||
|
||||
class ConfigTimerPopup(QuickConfigGui):
|
||||
def __init__(self, katrain, popup, **kwargs):
|
||||
self.settings = katrain.config("timer")
|
||||
super().__init__(katrain, popup, self.settings, **kwargs)
|
||||
self.spacing = 2
|
||||
Clock.schedule_once(self.build, 0)
|
||||
|
||||
def build(self, _dt):
|
||||
thrbox = GridLayout(spacing=1, padding=2, cols=2, rows=2, size_hint=(1, 2))
|
||||
thrbox.add_widget(ScaledLightLabel(text="Byo-yomi\nperiod length (s)", bold=True, size_hint=(2, 1), num_lines=2))
|
||||
thrbox.add_widget(LabelledIntInput(text="30", input_property="byo_length"))
|
||||
thrbox.add_widget(ScaledLightLabel(text="Byo-yomi\nnumber of periods", bold=True, size_hint=(2, 1), num_lines=2))
|
||||
thrbox.add_widget(LabelledIntInput(text="5", input_property="byo_num"))
|
||||
self.add_widget(thrbox)
|
||||
self.add_widget(StyledButton(text=f"Apply", on_press=lambda _: self.update_config(False)))
|
||||
|
||||
def update_config(self, save_to_file=False):
|
||||
for k, v in self.collect_properties(self).items():
|
||||
self.settings[k] = v
|
||||
if save_to_file:
|
||||
self.katrain.save_config()
|
||||
self.katrain.controls.periods_used = {"B": 0, "W": 0}
|
||||
self.katrain.update_state()
|
||||
self.popup.dismiss()
|
||||
+10
-4
@@ -8,6 +8,12 @@ DEFAULT_FONT = "fonts/NotoSans-Regular.ttf"
|
||||
RED = [0.8, 0.1, 0.1, 1]
|
||||
WHITE = [0.95, 0.95, 0.95, 1]
|
||||
|
||||
# gui colors
|
||||
|
||||
BACKGROUND_COLOR = [36 / 255, 48 / 255, 62 / 255, 1]
|
||||
BOX_BACKGROUND_COLOR = [46 / 255, 65 / 255, 88 / 255, 1]
|
||||
TEXT_COLOR = WHITE
|
||||
|
||||
# eval dots
|
||||
EVAL_COLORS = [
|
||||
[0.447, 0.129, 0.42, 1],
|
||||
@@ -34,9 +40,9 @@ TOP_MOVE_ALPHA = 0.4
|
||||
CHILD_SCALE = 0.95
|
||||
|
||||
# ponder light
|
||||
ENGINE_DOWN_COL = [0.8, 0, 0, 1]
|
||||
ENGINE_BUSY_COL = [0.9, 0.4, 0.1, 1]
|
||||
ENGINE_READY_COL = [0.117, 0.588, 0, 1]
|
||||
ENGINE_DOWN_COL = EVAL_COLORS[1]
|
||||
ENGINE_BUSY_COL = EVAL_COLORS[2]
|
||||
ENGINE_READY_COL = EVAL_COLORS[-1]
|
||||
|
||||
# info
|
||||
PV_COLOR = to_hexcol(RED)
|
||||
INFO_PV_COLOR = to_hexcol(RED)
|
||||
Binary file not shown.
@@ -214,6 +214,70 @@ msgstr "Analyzing move..."
|
||||
msgid "SGF Notes Hint"
|
||||
msgstr "Your SGF notes for this position here."
|
||||
|
||||
#. popup title
|
||||
# timer settings from here
|
||||
msgid "timer settings"
|
||||
msgstr "Adjust Timer Settings"
|
||||
|
||||
msgid "byoyomi length"
|
||||
msgstr "Time per Byo-yomi Period"
|
||||
|
||||
msgid "byoyomi periods"
|
||||
msgstr "Number of Periods"
|
||||
|
||||
#. submit button
|
||||
msgid "update timer"
|
||||
msgstr "Update Timer"
|
||||
|
||||
# new game settings from here
|
||||
msgid "New Game title"
|
||||
msgstr "Setup New Game"
|
||||
|
||||
msgid "board size"
|
||||
msgstr "Board size"
|
||||
|
||||
msgid "handicap"
|
||||
msgstr "Handicap"
|
||||
|
||||
msgid "komi"
|
||||
msgstr "Komi"
|
||||
|
||||
msgid "ruleset"
|
||||
msgstr "Rules"
|
||||
|
||||
#. ruleset names
|
||||
msgid "japanese"
|
||||
msgstr "Japanese"
|
||||
|
||||
msgid "chinese"
|
||||
msgstr "Chinese"
|
||||
|
||||
msgid "korean"
|
||||
msgstr "Korean"
|
||||
|
||||
msgid "aga"
|
||||
msgstr "AGA"
|
||||
|
||||
msgid "clear cache"
|
||||
msgstr "Clear Cache"
|
||||
|
||||
#. clear cache help
|
||||
msgid "avoids replaying"
|
||||
msgstr ""
|
||||
"Avoids replaying\n"
|
||||
"identical games"
|
||||
|
||||
#. hints on new game
|
||||
msgid "non square board hint"
|
||||
msgstr "Use x:y (e.g. 19:9) to play on a non-square board."
|
||||
|
||||
msgid "handicap hint"
|
||||
msgstr "Note that handicaps above 9 are not supported on non-square boards."
|
||||
|
||||
#. submit new game
|
||||
msgid "new game"
|
||||
msgstr "Start New Game"
|
||||
|
||||
#. misc errors and info messages
|
||||
msgid "wait-before-equalize"
|
||||
msgstr "Wait for initial analysis to complete before requesting a refinement."
|
||||
|
||||
Binary file not shown.
@@ -271,3 +271,77 @@ msgstr "Teaching Game Settings"
|
||||
#. TODO
|
||||
msgid "menu:aisettings"
|
||||
msgstr "AI Settings"
|
||||
|
||||
#. TODO
|
||||
msgid "timer settings"
|
||||
msgstr "Adjust Timer Settings"
|
||||
|
||||
#. TODO
|
||||
msgid "update timer"
|
||||
msgstr "Update Timer Settings"
|
||||
|
||||
#. TODO
|
||||
msgid "byoyomi length"
|
||||
msgstr "Time per 뵤요미 Period"
|
||||
|
||||
#. TODO
|
||||
msgid "byoyomi periods"
|
||||
msgstr "Number of 뵤요미"
|
||||
|
||||
#. TODO
|
||||
msgid "japanese"
|
||||
msgstr "Japanese Rules"
|
||||
|
||||
#. TODO
|
||||
msgid "aga"
|
||||
msgstr "American Go Association Rules"
|
||||
|
||||
#. TODO
|
||||
msgid "new game"
|
||||
msgstr "Start New Game"
|
||||
|
||||
#. TODO
|
||||
msgid "board size"
|
||||
msgstr "Board size"
|
||||
|
||||
#. TODO
|
||||
msgid "ruleset"
|
||||
msgstr "Rules"
|
||||
|
||||
#. TODO
|
||||
msgid "komi"
|
||||
msgstr "Komi"
|
||||
|
||||
#. TODO
|
||||
msgid "avoids replaying"
|
||||
msgstr ""
|
||||
"'avoids replaying\n"
|
||||
"identical games"
|
||||
|
||||
#. TODO
|
||||
msgid "clear cache"
|
||||
msgstr "Clear Cache"
|
||||
|
||||
#. TODO
|
||||
msgid "korean"
|
||||
msgstr "Korean Rules"
|
||||
|
||||
#. TODO
|
||||
msgid "chinese"
|
||||
msgstr "Chinese Rules"
|
||||
|
||||
#. TODO
|
||||
msgid "non square board hint"
|
||||
msgstr "Use x:y (e.g. 19:9) to play on a non-square board."
|
||||
|
||||
#. TODO
|
||||
msgid "handicap"
|
||||
msgstr "Handicap"
|
||||
|
||||
#. TODO
|
||||
msgid "handicap hint"
|
||||
msgstr "Note that handicaps above 9 are not supported on non-square boards."
|
||||
|
||||
#. TODO
|
||||
msgid "New Game title"
|
||||
msgstr "Setup New Game"
|
||||
Binary file not shown.
@@ -303,3 +303,77 @@ msgstr "Teaching Game Settings"
|
||||
#. TODO
|
||||
msgid "menu:aisettings"
|
||||
msgstr "AI Settings"
|
||||
|
||||
#. TODO
|
||||
msgid "timer settings"
|
||||
msgstr "Adjust Timer Settings"
|
||||
|
||||
#. TODO
|
||||
msgid "update timer"
|
||||
msgstr "Update Timer Settings"
|
||||
|
||||
#. TODO
|
||||
msgid "byoyomi length"
|
||||
msgstr "Time per Byo-yomi Period"
|
||||
|
||||
#. TODO
|
||||
msgid "byoyomi periods"
|
||||
msgstr "Number of Periods"
|
||||
|
||||
#. TODO
|
||||
msgid "japanese"
|
||||
msgstr "Japanese Rules"
|
||||
|
||||
#. TODO
|
||||
msgid "aga"
|
||||
msgstr "American Go Association Rules"
|
||||
|
||||
#. TODO
|
||||
msgid "new game"
|
||||
msgstr "Start New Game"
|
||||
|
||||
#. TODO
|
||||
msgid "board size"
|
||||
msgstr "Board size"
|
||||
|
||||
#. TODO
|
||||
msgid "ruleset"
|
||||
msgstr "Rules"
|
||||
|
||||
#. TODO
|
||||
msgid "komi"
|
||||
msgstr "Komi"
|
||||
|
||||
#. TODO
|
||||
msgid "avoids replaying"
|
||||
msgstr ""
|
||||
"'avoids replaying\n"
|
||||
"identical games"
|
||||
|
||||
#. TODO
|
||||
msgid "clear cache"
|
||||
msgstr "Clear Cache"
|
||||
|
||||
#. TODO
|
||||
msgid "korean"
|
||||
msgstr "Korean Rules"
|
||||
|
||||
#. TODO
|
||||
msgid "chinese"
|
||||
msgstr "Chinese Rules"
|
||||
|
||||
#. TODO
|
||||
msgid "non square board hint"
|
||||
msgstr "Use x:y (e.g. 19:9) to play on a non-square board."
|
||||
|
||||
#. TODO
|
||||
msgid "handicap"
|
||||
msgstr "Handicap"
|
||||
|
||||
#. TODO
|
||||
msgid "handicap hint"
|
||||
msgstr "Note that handicaps above 9 are not supported on non-square boards."
|
||||
|
||||
#. TODO
|
||||
msgid "New Game title"
|
||||
msgstr "Setup New Game"
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.9 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.7 KiB |
@@ -0,0 +1,246 @@
|
||||
#:kivy 1.11.0
|
||||
#:import i18n katrain.core.utils.i18n
|
||||
|
||||
|
||||
#:set CP_SPACING 6
|
||||
#:set CP_SMALL_SPACING 3
|
||||
#:set CP_PADDING 6
|
||||
|
||||
#:set INPUT_FONT_SIZE dp(25)
|
||||
#:set DESC_FONT_SIZE dp(20)
|
||||
|
||||
#:import BACKGROUND_COLOR katrain.gui.style.BACKGROUND_COLOR
|
||||
#:import BOX_BACKGROUND_COLOR katrain.gui.style.BOX_BACKGROUND_COLOR
|
||||
#:import TEXT_COLOR katrain.gui.style.TEXT_COLOR
|
||||
|
||||
|
||||
#MDTextField:
|
||||
# hint_text: "Helper text on focus"
|
||||
# helper_text: "This will disappear when you click off"
|
||||
# helper_text_mode: "on_focus"
|
||||
# font_size: INPUT_FONT_SIZE
|
||||
|
||||
<LabelledTextInput>:
|
||||
font_size: INPUT_FONT_SIZE
|
||||
helper_text_mode: "on_focus"
|
||||
hint_text: ""
|
||||
helper_text: ""
|
||||
|
||||
<LabelledIntInput>:
|
||||
font_size: INPUT_FONT_SIZE
|
||||
|
||||
<LabelledFloatInput>:
|
||||
font_size: INPUT_FONT_SIZE
|
||||
on_text: print(self.size,self.font_size)
|
||||
|
||||
<LabelledCheckBox>:
|
||||
color: WHITE
|
||||
size: DESC_FONT_SIZE*2,DESC_FONT_SIZE*2
|
||||
size_hint: None,None
|
||||
|
||||
<QuickInputButton@SizedRectangleButton>
|
||||
on_left_press: if self.target: self.target.text = self.text
|
||||
size: DESC_FONT_SIZE * 2, DESC_FONT_SIZE * 2
|
||||
font_size: DESC_FONT_SIZE
|
||||
|
||||
<I18NPopup>:
|
||||
background_color: [*[c*255/66 for c in BACKGROUND_COLOR[:3]], 1]
|
||||
title: i18n._(self.title_key)
|
||||
title_size: DESC_FONT_SIZE
|
||||
title_font: self.font_name
|
||||
size_hint: None, None
|
||||
|
||||
|
||||
<QuickConfigGui>:
|
||||
orientation: 'vertical'
|
||||
spacing: CP_SPACING
|
||||
padding: 4*CP_PADDING,CP_PADDING, 4*CP_PADDING, CP_PADDING
|
||||
|
||||
<DescriptionLabel@Label>:
|
||||
font_size: DESC_FONT_SIZE
|
||||
multiline: True
|
||||
text_size: self.size
|
||||
halign: 'center'
|
||||
valign: 'center'
|
||||
|
||||
<SmallDescriptionLabel@DescriptionLabel>
|
||||
font_size: DESC_FONT_SIZE * 0.66
|
||||
|
||||
|
||||
<ConfigTimerPopup>:
|
||||
MDGridLayout:
|
||||
cols: 2
|
||||
rows: 3
|
||||
spacing: CP_SPACING
|
||||
size_hint: 1,2
|
||||
DescriptionLabel:
|
||||
text: i18n._('byoyomi length')
|
||||
AnchorLayout:
|
||||
LabelledIntInput:
|
||||
text: "30"
|
||||
input_property: "timer/byo_length"
|
||||
DescriptionLabel:
|
||||
text: i18n._('byoyomi periods')
|
||||
AnchorLayout:
|
||||
LabelledIntInput:
|
||||
text: "5"
|
||||
input_property: "timer/byo_periods"
|
||||
DescriptionLabel:
|
||||
text: 'test'
|
||||
AnchorLayout:
|
||||
LabelledTextInput:
|
||||
value: i18n._('byoyomi periods')
|
||||
hint_text: i18n._('byoyomi periods')
|
||||
helper_text: i18n._('byoyomi length')
|
||||
AnchorLayout:
|
||||
size_hint: 1,1
|
||||
AutoSizedRoundedRectangleButton:
|
||||
padding_x: 15
|
||||
size_hint: None,0.5
|
||||
text: i18n._("update timer")
|
||||
on_press: root.update_config(True)
|
||||
|
||||
|
||||
<NewGamePopup>:
|
||||
restart: restart
|
||||
rules_spinner: rules_spinner
|
||||
GridLayout:
|
||||
rows: 6
|
||||
cols: 3
|
||||
size_hint: 1, 3
|
||||
spacing: CP_SPACING
|
||||
padding: CP_PADDING
|
||||
DescriptionLabel:
|
||||
text: i18n._("board size")
|
||||
AnchorLayout:
|
||||
LabelledTextInput:
|
||||
text: '19'
|
||||
id: boardsize
|
||||
input_property: 'game/size'
|
||||
AnchorLayout:
|
||||
MDBoxLayout:
|
||||
adaptive_size: True
|
||||
spacing: CP_SPACING
|
||||
QuickInputButton:
|
||||
text: '9'
|
||||
target: boardsize
|
||||
QuickInputButton:
|
||||
text: '13'
|
||||
target: boardsize
|
||||
QuickInputButton:
|
||||
text: '19'
|
||||
target: boardsize
|
||||
DescriptionLabel:
|
||||
text: i18n._("handicap")
|
||||
AnchorLayout:
|
||||
LabelledIntInput:
|
||||
text: '0'
|
||||
input_property: 'game/handicap'
|
||||
id: handicap
|
||||
AnchorLayout:
|
||||
MDBoxLayout:
|
||||
adaptive_size: True
|
||||
spacing: CP_SPACING
|
||||
QuickInputButton:
|
||||
text: '0'
|
||||
on_left_press: km.text='6.5'
|
||||
target: handicap
|
||||
QuickInputButton:
|
||||
text: '2'
|
||||
on_left_press: km.text='0.5'
|
||||
target: handicap
|
||||
QuickInputButton:
|
||||
text: '9'
|
||||
on_left_press: km.text='0.5'
|
||||
target: handicap
|
||||
DescriptionLabel:
|
||||
text: i18n._("komi")
|
||||
AnchorLayout:
|
||||
LabelledFloatInput:
|
||||
text: '6.5'
|
||||
id: km
|
||||
input_property: 'game/komi'
|
||||
AnchorLayout:
|
||||
MDBoxLayout:
|
||||
adaptive_size: True
|
||||
spacing: CP_SPACING
|
||||
QuickInputButton:
|
||||
text: '0.5'
|
||||
QuickInputButton:
|
||||
text: '6.5'
|
||||
QuickInputButton:
|
||||
text: '7.5'
|
||||
DescriptionLabel:
|
||||
text: i18n._("ruleset")
|
||||
AnchorLayout:
|
||||
LabelledSpinner:
|
||||
size_hint: 0.8,0.8
|
||||
input_property: 'game/rules'
|
||||
font_size: DESC_FONT_SIZE
|
||||
id: rules_spinner
|
||||
Label:
|
||||
DescriptionLabel:
|
||||
text: i18n._('clear cache')
|
||||
AnchorLayout:
|
||||
LabelledCheckBox:
|
||||
id: restart
|
||||
input_property: 'game/clear_cache'
|
||||
SmallDescriptionLabel:
|
||||
text: i18n._('avoids replaying')
|
||||
SmallDescriptionLabel:
|
||||
text: i18n._("non square board hint")
|
||||
SmallDescriptionLabel:
|
||||
text: i18n._("handicap hint")
|
||||
AnchorLayout:
|
||||
size_hint: 1,1
|
||||
AutoSizedRoundedRectangleButton:
|
||||
padding_x: 15
|
||||
size_hint: None,0.5
|
||||
text: i18n._("new game")
|
||||
on_press: root.update_config(True)
|
||||
|
||||
|
||||
# old
|
||||
|
||||
<LoadSGFPopup>:
|
||||
fast: fast
|
||||
rewind: rewind
|
||||
filesel: filesel
|
||||
orientation: 'vertical'
|
||||
Label:
|
||||
text: root.filesel.path
|
||||
size_hint: 1,0.05
|
||||
halign: 'left'
|
||||
FileChooserListView:
|
||||
id: filesel
|
||||
multiselect: False
|
||||
filters: ["*.sgf"]
|
||||
path: "."
|
||||
size_hint: 1,0.88
|
||||
BoxLayout:
|
||||
orientation: 'horizontal'
|
||||
size_hint: 1,0.07
|
||||
canvas.before:
|
||||
Color:
|
||||
rgb: 0.4, 0.4, 0.4
|
||||
Rectangle:
|
||||
pos: self.pos
|
||||
size: self.size
|
||||
Label:
|
||||
text: "Analyze Extra Fast"
|
||||
size_hint: 0.45,1
|
||||
halign: 'right'
|
||||
CheckBox:
|
||||
id: fast
|
||||
color: (0.95, 0.95, 0.95)
|
||||
size_hint: 0.05,1
|
||||
Label:
|
||||
text: "Rewind to Start"
|
||||
halign: 'right'
|
||||
size_hint: 0.45,1
|
||||
CheckBox:
|
||||
id: rewind
|
||||
active: True
|
||||
color: (0.95, 0.95, 0.95)
|
||||
size_hint: 0.05,1
|
||||
|
||||
@@ -49,7 +49,7 @@ setup(
|
||||
],
|
||||
python_requires=">=3.6, <4",
|
||||
entry_points={"console_scripts": ["katrain=katrain.__main__:run_app"]},
|
||||
classifiers=["Development Status :: 4 - Beta", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3"],
|
||||
classifiers=["Development Status :: 4 - Beta", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3",],
|
||||
packages=packages,
|
||||
package_data=package_data,
|
||||
)
|
||||
+24
-1
@@ -35,7 +35,30 @@ def test_alphago():
|
||||
def test_pandanet():
|
||||
file = os.path.join(os.path.dirname(__file__), "data/panda1.sgf")
|
||||
root = SGF.parse_file(file)
|
||||
root_props = {"GM", "EV", "US", "CoPyright", "GN", "RE", "PW", "WR", "NW", "PB", "BR", "NB", "PC", "DT", "SZ", "TM", "KM", "LT", "RR", "HA", "AB", "C"}
|
||||
root_props = {
|
||||
"GM",
|
||||
"EV",
|
||||
"US",
|
||||
"CoPyright",
|
||||
"GN",
|
||||
"RE",
|
||||
"PW",
|
||||
"WR",
|
||||
"NW",
|
||||
"PB",
|
||||
"BR",
|
||||
"NB",
|
||||
"PC",
|
||||
"DT",
|
||||
"SZ",
|
||||
"TM",
|
||||
"KM",
|
||||
"LT",
|
||||
"RR",
|
||||
"HA",
|
||||
"AB",
|
||||
"C",
|
||||
}
|
||||
assert root_props == root.properties.keys()
|
||||
|
||||
move = root
|
||||
|
||||
Reference in new issue
Block a user