Skip to content

Commit

Permalink
Merge pull request sanderland#325 from sanderland/1.7.2
Browse files Browse the repository at this point in the history
1.7.2
  • Loading branch information
sanderland authored Jan 23, 2021
2 parents 0822ee5 + 78f1cf2 commit 0bf25c1
Show file tree
Hide file tree
Showing 12 changed files with 128 additions and 74 deletions.
1 change: 1 addition & 0 deletions CONTRIBUTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ Many thanks to these additional authors:
* Carton He for contributions to sgf parsing and handling.
* "blamarche" for adding the board coordinates toggle.
* "pdeblanc" for adding the ancient chinese scoring option.
* "LiamHz" for adding the undo to main branch keyboard shortcut.

## Translators

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ In addition to shortcuts mentioned above and those shown in the main menu:
* **[Ctrl-delete]** Delete current node.
* **[c]** Collapse/Uncollapse the branch from the current node to the previous branching point.
* **[b]** Go back to the previous branching point.
* **[Shift-b]** Go back the the main branch.
* **[n]** As in clicking the forward red arrow, go to one move before the next mistake (orange or worse) by a human player.
* **[Shift-n]** As in clicking the backward red arrow, go to one move before the previous mistake.
* **[scroll up]**: Undo move. Only works when hovering the cursor over the board.
Expand Down
11 changes: 9 additions & 2 deletions THEMES.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,18 @@ Version 1.7 brings basic support for themes.
```
* All resources (including icons which can not be renamed for now) will be looked up in `<home dir>/.katrain` first, so files with identical names there can be used to override sounds and images.

## Available themes
## Installation

* See [here](https://github.com/sanderland/katrain/blob/master/themes/) for available themes.
* To install a theme, simply unzip the theme.zip to your .katrain folder.
* On windows you can find it in C:\Users\you\.katrain and on linux in ~/.katrain.
* When in doubt, the general settings dialog will also show the location.
* To uninstall a theme, remove theme.json and all relevant images from that folder.

## Available themes

### Alternate board/stones theme by "koast"

[Download](https://github.com/sanderland/katrain/blob/master/themes/koast-theme.zip)

![Preview](https://raw.githubusercontent.com/sanderland/katrain/master/themes/koast.png)

2 changes: 2 additions & 0 deletions katrain/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,8 @@ def _on_keyboard_down(self, _keyboard, keycode, _text, modifiers):
self.controls.set_status(i18n._("Copied SGF to clipboard."), STATUS_INFO)
elif keycode[1] == "v" and ctrl_pressed:
self.load_sgf_from_clipboard()
elif keycode[1] == "b" and shift_pressed:
self("undo", "main-branch")
elif keycode[1] in shortcuts.keys() and not ctrl_pressed:
shortcut = shortcuts[keycode[1]]
if isinstance(shortcut, Widget):
Expand Down
2 changes: 1 addition & 1 deletion katrain/core/constants.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
PROGRAM_NAME = "KaTrain"
VERSION = "1.7.1"
VERSION = "1.7.2"
HOMEPAGE = "https://github.com/sanderland/katrain"
CONFIG_MIN_VERSION = "1.7.0" # keep config files from this version
ANALYSIS_FORMAT_VERSION = "1.0"
Expand Down
87 changes: 46 additions & 41 deletions katrain/core/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def __init__(self, katrain, config):
self.write_stdin_thread = None
self.shell = False
self.write_queue = queue.Queue()

self.thread_lock = threading.Lock()
exe = config.get("katago", "").strip()
if config.get("altcommand", ""):
self.command = config["altcommand"]
Expand Down Expand Up @@ -92,39 +92,43 @@ def __init__(self, katrain, config):
self.start()

def start(self):
self.write_queue = queue.Queue()
try:
self.katrain.log(f"Starting KataGo with {self.command}", OUTPUT_DEBUG)
startupinfo = None
if hasattr(subprocess, "STARTUPINFO"):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # stop command box popups on win/pyinstaller
self.katago_process = subprocess.Popen(
self.command,
startupinfo=startupinfo,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=self.shell,
)
except (FileNotFoundError, PermissionError, OSError) as e:
self.katrain.log(
i18n._("Starting Kata failed").format(command=self.command, error=e),
OUTPUT_ERROR,
)
return # don't start
self.analysis_thread = threading.Thread(target=self._analysis_read_thread, daemon=True)
self.stderr_thread = threading.Thread(target=self._read_stderr_thread, daemon=True)
self.write_stdin_thread = threading.Thread(target=self._write_stdin_thread, daemon=True)
self.analysis_thread.start()
self.stderr_thread.start()
self.write_stdin_thread.start()
with self.thread_lock:
self.write_queue = queue.Queue()
try:
self.katrain.log(f"Starting KataGo with {self.command}", OUTPUT_DEBUG)
startupinfo = None
if hasattr(subprocess, "STARTUPINFO"):
startupinfo = subprocess.STARTUPINFO()
startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW # stop command box popups on win/pyinstaller
self.katago_process = subprocess.Popen(
self.command,
startupinfo=startupinfo,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
shell=self.shell,
)
except (FileNotFoundError, PermissionError, OSError) as e:
self.katrain.log(
i18n._("Starting Kata failed").format(command=self.command, error=e),
OUTPUT_ERROR,
)
return # don't start
self.analysis_thread = threading.Thread(target=self._analysis_read_thread, daemon=True)
self.stderr_thread = threading.Thread(target=self._read_stderr_thread, daemon=True)
self.write_stdin_thread = threading.Thread(target=self._write_stdin_thread, daemon=True)
self.analysis_thread.start()
self.stderr_thread.start()
self.write_stdin_thread.start()

def on_new_game(self):
self.base_priority += 1
for query_id in list(self.queries.keys()):
self.terminate_query(query_id)
self.queries = {}
if not self.is_idle():
with self.thread_lock:
for query_id in list(self.queries.keys()):
self.terminate_query(query_id)
self.queries = {}
self.write_queue = queue.Queue()

def restart(self):
self.queries = {}
Expand Down Expand Up @@ -248,16 +252,17 @@ def _write_stdin_thread(self): # flush only in a thread since it returns only w
query, callback, error_callback, next_move = self.write_queue.get(block=True, timeout=0.1)
except queue.Empty:
continue
if "id" not in query:
self.query_counter += 1
query["id"] = f"QUERY:{str(self.query_counter)}"
self.queries[query["id"]] = (callback, error_callback, time.time(), next_move)
self.katrain.log(f"Sending query {query['id']}: {json.dumps(query)}", OUTPUT_DEBUG)
try:
self.katago_process.stdin.write((json.dumps(query) + "\n").encode())
self.katago_process.stdin.flush()
except OSError as e:
self.check_alive(os_error=str(e), exception_if_dead=False)
with self.thread_lock:
if "id" not in query:
self.query_counter += 1
query["id"] = f"QUERY:{str(self.query_counter)}"
self.queries[query["id"]] = (callback, error_callback, time.time(), next_move)
self.katrain.log(f"Sending query {query['id']}: {json.dumps(query)}", OUTPUT_DEBUG)
try:
self.katago_process.stdin.write((json.dumps(query) + "\n").encode())
self.katago_process.stdin.flush()
except OSError as e:
self.check_alive(os_error=str(e), exception_if_dead=False)

def send_query(self, query, callback, error_callback, next_move=None):
self.write_queue.put((query, callback, error_callback, next_move))
Expand Down
15 changes: 14 additions & 1 deletion katrain/core/game.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,9 +279,14 @@ def undo(self, n_times=1, stop_on_mistake=None):
self._calculate_groups()
return
break_on_branch = False
break_on_main_branch = False
last_branching_node = cn
if n_times == "branch":
n_times = 9999
break_on_branch = True
elif n_times == "main-branch":
n_times = 9999
break_on_main_branch = True
for move in range(n_times):
if (
stop_on_mistake is not None
Expand All @@ -291,13 +296,21 @@ def undo(self, n_times=1, stop_on_mistake=None):
):
self.set_current_node(cn.parent)
return
previous_cn = cn
if cn.shortcut_from:
cn = cn.shortcut_from
elif not cn.is_root:
cn = cn.parent
else:
break # root
if break_on_branch and len(cn.children) > 1:
break
self.set_current_node(cn)
elif break_on_main_branch and cn.ordered_children[0] != previous_cn: # implies > 1 child
last_branching_node = cn
if break_on_main_branch:
cn = last_branching_node
if cn is not self.current_node:
self.set_current_node(cn)

def redo(self, n_times=1, stop_on_mistake=None):
if self.insert_mode:
Expand Down
2 changes: 1 addition & 1 deletion katrain/core/sgf_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ def play(self, move) -> "SGFNode":
def initial_player(self): # player for first node
root = self.root
if "PL" in root.properties: # explicit
return "B" if self.get_property("PL").upper().strip() == "B" else "W"
return "B" if self.root.get_property("PL").upper().strip() == "B" else "W"
elif root.children: # child exist, use it if not placement
for child in root.children:
for color in "BW":
Expand Down
73 changes: 49 additions & 24 deletions katrain/gui/popups.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import os
import re
import stat
import threading
import time
from typing import Any, Dict, List, Tuple, Union
from zipfile import ZipFile

Expand Down Expand Up @@ -426,7 +428,10 @@ def update_config(self, save_to_file=True):


class ConfigPopup(QuickConfigGui):
MODEL_ENDPOINTS = {"Latest distributed model": "https://katagotraining.org/api/networks/newest_training/"}
MODEL_ENDPOINTS = {
"Latest distributed model": "https://katagotraining.org/api/networks/newest_training/",
"Strongest distributed model": "https://katagotraining.org/api/networks/get_strongest/",
}
MODELS = {
"20 block model": "https://github.com/lightvector/KataGo/releases/download/v1.4.5/g170e-b20c256x2-s5303129600-d1228401921.bin.gz",
"30 block model": "https://github.com/lightvector/KataGo/releases/download/v1.4.5/g170-b30c320x2-s4824661760-d1229536699.bin.gz",
Expand Down Expand Up @@ -461,6 +466,7 @@ def __init__(self, katrain):
self.paths = [self.katrain.config("engine/model"), "katrain/models", DATA_FOLDER]
self.katago_paths = [self.katrain.config("engine/katago"), DATA_FOLDER]
Clock.schedule_once(self.check_katas)
self.last_clicked_download_models = 0
MDApp.get_running_app().bind(language=self.check_models)
MDApp.get_running_app().bind(language=self.check_katas)

Expand Down Expand Up @@ -506,7 +512,11 @@ def find_description(path):
self.paths.append(path) # persistent on paths with models found
model_files += files

model_files = sorted([(find_description(path), path) for path in model_files])
# no description to bottom
model_files = sorted(
[(find_description(path), path) for path in model_files],
key=lambda descpath: "Z" * 10 + path if descpath[0] == descpath[1] else descpath[0],
)
models_available_msg = i18n._("models available").format(num=len(model_files))
self.model_files.values = [models_available_msg] + [desc for desc, path in model_files]
self.model_files.value_keys = [""] + [path for desc, path in model_files]
Expand Down Expand Up @@ -557,18 +567,23 @@ def find_description(path):
self.katago_files.text = katas_available_msg

def download_models(self, *_largs):
if time.time() - self.last_clicked_download_models > 5:
self.last_clicked_download_models = time.time()
threading.Thread(target=self._download_models, daemon=True).start()

def _download_models(self):
def download_complete(req, tmp_path, path, model):
try:
os.rename(tmp_path, path)
self.katrain.log(f"Download of {model} model complete -> {path}", OUTPUT_INFO)
self.katrain.log(f"Download of {model} complete -> {path}", OUTPUT_INFO)
except Exception as e:
self.katrain.log(f"Download of {model} model complete, but could not move file: {e}", OUTPUT_ERROR)
self.katrain.log(f"Download of {model} complete, but could not move file: {e}", OUTPUT_ERROR)
self.check_models()

for c in self.download_progress_box.children:
if isinstance(c, ProgressLoader) and c.request:
c.request.cancel()
self.download_progress_box.clear_widgets()
Clock.schedule_once(lambda _dt: self.download_progress_box.clear_widgets(), -1) # main thread
downloading = False

dist_models = {k: v for k, v in self.katrain.config("dist_models", {}).items() if k in self.MODEL_ENDPOINTS}
Expand All @@ -577,6 +592,10 @@ def download_complete(req, tmp_path, path, model):
try:
http = urllib3.PoolManager()
response = http.request("GET", url)
if response.status != 200:
raise Exception(
f"Request to {url} returned code {response.status} != 200: {response.data.decode()}"
)
dist_models[name] = json.loads(response.data.decode("utf-8"))["model_file"]
except Exception as e:
self.katrain.log(f"Failed to retrieve info for model: {e}", OUTPUT_INFO)
Expand All @@ -590,27 +609,33 @@ def download_complete(req, tmp_path, path, model):
savepath = os.path.expanduser(os.path.join(DATA_FOLDER, filename))
savepath_tmp = savepath + ".part"
self.katrain.log(f"Downloading {name} model from {url} to {savepath_tmp}", OUTPUT_INFO)
progress = ProgressLoader(
download_url=url,
path_to_file=savepath_tmp,
downloading_text=f"Downloading {name} model: " + "{}",
label_downloading_text=f"Starting download for {name} model",
download_complete=lambda req, tmp=savepath_tmp, path=savepath, model=name: download_complete(
req, tmp, path, model
Clock.schedule_once(
lambda _dt, _savepath=savepath, _savepath_tmp=savepath_tmp, _url=url, _name=name: ProgressLoader(
self.download_progress_box,
download_url=_url,
path_to_file=_savepath_tmp,
downloading_text=f"Downloading {_name}: " + "{}",
label_downloading_text=f"Starting download for {_name}",
download_complete=lambda req, tmp=_savepath_tmp, path=_savepath, model=_name: download_complete(
req, tmp, path, model
),
download_redirected=lambda req, mname=_name: self.katrain.log(
f"Download {mname} redirected {req.resp_headers}", OUTPUT_DEBUG
),
download_error=lambda req, error, mname=_name: self.katrain.log(
f"Download of {mname} failed or cancelled ({error})", OUTPUT_ERROR
),
),
download_redirected=lambda req, mname=name: self.katrain.log(
f"Download {mname} redirected {req.resp_headers}", OUTPUT_DEBUG
),
download_error=lambda req, error, mname=name: self.katrain.log(
f"Download of {mname} failed or cancelled ({error})", OUTPUT_ERROR
),
)
progress.start(self.download_progress_box)
0,
) # main thread
downloading = True
if not downloading:
self.download_progress_box.add_widget(
Label(text=i18n._("All models downloaded"), font_name=i18n.font_name, text_size=(None, dp(50)))
)
Clock.schedule_once(
lambda _dt: self.download_progress_box.add_widget(
Label(text=i18n._("All models downloaded"), font_name=i18n.font_name, text_size=(None, dp(50)))
),
0,
) # main thread

def download_katas(self, *_largs):
def unzipped_name(zipfile):
Expand Down Expand Up @@ -641,7 +666,7 @@ def download_complete(req, tmp_path, path, binary):
os.remove(tmp_path)
else:
os.rename(tmp_path, path)
self.katrain.log(f"Download of katago binary {binary} model complete -> {path}", OUTPUT_INFO)
self.katrain.log(f"Download of katago binary {binary} complete -> {path}", OUTPUT_INFO)
except Exception as e:
self.katrain.log(
f"Download of katago binary {binary} complete, but could not move file: {e}", OUTPUT_ERROR
Expand Down
8 changes: 4 additions & 4 deletions katrain/gui/widgets/progress_loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,13 +59,13 @@ class ProgressLoader(BoxLayout):
request = ObjectProperty()
"""UrlRequest object."""

def __init__(self, **kwargs):
def __init__(self, root_instance, **kwargs):
super().__init__(**kwargs)
self.root_instance = None
self.root_instance = root_instance
self.request = None
Clock.schedule_once(self.start, 0)

def start(self, root_instance):
self.root_instance = root_instance
def start(self, _dt):
self.root_instance.add_widget(self)
self.request_download_file(self.download_url, self.path_to_file)
Clock.schedule_once(self.animation_show, 1)
Expand Down
Binary file modified themes/koast-theme.zip
Binary file not shown.
Binary file modified themes/koast.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 0bf25c1

Please sign in to comment.