new game and timer popups

This commit is contained in:
Sander Land committed 2020-05-28 22:19:29 +02:00
1 parent e0bfa3c416
commit d70515235a
29 files changed
+853 -506

No files matched your search

+1 -1
View File
@@ -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
+6 -13
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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."
}
}
+2 -2
View File
@@ -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
View File
@@ -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
+2 -2
View File
@@ -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):
+62
View File
@@ -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)
-2
View File
@@ -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
View File
@@ -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()
+1 -1
View File
@@ -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))
+1 -1
View File
@@ -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:
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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

+246
View File
@@ -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
+1 -1
View File
@@ -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
View File
@@ -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