diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..e8d0b8c --- /dev/null +++ b/.flake8 @@ -0,0 +1,3 @@ +[flake8] +ignore = E501, E203, W503, E402 # line length, space before binary op, line break before binary op, import not at top +exclude = .git,__pycache__,build,dist \ No newline at end of file diff --git a/katrain/__main__.py b/katrain/__main__.py index 0dce2bd..85703c1 100644 --- a/katrain/__main__.py +++ b/katrain/__main__.py @@ -1,7 +1,11 @@ """isort:skip_file""" # first, logging level lower +import json import os +from kivy.core.window import Window +from kivy.uix.widget import Widget + os.environ["KCFG_KIVY_LOG_LEVEL"] = os.environ.get("KCFG_KIVY_LOG_LEVEL", "warning") if "KIVY_AUDIO" not in os.environ: os.environ["KIVY_AUDIO"] = "sdl2" # some backends hard crash / this seems to be most stable @@ -46,9 +50,11 @@ from kivy.lang import Builder from kivy.resources import resource_add_path from kivy.uix.popup import Popup from kivy.uix.screenmanager import Screen -from katrain.core.ai import generate_ai_move -from kivy.core.window import Window +from kivy.resources import resource_find +from kivy.properties import NumericProperty, ObjectProperty, StringProperty +from kivy.clock import Clock from kivy.metrics import dp +from katrain.core.ai import generate_ai_move from katrain.core.lang import DEFAULT_LANGUAGE, i18n from katrain.core.constants import ( @@ -57,7 +63,6 @@ from katrain.core.constants import ( OUTPUT_INFO, OUTPUT_DEBUG, OUTPUT_EXTRA_DEBUG, - MODE_PLAY, MODE_ANALYZE, HOMEPAGE, VERSION, @@ -66,18 +71,23 @@ from katrain.core.constants import ( PLAYING_NORMAL, PLAYER_HUMAN, SGF_INTERNAL_COMMENTS_MARKER, + MODE_PLAY, + DATA_FOLDER, ) from katrain.gui.popups import ConfigTeacherPopup, ConfigTimerPopup, I18NPopup, SaveSGFPopup from katrain.core.base_katrain import KaTrainBase from katrain.core.engine import KataGoEngine from katrain.core.game import Game, IllegalMoveException, KaTrainSGF from katrain.core.sgf_parser import Move, ParseError -from katrain.gui.kivyutils import * from katrain.gui.popups import ConfigPopup, LoadSGFPopup, NewGamePopup, ConfigAIPopup -from katrain.gui.style import ENGINE_BUSY_COL, ENGINE_DOWN_COL, ENGINE_READY_COL -from katrain.gui.widgets import * -from katrain.gui.badukpan import AnalysisControls, BadukPanControls, BadukPanWidget -from katrain.gui.controlspanel import ControlsPanel +from katrain.gui.theme import Theme +from kivymd.app import MDApp + +# used in kv +from katrain.gui.kivyutils import * +from katrain.gui.widgets import MoveTree, I18NFileBrowser, SelectionSlider, ScoreGraph # noqa F401 +from katrain.gui.badukpan import AnalysisControls, BadukPanControls, BadukPanWidget # noqa F401 +from katrain.gui.controlspanel import ControlsPanel # noqa F401 class KaTrainGui(Screen, KaTrainBase): @@ -165,11 +175,11 @@ class KaTrainGui(Screen, KaTrainBase): # update engine status dot if not self.engine or not self.engine.katago_process or self.engine.katago_process.poll() is not None: - self.board_controls.engine_status_col = ENGINE_DOWN_COL + self.board_controls.engine_status_col = Theme.ENGINE_DOWN_COLOR elif len(self.engine.queries) == 0: - self.board_controls.engine_status_col = ENGINE_READY_COL + self.board_controls.engine_status_col = Theme.ENGINE_READY_COLOR else: - self.board_controls.engine_status_col = ENGINE_BUSY_COL + self.board_controls.engine_status_col = Theme.ENGINE_BUSY_COLOR self.board_controls.queries_remaining = len(self.engine.queries) # redraw board/stones @@ -590,7 +600,8 @@ class KaTrainGui(Screen, KaTrainBase): yappi.start() self.log("starting profiler", OUTPUT_ERROR) elif keycode[1] == "f10" and self.debug_level >= OUTPUT_EXTRA_DEBUG: - import yappi, time + import time + import yappi stats = yappi.get_func_stats() filename = f"callgrind.{int(time.time())}.prof" @@ -607,7 +618,6 @@ class KaTrainApp(MDApp): super().__init__() def build(self): - global DEFAULT_FONT self.icon = ICON # how you're supposed to set an icon self.title = f"KaTrain v{VERSION}" @@ -617,8 +627,22 @@ class KaTrainApp(MDApp): kv_file = find_package_resource("katrain/gui.kv") popup_kv_file = find_package_resource("katrain/popups.kv") - resource_add_path(PATHS["PACKAGE"]) - DEFAULT_FONT = resource_find(DEFAULT_FONT) + resource_add_path(PATHS["PACKAGE"] + "/sounds") + resource_add_path(PATHS["PACKAGE"] + "/img") + resource_add_path(os.path.abspath(os.path.expanduser(DATA_FOLDER))) # prefer resources in .katrain + + theme_file = resource_find("theme.json") + if theme_file: + try: + with open(theme_file) as f: + theme_overrides = json.load(f) + for k, v in theme_overrides.items(): + setattr(Theme, k, v) + print(f"[{theme_file}] Found theme override {k} = {v}") + except Exception as e: # noqa E722 + print(f"Failed to load theme file {theme_file}: {e}") + + Theme.DEFAULT_FONT = resource_find(Theme.DEFAULT_FONT) Builder.load_file(kv_file) Window.bind(on_request_close=self.on_request_close) diff --git a/katrain/core/ai.py b/katrain/core/ai.py index 457d4a1..7a0c8e4 100644 --- a/katrain/core/ai.py +++ b/katrain/core/ai.py @@ -125,11 +125,11 @@ def policy_weighted_move(policy_moves, lower_bound, weaken_fac): def generate_influence_territory_weights(ai_mode, ai_settings, policy_grid, size): thr_line = ai_settings["threshold"] - 1 # zero-based if ai_mode == AI_INFLUENCE: - weight = lambda x, y: (1 / ai_settings["line_weight"]) ** ( + weight = lambda x, y: (1 / ai_settings["line_weight"]) ** ( # noqa E731 max(0, thr_line - min(size[0] - 1 - x, x)) + max(0, thr_line - min(size[1] - 1 - y, y)) - ) + ) # noqa E731 else: - weight = lambda x, y: (1 / ai_settings["line_weight"]) ** ( + weight = lambda x, y: (1 / ai_settings["line_weight"]) ** ( # noqa E731 max(0, min(size[0] - 1 - x, x, size[1] - 1 - y, y) - thr_line) ) weighted_coords = [ diff --git a/katrain/core/base_katrain.py b/katrain/core/base_katrain.py index 819a30c..c5e2e49 100644 --- a/katrain/core/base_katrain.py +++ b/katrain/core/base_katrain.py @@ -6,7 +6,18 @@ from kivy import Config from kivy.storage.jsonstore import JsonStore from katrain.core.ai import ai_rank_estimation -from katrain.core.constants import * +from katrain.core.constants import ( + PLAYER_HUMAN, + PLAYER_AI, + PLAYING_NORMAL, + PLAYING_TEACHING, + OUTPUT_INFO, + OUTPUT_ERROR, + OUTPUT_DEBUG, + AI_DEFAULT, + CONFIG_MIN_VERSION, + DATA_FOLDER, +) from katrain.core.utils import find_package_resource @@ -44,7 +55,7 @@ class Player: class KaTrainBase: - USER_CONFIG_FILE = os.path.expanduser("~/.katrain/config.json") + USER_CONFIG_FILE = os.path.expanduser(os.path.join(DATA_FOLDER, "config.json")) PACKAGE_CONFIG_FILE = "katrain/config.json" """Settings, logging, and players functionality, so other classes like bots who need a katrain instance can be used without a GUI""" @@ -91,7 +102,7 @@ class KaTrainBase: else: # user file exists try: version = JsonStore(user_config_file).get("general")["version"] - except Exception: # broken file etc + except Exception: # noqa E722 broken file etc version = "0.0.0" if version < CONFIG_MIN_VERSION: backup = user_config_file + f".{version}.backup" diff --git a/katrain/core/constants.py b/katrain/core/constants.py index f2924d7..18c7085 100644 --- a/katrain/core/constants.py +++ b/katrain/core/constants.py @@ -3,6 +3,7 @@ VERSION = "1.7.0" HOMEPAGE = "https://github.com/sanderland/katrain" CONFIG_MIN_VERSION = "1.7.0" # keep config files from this version ANALYSIS_FORMAT_VERSION = "1.0" +DATA_FOLDER = "~/.katrain" OUTPUT_ERROR = -1 OUTPUT_KATAGO_STDERR = -0.5 diff --git a/katrain/core/game.py b/katrain/core/game.py index cc47ca2..feae7d8 100644 --- a/katrain/core/game.py +++ b/katrain/core/game.py @@ -215,7 +215,7 @@ class Game: if not copy_from_node.children: break copy_from_node = copy_from_node.ordered_children[0] - except: + except IllegalMoveException: pass # illegal move = stop self._calculate_groups() # recalculate groups self.katrain.controls.set_status( diff --git a/katrain/core/game_node.py b/katrain/core/game_node.py index 44430ac..7572eaf 100644 --- a/katrain/core/game_node.py +++ b/katrain/core/game_node.py @@ -15,7 +15,7 @@ from katrain.core.constants import ( from katrain.core.lang import i18n from katrain.core.sgf_parser import Move, SGFNode from katrain.core.utils import evaluation_class, pack_floats, unpack_floats, var_to_grid -from katrain.gui.style import INFO_PV_COLOR +from katrain.gui.theme import Theme def analysis_dumps(analysis): @@ -287,7 +287,7 @@ class GameNode(SGFNode): def make_pv(self, player, pv, interactive): pvtext = f"{player}{' '.join(pv)}" if interactive: - pvtext = f"[u][ref={pvtext}][color={INFO_PV_COLOR}]{pvtext}[/color][/ref][/u]" + pvtext = f"[u][ref={pvtext}][color={Theme.INFO_PV_COLOR}]{pvtext}[/color][/ref][/u]" return pvtext def comment(self, sgf=False, teach=False, details=False, interactive=True): diff --git a/katrain/core/lang.py b/katrain/core/lang.py index 7cff152..8bff10c 100644 --- a/katrain/core/lang.py +++ b/katrain/core/lang.py @@ -5,7 +5,7 @@ import sys from kivy._event import Observable from katrain.core.utils import find_package_resource -from katrain.gui.style import DEFAULT_FONT +from katrain.gui.theme import Theme class Lang(Observable): @@ -53,7 +53,7 @@ class Lang(Observable): return # get the right locales directory, and instantiate a gettext self.lang = lang - self.font_name = self.FONTS.get(lang) or DEFAULT_FONT + self.font_name = self.FONTS.get(lang) or Theme.DEFAULT_FONT i18n_dir, _ = os.path.split(find_package_resource("katrain/i18n/__init__.py")) locale_dir = os.path.join(i18n_dir, "locales") locales = gettext.translation("katrain", locale_dir, languages=[lang, DEFAULT_LANGUAGE]) diff --git a/katrain/core/sgf_parser.py b/katrain/core/sgf_parser.py index bf540ce..8cdc2d5 100644 --- a/katrain/core/sgf_parser.py +++ b/katrain/core/sgf_parser.py @@ -439,7 +439,7 @@ class SGF: re = "W+" elif "lack win" in lines[10]: re = "B+" - except: + except IndexError: pass if handicap < 0 or handicap > 9: @@ -527,7 +527,7 @@ class SGF: try: grlt = int(re.search(grlt_regex, line).group(1)) zipsu = int(re.search(zipsu_regex, line).group(1)) - except: + except: # noqa E722 return "" return gib_make_result(grlt, zipsu) @@ -561,7 +561,7 @@ class SGF: komi = int(re.search(r"GONGJE:(\d+),", line).group(1)) / 10 if komi: root.set_property("KM", komi) - except: + except: # noqa E722 pass if line.startswith("\\[GAMETAG="): @@ -570,7 +570,7 @@ class SGF: match = re.search(r"C(\d\d\d\d):(\d\d):(\d\d)", line) date = "{}-{}-{}".format(match.group(1), match.group(2), match.group(3)) root.set_property("DT", date) - except: + except: # noqa E722 pass if "RE" not in root.properties: @@ -583,7 +583,7 @@ class SGF: komi = int(re.search(r",G(\d+),", line).group(1)) / 10 if komi: root.set_property("KM", komi) - except: + except: # noqa E722 pass if line[0:3] == "INI": @@ -592,7 +592,7 @@ class SGF: setup = line.split() try: handicap = int(setup[3]) - except IndexError: + except ParseError: continue if handicap < 0 or handicap > 9: diff --git a/katrain/gui.kv b/katrain/gui.kv index 7b2df05..46674ff 100644 --- a/katrain/gui.kv +++ b/katrain/gui.kv @@ -10,30 +10,14 @@ #:import MODE_ANALYZE katrain.core.constants.MODE_ANALYZE #:import STATUS_INFO katrain.core.constants.STATUS_INFO +#:import Theme katrain.gui.theme.Theme +#:import BLACK katrain.gui.theme.BLACK +#:import LIGHTER_GREY katrain.gui.theme.LIGHTER_GREY -#:import WHITE katrain.gui.style.WHITE -#:import BLACK katrain.gui.style.BLACK -#:import LIGHTGREY katrain.gui.style.LIGHTGREY -#:import LIGHTERGREY katrain.gui.style.LIGHTERGREY -#:import GREEN katrain.gui.style.GREEN -#:import YELLOW katrain.gui.style.YELLOW -#:import RED katrain.gui.style.RED -#:import DARKRED katrain.gui.style.DARKRED -#:import ORANGE katrain.gui.style.ORANGE -#:import BACKGROUND_COLOR katrain.gui.style.BACKGROUND_COLOR -#:import BOX_BACKGROUND_COLOR katrain.gui.style.BOX_BACKGROUND_COLOR -#:import LIGHTER_BACKGROUND_COLOR katrain.gui.style.LIGHTER_BACKGROUND_COLOR -#:import TEXT_COLOR katrain.gui.style.TEXT_COLOR -#:import SCORE_COLOR katrain.gui.style.SCORE_COLOR -#:import WINRATE_COLOR katrain.gui.style.WINRATE_COLOR -#:import POINTLOSS_COLOR katrain.gui.style.POINTLOSS_COLOR -#:set BUTTON_INACTIVE_COLOR LIGHTGREY - -#:set CP_SPACING dp(6) -#:set CP_SMALL_SPACING dp(3) -#:set CP_PADDING dp(6) -#:set NOTES_FONT_SIZE sp(18) +#:set CP_SPACING Theme.CP_SPACING +#:set CP_SMALL_SPACING Theme.CP_SMALL_SPACING +#:set CP_PADDING Theme.CP_PADDING #:set EPSILON 1e-9 @@ -68,8 +52,8 @@ : ripple_duration_in_slow: 0.6 - text_color: TEXT_COLOR - background_color: BOX_BACKGROUND_COLOR + text_color: Theme.TEXT_COLOR + background_color: Theme.BOX_BACKGROUND_COLOR label: label text_size: root.size Label: @@ -85,27 +69,27 @@ width: root.label.texture_size[0] : - inactive_background_color: BACKGROUND_COLOR - active_background_color: BOX_BACKGROUND_COLOR + inactive_background_color: Theme.BACKGROUND_COLOR + active_background_color: Theme.BOX_BACKGROUND_COLOR background_color: self.active_background_color if self.state=='down' else self.inactive_background_color outline_color: self.active_outline_color if self.state=='down' else self.inactive_outline_color : - outline_color: WHITE + outline_color: Theme.BUTTON_BORDER_COLOR text_color: self.outline_color : - outline_color: WHITE + outline_color: Theme.BUTTON_BORDER_COLOR text_color: self.outline_color : - inactive_outline_color: BUTTON_INACTIVE_COLOR - active_outline_color: WHITE + inactive_outline_color: Theme.BUTTON_INACTIVE_COLOR + active_outline_color: Theme.BUTTON_BORDER_COLOR text_color: self.outline_color : - inactive_outline_color: BUTTON_INACTIVE_COLOR - active_outline_color: WHITE + inactive_outline_color: Theme.BUTTON_INACTIVE_COLOR + active_outline_color: Theme.BUTTON_BORDER_COLOR text_color: self.outline_color : @@ -115,7 +99,7 @@ background_radius: self.height/3.5 : - background_color: BOX_BACKGROUND_COLOR + background_color: Theme.BOX_BACKGROUND_COLOR : background_normal: '' @@ -131,20 +115,20 @@ : - background_color: LIGHTER_BACKGROUND_COLOR + background_color: Theme.LIGHTER_BACKGROUND_COLOR background_normal: '' lang_change_tracking: i18n._('') # for font - color: WHITE + color: Theme.INPUT_FONT_COLOR canvas.after: Color: - rgba: LIGHTGREY + rgba: LIGHT_GREY Line points: self.x,self.y,self.x+self.width,self.y width: 1 : -font_size: self.height * 0.5 - background_color: [*[c*255/88 for c in BACKGROUND_COLOR[:3]], 1] # compensate for texture + background_color: [*[c*255/88 for c in Theme.BACKGROUND_COLOR[:3]], 1] # compensate for texture option_cls: 'I18NSpinnerOption'