This commit is contained in:
Sander Land committed 2020-05-30 17:58:28 +02:00
1 parent d7915401e7
commit 48f6166d48
23 files changed
+700 -913

No files matched your search

+8 -3
View File
@@ -17,15 +17,19 @@ errors = False
po = {}
pofile = {}
num_todo = defaultdict(int)
for lang in locales:
pofile[lang] = os.path.join(localedir, lang, "LC_MESSAGES", "katrain.po")
po[lang] = polib.pofile(pofile[lang])
for entry in po[lang].translated_entries():
if "TODO" in entry.comment:
print(lang, "/", entry.msgid, "is TODO")
num_todo[lang] += 1
else:
strings_to_langs[entry.msgid][lang] = entry.msgstr
lang_to_strings[lang].add(entry.msgid)
if num_todo[lang]:
print(f"{lang} has {num_todo[lang]} TODO entries")
for lang in locales:
@@ -34,10 +38,11 @@ for lang in locales:
print("Message id", msgid, "found as ", strings_to_langs[msgid], "but missing in default", DEFAULT_LANG)
errors = True
elif DEFAULT_LANG in strings_to_langs[msgid]:
print("Message id", msgid, "missing in ", lang, "-> Adding it from", DEFAULT_LANG)
copied_msg = strings_to_langs[msgid][DEFAULT_LANG]
if lang == "haha":
copied_msg = re.sub(r"[a-zA-Z]{1,2}", "ㅋ", copied_msg)
entry = polib.POEntry(msgid=msgid, msgstr="ㅋㅋ" + copied_msg)
else:
print("Message id", msgid, "missing in ", lang, "-> Adding it from", DEFAULT_LANG)
entry = polib.POEntry(msgid=msgid, msgstr=copied_msg, comment="TODO")
po[lang].append(entry)
errors = True
+7 -9
View File
@@ -25,7 +25,7 @@ from katrain.core.utils import (
DEFAULT_LANGUAGE,
find_package_resource,
)
from katrain.core.constants import OUTPUT_ERROR, OUTPUT_KATAGO_STDERR, OUTPUT_INFO, OUTPUT_DEBUG, OUTPUT_EXTRA_DEBUG, MODE_PLAY
from katrain.core.constants import OUTPUT_ERROR, OUTPUT_KATAGO_STDERR, OUTPUT_INFO, OUTPUT_DEBUG, OUTPUT_EXTRA_DEBUG, MODE_PLAY, AI_STRATEGIES_RECOMMENDED_ORDER
from katrain.gui.popups import ConfigTeacherPopup, ConfigTimerPopup, I18NPopup
from katrain.core.base_katrain import KaTrainBase
from katrain.core.engine import KataGoEngine
@@ -33,7 +33,7 @@ from katrain.core.game import Game, IllegalMoveException, KaTrainSGF
from katrain.core.sgf_parser import Move, ParseError
from katrain.gui.kivyutils import *
from katrain.gui.widgets.graph import ScoreGraph
from katrain.gui.widgets.filebrowser import I18NFileChooserListView
from katrain.gui.widgets.filebrowser import I18NFileBrowser
from katrain.gui.badukpan import AnalysisControls, BadukPanControls, BadukPanWidget
from katrain.gui.controlspanel import ControlsPanel
from katrain.gui.popups import ConfigPopup, LoadSGFPopup, NewGamePopup, AIPopup
@@ -81,10 +81,6 @@ class KaTrainGui(Screen, KaTrainBase):
def play_analyze_mode(self):
return self.play_mode.play_analyze_mode
@property
def ai_strategies(self):
return list(self.config("ai").keys())
def start(self):
if self.engine:
return
@@ -163,7 +159,7 @@ class KaTrainGui(Screen, KaTrainBase):
def __call__(self, message, *args):
if self.game:
if message.endswith('popup'): # gui code needs to run in main kivy thread.
if message.endswith("popup"): # gui code needs to run in main kivy thread.
fn = getattr(self, f"_do_{message.replace('-', '_')}")
Clock.schedule_once(lambda _dt: fn(*args), -1)
else: # game related actions
@@ -180,7 +176,7 @@ class KaTrainGui(Screen, KaTrainBase):
if node is None or self.game.current_node == node:
mode = self.next_player_info.strategy
settings = self.config(f"ai/{mode}")
if settings:
if settings is not None:
ai_move(self.game, mode, settings)
else:
self.log(f"AI Mode {mode} not found!", OUTPUT_ERROR)
@@ -252,7 +248,8 @@ class KaTrainGui(Screen, KaTrainBase):
popup_contents.filesel.path = os.path.abspath(os.path.expanduser(self.config("general/sgf_load", ".")))
self.fileselect_popup = I18NPopup(title_key="load sgf title", size=[1200, 800], content=popup_contents).__self__
def readfile(files, _mouse):
def readfile(*args):
files = popup_contents.filesel.selection
self.fileselect_popup.dismiss()
try:
move_tree = KaTrainSGF.parse_file(files[0])
@@ -263,6 +260,7 @@ class KaTrainGui(Screen, KaTrainBase):
if not popup_contents.rewind.active:
self.game.redo(999)
popup_contents.filesel.on_success = readfile
popup_contents.filesel.on_submit = readfile
self.fileselect_popup.open()
+3 -4
View File
@@ -13,7 +13,7 @@
"_enable_ownership": true
},
"general": {
"sgf_load": "~/Downloads",
"sgf_load": "~/",
"sgf_save": "./sgfout",
"anim_pv_time": 0.5,
"debug_level": 1,
@@ -68,8 +68,7 @@
"lock_ai": true
},
"ai": {
"ai:default": {
},
"ai:default": {},
"ai:jigo": {
"target_score": 0.5
},
@@ -77,7 +76,7 @@
"strength": 0.5
},
"ai:policy": {
"opening_moves": 20
"opening_moves": 22.0
},
"ai:p:weighted": {
"pick_override": 1.0,
+5 -6
View File
@@ -5,7 +5,7 @@ import time
from typing import Dict, List, Tuple
from katrain.core.utils import var_to_grid
from katrain.core.constants import OUTPUT_INFO, OUTPUT_DEBUG
from katrain.core.constants import OUTPUT_INFO, OUTPUT_DEBUG, AI_STRATEGIES_POLICY, AI_POLICY, AI_WEIGHTED
from katrain.core.engine import EngineDiedException
from katrain.core.game import Game, GameNode, Move
@@ -33,9 +33,8 @@ def ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, GameNode
engine = game.engines[cn.next_player]
if engine.katago_process.poll() is not None: # TODO: clean up
raise EngineDiedException(f"Engine for {cn.next_player} ({engine.config}) died")
ai_mode = ai_mode.lower()
ai_thoughts = ""
if ("policy" in ai_mode or "p:" in ai_mode) and cn.policy: # pure policy based move
if (ai_mode in AI_STRATEGIES_POLICY) and cn.policy: # pure policy based move
policy_moves = cn.policy_ranking
pass_policy = cn.policy[-1]
top_5_pass = any([polmove[1].is_pass for polmove in policy_moves[:5]]) # dont make it jump around for the last few sensible non pass moves
@@ -44,20 +43,20 @@ def ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, GameNode
policy_grid = var_to_grid(cn.policy, size) # type: List[List[float]]
top_policy_move = policy_moves[0][1]
ai_thoughts += f"Using policy based strategy, base top 5 moves are {fmt_moves(policy_moves[:5])}. "
if "policy" in ai_mode and cn.depth <= ai_settings["opening_moves"]:
if ai_mode == AI_POLICY and cn.depth <= ai_settings["opening_moves"]:
ai_mode = "p:weighted"
ai_thoughts += f"Switching to weighted strategy in the opening {int(ai_settings['opening_moves'] * (game.board_size[0]*game.board_size[1]))} moves. "
ai_settings = {"pick_override": 0.9, "weaken_fac": 1, "lower_bound": 0.02}
if top_5_pass:
aimove = top_policy_move
ai_thoughts += "Playing top one because one of them is pass."
elif "policy" in ai_mode:
elif ai_mode == AI_POLICY:
aimove = top_policy_move
ai_thoughts += f"Playing top policy move {aimove.gtp()}."
elif policy_moves[0][0] > ai_settings["pick_override"]:
aimove = top_policy_move
ai_thoughts += f"Top policy move has weight > {ai_settings['pick_override']:.1%}, so overriding other strategies."
elif "weighted" in ai_mode:
elif ai_mode == AI_WEIGHTED:
lower_bound = max(0, ai_settings["lower_bound"]) * 2 # compensate for first halving in loop
weaken_fac = max(0.01, ai_settings["weaken_fac"])
weighted_coords = []
+1 -1
View File
@@ -66,7 +66,7 @@ class KaTrainBase:
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))
config_file = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 and sys.argv[1].endswith(".json") 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)
+33 -2
View File
@@ -8,8 +8,39 @@ GAME_TYPES = [PLAYING_NORMAL, PLAYING_TEACHING]
MODE_PLAY, MODE_ANALYZE = "play", "analyze"
AI_DEFAULT = "default"
AI_STRATEGIES = [AI_DEFAULT]
AI_DEFAULT = "ai:default"
AI_SCORELOSS = "ai:scoreloss"
AI_WEIGHTED = "ai:p:weighted"
AI_JIGO = "ai:jigo"
AI_POLICY = "ai:policy"
AI_PICK = "ai:p:pick"
AI_LOCAL = "ai:p:local"
AI_TENUKI = "ai:p:tenuki"
AI_INFLUENCE = "ai:p:influence"
AI_TERRITORY = "ai:p:territory"
AI_CONFIG_DEFAULT = AI_SCORELOSS
AI_STRATEGIES_ENGINE = [AI_DEFAULT, AI_SCORELOSS, AI_JIGO]
AI_STRATEGIES_POLICY = [AI_WEIGHTED, AI_POLICY, AI_PICK, AI_LOCAL, AI_TENUKI, AI_INFLUENCE, AI_TERRITORY]
AI_STRATEGIES = AI_STRATEGIES_ENGINE + AI_STRATEGIES_POLICY
AI_STRATEGIES_RECOMMENDED_ORDER = [AI_DEFAULT, AI_SCORELOSS, AI_WEIGHTED, AI_PICK, AI_POLICY, AI_LOCAL, AI_TENUKI, AI_TERRITORY, AI_INFLUENCE, AI_JIGO]
AI_STRENGTH = {
AI_DEFAULT: "9d",
AI_POLICY: "4d",
AI_JIGO: "?d",
AI_SCORELOSS: "5k",
AI_WEIGHTED: "5k",
AI_PICK: "8k",
AI_LOCAL: "5k",
AI_TENUKI: "8k",
AI_INFLUENCE: "8k",
AI_TERRITORY: "5k",
}
OUTPUT_ERROR = -1
OUTPUT_KATAGO_STDERR = -0.5
OUTPUT_INFO = 0
+16 -8
View File
@@ -1,7 +1,7 @@
import os
import sys
from numbers import Number
from typing import List, Tuple, Callable
from typing import List, Tuple, Callable, TypeVar
from kivy.lang import Observable
import gettext
@@ -12,8 +12,10 @@ try:
except:
import importlib_resources as pkg_resources
T = TypeVar("T")
def var_to_grid(array_var: List[Number], size: Tuple[int, int]) -> List[List[Number]]:
def var_to_grid(array_var: List[T], size: Tuple[int, int]) -> List[List[T]]:
"""convert ownership/policy to grid format such that grid[y][x] is for move with coords x,y"""
ix = 0
grid = [[]] * size[1]
@@ -55,11 +57,21 @@ class Lang(Observable):
def _(self, text):
return self.ugettext(text)
def set_widget_font(self, widget):
widget.font_name = self.font_name
for sub_widget in [getattr(widget, "_hint_lbl", None), getattr(widget, "_msg_lbl", None)]: # MDText
if sub_widget:
sub_widget.font_name = self.font_name
def fbind(self, name, func, *args):
if name == "_":
widget, property, *_ = args[0]
self.observers.append((widget, func, args))
widget.font_name = self.font_name
try:
self.set_widget_font(widget)
except Exception as e:
print(e)
# pass
else:
return super(Lang, self).fbind(name, func, *args)
@@ -88,11 +100,7 @@ class Lang(Observable):
for widget, func, args in self.observers:
try:
func(args[0], None, None)
print("setting", widget, widget.font_name, "->", self.font_name)
widget.font_name = self.font_name
for sub_widget in [getattr(widget, "_hint_lbl", None), getattr(widget, "_msg_lbl", None)]: # MDText
if sub_widget:
sub_widget.font_name = self.font_name
self.set_widget_font(widget)
except ReferenceError:
pass # proxy no longer exists
for cb in self.callbacks:
+33 -74
View File
@@ -1,20 +1,15 @@
#:kivy 1.11.0
#: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]
#:set ICON_BUTTON_COLOR [0.71, 0.78, 0.81, 1]
#:set ICON_BUTTON_COLOR_DOWN [0.51, 0.58, 0.51, 1]
#-- NEW
# TODO: refactor
#:set CP_SPACING 6
#:set CP_SMALL_SPACING 3
#:set CP_PADDING 6
#:import PLAYER_TYPES katrain.core.constants.PLAYER_TYPES
#:import PLAYER_HUMAN katrain.core.constants.PLAYER_HUMAN
#:import PLAYER_AI katrain.core.constants.PLAYER_AI
#:import RED katrain.gui.style.RED
#:import WHITE katrain.gui.style.WHITE
#:import BACKGROUND_COLOR katrain.gui.style.BACKGROUND_COLOR
@@ -147,7 +142,6 @@
width: 1
<I18NSpinner>:
text: self.values[0] if self.values else ''
font_size: self.height * 0.5
sync_height_frac: 1.0
background_color: [*[c*255/88 for c in BACKGROUND_COLOR[:3]], 1] # compensate for texture
@@ -173,59 +167,6 @@
size: image.size
font_size: root.min_size * 1.2 / (1 + 1 * len(self.text))
# -- old widgets TODO: remove
<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: TOOLTIP_COLOR
Rectangle:
size: self.size
pos: self.pos
Color:
rgba: BUTTON_COLOR
Line:
width: 1.1
rectangle: (*self.pos,*self.size)
<StyledIconButton@StyledButton>:
button_color: ICON_BUTTON_COLOR
button_color_down: ICON_BUTTON_COLOR_DOWN
icon_margin: 0.15 * self.size[0]
icon: ''
icon_size: min(self.size[0]-2*self.icon_margin,self.height-2*self.icon_margin)
Image:
size: (root.icon_size or 0,root.icon_size or 0) # square
pos: [root.pos[i] + (root.size[i] - root.icon_size)/2 for i in [0,1]] if root.icon_size else [0,0]
source: root.icon
mipmap: True
<LightLabel>:
color: TEXT_COLOR
text_size: self.size # don't overflow these
halign: 'center'
valign: 'middle'
<ScaledLightLabel>: # popups
color: TEXT_COLOR
font_size: 0.45*self.height/self.num_lines
<LightHelpLabel>:
font_size: self.height/10
halign: 'center'
valign: 'middle'
multiline: True
<ClickableLabel>:
color: WHITE
halign: 'center'
@@ -327,8 +268,8 @@
<PlayerInfo>:
captures: 0
player: 'B'
player_type: 'Player'
player_subtype: 'Normal game'
player_type: 'player:human'
player_subtype: 'game:normal'
background_color: BOX_BACKGROUND_COLOR if self.active else BACKGROUND_COLOR
outline_color: BOX_BACKGROUND_COLOR
outline_width: 2
@@ -342,11 +283,11 @@
orientation: 'vertical'
spacing: CP_SPACING
Label:
text: root.player_type
text: i18n._(root.player_type)
size_hint: 1,0.55
font_size: 0.8 * self.height
Label:
text: root.player_subtype
text: i18n._(root.player_subtype)
size_hint: 1,0.45
font_size: 0.7 * self.height
@@ -585,7 +526,7 @@
do_scroll_x: False
scroll_type: ['bars']
bar_width: 5
bar_color: BUTTON_COLOR
bar_color: LIGHTGREY
label: label
canvas.before:
Color:
@@ -874,8 +815,8 @@
spacing: CP_SPACING
padding: 0, CP_PADDING
player_type: player_type
player_subtype: player_subtype
player_subtype_label: player_subtype_label
player_subtype_human: player_subtype_human
player_subtype_ai: player_subtype_ai
MDBoxLayout:
size_hint: 0.25, 1
CircleWithText:
@@ -893,19 +834,37 @@
I18NSpinner:
id: player_type
value_refs: PLAYER_TYPES
on_text: root.setup_options(*args)
on_select: root.setup_options(*args)
MDBoxLayout:
size_hint: 0.5, 1
MDBoxLayout:
orientation: 'vertical'
spacing: CP_SPACING
size_hint_x: 1 if root.mode != PLAYER_AI else 0.0001
opacity:1 if root.mode != PLAYER_AI else 0
Label:
text: 'Player Type'
id: player_subtype_label
text: i18n._("gametype")
font_size: self.height * 0.5
BoxLayout:
I18NSpinner:
id: player_subtype
id: player_subtype_human
value_refs: []
on_text: root.update_global_player_info()
on_select: if root.mode==PLAYER_HUMAN: root.update_global_player_info()
MDBoxLayout:
orientation: 'vertical'
spacing: CP_SPACING
size_hint_x: 1 if root.mode == PLAYER_AI else 0.0001
opacity: 1 if root.mode == PLAYER_AI else 0
Label:
text: i18n._("aistrategy")
font_size: self.height * 0.5
BoxLayout:
I18NSpinner:
id: player_subtype_ai
value_refs: []
on_select: if root.mode==PLAYER_AI: root.update_global_player_info()
<PlayerSetupBlock>:
orientation: 'vertical'
+3 -3
View File
@@ -40,8 +40,8 @@ class ControlsPanel(BoxLayout):
def update_players(self, *_args):
for bw, player_info in self.katrain.players_info.items():
self.players[bw].player_type = i18n._(player_info.player_type)
self.players[bw].player_subtype = i18n._(player_info.player_subtype)
self.players[bw].player_type = player_info.player_type
self.players[bw].player_subtype = player_info.player_subtype
def set_status(self, msg, at_node=None):
self.status_msg = msg
@@ -64,7 +64,7 @@ class ControlsPanel(BoxLayout):
self.active_comment_node = current_node
if katrain.play_analyze_mode == MODE_PLAY and last_player_was_ai_playing_human:
if katrain.next_player_info.being_taught and current_node.children and current_node.children.auto_undo:
if katrain.next_player_info.being_taught and current_node.children and current_node.children[-1].auto_undo:
self.active_comment_node = current_node.children[-1]
elif current_node.parent:
self.active_comment_node = current_node.parent
+35 -135
View File
@@ -34,7 +34,7 @@ from kivymd.uix.button import BasePressedButton, BaseFlatButton
from kivymd.uix.navigationdrawer import MDNavigationDrawer
from kivy.core.text import Label as CoreLabel
from katrain.core.constants import GAME_TYPES
from katrain.core.constants import GAME_TYPES, AI_STRATEGIES_RECOMMENDED_ORDER, PLAYER_HUMAN, PLAYER_AI
from katrain.core.utils import i18n
#
@@ -179,35 +179,49 @@ class BGBoxLayout(BoxLayout, BackgroundMixin):
class I18NSpinner(Spinner):
__events__ = ["on_select"]
sync_height_frac = NumericProperty(1.0)
value_refs = ListProperty()
selected_index = NumericProperty(0)
font_name = StringProperty(DEFAULT_FONT)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.bind(size=self.update_dropdown_props, pos=self.update_dropdown_props, value_refs=self.i18n_values)
self.i18n_values()
MDApp.get_running_app().bind(language=self.i18n_values)
@property
def selected(self):
try:
selected = self.values.index(self.text)
selected = self.selected_index
return selected, self.value_refs[selected], self.values[selected]
except (ValueError, IndexError):
return 0, "", ""
def on_text(self, _widget, text):
try:
new_index = self.values.index(text)
if new_index != self.selected_index:
self.selected_index = new_index
self.dispatch("on_select")
except (ValueError, IndexError):
pass
def on_select(self, *args):
pass
def select_key(self, key):
try:
ix = self.value_refs.index(key)
self.text = self.values[ix]
except:
except (ValueError, IndexError):
pass
def i18n_values(self, *_args):
if self.value_refs:
selected = self.selected[0]
self.values = [i18n._(ref) for ref in self.value_refs]
self.text = self.values[selected]
self.text = self.values[self.selected_index]
self.font_name = i18n.font_name
self.update_dropdown_props()
@@ -230,29 +244,33 @@ class I18NSpinner(Spinner):
class PlayerSetup(MDBoxLayout):
player = OptionProperty("B", options=["B", "W"])
mode = StringProperty("")
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.setup_options, 0)
self.player_subtype_ai.value_refs = AI_STRATEGIES_RECOMMENDED_ORDER
self.player_subtype_human.value_refs = GAME_TYPES
self.setup_options()
def setup_options(self, *_args):
self.player_subtype.clear_widgets()
if self.player_type.selected[0] == 0: # human
self.player_subtype_label.text = i18n._("gametype")
self.player_subtype.value_refs = GAME_TYPES
else:
self.player_subtype_label.text = i18n._("aistrategy")
self.player_subtype.value_refs = MDApp.get_running_app().gui.ai_strategies # TODO:
self.player_subtype.text = self.player_subtype.values[0]
if self.player_type.selected[1] == self.mode:
return
self.mode = self.player_type.selected[1]
self.update_global_player_info()
@property
def player_type_dump(self):
return {"player_type": self.player_type.selected[1], "player_subtype": self.player_subtype.selected[1]}
if self.mode == PLAYER_AI:
return {"player_type": self.player_type.selected[1], "player_subtype": self.player_subtype_ai.selected[1]}
else:
return {"player_type": self.player_type.selected[1], "player_subtype": self.player_subtype_human.selected[1]}
def update_widget(self, player_type, player_subtype):
self.player_type.select_key(player_type) # should trigger setup options
self.player_subtype.select_key(player_subtype) # should trigger setup options
if self.mode == PLAYER_AI:
self.player_subtype_ai.select_key(player_subtype) # should trigger setup options
else:
self.player_subtype_human.select_key(player_subtype) # should trigger setup options
def update_global_player_info(self):
if self.parent and self.parent.update_global:
@@ -387,7 +405,7 @@ class CollapsablePanel(MDBoxLayout):
self.header.add_widget(Label()) # spacer
self.trigger_select(ix=None)
else:
self.header.add_widget(Label(text=i18n._(self.closed_label), halign="right", height=self.options_height))
self.header.add_widget(Label(text=i18n._(self.closed_label), font_name=i18n.font_name, halign="right", height=self.options_height))
self.header.add_widget(self.open_close_button)
super().clear_widgets()
@@ -438,60 +456,10 @@ class StatsBox(MDBoxLayout, BackgroundMixin):
player = StringProperty("")
# --- not checked
class ToolTipLabel(Label):
pass
class ToolTipBehavior(object): # TODO restyle
tooltip_text = StringProperty("")
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.tooltip = ToolTipLabel()
self.open = False
def on_touch_up(self, touch):
inside = self.collide_point(*self.to_widget(*touch.pos))
if inside and touch.button == "right" and self.tooltip_text:
if not self.open:
self.display_tooltip(touch.pos)
Clock.schedule_once(lambda _: self.set_position(touch.pos), 0)
elif not inside and self.open:
self.close_tooltip()
return super().on_touch_up(touch)
def close_tooltip(self):
self.open = False
Window.remove_widget(self.tooltip)
def on_size(self, *args):
mid = (self.pos[0] + self.width / 2, self.pos[1] + self.height / 2)
self.set_position(mid)
def set_position(self, pos):
self.tooltip.pos = (pos[0] - self.tooltip.texture_size[0], pos[1])
def display_tooltip(self, pos):
self.open = True
self.tooltip.text = self.tooltip_text
Window.add_widget(self.tooltip)
class ScaledLightLabel(LightLabel, ToolTipBehavior):
num_lines = NumericProperty(1)
class ClickableLabel(LeftButtonBehavior, Label):
pass
class LightHelpLabel(ScaledLightLabel):
pass
class ScrollableLabel(ScrollView, BackgroundMixin):
__events__ = ["on_ref_press"]
outline_color = ListProperty([0, 0, 0, 0]) # mixin not working for some reason
@@ -502,74 +470,6 @@ class ScrollableLabel(ScrollView, BackgroundMixin):
pass
class StyledButton(Button, LeftButtonBehavior, ToolTipBehavior):
button_color = ListProperty([])
button_color_down = ListProperty([])
radius = ListProperty((0,))
class StyledToggleButton(StyledButton, ToggleButtonBehavior, ToolTipBehavior):
value = StringProperty("")
allow_no_selection = BooleanProperty(False)
def _do_press(self):
if (self.last_touch and self.last_touch.button != "left") or (not self.allow_no_selection and self.state == "down"):
return
self._release_group(self)
self.state = "normal" if self.state == "down" else "down"
class ToggleButtonContainer(GridLayout):
__events__ = ("on_selection",)
options = ListProperty([])
labels = ListProperty([])
tooltips = ListProperty(None)
selected = StringProperty("")
group = StringProperty(None)
autosize = BooleanProperty(True)
spacing = ListProperty((1, 1))
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.rows = 1
self.cols = len(self.options)
Clock.schedule_once(self._build, 0)
def on_selection(self, *args):
pass
def _build(self, _dt):
self.cols = len(self.options)
self.group = self.group or str(random.random())
if not self.selected and self.options:
self.selected = self.options[0]
if len(self.labels) < len(self.options):
self.labels += self.options[len(self.labels) + 1 :]
def state_handler(*args):
self.dispatch("on_selection")
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,))
Clock.schedule_once(self._size, 0)
def _size(self, _dt):
if self.autosize:
for tb in self.children:
tb.size_hint = (tb.texture_size[0] + 10, 1)
@property
def value(self):
for tb in self.children:
if tb.state == "down":
return tb.value
if self.options:
return self.options[0]
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()
+14 -8
View File
@@ -7,11 +7,12 @@ from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.label import Label
from kivy.uix.popup import Popup
from kivymd.app import MDApp
from kivymd.uix.boxlayout import MDBoxLayout
from kivymd.uix.selectioncontrol import MDCheckbox
from kivymd.uix.textfield import MDTextField
from katrain.core.constants import OUTPUT_ERROR, OUTPUT_DEBUG, OUTPUT_INFO
from katrain.core.constants import OUTPUT_ERROR, OUTPUT_DEBUG, OUTPUT_INFO, AI_DEFAULT, AI_CONFIG_DEFAULT, AI_STRATEGIES_RECOMMENDED_ORDER
from katrain.core.engine import KataGoEngine
from katrain.core.utils import i18n, find_package_resource
from katrain.gui.kivyutils import I18NSpinner, BackgroundMixin
@@ -249,21 +250,22 @@ class AIPopup(QuickConfigGui):
def __init__(self, katrain):
super().__init__(katrain)
self.ai_select.bind(text=self.build_ai_options)
self.ai_select.value_refs = katrain.ai_strategies
self.ai_select.value_refs = AI_STRATEGIES_RECOMMENDED_ORDER
selected_strategies = {p.strategy for p in katrain.players_info.values()}
config_strategy = list((selected_strategies - {AI_DEFAULT}) or {AI_CONFIG_DEFAULT})[0]
self.ai_select.select_key(config_strategy)
self.build_ai_options()
self.ai_select.bind(text=self.build_ai_options)
def build_ai_options(self, *_args):
strategy = self.ai_select.selected[1]
print(strategy, "=strat", _args)
mode_settings = self.katrain.config(f"ai/{strategy}")
self.options_grid.clear_widgets()
self.help_label.text = i18n._(strategy.replace("ai:", "aihelp:"))
for k, v in mode_settings.items():
for k, v in sorted(mode_settings.items(), key=lambda kv: kv[0]):
self.options_grid.add_widget(DescriptionLabel(text=k))
self.options_grid.add_widget(wrap_anchor(LabelledFloatInput(text=str(v), input_property=f"{k}/{v}")))
self.options_grid.add_widget(wrap_anchor(LabelledFloatInput(text=str(v), input_property=f"ai/{strategy}/{k}")))
for _ in range((self.max_options - len(mode_settings)) * 2):
print(_, self.options_grid.rows, self.options_grid.cols, len(self.options_grid.children))
self.options_grid.add_widget(Label())
@@ -294,4 +296,8 @@ class ConfigPopup(QuickConfigGui):
class LoadSGFPopup(BoxLayout):
pass
def __init__(self, **kwargs):
super().__init__(**kwargs)
app = MDApp.get_running_app()
self.filesel.favorites = [(os.path.abspath(app.gui.config("general/sgf_load")), "SGF Load Dir"), (os.path.abspath(app.gui.config("general/sgf_save")), "SGF Save Dir")]
self.filesel.select_string = i18n._("Load File")
+11 -9
View File
@@ -45,7 +45,7 @@ from kivy.lang import Builder
from kivy.utils import platform
from kivy.clock import Clock
import string
from os.path import sep, dirname, expanduser, isdir, join
from os.path import sep, dirname, expanduser, isdir, join, getmtime
from os import walk
from functools import partial
@@ -56,7 +56,7 @@ if platform == "win":
def last_modified_first(files, filesystem):
return sorted(f for f in files if filesystem.is_dir(f)) + sorted([f for f in files if not filesystem.is_dir(f)], key=lambda f: -os.path.getmtime(f))
return sorted(f for f in files if filesystem.is_dir(f)) + sorted([f for f in files if not filesystem.is_dir(f)], key=lambda f: -getmtime(f))
def get_home_directory():
@@ -168,7 +168,7 @@ Builder.load_string(
self.parent.browser.path = self.path if self.collide_point(*args[1].pos) and self.path else self.parent.browser.path
on_is_open: self.is_open and self.parent.trigger_populate(self)
<FileBrowser>:
<I18NFileBrowser>:
orientation: 'vertical'
spacing: 5
padding: [6, 6, 6, 6]
@@ -224,12 +224,14 @@ Builder.load_string(
TextInput:
id: file_text
text: (root.selection and (root._shorten_filenames(root.selection) if root.multiselect else root.selection[0])) or ''
hint_text: 'Filename'
hint_text: i18n._('Filename')
multiline: False
Button:
height: '40dp'
AutoSizedRoundedRectangleButton:
id: select_button
padding_x: 15
height: '40dp'
size_hint_x: None
width: metrics.dp(100)
text: root.select_string
on_release: root.dispatch('on_success')
"""
@@ -312,8 +314,8 @@ class LinkTree(TreeView):
self.add_node(TreeLabel(text=path, path=parent + sep + path), node)
class FileBrowser(BoxLayout):
"""FileBrowser class, see module documentation for more information.
class I18NFileBrowser(BoxLayout):
"""I18NFileBrowser class, see module documentation for more information.
"""
__events__ = ("on_success", "on_submit")
@@ -436,7 +438,7 @@ class FileBrowser(BoxLayout):
pass
def __init__(self, **kwargs):
super(FileBrowser, self).__init__(**kwargs)
super(I18NFileBrowser, self).__init__(**kwargs)
Clock.schedule_once(self._post_init)
def _post_init(self, *largs):
Binary file not shown.
@@ -402,7 +402,6 @@ msgid "update settings"
msgstr "Update Settings"
# ai settings from here
msgid "ai settings"
msgstr "Advanced AI Settings"
@@ -459,8 +458,9 @@ msgstr "KataGo"
msgid "aihelp:default"
msgstr ""
"Full strength KataGo AI. Strength is affected by `max_visits` and "
"`model` in the general settings `engine` section and engine configuration file. No options are available here."
"Full strength KataGo AI. Strength is affected by `max_visits` and `model` "
"in the general settings `engine` section and engine configuration file. No "
"options are available here."
msgid "ai:jigo"
msgstr "KataJigo"
@@ -516,7 +516,7 @@ msgstr ""
"policy value is above `pick_override` to avoid obvious mistakes."
msgid "ai:p:local"
msgstr "Local Response Style"
msgstr "Local Style"
msgid "aihelp:p:local"
msgstr ""
@@ -531,7 +531,8 @@ msgid "aihelp:p:tenuki"
msgstr ""
"Picks `pick_n + pick_frac * <number of legal moves>` weighted away from the "
"last move and plays the best one. Plays top move if policy value is above "
"`pick_override` to avoid obvious mistakes."
"`pick_override` to avoid obvious mistakes. A higher `stddev` makes it play "
"further away."
msgid "ai:p:influence"
msgstr "Influential Style"
Binary file not shown.
File diff suppressed because it is too large. Load diff
Binary file not shown.
+11 -13
View File
@@ -138,7 +138,7 @@ msgstr ""
msgid "Engine died unexpectedly"
msgstr ""
"Engine died unexpectedly without sending output, possibly due to out of "
"memory: {e}"
"memory: {error}"
#. TODO
msgid "analysis:territory"
@@ -236,29 +236,23 @@ msgstr "Teaching Game"
msgid "aistrategy"
msgstr "AI Strategy"
#. TODO
msgid "game:normal"
msgstr "Normal Game"
msgstr "일반 게임"
#. TODO
msgid "player:human"
msgstr "AI"
msgstr "인간"
#. TODO
msgid "player:type"
msgstr "Player"
msgstr "인간/인곡지능"
#. TODO
msgid "gametype"
msgstr "Game Type"
msgstr "게임 종류"
#. TODO
msgid "player:ai"
msgstr "AI"
msgstr "인곡지능"
#. TODO
msgid "game:teach"
msgstr "Teaching Game"
msgstr "교직 게임"
#. TODO
msgid "menu:clocksettings"
@@ -650,3 +644,7 @@ msgstr "Homepage"
#. TODO
msgid "menu:manual"
msgstr "Manual"
#. TODO
msgid "ai settings"
msgstr "Advanced AI Settings"
Binary file not shown.
@@ -274,7 +274,7 @@ msgstr "Normal Game"
#. TODO
msgid "player:human"
msgstr "AI"
msgstr "Mens"
#. TODO
msgid "player:type"
@@ -682,3 +682,7 @@ msgstr "Homepage"
#. TODO
msgid "menu:manual"
msgstr "Manual"
#. TODO
msgid "ai settings"
msgstr "Advanced AI Settings"
+16 -23
View File
@@ -1,6 +1,7 @@
#:kivy 1.11.0
#:import i18n katrain.core.utils.i18n
#:import expanduser os.path.expanduser
#:import abspath os.path.abspath
#:set CP_SPACING 6
#:set CP_SMALL_SPACING 3
@@ -22,7 +23,7 @@
<LabelledTextInput>:
font_size: INPUT_FONT_SIZE
helper_text_mode: "on_focus"
helper_text_mode: "none"
hint_text: ""
helper_text: ""
@@ -32,7 +33,6 @@
helper_text_mode: "on_error"
helper_text: i18n._("file not found")
<LabelledIntInput>:
font_size: INPUT_FONT_SIZE
size_hint: 0.5, None
@@ -67,7 +67,7 @@
<DescriptionLabel>:
font_size: DESC_FONT_SIZE
multiline: True
text_size: self.size
text_size: self.width, None
halign: 'center'
valign: 'center'
@@ -101,13 +101,14 @@
AnchorLayout:
LabelledFloatInput:
input_property: "general/anim_pv_time"
helper_text: i18n._("engine:time:hint")
hint_text: i18n._("engine:time:hint")
DescriptionLabel:
text: i18n._("general:debug_level")
AnchorLayout:
LabelledIntInput:
input_property: "general/debug_level"
helper_text: i18n._("general:debug_level:hint")
helper_text_mode: "on_focus"
Label:
Label:
Label:
@@ -157,7 +158,7 @@
AnchorLayout:
LabelledFloatInput:
input_property: "engine/max_time"
helper_text: i18n._("engine:time:hint")
hint_text: i18n._("engine:time:hint")
DescriptionLabel:
text: i18n._("engine:wide_root_noise")
AnchorLayout:
@@ -360,15 +361,16 @@
BoxLayout:
size_hint: 1,1
DescriptionLabel:
font_size: 1.25*DESC_FONT_SIZE
font_size: DESC_FONT_SIZE
text: i18n._('Select AI')
AnchorLayout:
I18NSpinner:
size_hint: 1, 0.5
size_hint: 0.8, 0.5
id: ai_select
SmallDescriptionLabel:
id: help_label
size_hint: 1,1.25
text: i18n._('')
GridLayout:
size_hint: 1,4
id: options_grid
@@ -388,12 +390,6 @@
rewind: rewind
filesel: filesel
orientation: 'vertical'
I18NFileChooserListView:
id: filesel
multiselect: False
filters: ["*.sgf"]
path: "."
size_hint: 1,7
BoxLayout:
orientation: 'horizontal'
size_hint: 1,1
@@ -403,7 +399,6 @@
halign: 'right'
LabelledCheckBox:
id: fast
color: (0.95, 0.95, 0.95)
size_hint: 0.05,1
DescriptionLabel:
text: i18n._("load sgf rewind")
@@ -412,12 +407,10 @@
LabelledCheckBox:
id: rewind
active: True
color: (0.95, 0.95, 0.95)
size_hint: 0.05,1
AnchorLayout:
size_hint: 1,1.5
AutoSizedRoundedRectangleButton:
padding_x: 15
size_hint: None,0.5
text: i18n._("Load File")
I18NFileBrowser:
id: filesel
multiselect: False
filters: ["*.sgf"]
path: "."
size_hint: 1,7
+8
View File
@@ -0,0 +1,8 @@
import pytest
from katrain.core.constants import AI_STRATEGIES_RECOMMENDED_ORDER, AI_STRATEGIES
class TestAI:
def test_order(self):
assert set(AI_STRATEGIES_RECOMMENDED_ORDER) == set(AI_STRATEGIES)
+11 -7
View File
@@ -1,13 +1,17 @@
import pytest
from katrain.core.game import Game, IllegalMoveException, Move
from katrain.core.base_katrain import KaTrainBase, OUTPUT_INFO
class MockKaTrain:
def log(self, msg, lvl):
class MockKaTrain(KaTrainBase):
pass
# def log(self, message, level=OUTPUT_INFO):
# pass
class MockEngine:
def request_analysis(self, *args, **kwargs):
pass
@@ -18,7 +22,7 @@ class TestBoard:
return [c for c in b.chains if c]
def test_merge(self):
b = Game(MockKaTrain(), MockEngine(), config={"init_size": 9})
b = Game(MockKaTrain(), MockEngine())
b.play(Move.from_gtp("B9", player="B"))
b.play(Move.from_gtp("A3", player="B"))
b.play(Move.from_gtp("A9", player="B"))
@@ -27,7 +31,7 @@ class TestBoard:
assert 0 == len(b.prisoners)
def test_collide(self):
b = Game(MockKaTrain(), MockEngine(), config={"init_size": 9})
b = Game(MockKaTrain(), MockEngine())
b.play(Move.from_gtp("B9", player="B"))
with pytest.raises(IllegalMoveException):
b.play(Move.from_gtp("B9", player="W"))
@@ -36,7 +40,7 @@ class TestBoard:
assert 0 == len(b.prisoners)
def test_capture(self):
b = Game(MockKaTrain(), MockEngine(), config={"init_size": 9})
b = Game(MockKaTrain(), MockEngine())
b.play(Move.from_gtp("A2", player="B"))
b.play(Move.from_gtp("B1", player="W"))
b.play(Move.from_gtp("A1", player="W"))
@@ -57,7 +61,7 @@ class TestBoard:
assert 2 == len(b.prisoners)
def test_snapback(self):
b = Game(MockKaTrain(), MockEngine(), config={"init_size": 9})
b = Game(MockKaTrain(), MockEngine())
for move in ["C1", "D1", "E1", "C2", "D3", "E4", "F2", "F3", "F4"]:
b.play(Move.from_gtp(move, player="B"))
for move in ["D2", "E2", "C3", "D4", "C4"]:
@@ -75,7 +79,7 @@ class TestBoard:
assert 4 == len(b.prisoners)
def test_ko(self):
b = Game(MockKaTrain(), MockEngine(), config={"init_size": 9})
b = Game(MockKaTrain(), MockEngine())
for move in ["A2", "B1"]:
b.play(Move.from_gtp(move, player="B"))