Skip to content

Commit

Permalink
Merge pull request #308 from sanderland/1.7.1
Browse files Browse the repository at this point in the history
1.7.1
  • Loading branch information
sanderland committed Jan 16, 2021
2 parents 801f483 + c306bb0 commit a583944
Show file tree
Hide file tree
Showing 47 changed files with 1,250 additions and 284 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
dist_sgf

# additions
KataGoData
katrain/KataGo/KataGoData
Expand Down
6 changes: 4 additions & 2 deletions CONTRIBUTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,9 @@ Many thanks to these additional authors:
* "kaorahi" for bug fixes and SGF parser improvements.
* "ajkenny84" for the red-green colourblind theme.
* Lukasz Wierzbowski for the ability to paste urls for sgfs and helping fix alt-gr issues.
* Carton He for a fix to handling empty komi values in sgfs.
* "blamarche" for adding the board coordinates toggle
* Carton He for contributions to sgf parsing and handling.
* "blamarche" for adding the board coordinates toggle.
* "pdeblanc" for adding the ancient chinese scoring option.

## Translators

Expand All @@ -62,6 +63,7 @@ Many thanks to the following contributors for translations.
* Russian: Dmitry Ivankov and Alexander Kiselev
* Simplified Chinese: Qing Mu with contributions from "Medwin" and Viktor Lin
* Japanese: "kaorahi"
* Traditional Chinese: "Tony-Liou"

## Additional thanks to

Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
<a href="http://translate.google.com/translate?sl=en&tl=fr&u=https%3A%2F%2Fgithub.com%2Fsanderland%2Fkatrain%2Fblob%2Fmaster%2FREADME.md"><img alt="French" src="https://github.com/sanderland/katrain/blob/master/katrain/img/flags/flag-fr.png" width=50></a>
<a href="http://translate.google.com/translate?sl=en&tl=ru&u=https%3A%2F%2Fgithub.com%2Fsanderland%2Fkatrain%2Fblob%2Fmaster%2FREADME.md"><img alt="Russian" src="https://github.com/sanderland/katrain/blob/master/katrain/img/flags/flag-ru.png" width=50></a>
<br/>
<a href="http://translate.google.com/translate?sl=en&tl=zh-CN&u=https%3A%2F%2Fgithub.com%2Fsanderland%2Fkatrain%2Fblob%2Fmaster%2FREADME.md"><img alt="Chinese" src="https://github.com/sanderland/katrain/blob/master/katrain/img/flags/flag-cn.png" width=50></a>
<a href="http://translate.google.com/translate?sl=en&tl=zh-CN&u=https%3A%2F%2Fgithub.com%2Fsanderland%2Fkatrain%2Fblob%2Fmaster%2FREADME.md"><img alt="Simplified Chinese" src="https://github.com/sanderland/katrain/blob/master/katrain/img/flags/flag-cn.png" width=50></a>
<a href="http://translate.google.com/translate?sl=en&tl=zh-TW&u=https%3A%2F%2Fgithub.com%2Fsanderland%2Fkatrain%2Fblob%2Fmaster%2FREADME.md"><img alt="Traditional Chinese" src="https://github.com/sanderland/katrain/blob/master/katrain/img/flags/flag-tw.png" width=50></a>
<a href="http://translate.google.com/translate?sl=en&tl=ko&u=https%3A%2F%2Fgithub.com%2Fsanderland%2Fkatrain%2Fblob%2Fmaster%2FREADME.md"><img alt="Korean" src="https://github.com/sanderland/katrain/blob/master/katrain/img/flags/flag-ko.png" width=50></a>
<a href="http://translate.google.com/translate?sl=en&tl=ja&u=https%3A%2F%2Fgithub.com%2Fsanderland%2Fkatrain%2Fblob%2Fmaster%2FREADME.md"><img alt="Japanese" src="https://github.com/sanderland/katrain/blob/master/katrain/img/flags/flag-jp.png" width=50></a>

Expand Down Expand Up @@ -208,13 +209,13 @@ In addition to shortcuts mentioned above and those shown in the main menu:
## <a name="support"></a> Support / Contribute

[![GitHub issues](http://img.shields.io/github/issues/sanderland/katrain)](http://github.com/sanderland/katrain/issues)
[![Contributors](http://img.shields.io/static/v1?label=contributors&message=24&color=dcb424)](CONTRIBUTIONS.md)
[![Contributors](http://img.shields.io/static/v1?label=contributors&message=26&color=dcb424)](CONTRIBUTIONS.md)
[![Github sponsors](http://img.shields.io/static/v1?label=sponsor&message=%E2%9D%A4&logo=GitHub&color=dcb424&link=http://github.com/sponsors/sanderland/)](http://github.com/sponsors/sanderland)

* Ideas, feedback, and contributions to code or translations are all very welcome.
* For suggestions and planned improvements, see [open issues](http://github.com/sanderland/katrain/issues) on github to check if the functionality is already planned.
* I am looking for contributors of more translations of both this manual and the program itself. The best way to help with this is to contact me on discord.
* You can contact me on [discord](http://discord.gg/AjTPFpN) (Sander#3278) or [Reddit](http://reddit.com/u/sanderbaduk) to get help, discuss improvements, or simply show your appreciation.
* You can contact me on the [Leela Zero & Friends Discord](http://discord.gg/AjTPFpN) (use the #gui channel) to get help, discuss improvements, or simply show your appreciation.
* You can also donate to the project through [Github Sponsors](http://github.com/sponsors/sanderland).


Expand Down
2 changes: 1 addition & 1 deletion i18n.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
po[lang].append(copied_entry)
errors = True
else:
print(f"MISSING IN DEFAULT AND {lang}", strings_to_langs[msgid])
print(f"MISSING IN DEFAULT AND {lang}", msgid)
errors = True

for msgid, lang_entries in strings_to_langs.items():
Expand Down
Binary file modified katrain/KataGo/katago
Binary file not shown.
Binary file modified katrain/KataGo/katago.exe
Binary file not shown.
65 changes: 50 additions & 15 deletions katrain/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
os.environ["KIVY_AUDIO"] = "sdl2" # some backends hard crash / this seems to be most stable

import kivy

kivy.require("2.0.0")

# next, icon
Expand Down Expand Up @@ -42,6 +43,7 @@
from queue import Queue
import urllib3
import webbrowser
import time

from kivy.base import ExceptionHandler, ExceptionManager
from kivy.app import App
Expand Down Expand Up @@ -112,9 +114,8 @@ def __init__(self, **kwargs):
self.idle_analysis = False
self.message_queue = Queue()

self._keyboard = Window.request_keyboard(None, self, "")
self._keyboard.bind(on_key_down=self._on_keyboard_down)
Clock.schedule_interval(self.animate_pondering, 0.1)
self.last_key_down = None
self.last_focus_event = 0

def log(self, message, level=OUTPUT_INFO):
super().log(message, level)
Expand Down Expand Up @@ -156,7 +157,23 @@ def start(self):
self.board_gui.trainer_config = self.config("trainer")
self.engine = KataGoEngine(self, self.config("engine"))
threading.Thread(target=self._message_loop_thread, daemon=True).start()
self._do_new_game()
sgf_args = [
f
for f in sys.argv[1:]
if os.path.isfile(f) and any(f.lower().endswith(ext) for ext in ["sgf", "ngf", "gib"])
]
if sgf_args:
self.load_sgf_file(sgf_args[0], fast=True, rewind=True)
else:
self._do_new_game()

Clock.schedule_interval(self.animate_pondering, 0.1)
Window.request_keyboard(None, self, "").bind(on_key_down=self._on_keyboard_down, on_key_up=self._on_keyboard_up)

def set_focus_event(*args):
self.last_focus_event = time.time()

MDApp.get_running_app().root_window.bind(focus=set_focus_event)

def update_gui(self, cn, redraw_board=False):
# Handle prisoners and next player display
Expand All @@ -178,11 +195,11 @@ def update_gui(self, cn, redraw_board=False):
# 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 = Theme.ENGINE_DOWN_COLOR
elif len(self.engine.queries) == 0:
elif self.engine.is_idle():
self.board_controls.engine_status_col = Theme.ENGINE_READY_COLOR
else:
self.board_controls.engine_status_col = Theme.ENGINE_BUSY_COLOR
self.board_controls.queries_remaining = len(self.engine.queries)
self.board_controls.queries_remaining = self.engine.queries_remaining()

# redraw board/stones
if redraw_board:
Expand Down Expand Up @@ -223,7 +240,7 @@ def _do_update_state(
): # cn mismatch stops this if undo fired. avoid message loop here or fires repeatedly.
self._do_ai_move(cn)
Clock.schedule_once(self.board_gui.play_stone_sound, 0.25)
if len(self.engine.queries) == 0 and self.idle_analysis:
if self.engine.is_idle() and self.idle_analysis:
self("analyze-extra", "extra", continuous=True)
Clock.schedule_once(lambda _dt: self.update_gui(cn, redraw_board=redraw_board), -1) # trigger?

Expand Down Expand Up @@ -275,7 +292,13 @@ def _do_new_game(self, move_tree=None, analyze_fast=False, sgf_filename=None):
self.play_mode.switch_ui_mode() # for new game, go to play, for loaded, analyze
self.board_gui.animating_pv = None
self.engine.on_new_game() # clear queries
self.game = Game(self, self.engine, move_tree=move_tree, analyze_fast=analyze_fast, sgf_filename=sgf_filename)
self.game = Game(
self,
self.engine,
move_tree=move_tree,
analyze_fast=analyze_fast or not move_tree,
sgf_filename=sgf_filename,
)
if move_tree:
for bw, player_info in self.players_info.items():
player_info.player_type = PLAYER_HUMAN
Expand Down Expand Up @@ -532,6 +555,7 @@ def popup_open(self) -> Popup:
return first_child if isinstance(first_child, Popup) else None

def _on_keyboard_down(self, _keyboard, keycode, _text, modifiers):
self.last_key_down = keycode
ctrl_pressed = "ctrl" in modifiers
if self.controls.note.focus:
return # when making notes, don't allow keyboard shortcuts
Expand All @@ -549,11 +573,7 @@ def _on_keyboard_down(self, _keyboard, keycode, _text, modifiers):
return
shift_pressed = "shift" in modifiers
shortcuts = self.shortcuts
if keycode[1] == "tab":
self.play_mode.switch_ui_mode()
elif keycode[1] == "alt":
self.nav_drawer.set_state("toggle")
elif keycode[1] == "spacebar":
if keycode[1] == "spacebar":
self.toggle_continuous_analysis()
elif keycode[1] == "k":
self.board_gui.toggle_coordinates()
Expand Down Expand Up @@ -610,7 +630,23 @@ def _on_keyboard_down(self, _keyboard, keycode, _text, modifiers):
filename = f"callgrind.{int(time.time())}.prof"
stats.save(filename, type="callgrind")
self.log(f"wrote profiling results to {filename}", OUTPUT_ERROR)
return True

def _on_keyboard_up(self, _keyboard, keycode):
if keycode[1] in ["alt", "tab"]:
Clock.schedule_once(lambda *_args: self._single_key_action(keycode), 0.05)

def _single_key_action(self, keycode):
if (
self.controls.note.focus
or self.popup_open
or keycode != self.last_key_down
or time.time() - self.last_focus_event < 0.2 # this is here to prevent alt-tab from firing alt or tab
):
return
if keycode[1] == "alt":
self.nav_drawer.set_state("toggle")
elif keycode[1] == "tab":
self.play_mode.switch_ui_mode()


class KaTrainApp(MDApp):
Expand Down Expand Up @@ -651,7 +687,6 @@ def build(self):

Window.bind(on_request_close=self.on_request_close)
Window.bind(on_dropfile=lambda win, file: self.gui.load_sgf_file(file.decode("utf8")))

self.gui = KaTrainGui()
Builder.load_file(popup_kv_file)
return self.gui
Expand Down
4 changes: 2 additions & 2 deletions katrain/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"config": "katrain/KataGo/analysis_config.cfg",
"threads": 12,
"max_visits": 500,
"fast_visits": 50,
"fast_visits": 25,
"max_time": 8.0,
"wide_root_noise": 0.0,
"_enable_ownership": true
Expand All @@ -17,7 +17,7 @@
"anim_pv_time": 0.5,
"debug_level": 0,
"lang": "en",
"version": "1.7.0"
"version": "1.7.1"
},
"timer": {
"byo_length": 30,
Expand Down
2 changes: 1 addition & 1 deletion katrain/core/base_katrain.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ def config(self, setting, default=None):
cat, key = setting.split("/")
return self._config.get(cat, {}).get(key, default)
else:
return self._config[setting]
return self._config.get(setting, default)
except KeyError:
self.log(f"Missing configuration option {setting}", OUTPUT_ERROR)

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.0"
VERSION = "1.7.1"
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
65 changes: 43 additions & 22 deletions katrain/core/engine.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import copy
import json
import os
import queue
import shlex
import subprocess
import threading
Expand All @@ -14,7 +15,7 @@
from katrain.core.game_node import GameNode
from katrain.core.lang import i18n
from katrain.core.sgf_parser import Move
from katrain.core.utils import find_package_resource
from katrain.core.utils import find_package_resource, json_truncate_arrays


class EngineDiedException(Exception):
Expand All @@ -25,7 +26,13 @@ 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_ABBR = [("jp", "japanese"), ("cn", "chinese"), ("ko", "korean"), ("aga", "aga")]
RULESETS_ABBR = [
("jp", "japanese"),
("cn", "chinese"),
("ko", "korean"),
("aga", "aga"),
("stone_scoring", "stone_scoring"),
]
RULESETS = {fromkey: name for abbr, name in RULESETS_ABBR for fromkey in [abbr, name]}

@staticmethod
Expand All @@ -40,10 +47,11 @@ def __init__(self, katrain, config):
self.katago_process = None
self.base_priority = 0
self.override_settings = {"reportAnalysisWinratesAs": "BLACK"} # force these settings
self._lock = threading.Lock()
self.analysis_thread = None
self.stderr_thread = None
self.write_stdin_thread = None
self.shell = False
self.write_queue = queue.Queue()

exe = config.get("katago", "").strip()
if config.get("altcommand", ""):
Expand Down Expand Up @@ -84,6 +92,7 @@ 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
Expand All @@ -100,13 +109,16 @@ def start(self):
)
except (FileNotFoundError, PermissionError, OSError) as e:
self.katrain.log(
i18n._("Starting Kata failed").format(command=self.command, error=e), OUTPUT_ERROR,
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
Expand All @@ -129,7 +141,8 @@ def check_alive(self, os_error="", exception_if_dead=False):
else:
os_error += f"status {code}"
died_msg = i18n._("Engine died unexpectedly").format(error=os_error)
self.katrain.log(died_msg, OUTPUT_ERROR)
if code != 1: # deliberate exit, already showed message?
self.katrain.log(died_msg, OUTPUT_ERROR)
self.katago_process = None
else:
died_msg = i18n._("Engine died unexpectedly").format(error=os_error)
Expand All @@ -147,13 +160,15 @@ def shutdown(self, finish=False):
if process:
self.katago_process = None
process.terminate()
if self.stderr_thread:
self.stderr_thread.join()
if self.analysis_thread:
self.analysis_thread.join()
for t in [self.stderr_thread, self.analysis_thread, self.write_stdin_thread]:
if t:
t.join()

def is_idle(self):
return not self.queries
return not self.queries and self.write_queue.empty()

def queries_remaining(self):
return len(self.queries) + int(not self.write_queue.empty())

def _read_stderr_thread(self):
while self.katago_process is not None:
Expand Down Expand Up @@ -215,7 +230,7 @@ def _analysis_read_thread(self):
f"[{time_taken:.1f}][{query_id}][{'....' if partial_result else 'done'}] KataGo analysis received: {len(analysis.get('moveInfos',[]))} candidate moves, {analysis['rootInfo']['visits'] if results_exist else 'n/a'} visits",
OUTPUT_DEBUG,
)
self.katrain.log(line, OUTPUT_EXTRA_DEBUG)
self.katrain.log(json_truncate_arrays(analysis), OUTPUT_EXTRA_DEBUG)
try:
if callback and results_exist:
callback(analysis, partial_result)
Expand All @@ -227,19 +242,25 @@ def _analysis_read_thread(self):
self.katrain.log(f"Unexpected exception {e} while processing KataGo output {line}", OUTPUT_ERROR)
traceback.print_exc()

def send_query(self, query, callback, error_callback, next_move=None):
with self._lock:
self.query_counter += 1
def _write_stdin_thread(self): # flush only in a thread since it returns only when the other program reads
while self.katago_process is not None:
try:
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)
if self.katago_process:
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=True)
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))

def terminate_query(self, query_id):
if query_id is not None:
Expand Down Expand Up @@ -319,7 +340,7 @@ def request_analysis(
"includeMovesOwnership": ownership and not next_move,
"includePolicy": not next_move,
"initialStones": [[m.player, m.gtp()] for m in initial_stones],
"initialPlayer": analysis_node.root.next_player,
"initialPlayer": analysis_node.initial_player,
"moves": [[m.player, m.gtp()] for m in moves],
"overrideSettings": {**settings, **(extra_settings or {})},
}
Expand Down
Loading

0 comments on commit a583944

Please sign in to comment.