1.9 dev (#411)
* refactor priorities * performance report doodling * shutup pycharm * more doodling * doodle tricol * tweak report, fix bug in insert * reorder colors * another metric * sorting engines, trying to catch exc * restructure engine err * restructure engine err * test stuff * test report * test report * test and readme * test and readme * layout * engine recov * pixelfix and exes * readme * keyboard shortcut override * oops * mistakes only deeper * black and readme * readme * fix #421, scale repor * Add shortcut variables (#420) * Add shortcut variables * Address comments and missed shortcuts * fix shortcuts * rename, design report, fix report * make translators work more * relative mid/opening/endgame bounds * win size dbg * more errros * entry * jeff mode * fix * readme * edit THEMES.md * edit katrain/core/ai.py, edit katrain/gui/kivyutils.py * jp (#423) * edit THEMES.md * edit katrain/__main__.py, edit katrain/gui/kivyutils.py and 1 other changes * Update katrain.po (#425) 1.9 first French .po * edit katrain/i18n/locales/fr/LC_MESSAGES/katrain.mo * edit katrain/core/ai.py, edit katrain/gui/popups.py * bluedabadee * fix sgf and black * edit katrain/i18n/locales/fr/LC_MESSAGES/katrain.po * ko * Update zh-TW translation (#426) * cn * cn * Some corrections to Chinese translations (#427) * some corrections in cn and tw * better trans * edit CONTRIBUTIONS.md, edit katrain/i18n/locales/cn/LC_MESSAGES/katrain.mo and 1 other changes * de * tw * player what only * add green background to better value (#428) * edit katrain/gui/popups.py, edit katrain/gui/theme.py * Russian for 1.9 (#432) * edit katrain/i18n/locales/ru/LC_MESSAGES/katrain.po, edit katrain/gui.kv and 1 other changes * sound fix * sound fix * fmt * fix sound * edit THEMES.md, edit CONTRIBUTIONS.md and 1 other changes * sound fix * Do not use ffpyplayer on Windows (#434) * edit katrain/gui/sound.py * no pr * jeff theme * stopsound * sound and theme fix * font fix and more errs * French po corrections (#441) Former translations were misleading. It should be accurate now. * add mouse scroll to browser through node variations. (#442) * add mouse scroll to browser through node variations. * add mouse scroll to browser through node variations. * add mouse scroll to browser through node variations. * edit katrain/popups.kv, edit katrain/__main__.py and 2 other changes * windows binary, black * linux binary * edit INSTALL.md * edit .github/workflows/osxbuild.yaml * edit .github/workflows/osxbuild.yaml * edit .github/workflows/osxbuild.yaml Co-authored-by: Sander Land <sander.land@cognite.com> Co-authored-by: roux g. buciu <11182210+electricRGB@users.noreply.github.com> Co-authored-by: kaorahi <38910552+kaorahi@users.noreply.github.com> Co-authored-by: Dontbtme <66470440+Dontbtme@users.noreply.github.com> Co-authored-by: Liou, Guan-Cheng <Tony-Liou@users.noreply.github.com> Co-authored-by: Ching-yu Lin <60384727+chingyulin@users.noreply.github.com> Co-authored-by: xiaoyifang <105986+xiaoyifang@users.noreply.github.com> Co-authored-by: Alexander Kiselev <satoshi.yoda.register.264@gmail.com> Co-authored-by: Sander Land <Sander@bioeng219-lap.isd.kcl.ac.uk> Co-authored-by: Carton He <cartonhe@gmail.com> Co-authored-by: Sander Land <sander.land@aroni.fritz.box> Co-authored-by: Sander Land <sander.land@futurefacts.nl>
This commit is contained in:
50 files changed
+1657
-284
No files matched your search
@@ -52,7 +52,7 @@ jobs:
|
|||||||
pushd KaTrain.app/Contents/Resources/
|
pushd KaTrain.app/Contents/Resources/
|
||||||
ln -s ./venv/bin/KaTrain yourapp
|
ln -s ./venv/bin/KaTrain yourapp
|
||||||
popd
|
popd
|
||||||
./fix-bundle-metadata.sh KaTrain.app -n KaTrain -v "1.8.0" -a "Sander Land" -o "org.katrain.KaTrain" -i "../../katrain/katrain/img/icon.ico"
|
./fix-bundle-metadata.sh KaTrain.app -n KaTrain -v "1.9.0" -a "Sander Land" -o "org.katrain.KaTrain" -i "../../katrain/katrain/img/icon.ico"
|
||||||
./cleanup-app.sh KaTrain.app -g 1
|
./cleanup-app.sh KaTrain.app -g 1
|
||||||
./relocate.sh KaTrain.app
|
./relocate.sh KaTrain.app
|
||||||
- name: Create dmg
|
- name: Create dmg
|
||||||
@@ -67,12 +67,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
name: KaTrainOSX
|
name: KaTrainOSX
|
||||||
path: osx_app
|
path: osx_app
|
||||||
- name: Upload to GitHub Release
|
|
||||||
uses: softprops/action-gh-release@78c309ef59fdb9557cd6574f2e0be552936ed728
|
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
with:
|
|
||||||
files: KaTrainOSX/*
|
|
||||||
draft: true
|
|
||||||
fail_on_unmatched_files: true
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
dist_sgf
|
dist_sgf
|
||||||
|
sgftest
|
||||||
|
|
||||||
# additions
|
# additions
|
||||||
KataGoData
|
KataGoData
|
||||||
|
|||||||
+3
-2
@@ -52,7 +52,8 @@ Many thanks to these additional authors:
|
|||||||
* "blamarche" for adding the board coordinates toggle.
|
* "blamarche" for adding the board coordinates toggle.
|
||||||
* "pdeblanc" for adding the ancient chinese scoring option, fixing a bug in query termination
|
* "pdeblanc" for adding the ancient chinese scoring option, fixing a bug in query termination
|
||||||
* "LiamHz" for adding the 'back to main branch' keyboard shortcut.
|
* "LiamHz" for adding the 'back to main branch' keyboard shortcut.
|
||||||
* "xiaoyifang" for adding the reset analysis option, feature to save options on the loading screen.
|
* "xiaoyifang" for adding the reset analysis option, feature to save options on the loading screen, and scrolling through variations.
|
||||||
|
* "electricRGB" for help with adding configurable keyboard shortcuts.
|
||||||
|
|
||||||
## Translators
|
## Translators
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ Many thanks to the following contributors for translations.
|
|||||||
* Russian: Dmitry Ivankov and Alexander Kiselev
|
* Russian: Dmitry Ivankov and Alexander Kiselev
|
||||||
* Simplified Chinese: Qing Mu with contributions from "Medwin" and Viktor Lin
|
* Simplified Chinese: Qing Mu with contributions from "Medwin" and Viktor Lin
|
||||||
* Japanese: "kaorahi"
|
* Japanese: "kaorahi"
|
||||||
* Traditional Chinese: "Tony-Liou"
|
* Traditional Chinese: "Tony-Liou" with contributions from Ching-yu Lin
|
||||||
|
|
||||||
## Additional thanks to
|
## Additional thanks to
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# KataGo troubleshooting
|
||||||
|
|
||||||
|
This page lists common ways in which the provided KataGo fails to work out of the box, and how to resolve these issues.
|
||||||
|
If you find your problem is not in here, you can ask on the [Leela Zero & Friends Discord](http://discord.gg/AjTPFpN) (use the #gui channel),
|
||||||
|
providing detailed information about your error.
|
||||||
|
|
||||||
|
|
||||||
|
* [General](#General)
|
||||||
|
* [GPU vs CPU](#CPU)
|
||||||
|
* [Windows specific help](#Windows)
|
||||||
|
* [MacOS specific help](#Mac)
|
||||||
|
* [Linux specific help](#Linux)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## <a name="General"></a> General
|
||||||
|
|
||||||
|
### <a name="CPU"></a> GPU vs CPU
|
||||||
|
|
||||||
|
The standard executables assume you have a compatible graphics card (GPU).
|
||||||
|
If you don't, KataGo will fail to start in ways that are difficult for KaTrain to pick up.
|
||||||
|
|
||||||
|
On Windows and Linux, you should be able to resolve this by:
|
||||||
|
|
||||||
|
* Going to general and engine settings (F8)
|
||||||
|
* Click 'download katago versions' and wait for downloads to finish.
|
||||||
|
* Select a CPU based KataGo version (named 'Eigen' after the library it uses).
|
||||||
|
|
||||||
|
Keep in mind that a CPU based engine can be significantly slower, and you may want to set your maximum number of
|
||||||
|
visits to a lower number to compensate for this.
|
||||||
|
|
||||||
|
### <a name="Models"></a> KataGo model versions
|
||||||
|
|
||||||
|
KataGo models have changed over time, and selecting an older executable with a newer model can lead to errors.
|
||||||
|
Of the provided binaries, this is typically the case for the 1.6.1 'bigger boards' binary, which should
|
||||||
|
only be used with the standard 15/20/30/40 block models, and not the newer distributed training models.
|
||||||
|
|
||||||
|
|
||||||
|
## <a name="Mac"></a><img src="https://upload.wikimedia.org/wikipedia/commons/8/8a/Apple_Logo.svg" alt="macOs" height="35"/> For macOS users
|
||||||
|
|
||||||
|
### Running from source
|
||||||
|
|
||||||
|
Make sure you `brew install katago` or set the engine path to your own KataGo binary, as there is no executable included.
|
||||||
|
|
||||||
|
### New Macs with M1 architecture
|
||||||
|
|
||||||
|
Make sure you `brew install katago` as the provided executable does not work on rosetta.
|
||||||
|
|
||||||
|
### Getting more information about errors
|
||||||
|
|
||||||
|
On macOS, the .app distributable will not show a console, so you will need install using `pip` to see the console window.
|
||||||
|
|
||||||
|
## <a name="Windows"></a><img src="https://upload.wikimedia.org/wikipedia/commons/5/5f/Windows_logo_-_2012.svg" alt="Windows" height="35"/> For Windows users
|
||||||
|
|
||||||
|
### Getting more information about errors
|
||||||
|
|
||||||
|
Run DebugKaTrain.exe, which is released in the .zip file distributable in releases. This will show a console window
|
||||||
|
which typically tells you more.
|
||||||
|
|
||||||
|
|
||||||
|
## <a name="Linux"></a><img src="https://upload.wikimedia.org/wikipedia/commons/a/ab/Linux_Logo_in_Linux_Libertine_Font.svg" alt="Linux" height="35"/> For Linux users
|
||||||
|
|
||||||
|
### libzip compatibility
|
||||||
|
|
||||||
|
The most common KataGo issue relates to different libzip versions in the provided executables.
|
||||||
|
Although the provided executables should work on the latest versions of Ubuntu, various other versions and distros differ in their libzip version.
|
||||||
|
|
||||||
|
* First, try `sudo apt-get install libzip-dev` to
|
||||||
|
* Next, try installing all packages mentioned [here](INSTALL.md#LinuxTrouble).
|
||||||
|
* A final alternative is to go [here](https://github.com/lightvector/KataGo) and compile KataGo yourself.
|
||||||
|
|
||||||
|
### Getting more information about errors
|
||||||
|
|
||||||
|
Check the terminal output around startup time.
|
||||||
+15
-5
@@ -6,6 +6,8 @@
|
|||||||
* [Troubleshooting and installation from sources](#WindowsSources)
|
* [Troubleshooting and installation from sources](#WindowsSources)
|
||||||
* [Quick install guide for Linux](#LinuxQuick)
|
* [Quick install guide for Linux](#LinuxQuick)
|
||||||
* [Troubleshooting and installation from sources](#LinuxSources)
|
* [Troubleshooting and installation from sources](#LinuxSources)
|
||||||
|
* [Configuring Multiple GPUS](#GPU)
|
||||||
|
* [Troubleshooting KataGo](#KataGo)
|
||||||
|
|
||||||
## <img src="https://upload.wikimedia.org/wikipedia/commons/8/8a/Apple_Logo.svg" alt="macOs" height="35"/> Installation for macOS users
|
## <img src="https://upload.wikimedia.org/wikipedia/commons/8/8a/Apple_Logo.svg" alt="macOs" height="35"/> Installation for macOS users
|
||||||
|
|
||||||
@@ -15,8 +17,9 @@ The easiest way to install is probably [brew](https://brew.sh/). Simply run `bre
|
|||||||
|
|
||||||
You can also find downloadable .app files for macOS [here](https://github.com/sanderland/katrain/releases).
|
You can also find downloadable .app files for macOS [here](https://github.com/sanderland/katrain/releases).
|
||||||
Simply download, unzip the file, mount the .dmg and drag the .app file to your application folder, everything is included.
|
Simply download, unzip the file, mount the .dmg and drag the .app file to your application folder, everything is included.
|
||||||
|
The first time launching the application you may need to [control-click in finder to give permission for the 'unidentified' app to launch](https://support.apple.com/guide/mac-help/open-a-mac-app-from-an-unidentified-developer-mh40616/mac). This is simply a result of Apple charging $99/year to developers to be 'identified'.
|
||||||
|
|
||||||
Users with the last generation M1 macs with different architecture should then `brew install katago` in addition to this. KaTrain will automatically detect this katago binary.
|
Users with the last generation M1 macs with different architecture should then `brew install katago` in addition to this. KaTrain will automatically detect this KataGo binary.
|
||||||
|
|
||||||
### <a name="MacCommand"></a>Command line install guide
|
### <a name="MacCommand"></a>Command line install guide
|
||||||
|
|
||||||
@@ -99,7 +102,7 @@ It assumes you have a working Python 3.6+ installation.
|
|||||||
A binary for KataGo is included, but if you have compiled your own, press F8 to open general settings and change the
|
A binary for KataGo is included, but if you have compiled your own, press F8 to open general settings and change the
|
||||||
KataGo executable path to the relevant KataGo v1.4+ binary.
|
KataGo executable path to the relevant KataGo v1.4+ binary.
|
||||||
|
|
||||||
### Troubleshooting and advanced installation from sources
|
### <a name="LinuxTrouble"></a>Troubleshooting and advanced installation from sources
|
||||||
|
|
||||||
You can try to manually install dependencies to resolve some issues relating to missing dependencies,
|
You can try to manually install dependencies to resolve some issues relating to missing dependencies,
|
||||||
e.g. the binary 'wheel' is not provided, KataGo is not starting, or sounds are not working.
|
e.g. the binary 'wheel' is not provided, KataGo is not starting, or sounds are not working.
|
||||||
@@ -128,7 +131,9 @@ You can now start KaTrain by running `python3 -m katrain`
|
|||||||
|
|
||||||
In case KataGo does not start, an alternative is to go [here](https://github.com/lightvector/KataGo) and compile KataGo yourself.
|
In case KataGo does not start, an alternative is to go [here](https://github.com/lightvector/KataGo) and compile KataGo yourself.
|
||||||
|
|
||||||
## Configuring the GPU(s) KataGo uses
|
|
||||||
|
|
||||||
|
## <a name="GPU"></a> Configuring the GPU(s) KataGo uses
|
||||||
|
|
||||||
In most cases KataGo detects your configuration correctly, automatically searching for OpenCL devices and select the highest scoring device.
|
In most cases KataGo detects your configuration correctly, automatically searching for OpenCL devices and select the highest scoring device.
|
||||||
However, if you have multiple GPUs or want to force a specific device you will need to edit the 'analysis_config.cfg' file in the KataGo folder.
|
However, if you have multiple GPUs or want to force a specific device you will need to edit the 'analysis_config.cfg' file in the KataGo folder.
|
||||||
@@ -148,8 +153,8 @@ As you can see it scores about twice as high as the Intel UHD chip and KataGo ha
|
|||||||
|
|
||||||
* Open the 'analysis_config.cfg' file in the `katrain/KataGo` folder in your python packages, or local sources.
|
* Open the 'analysis_config.cfg' file in the `katrain/KataGo` folder in your python packages, or local sources.
|
||||||
If you can't find it, turn on `debug_level=1` in general settings and look for the command that is used to start KataGo.
|
If you can't find it, turn on `debug_level=1` in general settings and look for the command that is used to start KataGo.
|
||||||
* Search for `numNNServerThreadsPerModel` (~line 75), uncomment the line by deleting the # and set the value to 2. The line should read `numNNServerThreadsPerModel = 2`.
|
* Search for `numNNServerThreadsPerModel` (~line 108), uncomment the line by deleting the # and set the value to 2. The line should read `numNNServerThreadsPerModel = 2`.
|
||||||
* Search for `openclDeviceToUseThread` (~line 117), uncomment by deleting the # and set the values to the device ID numbers identified in the terminal.
|
* Search for `openclDeviceToUseThread` (~line 164), uncomment by deleting the # and set the values to the device ID numbers identified in the terminal.
|
||||||
From the example above, we would want to use devices 1 and 2, for the Intel and AMD GPUs, but not device 0 (the CPU). In our case, the lines should read:
|
From the example above, we would want to use devices 1 and 2, for the Intel and AMD GPUs, but not device 0 (the CPU). In our case, the lines should read:
|
||||||
```
|
```
|
||||||
openclDeviceToUseThread0 = 1
|
openclDeviceToUseThread0 = 1
|
||||||
@@ -165,3 +170,8 @@ openclDeviceToUseThread1 = 2
|
|||||||
Using OpenCL Device 1: Intel(R) UHD Graphics 630 (Intel Inc.) OpenCL 1.2
|
Using OpenCL Device 1: Intel(R) UHD Graphics 630 (Intel Inc.) OpenCL 1.2
|
||||||
Using OpenCL Device 2: AMD Radeon Pro 5500M Compute Engine (AMD) OpenCL 1.2
|
Using OpenCL Device 2: AMD Radeon Pro 5500M Compute Engine (AMD) OpenCL 1.2
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## <a name="KataGo"></a> Troubleshooting and advanced KataGo settings
|
||||||
|
|
||||||
|
See [here](ENGINE.md) for an overview of how to resolve various issues with KataGo.
|
||||||
@@ -1,21 +1,25 @@
|
|||||||
# Themes
|
# Themes
|
||||||
Version 1.7 brings basic support for themes.
|
|
||||||
|
Version 1.7 brings basic support for themes, and 1.9 extends it to include keyboard shortcuts and support for multiple theme files.
|
||||||
|
|
||||||
## Creating and editing themes
|
## Creating and editing themes
|
||||||
|
|
||||||
* Look at the `Theme` class in `katrain/gui/theme.py`.
|
* Look at the `Theme` class in [`katrain/gui/theme.py`](https://github.com/sanderland/katrain/blob/master/katrain/gui/theme.py).
|
||||||
* Make a `theme.json` file in your `<home dir>/.katrain` directory and specify any variables from the above class you want to override, e.g.
|
* Make a `theme<yourthemename>.json` file in your `<home dir>/.katrain` directory and specify any variables from the above class you want to override, e.g.
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"BACKGROUND_COLOR": [1,0,0,1]
|
"BACKGROUND_COLOR": [1,0,0,1],
|
||||||
|
"KEY_STOP_ANALYSIS": "f10",
|
||||||
|
"MISTAKE_SOUNDS": ["jeff.wav","what.wav"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
* 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.
|
* 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.
|
||||||
|
* If variables are specified in multiple theme files, the *latest* alphabetically takes precedence. That is, each later theme file overwrites the settings from any previous one.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
* To install a theme, simply unzip the theme.zip to your .katrain folder.
|
* 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.
|
* 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.
|
* 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.
|
* To uninstall a theme, remove theme.json and all relevant images from that folder.
|
||||||
|
|
||||||
@@ -37,3 +41,11 @@ Version 1.7 brings basic support for themes.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
|
### Jeff sounds
|
||||||
|
|
||||||
|
* This theme makes Jeff comment `Ahhh?` and `What?!` when you make mistakes.
|
||||||
|
* Sounds provided by Mikkgo.
|
||||||
|
|
||||||
|
[Download](https://github.com/sanderland/katrain/blob/master/themes/jeff-sounds.zip)
|
||||||
|
|
||||||
@@ -32,9 +32,11 @@ for lang in locales:
|
|||||||
strings_to_langs[entry.msgid][lang] = entry
|
strings_to_langs[entry.msgid][lang] = entry
|
||||||
strings_to_keys[entry.msgid][lang] = set(re.findall("{.*?}", entry.msgstr))
|
strings_to_keys[entry.msgid][lang] = set(re.findall("{.*?}", entry.msgstr))
|
||||||
if entry.msgid in lang_to_strings[lang]:
|
if entry.msgid in lang_to_strings[lang]:
|
||||||
print("duplicate", entry.msgid, "in", lang)
|
print("duplicate", entry.msgid, "in", lang, "--> deleting", entry.msgstr)
|
||||||
errors = True
|
errors = True
|
||||||
lang_to_strings[lang].add(entry.msgid)
|
po[lang].remove(entry)
|
||||||
|
else:
|
||||||
|
lang_to_strings[lang].add(entry.msgid)
|
||||||
if todos[lang] and any("todo" in a for a in sys.argv):
|
if todos[lang] and any("todo" in a for a in sys.argv):
|
||||||
print(f"========== {lang} has {len(todos[lang])} TODO entries ========== ")
|
print(f"========== {lang} has {len(todos[lang])} TODO entries ========== ")
|
||||||
for item in todos[lang]:
|
for item in todos[lang]:
|
||||||
@@ -76,24 +78,25 @@ for lang in locales:
|
|||||||
for msgid, lang_entries in strings_to_langs.items():
|
for msgid, lang_entries in strings_to_langs.items():
|
||||||
if lang in lang_entries and "TODO" in lang_entries[lang].comment:
|
if lang in lang_entries and "TODO" in lang_entries[lang].comment:
|
||||||
if any(e.msgstr == lang_entries[lang].msgstr for ll, e in lang_entries.items() if ll != lang):
|
if any(e.msgstr == lang_entries[lang].msgstr for ll, e in lang_entries.items() if ll != lang):
|
||||||
todo_comment = (
|
if lang_entries.get(DEFAULT_LANG):
|
||||||
f"TODO - {lang_entries[DEFAULT_LANG].comment}" if lang_entries[DEFAULT_LANG].comment else "TODO"
|
todo_comment = (
|
||||||
) # update todo
|
f"TODO - {lang_entries[DEFAULT_LANG].comment}" if lang_entries[DEFAULT_LANG].comment else "TODO"
|
||||||
if (
|
) # update todo
|
||||||
lang_entries[lang].msgstr != lang_entries[DEFAULT_LANG].msgstr
|
if (
|
||||||
or lang_entries[lang].comment.replace("\n", " ") != todo_comment
|
lang_entries[lang].msgstr != lang_entries[DEFAULT_LANG].msgstr
|
||||||
):
|
or lang_entries[lang].comment.replace("\n", " ") != todo_comment
|
||||||
print(
|
):
|
||||||
[
|
print(
|
||||||
lang_entries[lang].msgstr,
|
[
|
||||||
lang_entries[DEFAULT_LANG].msgstr,
|
lang_entries[lang].msgstr,
|
||||||
lang_entries[lang].comment,
|
lang_entries[DEFAULT_LANG].msgstr,
|
||||||
todo_comment,
|
lang_entries[lang].comment,
|
||||||
]
|
todo_comment,
|
||||||
)
|
]
|
||||||
lang_entries[lang].msgstr = lang_entries[DEFAULT_LANG].msgstr # update
|
)
|
||||||
lang_entries[lang].comment = todo_comment
|
lang_entries[lang].msgstr = lang_entries[DEFAULT_LANG].msgstr # update
|
||||||
print(f"{lang}/{msgid} todo entry updated")
|
lang_entries[lang].comment = todo_comment
|
||||||
|
print(f"{lang}/{msgid} todo entry updated")
|
||||||
|
|
||||||
po[lang].save(pofile[lang])
|
po[lang].save(pofile[lang])
|
||||||
mofile = pofile[lang].replace(".po", ".mo")
|
mofile = pofile[lang].replace(".po", ".mo")
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
+132
-67
@@ -1,10 +1,10 @@
|
|||||||
"""isort:skip_file"""
|
"""isort:skip_file"""
|
||||||
# first, logging level lower and force audio framework
|
# first, logging level lower
|
||||||
import os
|
import os
|
||||||
|
|
||||||
os.environ["KCFG_KIVY_LOG_LEVEL"] = os.environ.get("KCFG_KIVY_LOG_LEVEL", "warning")
|
os.environ["KCFG_KIVY_LOG_LEVEL"] = os.environ.get("KCFG_KIVY_LOG_LEVEL", "warning")
|
||||||
if "KIVY_AUDIO" not in os.environ:
|
# if "KIVY_AUDIO" not in os.environ: # trying default again
|
||||||
os.environ["KIVY_AUDIO"] = "sdl2" # some backends hard crash / this seems to be most stable
|
# os.environ["KIVY_AUDIO"] = "sdl2" # some backends hard crash / this seems to be most stable
|
||||||
|
|
||||||
import kivy
|
import kivy
|
||||||
|
|
||||||
@@ -30,6 +30,8 @@ from queue import Queue
|
|||||||
import urllib3
|
import urllib3
|
||||||
import webbrowser
|
import webbrowser
|
||||||
import time
|
import time
|
||||||
|
import random
|
||||||
|
import glob
|
||||||
|
|
||||||
from kivy.base import ExceptionHandler, ExceptionManager
|
from kivy.base import ExceptionHandler, ExceptionManager
|
||||||
from kivy.app import App
|
from kivy.app import App
|
||||||
@@ -65,7 +67,15 @@ from katrain.core.constants import (
|
|||||||
DATA_FOLDER,
|
DATA_FOLDER,
|
||||||
AI_DEFAULT,
|
AI_DEFAULT,
|
||||||
)
|
)
|
||||||
from katrain.gui.popups import ConfigTeacherPopup, ConfigTimerPopup, I18NPopup, SaveSGFPopup, ContributePopup
|
from katrain.gui.popups import (
|
||||||
|
ConfigTeacherPopup,
|
||||||
|
ConfigTimerPopup,
|
||||||
|
I18NPopup,
|
||||||
|
SaveSGFPopup,
|
||||||
|
ContributePopup,
|
||||||
|
EngineRecoveryPopup,
|
||||||
|
)
|
||||||
|
from katrain.gui.sound import play_sound
|
||||||
from katrain.core.base_katrain import KaTrainBase
|
from katrain.core.base_katrain import KaTrainBase
|
||||||
from katrain.core.engine import KataGoEngine
|
from katrain.core.engine import KataGoEngine
|
||||||
from katrain.core.contribute_engine import KataGoContributeEngine
|
from katrain.core.contribute_engine import KataGoContributeEngine
|
||||||
@@ -221,6 +231,14 @@ class KaTrainGui(Screen, KaTrainBase):
|
|||||||
if not self.contributing:
|
if not self.contributing:
|
||||||
last_player, next_player = self.players_info[cn.player], self.players_info[cn.next_player]
|
last_player, next_player = self.players_info[cn.player], self.players_info[cn.next_player]
|
||||||
if self.play_analyze_mode == MODE_PLAY and self.nav_drawer.state != "open" and self.popup_open is None:
|
if self.play_analyze_mode == MODE_PLAY and self.nav_drawer.state != "open" and self.popup_open is None:
|
||||||
|
points_lost = cn.points_lost
|
||||||
|
if (
|
||||||
|
last_player.human
|
||||||
|
and cn.analysis_complete
|
||||||
|
and points_lost is not None
|
||||||
|
and points_lost > self.config("trainer/eval_thresholds")[-4]
|
||||||
|
):
|
||||||
|
self.play_mistake_sound(cn)
|
||||||
teaching_undo = cn.player and last_player.being_taught and cn.parent
|
teaching_undo = cn.player and last_player.being_taught and cn.parent
|
||||||
if (
|
if (
|
||||||
teaching_undo
|
teaching_undo
|
||||||
@@ -437,6 +455,7 @@ class KaTrainGui(Screen, KaTrainBase):
|
|||||||
title_key="general settings title", size=[dp(1200), dp(950)], content=ConfigPopup(self)
|
title_key="general settings title", size=[dp(1200), dp(950)], content=ConfigPopup(self)
|
||||||
).__self__
|
).__self__
|
||||||
self.config_popup.content.popup = self.config_popup
|
self.config_popup.content.popup = self.config_popup
|
||||||
|
self.config_popup.title += ": " + self.config_file
|
||||||
self.config_popup.open()
|
self.config_popup.open()
|
||||||
|
|
||||||
def _do_contribute_popup(self):
|
def _do_contribute_popup(self):
|
||||||
@@ -456,10 +475,30 @@ class KaTrainGui(Screen, KaTrainBase):
|
|||||||
self.ai_settings_popup.content.popup = self.ai_settings_popup
|
self.ai_settings_popup.content.popup = self.ai_settings_popup
|
||||||
self.ai_settings_popup.open()
|
self.ai_settings_popup.open()
|
||||||
|
|
||||||
|
def _do_engine_recovery_popup(self, error_message, code):
|
||||||
|
current_open = self.popup_open
|
||||||
|
self.log(f"engine rec {current_open}", OUTPUT_ERROR)
|
||||||
|
if current_open and isinstance(current_open.content, EngineRecoveryPopup):
|
||||||
|
self.log(f"Not opening engine recovery popup with {error_message} as one is already open", OUTPUT_DEBUG)
|
||||||
|
return
|
||||||
|
popup = I18NPopup(
|
||||||
|
title_key="engine recovery",
|
||||||
|
size=[dp(600), dp(700)],
|
||||||
|
content=EngineRecoveryPopup(self, error_message=error_message, code=code),
|
||||||
|
).__self__
|
||||||
|
popup.content.popup = popup
|
||||||
|
popup.open()
|
||||||
|
|
||||||
|
def play_mistake_sound(self, node):
|
||||||
|
if self.config("timer/sound") and node.played_sound is None and Theme.MISTAKE_SOUNDS:
|
||||||
|
node.played_sound = True
|
||||||
|
play_sound(random.choice(Theme.MISTAKE_SOUNDS))
|
||||||
|
|
||||||
def load_sgf_file(self, file, fast=False, rewind=True):
|
def load_sgf_file(self, file, fast=False, rewind=True):
|
||||||
if self.contributing:
|
if self.contributing:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
file = os.path.abspath(file)
|
||||||
move_tree = KaTrainSGF.parse_file(file)
|
move_tree = KaTrainSGF.parse_file(file)
|
||||||
except (ParseError, FileNotFoundError) as e:
|
except (ParseError, FileNotFoundError) as e:
|
||||||
self.log(i18n._("Failed to load SGF").format(error=e), OUTPUT_ERROR)
|
self.log(i18n._("Failed to load SGF").format(error=e), OUTPUT_ERROR)
|
||||||
@@ -561,40 +600,49 @@ class KaTrainGui(Screen, KaTrainBase):
|
|||||||
or self.controls.move_tree.collide_point(*touch.pos)
|
or self.controls.move_tree.collide_point(*touch.pos)
|
||||||
):
|
):
|
||||||
if touch.is_mouse_scrolling:
|
if touch.is_mouse_scrolling:
|
||||||
if touch.button == "scrollup":
|
if self.board_gui.animating_pv is None:
|
||||||
self("redo")
|
if touch.button == "scrollup":
|
||||||
elif touch.button == "scrolldown":
|
self("redo")
|
||||||
self("undo")
|
elif touch.button == "scrolldown":
|
||||||
|
self("undo")
|
||||||
|
else:
|
||||||
|
if touch.button == "scrollup":
|
||||||
|
self.board_gui.adjust_animate_pv_index(1)
|
||||||
|
elif touch.button == "scrolldown":
|
||||||
|
self.board_gui.adjust_animate_pv_index(-1)
|
||||||
return super().on_touch_up(touch)
|
return super().on_touch_up(touch)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def shortcuts(self):
|
def shortcuts(self):
|
||||||
return {
|
return {
|
||||||
"q": self.analysis_controls.show_children,
|
k: v
|
||||||
"w": self.analysis_controls.eval,
|
for ks, v in [
|
||||||
"e": self.analysis_controls.hints,
|
(Theme.KEY_ANALYSIS_CONTROLS_SHOW_CHILDREN, self.analysis_controls.show_children),
|
||||||
"t": self.analysis_controls.ownership,
|
(Theme.KEY_ANALYSIS_CONTROLS_EVAL, self.analysis_controls.eval),
|
||||||
"r": self.analysis_controls.policy,
|
(Theme.KEY_ANALYSIS_CONTROLS_HINTS, self.analysis_controls.hints),
|
||||||
"enter": ("ai-move",),
|
(Theme.KEY_ANALYSIS_CONTROLS_OWNERSHIP, self.analysis_controls.ownership),
|
||||||
"numpadenter": ("ai-move",),
|
(Theme.KEY_ANALYSIS_CONTROLS_POLICY, self.analysis_controls.policy),
|
||||||
"a": ("analyze-extra", "extra"),
|
(Theme.KEY_AI_MOVE, ("ai-move",)),
|
||||||
"s": ("analyze-extra", "equalize"),
|
(Theme.KEY_ANALYZE_EXTRA_EXTRA, ("analyze-extra", "extra")),
|
||||||
"d": ("analyze-extra", "sweep"),
|
(Theme.KEY_ANALYZE_EXTRA_EQUALIZE, ("analyze-extra", "equalize")),
|
||||||
"f": ("analyze-extra", "alternative"),
|
(Theme.KEY_ANALYZE_EXTRA_SWEEP, ("analyze-extra", "sweep")),
|
||||||
"g": ("select-box",),
|
(Theme.KEY_ANALYZE_EXTRA_ALTERNATIVE, ("analyze-extra", "alternative")),
|
||||||
"h": ("reset-analysis",),
|
(Theme.KEY_SELECT_BOX, ("select-box",)),
|
||||||
"i": ("insert-mode",),
|
(Theme.KEY_RESET_ANALYSIS, ("reset-analysis",)),
|
||||||
"p": ("play", None),
|
(Theme.KEY_INSERT_MODE, ("insert-mode",)),
|
||||||
"l": ("selfplay-setup", "end", None),
|
(Theme.KEY_PASS, ("play", None)),
|
||||||
"b": ("undo", "branch"),
|
(Theme.KEY_SELFPLAY_TO_END, ("selfplay-setup", "end", None)),
|
||||||
"down": ("switch-branch", 1),
|
(Theme.KEY_NAV_PREV_BRANCH, ("undo", "branch")),
|
||||||
"up": ("switch-branch", -1),
|
(Theme.KEY_NAV_BRANCH_DOWN, ("switch-branch", 1)),
|
||||||
"f5": ("timer-popup",),
|
(Theme.KEY_NAV_BRANCH_UP, ("switch-branch", -1)),
|
||||||
"f6": ("teacher-popup",),
|
(Theme.KEY_TIMER_POPUP, ("timer-popup",)),
|
||||||
"f7": ("ai-popup",),
|
(Theme.KEY_TEACHER_POPUP, ("teacher-popup",)),
|
||||||
"f8": ("config-popup",),
|
(Theme.KEY_AI_POPUP, ("ai-popup",)),
|
||||||
"f9": ("contribute-popup",),
|
(Theme.KEY_CONFIG_POPUP, ("config-popup",)),
|
||||||
"escape": ("analyze-extra", "stop"),
|
(Theme.KEY_CONTRIBUTE_POPUP, ("contribute-popup",)),
|
||||||
|
(Theme.KEY_STOP_ANALYSIS, ("analyze-extra", "stop")),
|
||||||
|
]
|
||||||
|
for k in (ks if isinstance(ks, list) else [ks])
|
||||||
}
|
}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -607,67 +655,72 @@ class KaTrainGui(Screen, KaTrainBase):
|
|||||||
def _on_keyboard_down(self, _keyboard, keycode, _text, modifiers):
|
def _on_keyboard_down(self, _keyboard, keycode, _text, modifiers):
|
||||||
self.last_key_down = keycode
|
self.last_key_down = keycode
|
||||||
ctrl_pressed = "ctrl" in modifiers
|
ctrl_pressed = "ctrl" in modifiers
|
||||||
|
shift_pressed = "shift" in modifiers
|
||||||
if self.controls.note.focus:
|
if self.controls.note.focus:
|
||||||
return # when making notes, don't allow keyboard shortcuts
|
return # when making notes, don't allow keyboard shortcuts
|
||||||
popup = self.popup_open
|
popup = self.popup_open
|
||||||
if popup:
|
if popup:
|
||||||
if keycode[1] in ["f5", "f6", "f7", "f8", "f9"]: # switch between popups
|
if keycode[1] in [
|
||||||
|
Theme.KEY_DEEPERANALYSIS_POPUP,
|
||||||
|
Theme.KEY_REPORT_POPUP,
|
||||||
|
Theme.KEY_TIMER_POPUP,
|
||||||
|
Theme.KEY_TEACHER_POPUP,
|
||||||
|
Theme.KEY_AI_POPUP,
|
||||||
|
Theme.KEY_CONFIG_POPUP,
|
||||||
|
Theme.KEY_CONTRIBUTE_POPUP,
|
||||||
|
]: # switch between popups
|
||||||
popup.dismiss()
|
popup.dismiss()
|
||||||
return
|
return
|
||||||
elif keycode[1] in ["enter", "numpadenter"]:
|
elif keycode[1] in Theme.KEY_SUBMIT_POPUP:
|
||||||
fn = getattr(popup.content, "on_submit", None)
|
fn = getattr(popup.content, "on_submit", None)
|
||||||
if fn:
|
if fn:
|
||||||
fn()
|
fn()
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
shift_pressed = "shift" in modifiers
|
if keycode[1] == Theme.KEY_TOGGLE_CONTINUOUS_ANALYSIS:
|
||||||
shortcuts = self.shortcuts
|
|
||||||
if keycode[1] == "spacebar":
|
|
||||||
self.toggle_continuous_analysis()
|
self.toggle_continuous_analysis()
|
||||||
elif keycode[1] == "k":
|
elif keycode[1] == Theme.KEY_TOGGLE_COORDINATES:
|
||||||
self.board_gui.toggle_coordinates()
|
self.board_gui.toggle_coordinates()
|
||||||
elif keycode[1] in ["pause", "break", "f15"] and not ctrl_pressed:
|
elif keycode[1] in Theme.KEY_PAUSE_TIMER and not ctrl_pressed:
|
||||||
self.controls.timer.paused = not self.controls.timer.paused
|
self.controls.timer.paused = not self.controls.timer.paused
|
||||||
elif keycode[1] in ["`", "~", "f12"]:
|
elif keycode[1] in Theme.KEY_ZEN:
|
||||||
self.zen = (self.zen + 1) % 3
|
self.zen = (self.zen + 1) % 3
|
||||||
elif keycode[1] in ["left", "z"]:
|
elif keycode[1] in Theme.KEY_NAV_PREV:
|
||||||
self("undo", 1 + shift_pressed * 9 + ctrl_pressed * 9999)
|
self("undo", 1 + shift_pressed * 9 + ctrl_pressed * 9999)
|
||||||
elif keycode[1] in ["right", "x"]:
|
elif keycode[1] in Theme.KEY_NAV_NEXT:
|
||||||
self("redo", 1 + shift_pressed * 9 + ctrl_pressed * 9999)
|
self("redo", 1 + shift_pressed * 9 + ctrl_pressed * 9999)
|
||||||
elif keycode[1] == "home":
|
elif keycode[1] == Theme.KEY_NAV_GAME_START:
|
||||||
self("undo", 9999)
|
self("undo", 9999)
|
||||||
elif keycode[1] == "end":
|
elif keycode[1] == Theme.KEY_NAV_GAME_END:
|
||||||
self("redo", 9999)
|
self("redo", 9999)
|
||||||
elif keycode[1] == "pageup":
|
elif keycode[1] == Theme.KEY_MOVE_TREE_MAKE_SELECTED_NODE_MAIN_BRANCH:
|
||||||
self.controls.move_tree.make_selected_node_main_branch()
|
self.controls.move_tree.make_selected_node_main_branch()
|
||||||
elif keycode[1] == "n" and not ctrl_pressed:
|
elif keycode[1] == Theme.KEY_NAV_MISTAKE and not ctrl_pressed:
|
||||||
self("find-mistake", "undo" if shift_pressed else "redo")
|
self("find-mistake", "undo" if shift_pressed else "redo")
|
||||||
elif keycode[1] == "delete" and ctrl_pressed:
|
elif keycode[1] == Theme.KEY_MOVE_TREE_DELETE_SELECTED_NODE and ctrl_pressed:
|
||||||
self.controls.move_tree.delete_selected_node()
|
self.controls.move_tree.delete_selected_node()
|
||||||
elif keycode[1] == "c" and not ctrl_pressed:
|
elif keycode[1] == Theme.KEY_MOVE_TREE_TOGGLE_SELECTED_NODE_COLLAPSE and not ctrl_pressed:
|
||||||
self.controls.move_tree.toggle_selected_node_collapse()
|
self.controls.move_tree.toggle_selected_node_collapse()
|
||||||
elif keycode[1] == "n" and ctrl_pressed:
|
elif keycode[1] == Theme.KEY_NEW_GAME and ctrl_pressed:
|
||||||
self("new-game-popup")
|
self("new-game-popup")
|
||||||
elif keycode[1] == "l" and ctrl_pressed:
|
elif keycode[1] == Theme.KEY_LOAD_GAME and ctrl_pressed:
|
||||||
self("analyze-sgf-popup")
|
self("analyze-sgf-popup")
|
||||||
elif keycode[1] == "s" and ctrl_pressed:
|
elif keycode[1] == Theme.KEY_SAVE_GAME and ctrl_pressed:
|
||||||
self("save-game")
|
self("save-game")
|
||||||
elif keycode[1] == "d" and ctrl_pressed:
|
elif keycode[1] == Theme.KEY_SAVE_GAME_AS and ctrl_pressed:
|
||||||
self("save-game-as-popup")
|
self("save-game-as-popup")
|
||||||
elif keycode[1] == "c" and ctrl_pressed:
|
elif keycode[1] == Theme.KEY_COPY and ctrl_pressed:
|
||||||
Clipboard.copy(self.game.root.sgf())
|
Clipboard.copy(self.game.root.sgf())
|
||||||
self.controls.set_status(i18n._("Copied SGF to clipboard."), STATUS_INFO)
|
self.controls.set_status(i18n._("Copied SGF to clipboard."), STATUS_INFO)
|
||||||
elif keycode[1] == "v" and ctrl_pressed:
|
elif keycode[1] == Theme.KEY_PASTE and ctrl_pressed:
|
||||||
self.load_sgf_from_clipboard()
|
self.load_sgf_from_clipboard()
|
||||||
elif keycode[1] == "b" and shift_pressed:
|
elif keycode[1] == Theme.KEY_NAV_PREV_BRANCH and shift_pressed:
|
||||||
self("undo", "main-branch")
|
self("undo", "main-branch")
|
||||||
elif keycode[1] in shortcuts.keys() and not ctrl_pressed:
|
elif keycode[1] == Theme.KEY_DEEPERANALYSIS_POPUP:
|
||||||
shortcut = shortcuts[keycode[1]]
|
self.analysis_controls.dropdown.open_game_analysis_popup()
|
||||||
if isinstance(shortcut, Widget):
|
elif keycode[1] == Theme.KEY_REPORT_POPUP:
|
||||||
shortcut.trigger_action(duration=0)
|
self.analysis_controls.dropdown.open_report_popup()
|
||||||
else:
|
|
||||||
self(*shortcut)
|
|
||||||
elif keycode[1] == "f10" and self.debug_level >= OUTPUT_EXTRA_DEBUG:
|
elif keycode[1] == "f10" and self.debug_level >= OUTPUT_EXTRA_DEBUG:
|
||||||
import yappi
|
import yappi
|
||||||
|
|
||||||
@@ -682,6 +735,13 @@ class KaTrainGui(Screen, KaTrainBase):
|
|||||||
filename = f"callgrind.{int(time.time())}.prof"
|
filename = f"callgrind.{int(time.time())}.prof"
|
||||||
stats.save(filename, type="callgrind")
|
stats.save(filename, type="callgrind")
|
||||||
self.log(f"wrote profiling results to {filename}", OUTPUT_ERROR)
|
self.log(f"wrote profiling results to {filename}", OUTPUT_ERROR)
|
||||||
|
elif not ctrl_pressed:
|
||||||
|
shortcut = self.shortcuts.get(keycode[1])
|
||||||
|
if shortcut is not None:
|
||||||
|
if isinstance(shortcut, Widget):
|
||||||
|
shortcut.trigger_action(duration=0)
|
||||||
|
else:
|
||||||
|
self(*shortcut)
|
||||||
|
|
||||||
def _on_keyboard_up(self, _keyboard, keycode):
|
def _on_keyboard_up(self, _keyboard, keycode):
|
||||||
if keycode[1] in ["alt", "tab"]:
|
if keycode[1] in ["alt", "tab"]:
|
||||||
@@ -723,8 +783,8 @@ class KaTrainApp(MDApp):
|
|||||||
resource_add_path(PATHS["PACKAGE"] + "/img")
|
resource_add_path(PATHS["PACKAGE"] + "/img")
|
||||||
resource_add_path(os.path.abspath(os.path.expanduser(DATA_FOLDER))) # prefer resources in .katrain
|
resource_add_path(os.path.abspath(os.path.expanduser(DATA_FOLDER))) # prefer resources in .katrain
|
||||||
|
|
||||||
theme_file = resource_find("theme.json")
|
theme_files = glob.glob(os.path.join(os.path.expanduser(DATA_FOLDER), "theme*.json"))
|
||||||
if theme_file:
|
for theme_file in sorted(theme_files):
|
||||||
try:
|
try:
|
||||||
with open(theme_file) as f:
|
with open(theme_file) as f:
|
||||||
theme_overrides = json.load(f)
|
theme_overrides = json.load(f)
|
||||||
@@ -742,9 +802,11 @@ class KaTrainApp(MDApp):
|
|||||||
self.gui = KaTrainGui()
|
self.gui = KaTrainGui()
|
||||||
Builder.load_file(popup_kv_file)
|
Builder.load_file(popup_kv_file)
|
||||||
|
|
||||||
win_size = self.gui.config("ui_state/size", [])
|
win_left = win_top = win_size = None
|
||||||
win_left = self.gui.config("ui_state/left", None)
|
if self.gui.config("ui_state/restoresize", True):
|
||||||
win_top = self.gui.config("ui_state/top", None)
|
win_size = self.gui.config("ui_state/size", [])
|
||||||
|
win_left = self.gui.config("ui_state/left", None)
|
||||||
|
win_top = self.gui.config("ui_state/top", None)
|
||||||
if not win_size:
|
if not win_size:
|
||||||
window_scale_fac = 1
|
window_scale_fac = 1
|
||||||
try:
|
try:
|
||||||
@@ -755,6 +817,7 @@ class KaTrainApp(MDApp):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
window_scale_fac = 0.85
|
window_scale_fac = 0.85
|
||||||
win_size = [1300 * window_scale_fac, 1000 * window_scale_fac]
|
win_size = [1300 * window_scale_fac, 1000 * window_scale_fac]
|
||||||
|
self.gui.log(f"Setting window size to {win_size} and position to {[win_left, win_top]}", OUTPUT_DEBUG)
|
||||||
Window.size = (win_size[0], win_size[1])
|
Window.size = (win_size[0], win_size[1])
|
||||||
if win_left is not None and win_top is not None:
|
if win_left is not None and win_top is not None:
|
||||||
Window.left = win_left
|
Window.left = win_left
|
||||||
@@ -776,6 +839,7 @@ class KaTrainApp(MDApp):
|
|||||||
"homepage": HOMEPAGE + "#manual",
|
"homepage": HOMEPAGE + "#manual",
|
||||||
"support": HOMEPAGE + "#support",
|
"support": HOMEPAGE + "#support",
|
||||||
"contribute:signup": "http://katagotraining.org/accounts/signup/",
|
"contribute:signup": "http://katagotraining.org/accounts/signup/",
|
||||||
|
"engine:help": HOMEPAGE + "/blob/master/ENGINE.md",
|
||||||
}
|
}
|
||||||
if site_key in websites:
|
if site_key in websites:
|
||||||
webbrowser.open(websites[site_key])
|
webbrowser.open(websites[site_key])
|
||||||
@@ -814,6 +878,7 @@ def run_app():
|
|||||||
ex_type, ex, tb = sys.exc_info()
|
ex_type, ex, tb = sys.exc_info()
|
||||||
trace = "".join(traceback.format_tb(tb))
|
trace = "".join(traceback.format_tb(tb))
|
||||||
app = MDApp.get_running_app()
|
app = MDApp.get_running_app()
|
||||||
|
|
||||||
if app and app.gui:
|
if app and app.gui:
|
||||||
app.gui.log(
|
app.gui.log(
|
||||||
f"Exception {inst.__class__.__name__}: {', '.join(repr(a) for a in inst.args)}\n{trace}",
|
f"Exception {inst.__class__.__name__}: {', '.join(repr(a) for a in inst.args)}\n{trace}",
|
||||||
|
|||||||
+3
-2
@@ -8,7 +8,7 @@
|
|||||||
"max_visits": 500,
|
"max_visits": 500,
|
||||||
"fast_visits": 25,
|
"fast_visits": 25,
|
||||||
"max_time": 8.0,
|
"max_time": 8.0,
|
||||||
"wide_root_noise": 0.0,
|
"wide_root_noise": 0.04,
|
||||||
"_enable_ownership": true
|
"_enable_ownership": true
|
||||||
},
|
},
|
||||||
"contribute": {
|
"contribute": {
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
"anim_pv_time": 0.5,
|
"anim_pv_time": 0.5,
|
||||||
"debug_level": 0,
|
"debug_level": 0,
|
||||||
"lang": "en",
|
"lang": "en",
|
||||||
"version": "1.8.0",
|
"version": "1.9.0",
|
||||||
"load_fast_analysis": false,
|
"load_fast_analysis": false,
|
||||||
"load_sgf_rewind": true
|
"load_sgf_rewind": true
|
||||||
},
|
},
|
||||||
@@ -159,6 +159,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ui_state": {
|
"ui_state": {
|
||||||
|
"restoresize": true,
|
||||||
"size": [],
|
"size": [],
|
||||||
"play": {
|
"play": {
|
||||||
"analysis_controls": {
|
"analysis_controls": {
|
||||||
|
|||||||
+67
-2
@@ -33,9 +33,11 @@ from katrain.core.constants import (
|
|||||||
OUTPUT_DEBUG,
|
OUTPUT_DEBUG,
|
||||||
OUTPUT_ERROR,
|
OUTPUT_ERROR,
|
||||||
OUTPUT_INFO,
|
OUTPUT_INFO,
|
||||||
|
PRIORITY_EXTRA_AI_QUERY,
|
||||||
|
ADDITIONAL_MOVE_ORDER,
|
||||||
)
|
)
|
||||||
from katrain.core.game import Game, GameNode, Move
|
from katrain.core.game import Game, GameNode, Move
|
||||||
from katrain.core.utils import var_to_grid, weighted_selection_without_replacement
|
from katrain.core.utils import var_to_grid, weighted_selection_without_replacement, evaluation_class
|
||||||
|
|
||||||
|
|
||||||
def interp_ix(lst, x):
|
def interp_ix(lst, x):
|
||||||
@@ -91,6 +93,69 @@ def ai_rank_estimation(strategy, settings) -> int:
|
|||||||
return AI_STRENGTH[strategy]
|
return AI_STRENGTH[strategy]
|
||||||
|
|
||||||
|
|
||||||
|
def game_report(game, thresholds, depth_filter=None):
|
||||||
|
cn = game.current_node
|
||||||
|
nodes = cn.nodes_from_root
|
||||||
|
while cn.children: # main branch
|
||||||
|
cn = cn.children[0]
|
||||||
|
nodes.append(cn)
|
||||||
|
|
||||||
|
x, y = game.board_size
|
||||||
|
depth_filter = [math.ceil(board_frac * x * y) for board_frac in depth_filter or (0, 1e9)]
|
||||||
|
nodes = [n for n in nodes if n.move and not n.is_root and depth_filter[0] <= n.depth < depth_filter[1]]
|
||||||
|
histogram = [{"B": 0, "W": 0} for _ in thresholds]
|
||||||
|
ai_top_move_count = {"B": 0, "W": 0}
|
||||||
|
ai_approved_move_count = {"B": 0, "W": 0}
|
||||||
|
player_ptloss = {"B": [], "W": []}
|
||||||
|
weights = {"B": [], "W": []}
|
||||||
|
|
||||||
|
for n in nodes:
|
||||||
|
points_lost = n.points_lost
|
||||||
|
if n.points_lost is None:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
points_lost = max(0, points_lost)
|
||||||
|
bucket = len(thresholds) - 1 - evaluation_class(points_lost, thresholds)
|
||||||
|
player_ptloss[n.player].append(points_lost)
|
||||||
|
histogram[bucket][n.player] += 1
|
||||||
|
cands = n.parent.candidate_moves
|
||||||
|
filtered_cands = [d for d in cands if d["order"] < ADDITIONAL_MOVE_ORDER and "prior" in d]
|
||||||
|
weight = min(
|
||||||
|
1.0,
|
||||||
|
sum([max(d["pointsLost"], 0) * d["prior"] for d in filtered_cands])
|
||||||
|
/ (sum(d["prior"] for d in filtered_cands) or 1e-6),
|
||||||
|
) # complexity capped at 1
|
||||||
|
# adj_weight between 0.05 - 1, dependent on difficulty and points lost
|
||||||
|
adj_weight = max(0.05, min(1.0, max(weight, points_lost / 4)))
|
||||||
|
weights[n.player].append((weight, adj_weight))
|
||||||
|
if n.parent.analysis_complete:
|
||||||
|
ai_top_move_count[n.player] += int(cands[0]["move"] == n.move.gtp())
|
||||||
|
ai_approved_move_count[n.player] += int(
|
||||||
|
n.move.gtp()
|
||||||
|
in [d["move"] for d in filtered_cands if d["order"] == 0 or (d["pointsLost"] < 0.5 and d["order"] < 5)]
|
||||||
|
)
|
||||||
|
|
||||||
|
wt_loss = {
|
||||||
|
bw: sum(s * aw for s, (w, aw) in zip(player_ptloss[bw], weights[bw]))
|
||||||
|
/ (sum(aw for _, aw in weights[bw]) or 1e-6)
|
||||||
|
for bw in "BW"
|
||||||
|
}
|
||||||
|
sum_stats = {
|
||||||
|
bw: {
|
||||||
|
"accuracy": 100 * 0.75 ** wt_loss[bw],
|
||||||
|
"complexity": sum(w for w, aw in weights[bw]) / len(player_ptloss[bw]),
|
||||||
|
"mean_ptloss": sum(player_ptloss[bw]) / len(player_ptloss[bw]),
|
||||||
|
"weighted_ptloss": wt_loss[bw],
|
||||||
|
"ai_top_move": ai_top_move_count[bw] / len(player_ptloss[bw]),
|
||||||
|
"ai_top5_move": ai_approved_move_count[bw] / len(player_ptloss[bw]),
|
||||||
|
}
|
||||||
|
if len(player_ptloss[bw]) > 0
|
||||||
|
else {}
|
||||||
|
for bw in "BW"
|
||||||
|
}
|
||||||
|
return sum_stats, histogram, player_ptloss
|
||||||
|
|
||||||
|
|
||||||
def dirichlet_noise(num, dir_alpha=0.3):
|
def dirichlet_noise(num, dir_alpha=0.3):
|
||||||
sample = [random.gammavariate(dir_alpha, 1) for _ in range(num)]
|
sample = [random.gammavariate(dir_alpha, 1) for _ in range(num)]
|
||||||
sum_sample = sum(sample)
|
sum_sample = sum(sample)
|
||||||
@@ -173,7 +238,7 @@ def request_ai_analysis(game: Game, cn: GameNode, extra_settings: Dict) -> Optio
|
|||||||
cn,
|
cn,
|
||||||
callback=set_analysis,
|
callback=set_analysis,
|
||||||
error_callback=set_error,
|
error_callback=set_error,
|
||||||
priority=1_000,
|
priority=PRIORITY_EXTRA_AI_QUERY,
|
||||||
ownership=False,
|
ownership=False,
|
||||||
extra_settings=extra_settings,
|
extra_settings=extra_settings,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,21 +1,34 @@
|
|||||||
PROGRAM_NAME = "KaTrain"
|
PROGRAM_NAME = "KaTrain"
|
||||||
VERSION = "1.8.4"
|
VERSION = "1.9.0"
|
||||||
HOMEPAGE = "https://github.com/sanderland/katrain"
|
HOMEPAGE = "https://github.com/sanderland/katrain"
|
||||||
CONFIG_MIN_VERSION = "1.8.0" # keep config files from this version
|
CONFIG_MIN_VERSION = "1.9.0" # keep config files from this version
|
||||||
ANALYSIS_FORMAT_VERSION = "1.0"
|
ANALYSIS_FORMAT_VERSION = "1.0"
|
||||||
DATA_FOLDER = "~/.katrain"
|
DATA_FOLDER = "~/.katrain"
|
||||||
|
|
||||||
|
|
||||||
OUTPUT_ERROR = -1
|
OUTPUT_ERROR = -1
|
||||||
OUTPUT_KATAGO_STDERR = -0.5
|
OUTPUT_KATAGO_STDERR = -0.5
|
||||||
OUTPUT_INFO = 0
|
OUTPUT_INFO = 0
|
||||||
OUTPUT_DEBUG = 1
|
OUTPUT_DEBUG = 1
|
||||||
OUTPUT_EXTRA_DEBUG = 2
|
OUTPUT_EXTRA_DEBUG = 2
|
||||||
|
|
||||||
|
KATAGO_EXCEPTION = "KATAGO-INTERNAL-ERROR"
|
||||||
|
|
||||||
STATUS_ANALYSIS = 1.0 # same priority for analysis/info
|
STATUS_ANALYSIS = 1.0 # same priority for analysis/info
|
||||||
STATUS_INFO = 1.1
|
STATUS_INFO = 1.1
|
||||||
STATUS_TEACHING = 2.0
|
STATUS_TEACHING = 2.0
|
||||||
STATUS_ERROR = 1000.0
|
STATUS_ERROR = 1000.0
|
||||||
|
|
||||||
|
ADDITIONAL_MOVE_ORDER = 999
|
||||||
|
|
||||||
|
PRIORITY_GAME_ANALYSIS = -100
|
||||||
|
PRIORITY_SWEEP = -10 # sweep is live, but slow, so deprioritize
|
||||||
|
PRIORITY_ALTERNATIVES = 100 # extra analysis, live interaction
|
||||||
|
PRIORITY_EQUALIZE = 100
|
||||||
|
PRIORITY_EXTRA_ANALYSIS = 100
|
||||||
|
PRIORITY_DEFAULT = 1000 # new move, high pri
|
||||||
|
PRIORITY_EXTRA_AI_QUERY = 10_000
|
||||||
|
|
||||||
PLAYER_HUMAN, PLAYER_AI = "player:human", "player:ai"
|
PLAYER_HUMAN, PLAYER_AI = "player:human", "player:ai"
|
||||||
PLAYER_TYPES = [PLAYER_HUMAN, PLAYER_AI]
|
PLAYER_TYPES = [PLAYER_HUMAN, PLAYER_AI]
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import traceback
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
|
||||||
from katrain.core.constants import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_INFO, OUTPUT_KATAGO_STDERR, DATA_FOLDER
|
from katrain.core.constants import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_INFO, OUTPUT_KATAGO_STDERR, DATA_FOLDER
|
||||||
from katrain.core.engine import EngineDiedException, BaseEngine
|
from katrain.core.engine import BaseEngine
|
||||||
from katrain.core.game import BaseGame
|
from katrain.core.game import BaseGame
|
||||||
from katrain.core.lang import i18n
|
from katrain.core.lang import i18n
|
||||||
from katrain.core.sgf_parser import Move
|
from katrain.core.sgf_parser import Move
|
||||||
@@ -162,9 +162,9 @@ class KataGoContributeEngine(BaseEngine):
|
|||||||
self.stdout_thread.start()
|
self.stdout_thread.start()
|
||||||
self.stderr_thread.start()
|
self.stderr_thread.start()
|
||||||
|
|
||||||
def check_alive(self, os_error="", exception_if_dead=False):
|
def check_alive(self, os_error="", maybe_open_help=False):
|
||||||
ok = self.katago_process and self.katago_process.poll() is None
|
ok = self.katago_process and self.katago_process.poll() is None
|
||||||
if not ok and exception_if_dead:
|
if not ok:
|
||||||
if self.katago_process:
|
if self.katago_process:
|
||||||
code = self.katago_process and self.katago_process.poll()
|
code = self.katago_process and self.katago_process.poll()
|
||||||
if code == 3221225781:
|
if code == 3221225781:
|
||||||
@@ -175,10 +175,6 @@ class KataGoContributeEngine(BaseEngine):
|
|||||||
if code != 1 and not self.server_error: # deliberate exit, already showed message?
|
if code != 1 and not self.server_error: # deliberate exit, already showed message?
|
||||||
self.katrain.log(died_msg, OUTPUT_ERROR)
|
self.katrain.log(died_msg, OUTPUT_ERROR)
|
||||||
self.katago_process = None
|
self.katago_process = None
|
||||||
else:
|
|
||||||
died_msg = i18n._("Engine died unexpectedly").format(error=os_error)
|
|
||||||
if not self.server_error: # dont raise if already know what happened
|
|
||||||
raise EngineDiedException(died_msg)
|
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
def shutdown(self, finish=False):
|
def shutdown(self, finish=False):
|
||||||
@@ -210,8 +206,8 @@ class KataGoContributeEngine(BaseEngine):
|
|||||||
self.katrain.log(message, OUTPUT_KATAGO_STDERR)
|
self.katrain.log(message, OUTPUT_KATAGO_STDERR)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("ERROR in processing KataGo stderr:", line, "Exception", e)
|
print("ERROR in processing KataGo stderr:", line, "Exception", e)
|
||||||
elif self.katago_process:
|
elif self.katago_process and not self.check_alive():
|
||||||
self.check_alive(exception_if_dead=True)
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.katrain.log(f"Exception in reading stdout {e}", OUTPUT_DEBUG)
|
self.katrain.log(f"Exception in reading stdout {e}", OUTPUT_DEBUG)
|
||||||
return
|
return
|
||||||
@@ -273,8 +269,8 @@ class KataGoContributeEngine(BaseEngine):
|
|||||||
self.uploaded_games_count += 1
|
self.uploaded_games_count += 1
|
||||||
else:
|
else:
|
||||||
self.katrain.log(line, OUTPUT_KATAGO_STDERR)
|
self.katrain.log(line, OUTPUT_KATAGO_STDERR)
|
||||||
elif self.katago_process:
|
elif self.katago_process and not self.check_alive(): # stderr will do this
|
||||||
self.check_alive(exception_if_dead=False) # stderr will do this
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.katrain.log(f"Exception in reading stdout {e}", OUTPUT_DEBUG)
|
self.katrain.log(f"Exception in reading stdout {e}", OUTPUT_DEBUG)
|
||||||
return
|
return
|
||||||
+44
-33
@@ -5,7 +5,6 @@ import platform
|
|||||||
import queue
|
import queue
|
||||||
import shlex
|
import shlex
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
import traceback
|
import traceback
|
||||||
@@ -13,17 +12,20 @@ from typing import Callable, Dict, List, Optional
|
|||||||
|
|
||||||
from kivy.utils import platform as kivy_platform
|
from kivy.utils import platform as kivy_platform
|
||||||
|
|
||||||
from katrain.core.constants import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_KATAGO_STDERR, DATA_FOLDER
|
from katrain.core.constants import (
|
||||||
|
OUTPUT_DEBUG,
|
||||||
|
OUTPUT_ERROR,
|
||||||
|
OUTPUT_EXTRA_DEBUG,
|
||||||
|
OUTPUT_KATAGO_STDERR,
|
||||||
|
DATA_FOLDER,
|
||||||
|
KATAGO_EXCEPTION,
|
||||||
|
)
|
||||||
from katrain.core.game_node import GameNode
|
from katrain.core.game_node import GameNode
|
||||||
from katrain.core.lang import i18n
|
from katrain.core.lang import i18n
|
||||||
from katrain.core.sgf_parser import Move
|
from katrain.core.sgf_parser import Move
|
||||||
from katrain.core.utils import find_package_resource, json_truncate_arrays
|
from katrain.core.utils import find_package_resource, json_truncate_arrays
|
||||||
|
|
||||||
|
|
||||||
class EngineDiedException(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class BaseEngine: # some common elements between analysis and contribute engine
|
class BaseEngine: # some common elements between analysis and contribute engine
|
||||||
|
|
||||||
RULESETS_ABBR = [
|
RULESETS_ABBR = [
|
||||||
@@ -46,7 +48,7 @@ class BaseEngine: # some common elements between analysis and contribute engine
|
|||||||
if ruleset.strip().startswith("{"):
|
if ruleset.strip().startswith("{"):
|
||||||
try:
|
try:
|
||||||
ruleset = json.loads(ruleset)
|
ruleset = json.loads(ruleset)
|
||||||
except:
|
except json.JSONDecodeError:
|
||||||
pass
|
pass
|
||||||
if isinstance(ruleset, dict):
|
if isinstance(ruleset, dict):
|
||||||
return ruleset
|
return ruleset
|
||||||
@@ -73,17 +75,20 @@ class BaseEngine: # some common elements between analysis and contribute engine
|
|||||||
exepath, exename = os.path.split(exe)
|
exepath, exename = os.path.split(exe)
|
||||||
|
|
||||||
if exepath and not os.path.isfile(exe):
|
if exepath and not os.path.isfile(exe):
|
||||||
self.katrain.log(i18n._("Kata exe not found").format(exe=exe), OUTPUT_ERROR)
|
self.on_error(i18n._("Kata exe not found").format(exe=exe), "KATAGO-EXE")
|
||||||
return None
|
return None
|
||||||
elif not exepath:
|
elif not exepath:
|
||||||
paths = os.getenv("PATH", ".").split(os.pathsep) + ["/opt/homebrew/bin/"]
|
paths = os.getenv("PATH", ".").split(os.pathsep) + ["/opt/homebrew/bin/"]
|
||||||
exe_with_paths = [os.path.join(path, exe) for path in paths if os.path.isfile(os.path.join(path, exe))]
|
exe_with_paths = [os.path.join(path, exe) for path in paths if os.path.isfile(os.path.join(path, exe))]
|
||||||
if not exe_with_paths:
|
if not exe_with_paths:
|
||||||
self.katrain.log(i18n._("Kata exe not found in path").format(exe=exe), OUTPUT_ERROR)
|
self.on_error(i18n._("Kata exe not found in path").format(exe=exe), "KATAGO-EXE")
|
||||||
return None
|
return None
|
||||||
exe = exe_with_paths[0]
|
exe = exe_with_paths[0]
|
||||||
return exe
|
return exe
|
||||||
|
|
||||||
|
def on_error(self, message, code, allow_popup):
|
||||||
|
print("ERROR", message, code)
|
||||||
|
|
||||||
|
|
||||||
class KataGoEngine(BaseEngine):
|
class KataGoEngine(BaseEngine):
|
||||||
"""Starts and communicates with the KataGO analysis engine"""
|
"""Starts and communicates with the KataGO analysis engine"""
|
||||||
@@ -91,6 +96,7 @@ class KataGoEngine(BaseEngine):
|
|||||||
def __init__(self, katrain, config):
|
def __init__(self, katrain, config):
|
||||||
super().__init__(katrain, config)
|
super().__init__(katrain, config)
|
||||||
|
|
||||||
|
self.allow_recovery = self.config.get("allow_recovery", True) # if false, don't give popups
|
||||||
self.queries = {} # outstanding query id -> start time and callback
|
self.queries = {} # outstanding query id -> start time and callback
|
||||||
self.query_counter = 0
|
self.query_counter = 0
|
||||||
self.katago_process = None
|
self.katago_process = None
|
||||||
@@ -102,29 +108,32 @@ class KataGoEngine(BaseEngine):
|
|||||||
self.shell = False
|
self.shell = False
|
||||||
self.write_queue = queue.Queue()
|
self.write_queue = queue.Queue()
|
||||||
self.thread_lock = threading.Lock()
|
self.thread_lock = threading.Lock()
|
||||||
exe = config.get("katago", "").strip()
|
|
||||||
if config.get("altcommand", ""):
|
if config.get("altcommand", ""):
|
||||||
self.command = config["altcommand"]
|
self.command = config["altcommand"]
|
||||||
self.shell = True
|
self.shell = True
|
||||||
else:
|
else:
|
||||||
model = find_package_resource(config["model"])
|
model = find_package_resource(config["model"])
|
||||||
cfg = find_package_resource(config["config"])
|
cfg = find_package_resource(config["config"])
|
||||||
exe = self.get_engine_path(config["katago"])
|
exe = self.get_engine_path(config.get("katago", "").strip())
|
||||||
if not exe:
|
if not exe:
|
||||||
return
|
return
|
||||||
if not os.path.isfile(model):
|
if not os.path.isfile(model):
|
||||||
self.katrain.log(i18n._("Kata model not found").format(model=model), OUTPUT_ERROR)
|
self.on_error(i18n._("Kata model not found").format(model=model), code="KATAGO-FILES")
|
||||||
return # don't start
|
return # don't start
|
||||||
if not os.path.isfile(cfg):
|
if not os.path.isfile(cfg):
|
||||||
self.katrain.log(i18n._("Kata config not found").format(config=cfg), OUTPUT_ERROR)
|
self.on_error(i18n._("Kata config not found").format(config=cfg), code="KATAGO-FILES")
|
||||||
return # don't start
|
return # don't start
|
||||||
self.command = shlex.split(
|
self.command = shlex.split(
|
||||||
f'"{exe}" analysis -model "{model}" -config "{cfg}" -analysis-threads {config["threads"]} -override-config "homeDataDir={os.path.expanduser(DATA_FOLDER)}"'
|
f'"{exe}" analysis -model "{model}" -config "{cfg}" -analysis-threads {config["threads"]} -override-config "homeDataDir={os.path.expanduser(DATA_FOLDER)}"'
|
||||||
)
|
)
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def start(self):
|
def on_error(self, message, code=None, allow_popup=True):
|
||||||
|
self.katrain.log(message, OUTPUT_ERROR)
|
||||||
|
if self.allow_recovery and allow_popup:
|
||||||
|
self.katrain("engine_recovery_popup", message, code)
|
||||||
|
|
||||||
|
def start(self):
|
||||||
with self.thread_lock:
|
with self.thread_lock:
|
||||||
self.write_queue = queue.Queue()
|
self.write_queue = queue.Queue()
|
||||||
try:
|
try:
|
||||||
@@ -142,10 +151,7 @@ class KataGoEngine(BaseEngine):
|
|||||||
shell=self.shell,
|
shell=self.shell,
|
||||||
)
|
)
|
||||||
except (FileNotFoundError, PermissionError, OSError) as e:
|
except (FileNotFoundError, PermissionError, OSError) as e:
|
||||||
self.katrain.log(
|
self.on_error(i18n._("Starting Kata failed").format(command=self.command, error=e), code="c")
|
||||||
i18n._("Starting Kata failed").format(command=self.command, error=e),
|
|
||||||
OUTPUT_ERROR,
|
|
||||||
)
|
|
||||||
return # don't start
|
return # don't start
|
||||||
self.analysis_thread = threading.Thread(target=self._analysis_read_thread, daemon=True)
|
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.stderr_thread = threading.Thread(target=self._read_stderr_thread, daemon=True)
|
||||||
@@ -180,7 +186,7 @@ class KataGoEngine(BaseEngine):
|
|||||||
self.shutdown(finish=False)
|
self.shutdown(finish=False)
|
||||||
self.start()
|
self.start()
|
||||||
|
|
||||||
def check_alive(self, os_error="", exception_if_dead=False):
|
def check_alive(self, os_error="", exception_if_dead=False, maybe_open_recovery=False):
|
||||||
ok = self.katago_process and self.katago_process.poll() is None
|
ok = self.katago_process and self.katago_process.poll() is None
|
||||||
if not ok and exception_if_dead:
|
if not ok and exception_if_dead:
|
||||||
if self.katago_process:
|
if self.katago_process:
|
||||||
@@ -188,14 +194,12 @@ class KataGoEngine(BaseEngine):
|
|||||||
if code == 3221225781:
|
if code == 3221225781:
|
||||||
died_msg = i18n._("Engine missing DLL")
|
died_msg = i18n._("Engine missing DLL")
|
||||||
else:
|
else:
|
||||||
os_error += f"status {code}"
|
died_msg = i18n._("Engine died unexpectedly").format(error=f"{os_error} status {code}")
|
||||||
died_msg = i18n._("Engine died unexpectedly").format(error=os_error)
|
if code != 1: # deliberate exit
|
||||||
if code != 1: # deliberate exit, already showed message?
|
self.on_error(died_msg, code, allow_popup=maybe_open_recovery)
|
||||||
self.katrain.log(died_msg, OUTPUT_ERROR)
|
self.katago_process = None # return from threads
|
||||||
self.katago_process = None
|
|
||||||
else:
|
else:
|
||||||
died_msg = i18n._("Engine died unexpectedly").format(error=os_error)
|
self.katrain.log(i18n._("Engine died unexpectedly").format(error=os_error), OUTPUT_DEBUG)
|
||||||
raise EngineDiedException(died_msg)
|
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
def wait_to_finish(self):
|
def wait_to_finish(self):
|
||||||
@@ -227,14 +231,18 @@ class KataGoEngine(BaseEngine):
|
|||||||
try:
|
try:
|
||||||
line = self.katago_process.stderr.readline()
|
line = self.katago_process.stderr.readline()
|
||||||
if line:
|
if line:
|
||||||
|
if b"Uncaught exception" in line or b"what()" in line: # linux=what
|
||||||
|
msg = f"KataGo Engine Failed: {line.decode(errors='ignore')[9:].strip()}"
|
||||||
|
self.on_error(msg, KATAGO_EXCEPTION)
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
self.katrain.log(line.decode(errors="ignore").strip(), OUTPUT_KATAGO_STDERR)
|
self.katrain.log(line.decode(errors="ignore").strip(), OUTPUT_KATAGO_STDERR)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("ERROR in processing KataGo stderr:", line, "Exception", e)
|
print("ERROR in processing KataGo stderr:", line, "Exception", e)
|
||||||
elif self.katago_process:
|
elif not self.check_alive(exception_if_dead=True):
|
||||||
self.check_alive(exception_if_dead=True)
|
return
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.katrain.log(f"Exception in reading stdout {e}", OUTPUT_DEBUG)
|
self.katrain.log(f"Exception in reading stderr: {e}", OUTPUT_DEBUG)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _analysis_read_thread(self):
|
def _analysis_read_thread(self):
|
||||||
@@ -242,13 +250,15 @@ class KataGoEngine(BaseEngine):
|
|||||||
try:
|
try:
|
||||||
line = self.katago_process.stdout.readline().strip()
|
line = self.katago_process.stdout.readline().strip()
|
||||||
if self.katago_process and not line:
|
if self.katago_process and not line:
|
||||||
self.check_alive(exception_if_dead=True)
|
if not self.check_alive(exception_if_dead=True, maybe_open_recovery=True):
|
||||||
|
return
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
self.check_alive(os_error=str(e), exception_if_dead=True)
|
self.check_alive(os_error=str(e), exception_if_dead=True, maybe_open_recovery=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
if b"Uncaught exception" in line:
|
if b"Uncaught exception" in line:
|
||||||
self.katrain.log(f"KataGo Engine Failed: {line.decode(errors='ignore')}", OUTPUT_ERROR)
|
msg = f"KataGo Engine Failed: {line.decode(errors='ignore')}"
|
||||||
|
self.on_error(msg, KATAGO_EXCEPTION)
|
||||||
return
|
return
|
||||||
if not line:
|
if not line:
|
||||||
continue
|
continue
|
||||||
@@ -314,7 +324,8 @@ class KataGoEngine(BaseEngine):
|
|||||||
self.katago_process.stdin.write((json.dumps(query) + "\n").encode())
|
self.katago_process.stdin.write((json.dumps(query) + "\n").encode())
|
||||||
self.katago_process.stdin.flush()
|
self.katago_process.stdin.flush()
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
self.check_alive(os_error=str(e), exception_if_dead=False)
|
self.katrain.log(f"Exception in writing to katago: {e}", OUTPUT_DEBUG)
|
||||||
|
return # some other thread will take care of this
|
||||||
|
|
||||||
def send_query(self, query, callback, error_callback, next_move=None, node=None):
|
def send_query(self, query, callback, error_callback, next_move=None, node=None):
|
||||||
self.write_queue.put((query, callback, error_callback, next_move, node))
|
self.write_queue.put((query, callback, error_callback, next_move, node))
|
||||||
|
|||||||
+30
-17
@@ -1,7 +1,6 @@
|
|||||||
import copy
|
import copy
|
||||||
import math
|
import math
|
||||||
import os
|
import os
|
||||||
import random
|
|
||||||
import re
|
import re
|
||||||
import threading
|
import threading
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
@@ -21,6 +20,12 @@ from katrain.core.constants import (
|
|||||||
STATUS_ERROR,
|
STATUS_ERROR,
|
||||||
STATUS_INFO,
|
STATUS_INFO,
|
||||||
STATUS_TEACHING,
|
STATUS_TEACHING,
|
||||||
|
PRIORITY_GAME_ANALYSIS,
|
||||||
|
PRIORITY_EXTRA_ANALYSIS,
|
||||||
|
PRIORITY_SWEEP,
|
||||||
|
PRIORITY_ALTERNATIVES,
|
||||||
|
PRIORITY_EQUALIZE,
|
||||||
|
PRIORITY_DEFAULT,
|
||||||
)
|
)
|
||||||
from katrain.core.engine import KataGoEngine
|
from katrain.core.engine import KataGoEngine
|
||||||
from katrain.core.game_node import GameNode
|
from katrain.core.game_node import GameNode
|
||||||
@@ -92,7 +97,7 @@ class BaseGame:
|
|||||||
if handicap:
|
if handicap:
|
||||||
self.root.place_handicap_stones(handicap)
|
self.root.place_handicap_stones(handicap)
|
||||||
|
|
||||||
if not self.root.get_property("RU"):
|
if not self.root.get_property("RU"): # if rules missing in sgf, inherit current
|
||||||
self.root.set_property("RU", katrain.config("game/rules"))
|
self.root.set_property("RU", katrain.config("game/rules"))
|
||||||
|
|
||||||
self.set_current_node(self.root)
|
self.set_current_node(self.root)
|
||||||
@@ -439,11 +444,11 @@ class Game(BaseGame):
|
|||||||
self.region_of_interest = None
|
self.region_of_interest = None
|
||||||
|
|
||||||
threading.Thread(
|
threading.Thread(
|
||||||
target=lambda: self.analyze_all_nodes(-1_000_000, analyze_fast=analyze_fast, even_if_present=False),
|
target=lambda: self.analyze_all_nodes(analyze_fast=analyze_fast, even_if_present=False),
|
||||||
daemon=True,
|
daemon=True,
|
||||||
).start() # return faster, but bypass Kivy Clock
|
).start() # return faster, but bypass Kivy Clock
|
||||||
|
|
||||||
def analyze_all_nodes(self, priority=0, analyze_fast=False, even_if_present=True):
|
def analyze_all_nodes(self, priority=PRIORITY_GAME_ANALYSIS, analyze_fast=False, even_if_present=True):
|
||||||
for node in self.root.nodes_in_tree:
|
for node in self.root.nodes_in_tree:
|
||||||
# forced, or not present, or something went wrong in loading
|
# forced, or not present, or something went wrong in loading
|
||||||
if even_if_present or not node.analysis_from_sgf or not node.load_analysis():
|
if even_if_present or not node.analysis_from_sgf or not node.load_analysis():
|
||||||
@@ -502,14 +507,14 @@ class Game(BaseGame):
|
|||||||
]
|
]
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
if copy_from_node.move not in already_inserted_moves:
|
for m in copy_from_node.move_with_placements:
|
||||||
for m in copy_from_node.move_with_placements:
|
if m not in already_inserted_moves:
|
||||||
self._validate_move_and_update_chains(m, True)
|
self._validate_move_and_update_chains(m, True)
|
||||||
# this inserts
|
# this inserts
|
||||||
copy_to_node = GameNode(
|
copy_to_node = GameNode(
|
||||||
parent=copy_to_node, properties=copy.deepcopy(copy_from_node.properties)
|
parent=copy_to_node, properties=copy.deepcopy(copy_from_node.properties)
|
||||||
)
|
)
|
||||||
num_copied += 1
|
num_copied += 1
|
||||||
if not copy_from_node.children:
|
if not copy_from_node.children:
|
||||||
break
|
break
|
||||||
copy_from_node = copy_from_node.ordered_children[0]
|
copy_from_node = copy_from_node.ordered_children[0]
|
||||||
@@ -571,18 +576,26 @@ class Game(BaseGame):
|
|||||||
self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS)
|
self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS)
|
||||||
self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS)
|
self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS)
|
||||||
cn.analyze(
|
cn.analyze(
|
||||||
engine, visits=visits, priority=-1_000, region_of_interest=self.region_of_interest, time_limit=False
|
engine,
|
||||||
|
visits=visits,
|
||||||
|
priority=PRIORITY_EXTRA_ANALYSIS,
|
||||||
|
region_of_interest=self.region_of_interest,
|
||||||
|
time_limit=False,
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
if mode == "game":
|
if mode == "game":
|
||||||
nodes = self.root.nodes_in_tree
|
nodes = self.root.nodes_in_tree
|
||||||
|
only_mistakes = kwargs.get("mistakes_only", False)
|
||||||
|
threshold = self.katrain.config("trainer/eval_thresholds")[-4]
|
||||||
if "visits" in kwargs:
|
if "visits" in kwargs:
|
||||||
visits = kwargs["visits"]
|
visits = kwargs["visits"]
|
||||||
else:
|
else:
|
||||||
min_visits = min(node.analysis_visits_requested for node in nodes)
|
min_visits = min(node.analysis_visits_requested for node in nodes)
|
||||||
visits = min_visits + engine.config["max_visits"]
|
visits = min_visits + engine.config["max_visits"]
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
node.analyze(engine, visits=visits, priority=-1_000_000, time_limit=False, report_every=None)
|
max_point_loss = max(c.points_lost or 0 for c in [node] + node.children)
|
||||||
|
if not only_mistakes or max_point_loss > threshold:
|
||||||
|
node.analyze(engine, visits=visits, priority=-1_000_000, time_limit=False, report_every=None)
|
||||||
self.katrain.controls.set_status(i18n._("game re-analysis").format(visits=visits), STATUS_ANALYSIS)
|
self.katrain.controls.set_status(i18n._("game re-analysis").format(visits=visits), STATUS_ANALYSIS)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -613,19 +626,19 @@ class Game(BaseGame):
|
|||||||
]
|
]
|
||||||
visits = engine.config["fast_visits"]
|
visits = engine.config["fast_visits"]
|
||||||
self.katrain.controls.set_status(i18n._("sweep analysis").format(visits=visits), STATUS_ANALYSIS)
|
self.katrain.controls.set_status(i18n._("sweep analysis").format(visits=visits), STATUS_ANALYSIS)
|
||||||
priority = -1_000_000_000
|
priority = PRIORITY_SWEEP
|
||||||
elif mode in ["equalize", "alternative", "local"]:
|
elif mode in ["equalize", "alternative", "local"]:
|
||||||
if not cn.analysis_complete and mode != "local":
|
if not cn.analysis_complete and mode != "local":
|
||||||
self.katrain.controls.set_status(i18n._("wait-before-extra-analysis"), STATUS_INFO, self.current_node)
|
self.katrain.controls.set_status(i18n._("wait-before-extra-analysis"), STATUS_INFO, self.current_node)
|
||||||
return
|
return
|
||||||
if mode == "alternative": # also do a quick update on current candidates so it doesn't look too weird
|
if mode == "alternative": # also do a quick update on current candidates so it doesn't look too weird
|
||||||
self.katrain.controls.set_status(i18n._("alternative analysis"), STATUS_ANALYSIS)
|
self.katrain.controls.set_status(i18n._("alternative analysis"), STATUS_ANALYSIS)
|
||||||
cn.analyze(engine, priority=-500, time_limit=False, find_alternatives="alternative")
|
cn.analyze(engine, priority=PRIORITY_ALTERNATIVES, time_limit=False, find_alternatives="alternative")
|
||||||
visits = engine.config["fast_visits"]
|
visits = engine.config["fast_visits"]
|
||||||
else: # equalize
|
else: # equalize
|
||||||
visits = max(d["visits"] for d in cn.analysis["moves"].values())
|
visits = max(d["visits"] for d in cn.analysis["moves"].values())
|
||||||
self.katrain.controls.set_status(i18n._("equalizing analysis").format(visits=visits), STATUS_ANALYSIS)
|
self.katrain.controls.set_status(i18n._("equalizing analysis").format(visits=visits), STATUS_ANALYSIS)
|
||||||
priority = -1_000
|
priority = PRIORITY_EQUALIZE
|
||||||
analyze_moves = [Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items()]
|
analyze_moves = [Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items()]
|
||||||
else:
|
else:
|
||||||
raise ValueError("Invalid analysis mode")
|
raise ValueError("Invalid analysis mode")
|
||||||
@@ -653,7 +666,7 @@ class Game(BaseGame):
|
|||||||
self.engines[node.player].request_analysis(
|
self.engines[node.player].request_analysis(
|
||||||
node,
|
node,
|
||||||
callback=lambda result, _partial: set_analysis(node, result),
|
callback=lambda result, _partial: set_analysis(node, result),
|
||||||
priority=-1000,
|
priority=PRIORITY_DEFAULT,
|
||||||
analyze_fast=True,
|
analyze_fast=True,
|
||||||
extra_settings=engine_settings,
|
extra_settings=engine_settings,
|
||||||
**analysis_kwargs,
|
**analysis_kwargs,
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ from katrain.core.constants import (
|
|||||||
SGF_INTERNAL_COMMENTS_MARKER,
|
SGF_INTERNAL_COMMENTS_MARKER,
|
||||||
SGF_SEPARATOR_MARKER,
|
SGF_SEPARATOR_MARKER,
|
||||||
VERSION,
|
VERSION,
|
||||||
|
PRIORITY_DEFAULT,
|
||||||
|
ADDITIONAL_MOVE_ORDER,
|
||||||
)
|
)
|
||||||
from katrain.core.lang import i18n
|
from katrain.core.lang import i18n
|
||||||
from katrain.core.sgf_parser import Move, SGFNode
|
from katrain.core.sgf_parser import Move, SGFNode
|
||||||
@@ -39,6 +41,7 @@ class GameNode(SGFNode):
|
|||||||
def __init__(self, parent=None, properties=None, move=None):
|
def __init__(self, parent=None, properties=None, move=None):
|
||||||
super().__init__(parent=parent, properties=properties, move=move)
|
super().__init__(parent=parent, properties=properties, move=move)
|
||||||
self.auto_undo = None # None = not analyzed. False: not undone (good move). True: undone (bad move)
|
self.auto_undo = None # None = not analyzed. False: not undone (good move). True: undone (bad move)
|
||||||
|
self.played_sound = None
|
||||||
self.ai_thoughts = ""
|
self.ai_thoughts = ""
|
||||||
self.note = ""
|
self.note = ""
|
||||||
self.move_number = 0
|
self.move_number = 0
|
||||||
@@ -182,7 +185,7 @@ class GameNode(SGFNode):
|
|||||||
def analyze(
|
def analyze(
|
||||||
self,
|
self,
|
||||||
engine,
|
engine,
|
||||||
priority=0,
|
priority=PRIORITY_DEFAULT,
|
||||||
visits=None,
|
visits=None,
|
||||||
time_limit=True,
|
time_limit=True,
|
||||||
refine_move=None,
|
refine_move=None,
|
||||||
@@ -211,13 +214,17 @@ class GameNode(SGFNode):
|
|||||||
if cur is None:
|
if cur is None:
|
||||||
self.analysis["moves"][move_gtp] = {
|
self.analysis["moves"][move_gtp] = {
|
||||||
"move": move_gtp,
|
"move": move_gtp,
|
||||||
"order": 999,
|
"order": ADDITIONAL_MOVE_ORDER,
|
||||||
**move_analysis,
|
**move_analysis,
|
||||||
} # some default values for keys missing in rootInfo
|
} # some default values for keys missing in rootInfo
|
||||||
else:
|
else:
|
||||||
cur["order"] = min(cur["order"], move_analysis.get("order", 999)) # parent arriving after child
|
cur["order"] = min(
|
||||||
|
cur["order"], move_analysis.get("order", ADDITIONAL_MOVE_ORDER)
|
||||||
|
) # parent arriving after child
|
||||||
if cur["visits"] < move_analysis["visits"]:
|
if cur["visits"] < move_analysis["visits"]:
|
||||||
cur.update(move_analysis)
|
cur.update(move_analysis)
|
||||||
|
else: # prior etc only
|
||||||
|
cur.update({k: v for k, v in move_analysis.items() if k not in cur})
|
||||||
|
|
||||||
def set_analysis(
|
def set_analysis(
|
||||||
self,
|
self,
|
||||||
@@ -238,7 +245,7 @@ class GameNode(SGFNode):
|
|||||||
del m["order"]
|
del m["order"]
|
||||||
elif refine_move is None: # normal update: old moves to end, new order matters. also for region?
|
elif refine_move is None: # normal update: old moves to end, new order matters. also for region?
|
||||||
for move_dict in self.analysis["moves"].values():
|
for move_dict in self.analysis["moves"].values():
|
||||||
move_dict["order"] = 999 # old moves to end
|
move_dict["order"] = ADDITIONAL_MOVE_ORDER # old moves to end
|
||||||
for move_analysis in analysis_json["moveInfos"]:
|
for move_analysis in analysis_json["moveInfos"]:
|
||||||
self.update_move_analysis(move_analysis, move_analysis["move"])
|
self.update_move_analysis(move_analysis, move_analysis["move"])
|
||||||
self.analysis["ownership"] = analysis_json.get("ownership")
|
self.analysis["ownership"] = analysis_json.get("ownership")
|
||||||
@@ -344,7 +351,6 @@ class GameNode(SGFNode):
|
|||||||
if previous_top_move.get("pv") and (sgf or details):
|
if previous_top_move.get("pv") and (sgf or details):
|
||||||
pv = self.make_pv(single_move.player, previous_top_move["pv"], interactive)
|
pv = self.make_pv(single_move.player, previous_top_move["pv"], interactive)
|
||||||
text += i18n._("Info:PV").format(pv=pv) + "\n"
|
text += i18n._("Info:PV").format(pv=pv) + "\n"
|
||||||
|
|
||||||
if sgf or details or teach:
|
if sgf or details or teach:
|
||||||
currmove_pol_rank, currmove_pol_prob, policy_ranking = self.move_policy_stats()
|
currmove_pol_rank, currmove_pol_prob, policy_ranking = self.move_policy_stats()
|
||||||
if currmove_pol_rank is not None:
|
if currmove_pol_rank is not None:
|
||||||
@@ -417,10 +423,13 @@ class GameNode(SGFNode):
|
|||||||
root_score = self.analysis["root"]["scoreLead"]
|
root_score = self.analysis["root"]["scoreLead"]
|
||||||
root_winrate = self.analysis["root"]["winrate"]
|
root_winrate = self.analysis["root"]["winrate"]
|
||||||
move_dicts = list(self.analysis["moves"].values()) # prevent incoming analysis from causing crash
|
move_dicts = list(self.analysis["moves"].values()) # prevent incoming analysis from causing crash
|
||||||
|
top_move = [d for d in move_dicts if d["order"] == 0]
|
||||||
|
top_score_lead = top_move[0]["scoreLead"] if top_move else root_score
|
||||||
return sorted(
|
return sorted(
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"pointsLost": self.player_sign(self.next_player) * (root_score - d["scoreLead"]),
|
"pointsLost": self.player_sign(self.next_player) * (root_score - d["scoreLead"]),
|
||||||
|
"relativePointsLost": self.player_sign(self.next_player) * (top_score_lead - d["scoreLead"]),
|
||||||
"winrateLost": self.player_sign(self.next_player) * (root_winrate - d["winrate"]),
|
"winrateLost": self.player_sign(self.next_player) * (root_winrate - d["winrate"]),
|
||||||
**d,
|
**d,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -448,7 +448,7 @@ class SGF:
|
|||||||
try:
|
try:
|
||||||
self.ix = self.contents.index("(") + 1
|
self.ix = self.contents.index("(") + 1
|
||||||
except ValueError:
|
except ValueError:
|
||||||
raise ParseError("Parse error: Expected '('")
|
raise ParseError(f"Parse error: Expected '(' at start, found {self.contents[:50]}")
|
||||||
self.root = self._NODE_CLASS()
|
self.root = self._NODE_CLASS()
|
||||||
self._parse_branch(self.root)
|
self._parse_branch(self.root)
|
||||||
|
|
||||||
@@ -465,7 +465,7 @@ class SGF:
|
|||||||
self._parse_branch(self._NODE_CLASS(parent=current_move))
|
self._parse_branch(self._NODE_CLASS(parent=current_move))
|
||||||
elif matched_item == ";":
|
elif matched_item == ";":
|
||||||
# ignore ;) for old SGF
|
# ignore ;) for old SGF
|
||||||
useless = self.ix < len(self.contents) and self.contents[self.ix] == ")"
|
useless = self.ix < len(self.contents) and self.contents[self.ix :].strip() == ")"
|
||||||
# ignore ; that generate empty nodes
|
# ignore ; that generate empty nodes
|
||||||
if not (current_move.empty or useless):
|
if not (current_move.empty or useless):
|
||||||
current_move = self._NODE_CLASS(parent=current_move)
|
current_move = self._NODE_CLASS(parent=current_move)
|
||||||
|
|||||||
+58
-2
@@ -25,11 +25,13 @@
|
|||||||
#<Label>:
|
#<Label>:
|
||||||
# canvas.before:
|
# canvas.before:
|
||||||
# Color:
|
# Color:
|
||||||
# rgba: RED
|
# rgba: [1,0,0,1]
|
||||||
# Line:
|
# Line:
|
||||||
# rectangle: (*self.pos,*self.size)
|
# rectangle: (*self.pos,*self.size)
|
||||||
# width: 1
|
# width: 1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# mixins
|
# mixins
|
||||||
<BackgroundMixin>:
|
<BackgroundMixin>:
|
||||||
canvas.before:
|
canvas.before:
|
||||||
@@ -45,6 +47,49 @@
|
|||||||
rounded_rectangle: (self.x + root.outline_width, self.y + root.outline_width, self.width - 2 * root.outline_width, self.height - 2*root.outline_width, root.background_radius,root.background_radius,root.background_radius,root.background_radius,max(1,root.height))
|
rounded_rectangle: (self.x + root.outline_width, self.y + root.outline_width, self.width - 2 * root.outline_width, self.height - 2*root.outline_width, root.background_radius,root.background_radius,root.background_radius,root.background_radius,max(1,root.height))
|
||||||
width: root.outline_width or 1
|
width: root.outline_width or 1
|
||||||
|
|
||||||
|
# table cells, stats panel mainly
|
||||||
|
|
||||||
|
<TableCellLabel>:
|
||||||
|
font_size: self.height/3
|
||||||
|
padding: [Theme.CP_PADDING,Theme.CP_PADDING]
|
||||||
|
line_height: 0.8
|
||||||
|
halign: 'center'
|
||||||
|
canvas.before:
|
||||||
|
Color:
|
||||||
|
rgba: root.background_color
|
||||||
|
Rectangle:
|
||||||
|
size: self.size
|
||||||
|
pos: self.pos
|
||||||
|
canvas:
|
||||||
|
Color:
|
||||||
|
rgba: root.outline_color
|
||||||
|
Line: # left
|
||||||
|
points: (root.x + root.outline_width,root.y,root.x + root.outline_width,root.y+root.size[1])
|
||||||
|
width: root.outline_width if 'left' in root.outlines else 1e-6
|
||||||
|
cap: 'square'
|
||||||
|
Line: # right
|
||||||
|
points: (root.x + root.size[0] - root.outline_width,root.y,root.x + root.size[0] - root.outline_width,root.y+root.size[1])
|
||||||
|
width: root.outline_width if 'right' in root.outlines else 1e-6
|
||||||
|
cap: 'square'
|
||||||
|
Line: # top
|
||||||
|
points: (root.x, root.y+root.size[1]-root.outline_width, root.x + root.size[0], root.y+root.size[1]-root.outline_width)
|
||||||
|
width: root.outline_width if 'top' in root.outlines else 1e-6
|
||||||
|
cap: 'square'
|
||||||
|
Line: # bottom
|
||||||
|
points: (root.x, root.y+root.outline_width, root.x + root.size[0], root.y+root.outline_width)
|
||||||
|
width: root.outline_width * int('bottom' in root.outlines) + 1e-6
|
||||||
|
cap: 'square'
|
||||||
|
|
||||||
|
<TableHeaderLabel>:
|
||||||
|
font_size: self.height/2.5
|
||||||
|
|
||||||
|
<TableStatLabel>:
|
||||||
|
canvas.before:
|
||||||
|
Color:
|
||||||
|
rgba: root.bar_color
|
||||||
|
Rectangle:
|
||||||
|
pos: root.x if root.side=='right' else root.x+root.size[0]*(1 - min(1,root.value/root.scale)), root.y
|
||||||
|
size: root.size[0] * min(1,root.value/root.scale), root.size[1]
|
||||||
|
|
||||||
|
|
||||||
# new widgets
|
# new widgets
|
||||||
@@ -273,7 +318,8 @@
|
|||||||
padding: [2*CP_PADDING,CP_PADDING,CP_PADDING,CP_PADDING]
|
padding: [2*CP_PADDING,CP_PADDING,CP_PADDING,CP_PADDING]
|
||||||
subtype_label: subtype_label
|
subtype_label: subtype_label
|
||||||
CircleWithText:
|
CircleWithText:
|
||||||
size_hint: 0.32, 1
|
size_hint_x: 0.32 if root.alignment=='right' else EPSILON
|
||||||
|
opacity: 1 if root.alignment=='right' else 0
|
||||||
player: root.player
|
player: root.player
|
||||||
text: str(root.captures)
|
text: str(root.captures)
|
||||||
BoxLayout:
|
BoxLayout:
|
||||||
@@ -299,6 +345,11 @@
|
|||||||
valign: 'middle'
|
valign: 'middle'
|
||||||
shorten: root.player_type==PLAYER_HUMAN # overflow/cut rank off AI names
|
shorten: root.player_type==PLAYER_HUMAN # overflow/cut rank off AI names
|
||||||
max_lines: 1
|
max_lines: 1
|
||||||
|
CircleWithText:
|
||||||
|
size_hint_x: EPSILON if root.alignment=='right' else 0.32
|
||||||
|
opacity: 0 if root.alignment=='right' else 1
|
||||||
|
player: root.player
|
||||||
|
text: str(root.captures)
|
||||||
|
|
||||||
<PauseButton>:
|
<PauseButton>:
|
||||||
height: self.width
|
height: self.width
|
||||||
@@ -765,6 +816,11 @@
|
|||||||
icon: 'analysis.png'
|
icon: 'analysis.png'
|
||||||
shortcut: ''
|
shortcut: ''
|
||||||
on_action: root.open_game_analysis_popup()
|
on_action: root.open_game_analysis_popup()
|
||||||
|
AnalysisMenuItem:
|
||||||
|
text: i18n._("analysis:report")
|
||||||
|
icon: 'analysis.png'
|
||||||
|
shortcut: 'F3'
|
||||||
|
on_action: root.open_report_popup()
|
||||||
AnalysisMenuItem:
|
AnalysisMenuItem:
|
||||||
text: i18n._("analysis:continuous")
|
text: i18n._("analysis:continuous")
|
||||||
icon: 'play.png'
|
icon: 'play.png'
|
||||||
|
|||||||
+32
-12
@@ -4,7 +4,6 @@ import time
|
|||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
from kivy.clock import Clock
|
from kivy.clock import Clock
|
||||||
from kivy.core.audio import SoundLoader
|
|
||||||
from kivy.core.window import Window
|
from kivy.core.window import Window
|
||||||
from kivy.graphics.context_instructions import Color
|
from kivy.graphics.context_instructions import Color
|
||||||
from kivy.graphics.vertex_instructions import Ellipse, Line, Rectangle
|
from kivy.graphics.vertex_instructions import Ellipse, Line, Rectangle
|
||||||
@@ -15,6 +14,7 @@ from kivy.uix.widget import Widget
|
|||||||
from kivymd.app import MDApp
|
from kivymd.app import MDApp
|
||||||
from kivymd.uix.boxlayout import MDBoxLayout
|
from kivymd.uix.boxlayout import MDBoxLayout
|
||||||
from kivymd.uix.floatlayout import MDFloatLayout
|
from kivymd.uix.floatlayout import MDFloatLayout
|
||||||
|
from katrain.gui.sound import play_sound
|
||||||
|
|
||||||
from katrain.core.constants import (
|
from katrain.core.constants import (
|
||||||
MODE_PLAY,
|
MODE_PLAY,
|
||||||
@@ -33,7 +33,7 @@ from katrain.core.game import Move
|
|||||||
from katrain.core.lang import i18n
|
from katrain.core.lang import i18n
|
||||||
from katrain.core.utils import evaluation_class, format_visits, var_to_grid, json_truncate_arrays
|
from katrain.core.utils import evaluation_class, format_visits, var_to_grid, json_truncate_arrays
|
||||||
from katrain.gui.kivyutils import draw_circle, draw_text, cached_texture
|
from katrain.gui.kivyutils import draw_circle, draw_text, cached_texture
|
||||||
from katrain.gui.popups import I18NPopup, ReAnalyzeGamePopup
|
from katrain.gui.popups import I18NPopup, ReAnalyzeGamePopup, GameReportPopup
|
||||||
from katrain.gui.theme import Theme
|
from katrain.gui.theme import Theme
|
||||||
|
|
||||||
|
|
||||||
@@ -53,6 +53,7 @@ class BadukPanWidget(Widget):
|
|||||||
|
|
||||||
self.active_pv_moves = []
|
self.active_pv_moves = []
|
||||||
self.animating_pv = None
|
self.animating_pv = None
|
||||||
|
self.animating_pv_index = None
|
||||||
self.last_mouse_pos = (0, 0)
|
self.last_mouse_pos = (0, 0)
|
||||||
Window.bind(mouse_pos=self.on_mouse_pos)
|
Window.bind(mouse_pos=self.on_mouse_pos)
|
||||||
self.redraw_board_contents_trigger = Clock.create_trigger(self.draw_board_contents, 0.05)
|
self.redraw_board_contents_trigger = Clock.create_trigger(self.draw_board_contents, 0.05)
|
||||||
@@ -70,12 +71,7 @@ class BadukPanWidget(Widget):
|
|||||||
return self.draw_coords_enabled
|
return self.draw_coords_enabled
|
||||||
|
|
||||||
def play_stone_sound(self, *_args):
|
def play_stone_sound(self, *_args):
|
||||||
if self.katrain.config("timer/sound"):
|
play_sound(random.choice(Theme.STONE_SOUNDS))
|
||||||
if not self.stones_sounds:
|
|
||||||
self.stones_sounds = [SoundLoader.load(file) for file in Theme.STONE_SOUNDS]
|
|
||||||
sound = random.choice(self.stones_sounds)
|
|
||||||
if sound:
|
|
||||||
sound.play()
|
|
||||||
|
|
||||||
# stone placement functions
|
# stone placement functions
|
||||||
def _find_closest(self, pos, gridpos):
|
def _find_closest(self, pos, gridpos):
|
||||||
@@ -107,7 +103,8 @@ class BadukPanWidget(Widget):
|
|||||||
self.redraw_hover_contents_trigger()
|
self.redraw_hover_contents_trigger()
|
||||||
|
|
||||||
def on_touch_down(self, touch):
|
def on_touch_down(self, touch):
|
||||||
self.set_animating_pv(None, None) # any click kills PV from label/move
|
if touch.button not in ["scrollup", "scrolldown"]:
|
||||||
|
self.set_animating_pv(None, None) # any click kills PV from label/move
|
||||||
if "button" in touch.profile and touch.button != "left":
|
if "button" in touch.profile and touch.button != "left":
|
||||||
return
|
return
|
||||||
if self.selecting_region_of_interest:
|
if self.selecting_region_of_interest:
|
||||||
@@ -685,8 +682,7 @@ class BadukPanWidget(Widget):
|
|||||||
animating_pv = self.animating_pv
|
animating_pv = self.animating_pv
|
||||||
if animating_pv:
|
if animating_pv:
|
||||||
pv, node, start_time, _ = animating_pv
|
pv, node, start_time, _ = animating_pv
|
||||||
delay = self.katrain.config("general/anim_pv_time", 0.5)
|
up_to_move = self.get_animate_pv_index()
|
||||||
up_to_move = (time.time() - start_time) / delay
|
|
||||||
self.draw_pv(pv, node, up_to_move)
|
self.draw_pv(pv, node, up_to_move)
|
||||||
|
|
||||||
if getattr(self.katrain.game, "region_of_interest", None):
|
if getattr(self.katrain.game, "region_of_interest", None):
|
||||||
@@ -749,6 +745,7 @@ class BadukPanWidget(Widget):
|
|||||||
draw_text(pos=board_coords, text=str(i + 1), font_size=self.grid_size * sizefac / 1.45, font_name="Roboto")
|
draw_text(pos=board_coords, text=str(i + 1), font_size=self.grid_size * sizefac / 1.45, font_name="Roboto")
|
||||||
|
|
||||||
def set_animating_pv(self, pv, node):
|
def set_animating_pv(self, pv, node):
|
||||||
|
self.animating_pv_index = None
|
||||||
if pv is None:
|
if pv is None:
|
||||||
self.animating_pv = None
|
self.animating_pv = None
|
||||||
elif node is not None and (
|
elif node is not None and (
|
||||||
@@ -757,17 +754,40 @@ class BadukPanWidget(Widget):
|
|||||||
self.animating_pv = (pv, node, time.time(), self.last_mouse_pos)
|
self.animating_pv = (pv, node, time.time(), self.last_mouse_pos)
|
||||||
self.redraw_hover_contents_trigger()
|
self.redraw_hover_contents_trigger()
|
||||||
|
|
||||||
|
def adjust_animate_pv_index(self, delta=1):
|
||||||
|
self.animating_pv_index = max(0, self.get_animate_pv_index() + delta)
|
||||||
|
|
||||||
|
def get_animate_pv_index(self):
|
||||||
|
if self.animating_pv_index is None:
|
||||||
|
if self.animating_pv:
|
||||||
|
pv, node, start_time, _ = self.animating_pv
|
||||||
|
delay = self.katrain.config("general/anim_pv_time", 0.5)
|
||||||
|
return (time.time() - start_time) / delay
|
||||||
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
return self.animating_pv_index
|
||||||
|
|
||||||
def show_pv_from_comments(self, pv_str):
|
def show_pv_from_comments(self, pv_str):
|
||||||
self.set_animating_pv(pv_str[1:].split(" "), self.katrain.controls.active_comment_node.parent)
|
self.set_animating_pv(pv_str[1:].split(" "), self.katrain.controls.active_comment_node.parent)
|
||||||
|
|
||||||
|
|
||||||
class AnalysisDropDown(DropDown):
|
class AnalysisDropDown(DropDown):
|
||||||
def open_game_analysis_popup(self, *_args):
|
def open_game_analysis_popup(self, *_args):
|
||||||
analysis_popup = I18NPopup(title_key="analysis:game", size=[dp(500), dp(300)], content=ReAnalyzeGamePopup())
|
analysis_popup = I18NPopup(title_key="analysis:game", size=[dp(500), dp(350)], content=ReAnalyzeGamePopup())
|
||||||
analysis_popup.content.popup = analysis_popup
|
analysis_popup.content.popup = analysis_popup
|
||||||
analysis_popup.content.katrain = MDApp.get_running_app().gui
|
analysis_popup.content.katrain = MDApp.get_running_app().gui
|
||||||
analysis_popup.open()
|
analysis_popup.open()
|
||||||
|
|
||||||
|
def open_report_popup(self, *_args):
|
||||||
|
report_popup = I18NPopup(
|
||||||
|
title_key="analysis:report",
|
||||||
|
size=[dp(750), dp(750)],
|
||||||
|
content=GameReportPopup(katrain=MDApp.get_running_app().gui),
|
||||||
|
)
|
||||||
|
report_popup.content.popup = report_popup
|
||||||
|
report_popup.open()
|
||||||
|
|
||||||
|
|
||||||
class AnalysisControls(MDBoxLayout):
|
class AnalysisControls(MDBoxLayout):
|
||||||
dropdown = ObjectProperty(None)
|
dropdown = ObjectProperty(None)
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from kivy.clock import Clock
|
from kivy.clock import Clock
|
||||||
from kivy.core.audio import SoundLoader
|
|
||||||
from kivy.properties import ObjectProperty, OptionProperty
|
from kivy.properties import ObjectProperty, OptionProperty
|
||||||
from kivy.uix.boxlayout import BoxLayout
|
from kivy.uix.boxlayout import BoxLayout
|
||||||
from kivymd.uix.floatlayout import MDFloatLayout
|
from kivymd.uix.floatlayout import MDFloatLayout
|
||||||
@@ -18,6 +17,7 @@ from katrain.core.constants import (
|
|||||||
from katrain.core.lang import rank_label
|
from katrain.core.lang import rank_label
|
||||||
from katrain.gui.kivyutils import AnalysisToggle, CollapsablePanel
|
from katrain.gui.kivyutils import AnalysisToggle, CollapsablePanel
|
||||||
from katrain.gui.theme import Theme
|
from katrain.gui.theme import Theme
|
||||||
|
from katrain.gui.sound import play_sound, stop_sound
|
||||||
|
|
||||||
|
|
||||||
class PlayAnalyzeSelect(MDFloatLayout):
|
class PlayAnalyzeSelect(MDFloatLayout):
|
||||||
@@ -80,10 +80,6 @@ class ControlsPanel(BoxLayout):
|
|||||||
self.status_state = (None, -1e9, None)
|
self.status_state = (None, -1e9, None)
|
||||||
self.active_comment_node = None
|
self.active_comment_node = None
|
||||||
self.last_timer_update = (None, 0, False)
|
self.last_timer_update = (None, 0, False)
|
||||||
self.beep = SoundLoader.load(Theme.COUNTDOWN_SOUND)
|
|
||||||
self.boing = SoundLoader.load(Theme.MINIMUM_TIME_PASSED_SOUND)
|
|
||||||
if self.boing:
|
|
||||||
self.boing.volume = 0.1
|
|
||||||
self.beep_start = 5.2
|
self.beep_start = 5.2
|
||||||
self.timer_interval = 0.07
|
self.timer_interval = 0.07
|
||||||
|
|
||||||
@@ -228,13 +224,12 @@ class ControlsPanel(BoxLayout):
|
|||||||
if (
|
if (
|
||||||
min_use
|
min_use
|
||||||
and not new_beeping
|
and not new_beeping
|
||||||
and self.boing
|
|
||||||
and boing_at_remaining - self.timer_interval
|
and boing_at_remaining - self.timer_interval
|
||||||
< time_remaining
|
< time_remaining
|
||||||
< boing_at_remaining + self.timer_interval
|
< boing_at_remaining + self.timer_interval
|
||||||
and player.periods_used < byo_num
|
and player.periods_used < byo_num
|
||||||
):
|
):
|
||||||
self.boing.play()
|
play_sound(Theme.MINIMUM_TIME_PASSED_SOUND, volume=0.1)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
new_beeping = False
|
new_beeping = False
|
||||||
@@ -247,10 +242,9 @@ class ControlsPanel(BoxLayout):
|
|||||||
|
|
||||||
if sounds_on:
|
if sounds_on:
|
||||||
if beeping and not new_beeping and not used_period:
|
if beeping and not new_beeping and not used_period:
|
||||||
self.beep.stop()
|
stop_sound(Theme.COUNTDOWN_SOUND)
|
||||||
elif not beeping and new_beeping and self.beep:
|
elif not beeping and new_beeping:
|
||||||
self.beep.volume = 0.5 if periods_rem > 1 else 1
|
play_sound(Theme.COUNTDOWN_SOUND, volume=0.5 if periods_rem > 1 else 1)
|
||||||
self.beep.play()
|
|
||||||
|
|
||||||
self.last_timer_update = (current_node, now, new_beeping)
|
self.last_timer_update = (current_node, now, new_beeping)
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,38 @@ class BackgroundMixin(Widget): # -- mixins
|
|||||||
outline_width = NumericProperty(1)
|
outline_width = NumericProperty(1)
|
||||||
|
|
||||||
|
|
||||||
|
class BackgroundLabel(BackgroundMixin, Label):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TableCellLabel(Label):
|
||||||
|
background_color = ListProperty([0, 0, 0, 0])
|
||||||
|
line_width = NumericProperty(0)
|
||||||
|
outlines = ListProperty([])
|
||||||
|
outline_color = Theme.LINE_COLOR
|
||||||
|
outline_width = NumericProperty(1.1)
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
kwargs["font_name"] = kwargs.get("font_name", i18n.font_name)
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class TableStatLabel(TableCellLabel):
|
||||||
|
side = StringProperty("right")
|
||||||
|
value = NumericProperty(0)
|
||||||
|
scale = NumericProperty(100)
|
||||||
|
bar_color = ListProperty([0, 0, 0, 1])
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
if "outlines" not in kwargs:
|
||||||
|
self.outlines = ["left"] if self.side == "right" else ["right"]
|
||||||
|
|
||||||
|
|
||||||
|
class TableHeaderLabel(TableCellLabel):
|
||||||
|
outlines = ["bottom"]
|
||||||
|
|
||||||
|
|
||||||
class LeftButtonBehavior(ButtonBehavior): # stops buttons etc activating on right click
|
class LeftButtonBehavior(ButtonBehavior): # stops buttons etc activating on right click
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
self.register_event_type("on_left_release")
|
self.register_event_type("on_left_release")
|
||||||
@@ -371,7 +403,7 @@ class PlayerSetupBlock(MDBoxLayout):
|
|||||||
|
|
||||||
|
|
||||||
class PlayerInfo(MDBoxLayout, BackgroundMixin):
|
class PlayerInfo(MDBoxLayout, BackgroundMixin):
|
||||||
captures = NumericProperty(0)
|
captures = ObjectProperty(0)
|
||||||
player = OptionProperty("B", options=["B", "W"])
|
player = OptionProperty("B", options=["B", "W"])
|
||||||
player_type = StringProperty("Player")
|
player_type = StringProperty("Player")
|
||||||
komi = NumericProperty(0)
|
komi = NumericProperty(0)
|
||||||
@@ -379,6 +411,7 @@ class PlayerInfo(MDBoxLayout, BackgroundMixin):
|
|||||||
name = StringProperty("", allownone=True)
|
name = StringProperty("", allownone=True)
|
||||||
rank = StringProperty("", allownone=True)
|
rank = StringProperty("", allownone=True)
|
||||||
active = BooleanProperty(True)
|
active = BooleanProperty(True)
|
||||||
|
alignment = StringProperty("right")
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
|
|||||||
+136
-17
@@ -14,6 +14,7 @@ from kivy.metrics import dp
|
|||||||
from kivy.properties import BooleanProperty, ListProperty, NumericProperty, ObjectProperty, StringProperty
|
from kivy.properties import BooleanProperty, ListProperty, NumericProperty, ObjectProperty, StringProperty
|
||||||
from kivy.uix.anchorlayout import AnchorLayout
|
from kivy.uix.anchorlayout import AnchorLayout
|
||||||
from kivy.uix.boxlayout import BoxLayout
|
from kivy.uix.boxlayout import BoxLayout
|
||||||
|
from kivy.uix.gridlayout import GridLayout
|
||||||
from kivy.uix.label import Label
|
from kivy.uix.label import Label
|
||||||
from kivy.uix.popup import Popup
|
from kivy.uix.popup import Popup
|
||||||
from kivy.utils import platform
|
from kivy.utils import platform
|
||||||
@@ -22,7 +23,7 @@ from kivymd.uix.boxlayout import MDBoxLayout
|
|||||||
from kivymd.uix.selectioncontrol import MDCheckbox
|
from kivymd.uix.selectioncontrol import MDCheckbox
|
||||||
from kivymd.uix.textfield import MDTextField
|
from kivymd.uix.textfield import MDTextField
|
||||||
|
|
||||||
from katrain.core.ai import ai_rank_estimation
|
from katrain.core.ai import ai_rank_estimation, game_report
|
||||||
from katrain.core.constants import (
|
from katrain.core.constants import (
|
||||||
AI_CONFIG_DEFAULT,
|
AI_CONFIG_DEFAULT,
|
||||||
AI_DEFAULT,
|
AI_DEFAULT,
|
||||||
@@ -35,11 +36,24 @@ from katrain.core.constants import (
|
|||||||
OUTPUT_INFO,
|
OUTPUT_INFO,
|
||||||
SGF_INTERNAL_COMMENTS_MARKER,
|
SGF_INTERNAL_COMMENTS_MARKER,
|
||||||
STATUS_INFO,
|
STATUS_INFO,
|
||||||
|
PLAYER_HUMAN,
|
||||||
|
ADDITIONAL_MOVE_ORDER,
|
||||||
)
|
)
|
||||||
from katrain.core.engine import KataGoEngine
|
from katrain.core.engine import KataGoEngine
|
||||||
from katrain.core.lang import i18n, rank_label
|
from katrain.core.lang import i18n, rank_label
|
||||||
from katrain.core.utils import PATHS, find_package_resource
|
from katrain.core.sgf_parser import Move
|
||||||
from katrain.gui.kivyutils import BackgroundMixin, I18NSpinner
|
from katrain.core.utils import PATHS, find_package_resource, evaluation_class
|
||||||
|
from katrain.gui.kivyutils import (
|
||||||
|
BackgroundMixin,
|
||||||
|
I18NSpinner,
|
||||||
|
BackgroundLabel,
|
||||||
|
TableHeaderLabel,
|
||||||
|
TableCellLabel,
|
||||||
|
TableStatLabel,
|
||||||
|
PlayerInfo,
|
||||||
|
SizedRectangleButton,
|
||||||
|
AutoSizedRectangleButton,
|
||||||
|
)
|
||||||
from katrain.gui.theme import Theme
|
from katrain.gui.theme import Theme
|
||||||
from katrain.gui.widgets.progress_loader import ProgressLoader
|
from katrain.gui.widgets.progress_loader import ProgressLoader
|
||||||
|
|
||||||
@@ -353,9 +367,9 @@ class ConfigTeacherPopup(QuickConfigGui):
|
|||||||
for k in ["dot color", "point loss threshold", "num undos", "show dots", "save dots"]:
|
for k in ["dot color", "point loss threshold", "num undos", "show dots", "save dots"]:
|
||||||
self.options_grid.add_widget(DescriptionLabel(text=i18n._(k), font_name=i18n.font_name, font_size=dp(17)))
|
self.options_grid.add_widget(DescriptionLabel(text=i18n._(k), font_name=i18n.font_name, font_size=dp(17)))
|
||||||
|
|
||||||
for i, (color, threshold, undo, show_dot, savesgf) in enumerate(
|
for i, color, threshold, undo, show_dot, savesgf in list(
|
||||||
zip(Theme.EVAL_COLORS[theme], thresholds, undos, show_dots, savesgfs)
|
zip(range(len(thresholds)), Theme.EVAL_COLORS[theme], thresholds, undos, show_dots, savesgfs)
|
||||||
):
|
)[::-1]:
|
||||||
self.add_option_widgets(
|
self.add_option_widgets(
|
||||||
[
|
[
|
||||||
BackgroundMixin(background_color=color, size_hint=[0.9, 0.9]),
|
BackgroundMixin(background_color=color, size_hint=[0.9, 0.9]),
|
||||||
@@ -438,6 +452,16 @@ class ConfigAIPopup(QuickConfigGui):
|
|||||||
Clock.schedule_once(self.katrain.controls.update_players, 0)
|
Clock.schedule_once(self.katrain.controls.update_players, 0)
|
||||||
|
|
||||||
|
|
||||||
|
class EngineRecoveryPopup(QuickConfigGui):
|
||||||
|
error_message = StringProperty("")
|
||||||
|
code = ObjectProperty(None)
|
||||||
|
|
||||||
|
def __init__(self, katrain, error_message, code):
|
||||||
|
super().__init__(katrain)
|
||||||
|
self.error_message = str(error_message)
|
||||||
|
self.code = code
|
||||||
|
|
||||||
|
|
||||||
class BaseConfigPopup(QuickConfigGui):
|
class BaseConfigPopup(QuickConfigGui):
|
||||||
MODEL_ENDPOINTS = {
|
MODEL_ENDPOINTS = {
|
||||||
"Latest distributed model": "https://katagotraining.org/api/networks/newest_training/",
|
"Latest distributed model": "https://katagotraining.org/api/networks/newest_training/",
|
||||||
@@ -455,20 +479,20 @@ class BaseConfigPopup(QuickConfigGui):
|
|||||||
|
|
||||||
KATAGOS = {
|
KATAGOS = {
|
||||||
"win": {
|
"win": {
|
||||||
"OpenCL v1.8.1": "https://github.com/lightvector/KataGo/releases/download/v1.8.1/katago-v1.8.1-opencl-windows-x64.zip",
|
"OpenCL v1.9.0": "https://github.com/lightvector/KataGo/releases/download/v1.9.0/katago-v1.9.0-opencl-windows-x64.zip",
|
||||||
"Eigen AVX2 (Modern CPUs) v1.8.1": "https://github.com/lightvector/KataGo/releases/download/v1.8.1/katago-v1.8.1-eigenavx2-windows-x64.zip",
|
"Eigen AVX2 (Modern CPUs) v1.9.0": "https://github.com/lightvector/KataGo/releases/download/v1.9.0/katago-v1.9.0-eigenavx2-windows-x64.zip",
|
||||||
"Eigen (CPU, Non-optimized) v1.8.1": "https://github.com/lightvector/KataGo/releases/download/v1.8.1/katago-v1.8.1-eigen-windows-x64.zip",
|
"Eigen (CPU, Non-optimized) v1.9.0": "https://github.com/lightvector/KataGo/releases/download/v1.9.0/katago-v1.9.0-eigen-windows-x64.zip",
|
||||||
"OpenCL v1.6.1 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.6.1%2Bbs29/katago-v1.6.1+bs29-gpu-opencl-windows-x64.zip",
|
"OpenCL v1.9 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.9.0/katago-v1.9.0-opencl-windows-x64+bs29.zip",
|
||||||
},
|
},
|
||||||
"linux": {
|
"linux": {
|
||||||
"OpenCL v1.8.1": "https://github.com/lightvector/KataGo/releases/download/v1.8.1/katago-v1.8.1-opencl-linux-x64.zip",
|
"OpenCL v1.9.0": "https://github.com/lightvector/KataGo/releases/download/v1.9.0/katago-v1.9.0-opencl-linux-x64.zip",
|
||||||
"Eigen AVX2 (Modern CPUs) v1.8.1": "https://github.com/lightvector/KataGo/releases/download/v1.8.1/katago-v1.8.1-eigenavx2-linux-x64.zip",
|
"Eigen AVX2 (Modern CPUs) v1.9.0": "https://github.com/lightvector/KataGo/releases/download/v1.9.0/katago-v1.9.0-eigenavx2-linux-x64.zip",
|
||||||
"Eigen (CPU, Non-optimized) v1.8.1": "https://github.com/lightvector/KataGo/releases/download/v1.8.1/katago-v1.8.1-eigen-linux-x64.zip",
|
"Eigen (CPU, Non-optimized) v1.9.0": "https://github.com/lightvector/KataGo/releases/download/v1.9.0/katago-v1.9.0-eigen-linux-x64.zip",
|
||||||
"OpenCL v1.6.1 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.6.1%2Bbs29/katago-v1.6.1+bs29-gpu-opencl-linux-x64.zip",
|
"OpenCL v1.9 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.9.0/katago-v1.9.0-opencl-linux-x64+bs29.zip",
|
||||||
},
|
},
|
||||||
"just-descriptions": {
|
"just-descriptions": {
|
||||||
"CUDA v1.8.1 (Windows)": "https://github.com/lightvector/KataGo/releases/download/v1.8.1/katago-v1.8.1-gpu-cuda10.2-windows-x64.zip",
|
"CUDA v1.9.0 (Windows)": "https://github.com/lightvector/KataGo/releases/download/v1.9.0/katago-v1.9.0-cuda11.2-windows-x64.zip",
|
||||||
"CUDA v1.8.1 (Linux)": "https://github.com/lightvector/KataGo/releases/download/v1.8.1/katago-v1.8.1-gpu-cuda10.2-linux-x64.zip",
|
"CUDA v1.9.0 (Linux)": "https://github.com/lightvector/KataGo/releases/download/v1.9.0/katago-v1.9.0-cuda11.1-linux-x64.zip",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -565,7 +589,10 @@ class BaseConfigPopup(QuickConfigGui):
|
|||||||
self.paths.append(path) # persistent on paths with models found
|
self.paths.append(path) # persistent on paths with models found
|
||||||
kata_files += files
|
kata_files += files
|
||||||
|
|
||||||
kata_files = [(path, find_description(path)) for path in sorted(kata_files, key=lambda f: "bs29" in f)]
|
kata_files = sorted(
|
||||||
|
[(path, find_description(path)) for path in kata_files],
|
||||||
|
key=lambda f: ("bs29" in f[0]) * 0.1 - (f[0] != f[1]),
|
||||||
|
)
|
||||||
katas_available_msg = i18n._("katago binaries available").format(num=len(kata_files))
|
katas_available_msg = i18n._("katago binaries available").format(num=len(kata_files))
|
||||||
self.katago_files.values = [katas_available_msg, i18n._("default katago option")] + [
|
self.katago_files.values = [katas_available_msg, i18n._("default katago option")] + [
|
||||||
desc for path, desc in kata_files
|
desc for path, desc in kata_files
|
||||||
@@ -815,3 +842,95 @@ class ReAnalyzeGamePopup(BoxLayout):
|
|||||||
|
|
||||||
def on_submit(self):
|
def on_submit(self):
|
||||||
self.button.trigger_action(duration=0)
|
self.button.trigger_action(duration=0)
|
||||||
|
|
||||||
|
|
||||||
|
class GameReportPopup(BoxLayout):
|
||||||
|
def __init__(self, katrain, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
self.katrain = katrain
|
||||||
|
self.depth_filter = None
|
||||||
|
Clock.schedule_once(self._refresh, 0)
|
||||||
|
|
||||||
|
def set_depth_filter(self, filter):
|
||||||
|
self.depth_filter = filter
|
||||||
|
Clock.schedule_once(self._refresh, 0)
|
||||||
|
|
||||||
|
def _refresh(self, _dt=0):
|
||||||
|
game = self.katrain.game
|
||||||
|
thresholds = self.katrain.config("trainer/eval_thresholds")
|
||||||
|
|
||||||
|
sum_stats, histogram, player_ptloss = game_report(game, depth_filter=self.depth_filter, thresholds=thresholds)
|
||||||
|
labels = [f"≥ {pt}" if pt > 0 else f"< {thresholds[-2]}" for pt in thresholds]
|
||||||
|
|
||||||
|
table = GridLayout(cols=3, rows=6 + len(thresholds))
|
||||||
|
colors = [
|
||||||
|
[cp * 0.75 for cp in col[:3]] + [1] for col in Theme.EVAL_COLORS[self.katrain.config("trainer/theme")]
|
||||||
|
]
|
||||||
|
|
||||||
|
table.add_widget(TableHeaderLabel(text="", background_color=Theme.BACKGROUND_COLOR))
|
||||||
|
table.add_widget(TableHeaderLabel(text=i18n._("header:keystats"), background_color=Theme.BACKGROUND_COLOR))
|
||||||
|
table.add_widget(TableHeaderLabel(text="", background_color=Theme.BACKGROUND_COLOR))
|
||||||
|
|
||||||
|
for i, (label, fmt, stat, scale, more_is_better) in enumerate(
|
||||||
|
[
|
||||||
|
("accuracy", "{:.1f}", "accuracy", 100, True),
|
||||||
|
("meanpointloss", "{:.1f}", "mean_ptloss", 5, False),
|
||||||
|
("aitopmove", "{:.1%}", "ai_top_move", 1, True),
|
||||||
|
("aitop5", "{:.1%}", "ai_top5_move", 1, True),
|
||||||
|
]
|
||||||
|
):
|
||||||
|
|
||||||
|
statcell = {
|
||||||
|
bw: TableStatLabel(
|
||||||
|
text=fmt.format(sum_stats[bw][stat]) if stat in sum_stats[bw] else "",
|
||||||
|
side=side,
|
||||||
|
value=sum_stats[bw].get(stat, 0),
|
||||||
|
scale=scale,
|
||||||
|
bar_color=Theme.STAT_BETTER_COLOR
|
||||||
|
if (sum_stats[bw].get(stat, 0) < sum_stats[Move.opponent_player(bw)].get(stat, 0)) ^ more_is_better
|
||||||
|
else Theme.STAT_WORSE_COLOR,
|
||||||
|
background_color=Theme.BOX_BACKGROUND_COLOR,
|
||||||
|
)
|
||||||
|
for (bw, side) in zip("BW", ["left", "right"])
|
||||||
|
}
|
||||||
|
table.add_widget(statcell["B"])
|
||||||
|
table.add_widget(TableCellLabel(text=i18n._(f"stat:{label}"), background_color=Theme.BOX_BACKGROUND_COLOR))
|
||||||
|
table.add_widget(statcell["W"])
|
||||||
|
|
||||||
|
table.add_widget(TableHeaderLabel(text=i18n._("header:num moves"), background_color=Theme.BACKGROUND_COLOR))
|
||||||
|
table.add_widget(TableHeaderLabel(text=i18n._("stats:pointslost"), background_color=Theme.BACKGROUND_COLOR))
|
||||||
|
table.add_widget(TableHeaderLabel(text=i18n._("header:num moves"), background_color=Theme.BACKGROUND_COLOR))
|
||||||
|
|
||||||
|
for i, (col, label, pt) in enumerate(zip(colors[::-1], labels[::-1], thresholds[::-1])):
|
||||||
|
statcell = {
|
||||||
|
bw: TableStatLabel(
|
||||||
|
text=str(histogram[i][bw]),
|
||||||
|
side=side,
|
||||||
|
value=histogram[i][bw],
|
||||||
|
scale=len(player_ptloss[bw]) + 1e-6,
|
||||||
|
bar_color=col,
|
||||||
|
background_color=Theme.BOX_BACKGROUND_COLOR,
|
||||||
|
)
|
||||||
|
for (bw, side) in zip("BW", ["left", "right"])
|
||||||
|
}
|
||||||
|
table.add_widget(statcell["B"])
|
||||||
|
table.add_widget(TableCellLabel(text=label, background_color=col))
|
||||||
|
table.add_widget(statcell["W"])
|
||||||
|
|
||||||
|
self.stats.clear_widgets()
|
||||||
|
self.stats.add_widget(table)
|
||||||
|
|
||||||
|
for bw, player_info in self.katrain.players_info.items():
|
||||||
|
self.player_infos[bw].player_type = player_info.player_type
|
||||||
|
self.player_infos[bw].captures = "" # ;)
|
||||||
|
self.player_infos[bw].player_subtype = player_info.player_subtype
|
||||||
|
self.player_infos[bw].name = player_info.name
|
||||||
|
self.player_infos[bw].rank = (
|
||||||
|
player_info.sgf_rank
|
||||||
|
if player_info.player_type == PLAYER_HUMAN
|
||||||
|
else rank_label(player_info.calculated_rank)
|
||||||
|
)
|
||||||
|
|
||||||
|
# if not done analyzing, check again in 1s
|
||||||
|
if not self.katrain.engine.is_idle():
|
||||||
|
Clock.schedule_once(self._refresh, 1)
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
from kivy.clock import Clock
|
||||||
|
from kivymd.app import MDApp
|
||||||
|
from kivy.core.audio import SoundLoader
|
||||||
|
from kivy.utils import platform
|
||||||
|
|
||||||
|
cached_sounds = {}
|
||||||
|
|
||||||
|
# prefer ffpyplayer on linux, then others, avoid gst and avoid or ffpyplayer on windows
|
||||||
|
ranking = [("ffpy", 98 if platform == "win" else -2), ("sdl", -1), ("gst", 99), ("", 0)]
|
||||||
|
|
||||||
|
try:
|
||||||
|
SoundLoader._classes.sort(key=lambda cls: [v for k, v in ranking if k in cls.__name__.lower()][0])
|
||||||
|
except Exception as e:
|
||||||
|
print("Exception sorting sound loaders: ", e) # private vars, so could break with versions etc
|
||||||
|
|
||||||
|
|
||||||
|
def play_sound(file, volume=1, cache=True):
|
||||||
|
def _play(sound):
|
||||||
|
if sound:
|
||||||
|
sound.play()
|
||||||
|
sound.seek(0)
|
||||||
|
|
||||||
|
app = MDApp.get_running_app()
|
||||||
|
if app and app.gui and app.gui.config("timer/sound"):
|
||||||
|
sound = cached_sounds.get(file)
|
||||||
|
if sound is None:
|
||||||
|
sound = SoundLoader.load(file)
|
||||||
|
if cache:
|
||||||
|
cached_sounds[file] = sound
|
||||||
|
if sound is not None:
|
||||||
|
sound.volume = volume
|
||||||
|
Clock.schedule_once(lambda _dt: _play(sound), 0)
|
||||||
|
|
||||||
|
|
||||||
|
def stop_sound(file):
|
||||||
|
sound = cached_sounds.get(file)
|
||||||
|
if sound:
|
||||||
|
sound.stop()
|
||||||
+58
-1
@@ -47,6 +47,8 @@ class Theme:
|
|||||||
PLAY_ANALYZE_TAB_COLOR = YELLOW
|
PLAY_ANALYZE_TAB_COLOR = YELLOW
|
||||||
INPUT_FONT_COLOR = WHITE
|
INPUT_FONT_COLOR = WHITE
|
||||||
MISTAKE_BUTTON_COLOR = [0.79, 0.06, 0.06, 1]
|
MISTAKE_BUTTON_COLOR = [0.79, 0.06, 0.06, 1]
|
||||||
|
STAT_WORSE_COLOR = [0.8, 0.4, 0.2, 1]
|
||||||
|
STAT_BETTER_COLOR = [0.2, 0.5, 0.1, 1]
|
||||||
|
|
||||||
# gui spacing
|
# gui spacing
|
||||||
RIGHT_PANEL_ASPECT_RATIO = 0.4 # W/H
|
RIGHT_PANEL_ASPECT_RATIO = 0.4 # W/H
|
||||||
@@ -68,6 +70,7 @@ class Theme:
|
|||||||
STONE_SOUNDS = [f"stone{i}.wav" for i in [1, 2, 3, 4, 5]]
|
STONE_SOUNDS = [f"stone{i}.wav" for i in [1, 2, 3, 4, 5]]
|
||||||
COUNTDOWN_SOUND = "countdownbeep.wav"
|
COUNTDOWN_SOUND = "countdownbeep.wav"
|
||||||
MINIMUM_TIME_PASSED_SOUND = "boing.wav"
|
MINIMUM_TIME_PASSED_SOUND = "boing.wav"
|
||||||
|
MISTAKE_SOUNDS = []
|
||||||
|
|
||||||
# eval dots
|
# eval dots
|
||||||
EVAL_COLORS = {
|
EVAL_COLORS = {
|
||||||
@@ -109,7 +112,7 @@ class Theme:
|
|||||||
PV_TEXT_COLORS = {"W": BLACK, "B": WHITE} # numbers in PV
|
PV_TEXT_COLORS = {"W": BLACK, "B": WHITE} # numbers in PV
|
||||||
|
|
||||||
# board
|
# board
|
||||||
LINE_COLOR = [0, 0, 0]
|
LINE_COLOR = [0, 0, 0, 1]
|
||||||
STARPOINT_SIZE = 0.1
|
STARPOINT_SIZE = 0.1
|
||||||
BOARD_COLOR = [0.85, 0.68, 0.40, 1]
|
BOARD_COLOR = [0.85, 0.68, 0.40, 1]
|
||||||
STONE_SIZE = 0.505 # texture edge is transparent
|
STONE_SIZE = 0.505 # texture edge is transparent
|
||||||
@@ -147,3 +150,57 @@ class Theme:
|
|||||||
MOVE_TREE_INSERT_OTHER = LIGHT_ORANGE
|
MOVE_TREE_INSERT_OTHER = LIGHT_ORANGE
|
||||||
MOVE_TREE_COLLAPSED = LIGHT_GREY
|
MOVE_TREE_COLLAPSED = LIGHT_GREY
|
||||||
MOVE_TREE_STONE_OUTLINE_COLORS = {"W": BLACK, "B": WHITE}
|
MOVE_TREE_STONE_OUTLINE_COLORS = {"W": BLACK, "B": WHITE}
|
||||||
|
|
||||||
|
# keyboard shortcuts
|
||||||
|
KEY_AI_MOVE = ["enter", "numpadenter"]
|
||||||
|
KEY_PASS = "p"
|
||||||
|
|
||||||
|
KEY_DEEPERANALYSIS_POPUP = "f2"
|
||||||
|
KEY_REPORT_POPUP = "f3"
|
||||||
|
KEY_TIMER_POPUP = "f5"
|
||||||
|
KEY_TEACHER_POPUP = "f6"
|
||||||
|
KEY_AI_POPUP = "f7"
|
||||||
|
KEY_CONFIG_POPUP = "f8"
|
||||||
|
KEY_CONTRIBUTE_POPUP = "f9"
|
||||||
|
|
||||||
|
KEY_NEW_GAME = "n"
|
||||||
|
KEY_SAVE_GAME = "s"
|
||||||
|
KEY_SAVE_GAME_AS = "d"
|
||||||
|
KEY_LOAD_GAME = "l"
|
||||||
|
KEY_SUBMIT_POPUP = ["enter", "numpadenter"]
|
||||||
|
|
||||||
|
KEY_ANALYSIS_CONTROLS_SHOW_CHILDREN = "q"
|
||||||
|
KEY_ANALYSIS_CONTROLS_EVAL = "w"
|
||||||
|
KEY_ANALYSIS_CONTROLS_HINTS = "e"
|
||||||
|
KEY_ANALYSIS_CONTROLS_POLICY = "r"
|
||||||
|
KEY_ANALYSIS_CONTROLS_OWNERSHIP = "t"
|
||||||
|
|
||||||
|
KEY_ANALYZE_EXTRA_EXTRA = "a"
|
||||||
|
KEY_ANALYZE_EXTRA_EQUALIZE = "s"
|
||||||
|
KEY_ANALYZE_EXTRA_SWEEP = "d"
|
||||||
|
KEY_ANALYZE_EXTRA_ALTERNATIVE = "f"
|
||||||
|
KEY_SELECT_BOX = "g"
|
||||||
|
KEY_RESET_ANALYSIS = "h"
|
||||||
|
KEY_INSERT_MODE = "i"
|
||||||
|
KEY_SELFPLAY_TO_END = "l"
|
||||||
|
KEY_STOP_ANALYSIS = "escape"
|
||||||
|
KEY_TOGGLE_CONTINUOUS_ANALYSIS = "spacebar"
|
||||||
|
|
||||||
|
KEY_COPY = "c"
|
||||||
|
KEY_PASTE = "v"
|
||||||
|
|
||||||
|
KEY_NAV_BRANCH_DOWN = "down"
|
||||||
|
KEY_NAV_BRANCH_UP = "up"
|
||||||
|
KEY_NAV_NEXT = ["right", "x"]
|
||||||
|
KEY_NAV_PREV = ["left", "z"]
|
||||||
|
KEY_NAV_GAME_START = "home"
|
||||||
|
KEY_NAV_GAME_END = "end"
|
||||||
|
KEY_NAV_PREV_BRANCH = "b"
|
||||||
|
KEY_NAV_MISTAKE = "n"
|
||||||
|
KEY_MOVE_TREE_DELETE_SELECTED_NODE = "delete"
|
||||||
|
KEY_MOVE_TREE_MAKE_SELECTED_NODE_MAIN_BRANCH = "pageup"
|
||||||
|
KEY_MOVE_TREE_TOGGLE_SELECTED_NODE_COLLAPSE = "c"
|
||||||
|
|
||||||
|
KEY_PAUSE_TIMER = ["pause", "break", "f15"]
|
||||||
|
KEY_TOGGLE_COORDINATES = "k"
|
||||||
|
KEY_ZEN = ["`", "~", "f12"]
|
||||||
Binary file not shown.
@@ -838,10 +838,80 @@ msgstr "这会产生部分的对局让你接著下。例如:你可以使用它
|
|||||||
msgid "editgame"
|
msgid "editgame"
|
||||||
msgstr "编辑对局资讯"
|
msgstr "编辑对局资讯"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "new zealand"
|
msgid "new zealand"
|
||||||
msgstr "New Zealand"
|
msgstr "新西兰"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "tromp-taylor"
|
msgid "tromp-taylor"
|
||||||
msgstr "Tromp-Taylor"
|
msgstr "Tromp-Taylor"
|
||||||
|
|
||||||
|
#. View a player performance report
|
||||||
|
msgid "analysis:report"
|
||||||
|
msgstr "表现报告"
|
||||||
|
|
||||||
|
msgid "engine died popup opening message"
|
||||||
|
msgstr ""
|
||||||
|
"KataGo引擎启动失败或崩溃。\n"
|
||||||
|
"\n"
|
||||||
|
"错误:{error_message}\n"
|
||||||
|
"\n"
|
||||||
|
"错误码:{code}."
|
||||||
|
|
||||||
|
#. Link in tags should not be touched (uses link_here msgid)
|
||||||
|
msgid "go to engine help page"
|
||||||
|
msgstr "如果你还有任何困难,请参考疑难排解页面 {link}"
|
||||||
|
|
||||||
|
#. Link in tags should not be touched.
|
||||||
|
msgid "change engine suggestion"
|
||||||
|
msgstr "首先,在 {link} 中,尝试下载且更换为不同版本的KataGo执行档是否能解决问题。"
|
||||||
|
|
||||||
|
#. as in "click here" to go to this page,
|
||||||
|
msgid "link_here"
|
||||||
|
msgstr "点击此处"
|
||||||
|
|
||||||
|
#. popup title
|
||||||
|
msgid "engine recovery"
|
||||||
|
msgstr "KataGo设定指南"
|
||||||
|
|
||||||
|
#. label in little popup for analyzing entire game
|
||||||
|
msgid "reanalyze mistakes only"
|
||||||
|
msgstr "仅分析错误操作"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:accuracy"
|
||||||
|
msgstr "准确度评分"
|
||||||
|
|
||||||
|
#. header in performance report
|
||||||
|
msgid "header:keystats"
|
||||||
|
msgstr "关键数据"
|
||||||
|
|
||||||
|
#. header in performance report
|
||||||
|
msgid "header:num moves"
|
||||||
|
msgstr "步数"
|
||||||
|
|
||||||
|
#. opening of the game filter in performance report
|
||||||
|
msgid "Opening"
|
||||||
|
msgstr "开局"
|
||||||
|
|
||||||
|
#. end of the game filter in performance report
|
||||||
|
msgid "Endgame"
|
||||||
|
msgstr "终盘"
|
||||||
|
|
||||||
|
#. middle of the game filter in performance report
|
||||||
|
msgid "Midgame"
|
||||||
|
msgstr "中盘"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:meanpointloss"
|
||||||
|
msgstr "平均损失目数"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:aitop5"
|
||||||
|
msgstr "AI前5佳选点重合度 %"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:aitopmove"
|
||||||
|
msgstr "AI最佳选点重合度 %"
|
||||||
|
|
||||||
|
#. game filter in performance report
|
||||||
|
msgid "Entire Game"
|
||||||
|
msgstr "全局"
|
||||||
Binary file not shown.
@@ -435,9 +435,7 @@ msgstr ""
|
|||||||
"Einstellungen auf die richtige KataGo-Datei zeigt."
|
"Einstellungen auf die richtige KataGo-Datei zeigt."
|
||||||
|
|
||||||
msgid "Engine died unexpectedly"
|
msgid "Engine died unexpectedly"
|
||||||
msgstr ""
|
msgstr "Engine ist unerwartet abgestürzt: {error}"
|
||||||
"Engine ist unerwartet abgestürzt, ohne Ausgabe zu senden, wahrscheinlich "
|
|
||||||
"wegen zu wenig Speicher: {error}"
|
|
||||||
|
|
||||||
#. AI names, help etc
|
#. AI names, help etc
|
||||||
msgid "strength:kyu"
|
msgid "strength:kyu"
|
||||||
@@ -905,10 +903,84 @@ msgstr ""
|
|||||||
msgid "editgame"
|
msgid "editgame"
|
||||||
msgstr "Spiel editieren"
|
msgstr "Spiel editieren"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "new zealand"
|
msgid "new zealand"
|
||||||
msgstr "New Zealand"
|
msgstr "Neuseeländisch"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "tromp-taylor"
|
msgid "tromp-taylor"
|
||||||
msgstr "Tromp-Taylor"
|
msgstr "Tromp-Taylor"
|
||||||
|
|
||||||
|
#. - View a player performance report
|
||||||
|
msgid "analysis:report"
|
||||||
|
msgstr "Leistungsbericht"
|
||||||
|
|
||||||
|
msgid "engine died popup opening message"
|
||||||
|
msgstr ""
|
||||||
|
"Das KataGo Engine konnte nicht starten, oder ist abgestürzt.\n"
|
||||||
|
"\n"
|
||||||
|
"Der Fehler war: [color=#CC2222]{error_message}[/color]\n"
|
||||||
|
"\n"
|
||||||
|
"Der Fehlercode war: [color=#CC2222]{code}[/color]."
|
||||||
|
|
||||||
|
#. - Link in tags should not be touched (uses link_here msgid)
|
||||||
|
msgid "go to engine help page"
|
||||||
|
msgstr ""
|
||||||
|
"Wenn sie immer noch Schwierigkeiten haben, {link} für eine Hilfe-Seite auf "
|
||||||
|
"dem neuesten Stand."
|
||||||
|
|
||||||
|
#. - Link in tags should not be touched.
|
||||||
|
msgid "change engine suggestion"
|
||||||
|
msgstr ""
|
||||||
|
"Versuchen sie erstmal KataGo Versionen runterzuladen und ob eine andere "
|
||||||
|
"ausführbare Datei in {link} hilft. "
|
||||||
|
|
||||||
|
#. - as in "click here" to go to this page,
|
||||||
|
msgid "link_here"
|
||||||
|
msgstr "klicken sie hier"
|
||||||
|
|
||||||
|
#. - popup title
|
||||||
|
msgid "engine recovery"
|
||||||
|
msgstr "Hilfe für KataGo Konfiguration"
|
||||||
|
|
||||||
|
#. - label in little popup for analyzing entire game
|
||||||
|
msgid "reanalyze mistakes only"
|
||||||
|
msgstr "Analysis auf Fehler begrenzen"
|
||||||
|
|
||||||
|
#. - statistic in performance report
|
||||||
|
msgid "stat:accuracy"
|
||||||
|
msgstr "Genauigkeitsbewertung"
|
||||||
|
|
||||||
|
#. - header in performance report
|
||||||
|
msgid "header:keystats"
|
||||||
|
msgstr "Schlüsselstatistiken"
|
||||||
|
|
||||||
|
#. - header in performance report
|
||||||
|
msgid "header:num moves"
|
||||||
|
msgstr "# Züge"
|
||||||
|
|
||||||
|
#. - opening of the game filter in performance report
|
||||||
|
msgid "Opening"
|
||||||
|
msgstr "Eröffnung"
|
||||||
|
|
||||||
|
#. - end of the game filter in performance report
|
||||||
|
msgid "Endgame"
|
||||||
|
msgstr "Endspiel"
|
||||||
|
|
||||||
|
#. - middle of the game filter in performance report
|
||||||
|
msgid "Midgame"
|
||||||
|
msgstr "Mittelspiel"
|
||||||
|
|
||||||
|
#. - statistic in performance report
|
||||||
|
msgid "stat:meanpointloss"
|
||||||
|
msgstr "Durchschnittlicher Punkteverlust"
|
||||||
|
|
||||||
|
#. - statistic in performance report
|
||||||
|
msgid "stat:aitop5"
|
||||||
|
msgstr "KI Top 5 Übereinstimmung %"
|
||||||
|
|
||||||
|
#. - statistic in performance report
|
||||||
|
msgid "stat:aitopmove"
|
||||||
|
msgstr "Bester KI Zug Übereinstimmung %"
|
||||||
|
|
||||||
|
#. - game filter in performance report
|
||||||
|
msgid "Entire Game"
|
||||||
|
msgstr "Gesamtes Spiel"
|
||||||
Binary file not shown.
@@ -105,6 +105,50 @@ msgstr "Fast analysis of all moves"
|
|||||||
msgid "analysis:game"
|
msgid "analysis:game"
|
||||||
msgstr "Deeper full game analysis"
|
msgstr "Deeper full game analysis"
|
||||||
|
|
||||||
|
#. View a player performance report
|
||||||
|
msgid "analysis:report"
|
||||||
|
msgstr "Performance Report"
|
||||||
|
|
||||||
|
#. game filter in performance report
|
||||||
|
msgid "Entire Game"
|
||||||
|
msgstr "Entire Game"
|
||||||
|
|
||||||
|
#. opening of the game filter in performance report
|
||||||
|
msgid "Opening"
|
||||||
|
msgstr "Opening"
|
||||||
|
|
||||||
|
#. middle of the game filter in performance report
|
||||||
|
msgid "Midgame"
|
||||||
|
msgstr "Midgame"
|
||||||
|
|
||||||
|
#. end of the game filter in performance report
|
||||||
|
msgid "Endgame"
|
||||||
|
msgstr "Endgame"
|
||||||
|
|
||||||
|
#. header in performance report
|
||||||
|
msgid "header:keystats"
|
||||||
|
msgstr "Key Statistics"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:accuracy"
|
||||||
|
msgstr "Accuracy Rating"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:meanpointloss"
|
||||||
|
msgstr "Mean Point Loss"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:aitopmove"
|
||||||
|
msgstr "AI Best Move Match %"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:aitop5"
|
||||||
|
msgstr "AI Top 5 Match %"
|
||||||
|
|
||||||
|
#. header in performance report
|
||||||
|
msgid "header:num moves"
|
||||||
|
msgstr "# Moves"
|
||||||
|
|
||||||
#. Analyze again but exclude all currently considered moves
|
#. Analyze again but exclude all currently considered moves
|
||||||
msgid "analysis:alternatives"
|
msgid "analysis:alternatives"
|
||||||
msgstr "Find alternative moves"
|
msgstr "Find alternative moves"
|
||||||
@@ -648,15 +692,41 @@ msgid "Starting Kata failed"
|
|||||||
msgstr "Starting KataGo with command '{command}' failed with error {error}."
|
msgstr "Starting KataGo with command '{command}' failed with error {error}."
|
||||||
|
|
||||||
msgid "Engine died unexpectedly"
|
msgid "Engine died unexpectedly"
|
||||||
msgstr ""
|
msgstr "Engine died unexpectedly without sending output: {error}"
|
||||||
"Engine died unexpectedly without sending output, possibly due to out of "
|
|
||||||
"memory: {error}"
|
|
||||||
|
|
||||||
msgid "Engine missing DLL"
|
msgid "Engine missing DLL"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Failed to start KataGo due to missing DLL. Check the requirements in the "
|
"Failed to start KataGo due to missing DLL. Check the requirements in the "
|
||||||
"KataGo documentation, or try running the binary by itself for details. "
|
"KataGo documentation, or try running the binary by itself for details. "
|
||||||
|
|
||||||
|
#. popup title
|
||||||
|
msgid "engine recovery"
|
||||||
|
msgstr "Help with KataGo setup"
|
||||||
|
|
||||||
|
msgid "engine died popup opening message"
|
||||||
|
msgstr ""
|
||||||
|
"The KataGo Engine failed to start, or crashed.\n"
|
||||||
|
"\n"
|
||||||
|
"The error was: [color=#CC2222]{error_message}[/color]\n"
|
||||||
|
"\n"
|
||||||
|
"The error code was: [color=#CC2222]{code}[/color]."
|
||||||
|
|
||||||
|
#. Link in tags should not be touched.
|
||||||
|
msgid "change engine suggestion"
|
||||||
|
msgstr ""
|
||||||
|
"First, try downloading KataGo versions and try if changing to a different "
|
||||||
|
"executable helps in {link} ."
|
||||||
|
|
||||||
|
#. Link in tags should not be touched (uses link_here msgid)
|
||||||
|
msgid "go to engine help page"
|
||||||
|
msgstr ""
|
||||||
|
"If you are still having difficulties, {link} for an up-to-date "
|
||||||
|
"troubleshooting page."
|
||||||
|
|
||||||
|
#. as in "click here" to go to this page,
|
||||||
|
msgid "link_here"
|
||||||
|
msgstr "click here"
|
||||||
|
|
||||||
#. AI names, help etc
|
#. AI names, help etc
|
||||||
msgid "strength:kyu"
|
msgid "strength:kyu"
|
||||||
msgstr "k"
|
msgstr "k"
|
||||||
@@ -826,6 +896,10 @@ msgstr "All available binaries already downloaded"
|
|||||||
msgid "reanalyze max visits"
|
msgid "reanalyze max visits"
|
||||||
msgstr "Number of visits per move"
|
msgstr "Number of visits per move"
|
||||||
|
|
||||||
|
#. label in little popup for analyzing entire game
|
||||||
|
msgid "reanalyze mistakes only"
|
||||||
|
msgstr "Limit analysis to mistakes only"
|
||||||
|
|
||||||
#. on move tree editing
|
#. on move tree editing
|
||||||
msgid "Delete Node"
|
msgid "Delete Node"
|
||||||
msgstr "Delete Node"
|
msgstr "Delete Node"
|
||||||
|
|||||||
Binary file not shown.
@@ -441,9 +441,7 @@ msgstr ""
|
|||||||
"'engine' al valor correcto del ejecutable KataGo."
|
"'engine' al valor correcto del ejecutable KataGo."
|
||||||
|
|
||||||
msgid "Engine died unexpectedly"
|
msgid "Engine died unexpectedly"
|
||||||
msgstr ""
|
msgstr "El motor murió inesperadamente sin mandar alguna señal: {error}"
|
||||||
"El motor murió inesperadamente sin mandar alguna señal, posiblemente debido "
|
|
||||||
"a memoria: {error}"
|
|
||||||
|
|
||||||
#. AI names, help etc
|
#. AI names, help etc
|
||||||
msgid "strength:kyu"
|
msgid "strength:kyu"
|
||||||
@@ -924,3 +922,80 @@ msgstr "New Zealand"
|
|||||||
#. TODO
|
#. TODO
|
||||||
msgid "tromp-taylor"
|
msgid "tromp-taylor"
|
||||||
msgstr "Tromp-Taylor"
|
msgstr "Tromp-Taylor"
|
||||||
|
|
||||||
|
#. TODO - View a player performance report
|
||||||
|
msgid "analysis:report"
|
||||||
|
msgstr "Performance Report"
|
||||||
|
|
||||||
|
#. TODO
|
||||||
|
msgid "engine died popup opening message"
|
||||||
|
msgstr ""
|
||||||
|
"The KataGo Engine failed to start, or crashed.\n"
|
||||||
|
"\n"
|
||||||
|
"The error was: [color=#661111]{error_message}[/color]\n"
|
||||||
|
"\n"
|
||||||
|
"The error code was: [color=#661111]{code}[/color]."
|
||||||
|
|
||||||
|
#. TODO - Link in tags should not be touched.
|
||||||
|
msgid "change engine suggestion"
|
||||||
|
msgstr ""
|
||||||
|
"First, try downloading KataGo versions and try if changing to a different "
|
||||||
|
"executable helps in {link} ."
|
||||||
|
|
||||||
|
#. TODO - as in "click here" to go to this page,
|
||||||
|
msgid "link_here"
|
||||||
|
msgstr "click here"
|
||||||
|
|
||||||
|
#. TODO - Link in tags should not be touched (uses link_here msgid)
|
||||||
|
msgid "go to engine help page"
|
||||||
|
msgstr ""
|
||||||
|
"If you are still having difficulties, {link} for an up-to-date "
|
||||||
|
"troubleshooting page."
|
||||||
|
|
||||||
|
#. TODO - popup title
|
||||||
|
msgid "engine recovery"
|
||||||
|
msgstr "Help with KataGo setup"
|
||||||
|
|
||||||
|
#. TODO - label in little popup for analyzing entire game
|
||||||
|
msgid "reanalyze mistakes only"
|
||||||
|
msgstr "Limit analysis to mistakes only"
|
||||||
|
|
||||||
|
#. TODO - statistic in performance report
|
||||||
|
msgid "stat:accuracy"
|
||||||
|
msgstr "Accuracy Rating"
|
||||||
|
|
||||||
|
#. TODO - header in performance report
|
||||||
|
msgid "header:keystats"
|
||||||
|
msgstr "Key Statistics"
|
||||||
|
|
||||||
|
#. TODO - header in performance report
|
||||||
|
msgid "header:num moves"
|
||||||
|
msgstr "# Moves"
|
||||||
|
|
||||||
|
#. TODO - opening of the game filter in performance report
|
||||||
|
msgid "Opening"
|
||||||
|
msgstr "Opening"
|
||||||
|
|
||||||
|
#. TODO - end of the game filter in performance report
|
||||||
|
msgid "Endgame"
|
||||||
|
msgstr "Endgame"
|
||||||
|
|
||||||
|
#. TODO - middle of the game filter in performance report
|
||||||
|
msgid "Midgame"
|
||||||
|
msgstr "Midgame"
|
||||||
|
|
||||||
|
#. TODO - statistic in performance report
|
||||||
|
msgid "stat:meanpointloss"
|
||||||
|
msgstr "Mean Point Loss"
|
||||||
|
|
||||||
|
#. TODO - statistic in performance report
|
||||||
|
msgid "stat:aitop5"
|
||||||
|
msgstr "AI Top 5 Match %"
|
||||||
|
|
||||||
|
#. TODO - statistic in performance report
|
||||||
|
msgid "stat:aitopmove"
|
||||||
|
msgstr "AI Best Move Match %"
|
||||||
|
|
||||||
|
#. TODO - game filter in performance report
|
||||||
|
msgid "Entire Game"
|
||||||
|
msgstr "Entire Game"
|
||||||
Binary file not shown.
@@ -80,8 +80,8 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "analysis:policy"
|
msgid "analysis:policy"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Coups\n"
|
"Carte des\n"
|
||||||
"intuitifs"
|
"intuitions"
|
||||||
|
|
||||||
msgid "analysis:territory"
|
msgid "analysis:territory"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -90,7 +90,7 @@ msgstr ""
|
|||||||
|
|
||||||
#. analysis menu items
|
#. analysis menu items
|
||||||
msgid "analysis:extra"
|
msgid "analysis:extra"
|
||||||
msgstr "Analyser davantage"
|
msgstr "Approfondir l'analyse"
|
||||||
|
|
||||||
msgid "analysis:equalize"
|
msgid "analysis:equalize"
|
||||||
msgstr "Égaliser l'analyse des coups"
|
msgstr "Égaliser l'analyse des coups"
|
||||||
@@ -99,7 +99,7 @@ msgid "analysis:sweep"
|
|||||||
msgstr "Analyser tous les possibles"
|
msgstr "Analyser tous les possibles"
|
||||||
|
|
||||||
msgid "analysis:game"
|
msgid "analysis:game"
|
||||||
msgstr "Analyser davantage la partie"
|
msgstr "Approfondir l'analyse de partie"
|
||||||
|
|
||||||
msgid "analysis:continuous"
|
msgid "analysis:continuous"
|
||||||
msgstr "Lancer/Arrêter l'analyse continue"
|
msgstr "Lancer/Arrêter l'analyse continue"
|
||||||
@@ -302,7 +302,6 @@ msgstr "Coréennes"
|
|||||||
msgid "aga"
|
msgid "aga"
|
||||||
msgstr "AGA"
|
msgstr "AGA"
|
||||||
|
|
||||||
#. TODO check
|
|
||||||
msgid "stone_scoring"
|
msgid "stone_scoring"
|
||||||
msgstr "Chinoises anciennes"
|
msgstr "Chinoises anciennes"
|
||||||
|
|
||||||
@@ -506,10 +505,7 @@ msgstr ""
|
|||||||
"généraux"
|
"généraux"
|
||||||
|
|
||||||
msgid "Engine died unexpectedly"
|
msgid "Engine died unexpectedly"
|
||||||
msgstr ""
|
msgstr "Le moteur de jeu est tombé en panne: {error}"
|
||||||
"Le moteur de jeu est tombé en panne, \n"
|
|
||||||
"possiblement pour cause de \n"
|
|
||||||
"mémoire insuffisante : {error}"
|
|
||||||
|
|
||||||
msgid "teaching undo message"
|
msgid "teaching undo message"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
@@ -522,13 +518,13 @@ msgid "sweep analysis"
|
|||||||
msgstr "Analyse de tous les possibles ({visits} visites/coup)."
|
msgstr "Analyse de tous les possibles ({visits} visites/coup)."
|
||||||
|
|
||||||
msgid "extra analysis"
|
msgid "extra analysis"
|
||||||
msgstr "Analyse supplémentaire de {visits} visites."
|
msgstr "Analyse approfondie ({visits} visites)."
|
||||||
|
|
||||||
msgid "equalizing analysis"
|
msgid "equalizing analysis"
|
||||||
msgstr "Égalisation de l'analyse ({visits} visites/coup considéré)."
|
msgstr "Égalisation de l'analyse ({visits} visites/coup considéré)."
|
||||||
|
|
||||||
msgid "game re-analysis"
|
msgid "game re-analysis"
|
||||||
msgstr "Analyse supplémentaire de la partie ({visits} visites/coup)."
|
msgstr "Analyse de partie approfondie ({visits} visites/coup)."
|
||||||
|
|
||||||
#. AI names, help etc
|
#. AI names, help etc
|
||||||
msgid "strength:kyu"
|
msgid "strength:kyu"
|
||||||
@@ -899,10 +895,68 @@ msgstr ""
|
|||||||
msgid "editgame"
|
msgid "editgame"
|
||||||
msgstr "Partie en cours"
|
msgstr "Partie en cours"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "new zealand"
|
msgid "new zealand"
|
||||||
msgstr "New Zealand"
|
msgstr "Néo-zélandaises"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "tromp-taylor"
|
msgid "tromp-taylor"
|
||||||
msgstr "Tromp-Taylor"
|
msgstr "Tromp-Taylor"
|
||||||
|
|
||||||
|
msgid "analysis:report"
|
||||||
|
msgstr "Rapport de partie"
|
||||||
|
|
||||||
|
msgid "engine died popup opening message"
|
||||||
|
msgstr ""
|
||||||
|
"Le moteur de jeu KataGo ne fonctionne pas/plus.\n"
|
||||||
|
"\n"
|
||||||
|
"Rapport d'erreur : [color=#CC2222]{error_message}[/color]\n"
|
||||||
|
"\n"
|
||||||
|
"Code d'erreur : [color=#CC2222]{code}[/color]"
|
||||||
|
|
||||||
|
msgid "go to engine help page"
|
||||||
|
msgstr ""
|
||||||
|
"Si le problème persiste, vous pouvez consulter une page d'aide tenue à jour "
|
||||||
|
"en cliquant sur {link}."
|
||||||
|
|
||||||
|
msgid "change engine suggestion"
|
||||||
|
msgstr ""
|
||||||
|
"L'exécutable et le modèle KataGo utilisés doivent être compatibles. Vous "
|
||||||
|
"pouvez en télécharger et/ou en sélectionner d'autres dans les {link}. "
|
||||||
|
|
||||||
|
msgid "link_here"
|
||||||
|
msgstr "ce lien"
|
||||||
|
|
||||||
|
msgid "engine recovery"
|
||||||
|
msgstr "Aide au dépannage de KataGo"
|
||||||
|
|
||||||
|
msgid "reanalyze mistakes only"
|
||||||
|
msgstr "Restreindre l'analyse aux seules erreurs"
|
||||||
|
|
||||||
|
msgid "stat:accuracy"
|
||||||
|
msgstr "Acuité du jeu"
|
||||||
|
|
||||||
|
msgid "header:keystats"
|
||||||
|
msgstr "Statistiques des coups"
|
||||||
|
|
||||||
|
msgid "header:num moves"
|
||||||
|
msgstr "Nombre de coups"
|
||||||
|
|
||||||
|
msgid "Opening"
|
||||||
|
msgstr "Début de Partie"
|
||||||
|
|
||||||
|
msgid "Endgame"
|
||||||
|
msgstr "Fin de Partie"
|
||||||
|
|
||||||
|
msgid "Midgame"
|
||||||
|
msgstr "Milieu de Partie"
|
||||||
|
|
||||||
|
msgid "stat:meanpointloss"
|
||||||
|
msgstr "Perte moyenne"
|
||||||
|
|
||||||
|
msgid "stat:aitop5"
|
||||||
|
msgstr "L'un des 5 coups prioritaires"
|
||||||
|
|
||||||
|
msgid "stat:aitopmove"
|
||||||
|
msgstr "Ce que jouerait KataGo"
|
||||||
|
|
||||||
|
msgid "Entire Game"
|
||||||
|
msgstr "Partie entière"
|
||||||
Binary file not shown.
@@ -336,7 +336,9 @@ msgid "point loss threshold"
|
|||||||
msgstr "何目より損なら"
|
msgstr "何目より損なら"
|
||||||
|
|
||||||
msgid "num undos"
|
msgid "num undos"
|
||||||
msgstr "再考を促す回数"
|
msgstr ""
|
||||||
|
"再考を促す回数\n"
|
||||||
|
"(指導対局時)"
|
||||||
|
|
||||||
msgid "dot color"
|
msgid "dot color"
|
||||||
msgstr "色"
|
msgstr "色"
|
||||||
@@ -450,7 +452,7 @@ msgstr ""
|
|||||||
"クリップボードの内容:{contents}..."
|
"クリップボードの内容:{contents}..."
|
||||||
|
|
||||||
msgid "Failed to load SGF"
|
msgid "Failed to load SGF"
|
||||||
msgstr "SGFのロードに失敗しました: {error}"
|
msgstr "SGFのロードに失敗しました:{error}"
|
||||||
|
|
||||||
msgid "Kata exe not found"
|
msgid "Kata exe not found"
|
||||||
msgstr "KataGoの実行ファイル {exe} がありません。一般設定を確認してください。"
|
msgstr "KataGoの実行ファイル {exe} がありません。一般設定を確認してください。"
|
||||||
@@ -470,7 +472,7 @@ msgid "Starting Kata failed"
|
|||||||
msgstr "コマンド '{command}' によるKataGoの起動に失敗しました。エラー {error}"
|
msgstr "コマンド '{command}' によるKataGoの起動に失敗しました。エラー {error}"
|
||||||
|
|
||||||
msgid "Engine died unexpectedly"
|
msgid "Engine died unexpectedly"
|
||||||
msgstr "出力なしで予期せずエンジンが止まりました。メモリ不足かもしれません:{error}"
|
msgstr "出力なしで予期せずエンジンが止まりました:{error}"
|
||||||
|
|
||||||
#. AI names, help etc
|
#. AI names, help etc
|
||||||
msgid "strength:kyu"
|
msgid "strength:kyu"
|
||||||
@@ -882,10 +884,78 @@ msgstr ""
|
|||||||
msgid "editgame"
|
msgid "editgame"
|
||||||
msgstr "対局情報を編集"
|
msgstr "対局情報を編集"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "new zealand"
|
msgid "new zealand"
|
||||||
msgstr "New Zealand"
|
msgstr "ニュージーランド"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "tromp-taylor"
|
msgid "tromp-taylor"
|
||||||
msgstr "Tromp-Taylor"
|
msgstr "Tromp-Taylor"
|
||||||
|
|
||||||
|
#. View a player performance report
|
||||||
|
msgid "analysis:report"
|
||||||
|
msgstr "評価レポート"
|
||||||
|
|
||||||
|
msgid "engine died popup opening message"
|
||||||
|
msgstr ""
|
||||||
|
"おおっと!KataGoエンジンが起動に失敗したか、クラッシュしました。\n"
|
||||||
|
" エラー内容:{error_message}\n"
|
||||||
|
"エラーコード {code}"
|
||||||
|
|
||||||
|
#. Link in tags should not be touched (uses link_here msgid)
|
||||||
|
msgid "go to engine help page"
|
||||||
|
msgstr "それでもだめなら、{link}して最新のトラブルシューティングページへ。"
|
||||||
|
|
||||||
|
#. Link in tags should not be touched.
|
||||||
|
msgid "change engine suggestion"
|
||||||
|
msgstr "まずは、{link}から別バージョンのKataGoをダウンロードしてうまくいくか試してください。"
|
||||||
|
|
||||||
|
#. as in "click here" to go to this page,
|
||||||
|
msgid "link_here"
|
||||||
|
msgstr "ここをクリック"
|
||||||
|
|
||||||
|
#. popup title
|
||||||
|
msgid "engine recovery"
|
||||||
|
msgstr "KataGoセットアップのヘルプ"
|
||||||
|
|
||||||
|
#. label in little popup for analyzing entire game
|
||||||
|
msgid "reanalyze mistakes only"
|
||||||
|
msgstr "分析を悪手だけに限定"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:accuracy"
|
||||||
|
msgstr "正確度の評点"
|
||||||
|
|
||||||
|
#. header in performance report
|
||||||
|
msgid "header:keystats"
|
||||||
|
msgstr "基本統計"
|
||||||
|
|
||||||
|
#. header in performance report
|
||||||
|
msgid "header:num moves"
|
||||||
|
msgstr "手数"
|
||||||
|
|
||||||
|
#. opening of the game filter in performance report
|
||||||
|
msgid "Opening"
|
||||||
|
msgstr "序盤"
|
||||||
|
|
||||||
|
#. end of the game filter in performance report
|
||||||
|
msgid "Endgame"
|
||||||
|
msgstr "終盤"
|
||||||
|
|
||||||
|
#. middle of the game filter in performance report
|
||||||
|
msgid "Midgame"
|
||||||
|
msgstr "中盤"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:meanpointloss"
|
||||||
|
msgstr "平均損失目数"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:aitop5"
|
||||||
|
msgstr "AIの上位5候補手との一致率"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:aitopmove"
|
||||||
|
msgstr "AIの最善手との一致率"
|
||||||
|
|
||||||
|
#. game filter in performance report
|
||||||
|
msgid "Entire Game"
|
||||||
|
msgstr "全局"
|
||||||
Binary file not shown.
@@ -446,7 +446,7 @@ msgstr ""
|
|||||||
" 값이 올바른 KataGo 실행 파일 경로가 맞는지 확인해 주십시오."
|
" 값이 올바른 KataGo 실행 파일 경로가 맞는지 확인해 주십시오."
|
||||||
|
|
||||||
msgid "Engine died unexpectedly"
|
msgid "Engine died unexpectedly"
|
||||||
msgstr "엔진이 출력을 보내지 않고 예상치 않게 종료되었습니다. 메모리 용량 초과일 가능성이 있습니다: {error}."
|
msgstr "엔진이 출력을 보내지 않고 예상치 않게 종료되었습니다: {error}."
|
||||||
|
|
||||||
#. AI names, help etc
|
#. AI names, help etc
|
||||||
msgid "strength:kyu"
|
msgid "strength:kyu"
|
||||||
@@ -837,10 +837,64 @@ msgstr ""
|
|||||||
msgid "editgame"
|
msgid "editgame"
|
||||||
msgstr "대국 정보 편집"
|
msgstr "대국 정보 편집"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "new zealand"
|
msgid "new zealand"
|
||||||
msgstr "New Zealand"
|
msgstr "뉴질랜드"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "tromp-taylor"
|
msgid "tromp-taylor"
|
||||||
msgstr "Tromp-Taylor"
|
msgstr "Tromp-Taylor"
|
||||||
|
|
||||||
|
msgid "analysis:report"
|
||||||
|
msgstr "기력 보고서"
|
||||||
|
|
||||||
|
msgid "engine died popup opening message"
|
||||||
|
msgstr ""
|
||||||
|
"KataGo 엔진을 시작할 수 없거나, 갑자기 멈추었습니다.\n"
|
||||||
|
"\n"
|
||||||
|
"오류: [color=#CC2222]{error_message}[/color]\n"
|
||||||
|
"\n"
|
||||||
|
"오류 코드:: [color=#CC2222]{code}[/color]."
|
||||||
|
|
||||||
|
msgid "go to engine help page"
|
||||||
|
msgstr "아직 문제를 겪고 있으시다면, {link}에서 최신 문제 해결법을 찾아보십시오."
|
||||||
|
|
||||||
|
msgid "change engine suggestion"
|
||||||
|
msgstr "우선, KataGo 버전들을 내려받아 보시고, {link}에서 다른 실행 파일로 바꾸면 괜찮아지는지 확인해 보십시오."
|
||||||
|
|
||||||
|
msgid "link_here"
|
||||||
|
msgstr "여기"
|
||||||
|
|
||||||
|
msgid "engine recovery"
|
||||||
|
msgstr "KataGo 설정 도움말"
|
||||||
|
|
||||||
|
msgid "reanalyze mistakes only"
|
||||||
|
msgstr "실수만 분석하기"
|
||||||
|
|
||||||
|
msgid "stat:accuracy"
|
||||||
|
msgstr "정확도 지표"
|
||||||
|
|
||||||
|
msgid "header:keystats"
|
||||||
|
msgstr "주요 통계"
|
||||||
|
|
||||||
|
msgid "header:num moves"
|
||||||
|
msgstr "착수 수"
|
||||||
|
|
||||||
|
msgid "Opening"
|
||||||
|
msgstr "포석"
|
||||||
|
|
||||||
|
msgid "Endgame"
|
||||||
|
msgstr "끝내기"
|
||||||
|
|
||||||
|
msgid "Midgame"
|
||||||
|
msgstr "중반"
|
||||||
|
|
||||||
|
msgid "stat:meanpointloss"
|
||||||
|
msgstr "평균 집 손해"
|
||||||
|
|
||||||
|
msgid "stat:aitop5"
|
||||||
|
msgstr "인공지능 최선 5수 일치율(%)"
|
||||||
|
|
||||||
|
msgid "stat:aitopmove"
|
||||||
|
msgstr "인공지능 최선수 일치율(%)"
|
||||||
|
|
||||||
|
msgid "Entire Game"
|
||||||
|
msgstr "모두"
|
||||||
Binary file not shown.
@@ -856,10 +856,71 @@ msgstr ""
|
|||||||
msgid "editgame"
|
msgid "editgame"
|
||||||
msgstr "Редактировать игры"
|
msgstr "Редактировать игры"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "new zealand"
|
msgid "new zealand"
|
||||||
msgstr "New Zealand"
|
msgstr "Новозеландские"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "tromp-taylor"
|
msgid "tromp-taylor"
|
||||||
msgstr "Tromp-Taylor"
|
msgstr "Тромп-Тейлор"
|
||||||
|
|
||||||
|
msgid "analysis:report"
|
||||||
|
msgstr "Отчёт о производительности"
|
||||||
|
|
||||||
|
msgid "engine died popup opening message"
|
||||||
|
msgstr ""
|
||||||
|
"Движок KataGo не смог запуститься или сломался\n"
|
||||||
|
"\n"
|
||||||
|
"Ошибка: [color=#CC2222]{error_message}[/color]\n"
|
||||||
|
"\n"
|
||||||
|
"Код ошибки: [color=#CC2222]{code}[/color]."
|
||||||
|
|
||||||
|
msgid "go to engine help page"
|
||||||
|
msgstr ""
|
||||||
|
"Если у вас до сих пор проблемы, вот ссылка {link} на страницу устранения "
|
||||||
|
"неполадок."
|
||||||
|
|
||||||
|
msgid "change engine suggestion"
|
||||||
|
msgstr ""
|
||||||
|
"Во-первых, попробуйте скачать и выбрать другую версию движка KataGo {link}"
|
||||||
|
|
||||||
|
msgid "link_here"
|
||||||
|
msgstr "Нажмите здесь"
|
||||||
|
|
||||||
|
msgid "engine recovery"
|
||||||
|
msgstr "Помощь в настройке KataGo"
|
||||||
|
|
||||||
|
msgid "reanalyze mistakes only"
|
||||||
|
msgstr "Анализировать только ошибки"
|
||||||
|
|
||||||
|
msgid "stat:accuracy"
|
||||||
|
msgstr "Рейтинг точности"
|
||||||
|
|
||||||
|
msgid "header:keystats"
|
||||||
|
msgstr "Ключевые статистики"
|
||||||
|
|
||||||
|
msgid "header:num moves"
|
||||||
|
msgstr "Количество ходов"
|
||||||
|
|
||||||
|
msgid "Opening"
|
||||||
|
msgstr "Начало игры"
|
||||||
|
|
||||||
|
msgid "Endgame"
|
||||||
|
msgstr "Окончание игры"
|
||||||
|
|
||||||
|
msgid "Midgame"
|
||||||
|
msgstr "Середина игры"
|
||||||
|
|
||||||
|
msgid "stat:meanpointloss"
|
||||||
|
msgstr "Главные потери очков"
|
||||||
|
|
||||||
|
msgid "stat:aitop5"
|
||||||
|
msgstr ""
|
||||||
|
"% совпадения с\n"
|
||||||
|
"топ-5 ходами ИИ"
|
||||||
|
|
||||||
|
msgid "stat:aitopmove"
|
||||||
|
msgstr ""
|
||||||
|
"% совпадения с\n"
|
||||||
|
"идеальными ходами ИИ"
|
||||||
|
|
||||||
|
msgid "Entire Game"
|
||||||
|
msgstr "Полная игра"
|
||||||
Binary file not shown.
@@ -864,10 +864,80 @@ msgstr "這會產生部分的對局讓你接著下。例如:你可以使用它
|
|||||||
msgid "editgame"
|
msgid "editgame"
|
||||||
msgstr "編輯對局資訊"
|
msgstr "編輯對局資訊"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "new zealand"
|
msgid "new zealand"
|
||||||
msgstr "New Zealand"
|
msgstr "紐西蘭"
|
||||||
|
|
||||||
#. TODO
|
|
||||||
msgid "tromp-taylor"
|
msgid "tromp-taylor"
|
||||||
msgstr "Tromp-Taylor"
|
msgstr "Tromp-Taylor"
|
||||||
|
|
||||||
|
#. View a player performance report
|
||||||
|
msgid "analysis:report"
|
||||||
|
msgstr "表現報告"
|
||||||
|
|
||||||
|
msgid "engine died popup opening message"
|
||||||
|
msgstr ""
|
||||||
|
"KataGo引擎啟動失敗或崩潰。\n"
|
||||||
|
"\n"
|
||||||
|
"錯誤:{error_message}\n"
|
||||||
|
"\n"
|
||||||
|
"錯誤碼:{code}."
|
||||||
|
|
||||||
|
#. Link in tags should not be touched (uses link_here msgid)
|
||||||
|
msgid "go to engine help page"
|
||||||
|
msgstr "如果你還有任何困難,請參考疑難排解頁面 {link}"
|
||||||
|
|
||||||
|
#. Link in tags should not be touched.
|
||||||
|
msgid "change engine suggestion"
|
||||||
|
msgstr "首先,在 {link} 中,嘗試下載且更換為不同版本的KataGo執行檔是否能解決問題。"
|
||||||
|
|
||||||
|
#. as in "click here" to go to this page,
|
||||||
|
msgid "link_here"
|
||||||
|
msgstr "點擊此處"
|
||||||
|
|
||||||
|
#. popup title
|
||||||
|
msgid "engine recovery"
|
||||||
|
msgstr "KataGo設定指南"
|
||||||
|
|
||||||
|
#. label in little popup for analyzing entire game
|
||||||
|
msgid "reanalyze mistakes only"
|
||||||
|
msgstr "僅分析錯誤操作"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:accuracy"
|
||||||
|
msgstr "準確度評分"
|
||||||
|
|
||||||
|
#. header in performance report
|
||||||
|
msgid "header:keystats"
|
||||||
|
msgstr "關鍵數據"
|
||||||
|
|
||||||
|
#. header in performance report
|
||||||
|
msgid "header:num moves"
|
||||||
|
msgstr "步數"
|
||||||
|
|
||||||
|
#. opening of the game filter in performance report
|
||||||
|
msgid "Opening"
|
||||||
|
msgstr "開局"
|
||||||
|
|
||||||
|
#. end of the game filter in performance report
|
||||||
|
msgid "Endgame"
|
||||||
|
msgstr "終盤"
|
||||||
|
|
||||||
|
#. middle of the game filter in performance report
|
||||||
|
msgid "Midgame"
|
||||||
|
msgstr "中盤"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:meanpointloss"
|
||||||
|
msgstr "平均損失目數"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:aitop5"
|
||||||
|
msgstr "AI前5佳選點重合度 %"
|
||||||
|
|
||||||
|
#. statistic in performance report
|
||||||
|
msgid "stat:aitopmove"
|
||||||
|
msgstr "AI最佳選點重合度 %"
|
||||||
|
|
||||||
|
#. game filter in performance report
|
||||||
|
msgid "Entire Game"
|
||||||
|
msgstr "全局"
|
||||||
+81
-10
@@ -79,7 +79,6 @@
|
|||||||
font_size: sp(Theme.DESC_FONT_SIZE) * 0.66
|
font_size: sp(Theme.DESC_FONT_SIZE) * 0.66
|
||||||
|
|
||||||
<ConfigPopup>:
|
<ConfigPopup>:
|
||||||
configfile: configfile
|
|
||||||
model_path: model_path
|
model_path: model_path
|
||||||
katago_path: katago_path
|
katago_path: katago_path
|
||||||
model_files: model_files
|
model_files: model_files
|
||||||
@@ -201,11 +200,11 @@
|
|||||||
input_property: "general/debug_level"
|
input_property: "general/debug_level"
|
||||||
helper_text: i18n._("general:debug_level:hint")
|
helper_text: i18n._("general:debug_level:hint")
|
||||||
helper_text_mode: "on_focus"
|
helper_text_mode: "on_focus"
|
||||||
SmallDescriptionLabel:
|
DescriptionLabel:
|
||||||
text: i18n._('config file path')
|
text: 'Restore window size on startup'
|
||||||
SmallDescriptionLabel:
|
AnchorLayout:
|
||||||
id: configfile
|
LabelledCheckBox:
|
||||||
text: app.gui.config_file
|
input_property: "ui_state/restoresize"
|
||||||
BackgroundMixin:
|
BackgroundMixin:
|
||||||
background_color: LIGHT_GREY
|
background_color: LIGHT_GREY
|
||||||
size_hint: None, 1
|
size_hint: None, 1
|
||||||
@@ -255,7 +254,13 @@
|
|||||||
height: sp(40)
|
height: sp(40)
|
||||||
text: i18n._("update settings")
|
text: i18n._("update settings")
|
||||||
on_press: root.update_config(True)
|
on_press: root.update_config(True)
|
||||||
|
# BoxLayout:
|
||||||
|
# orientation: 'vertical'
|
||||||
|
# SmallDescriptionLabel:
|
||||||
|
# text: i18n._('config file path')
|
||||||
|
# SmallDescriptionLabel:
|
||||||
|
# id: configfile
|
||||||
|
# text:
|
||||||
|
|
||||||
<ContributePopup>:
|
<ContributePopup>:
|
||||||
katago_path: katago_path
|
katago_path: katago_path
|
||||||
@@ -789,6 +794,16 @@
|
|||||||
text: i18n._("update ai settings")
|
text: i18n._("update ai settings")
|
||||||
on_press: root.update_config(True)
|
on_press: root.update_config(True)
|
||||||
|
|
||||||
|
<EngineRecoveryPopup>:
|
||||||
|
Label:
|
||||||
|
padding: CP_PADDING, CP_PADDING*3
|
||||||
|
text: i18n._("engine died popup opening message").format(code=root.code,error_message=root.error_message) + "\n\n" + i18n._("change engine suggestion").format(link="[color=#CCCC11][u][ref=engine_settings]"+i18n._('menu:settings')+"[/ref][/u][/color]") + "\n\n" + i18n._("go to engine help page").format(link="[color=#CCCC11][u][ref=engine_help]"+i18n._('link_here')+"[/ref][/u][/color]")
|
||||||
|
text_size: self.size
|
||||||
|
font_size: sp(Theme.DESC_FONT_SIZE)
|
||||||
|
markup: True
|
||||||
|
valign: 'top'
|
||||||
|
on_ref_press: app.webbrowser('engine:help') if args[1]=='engine_help' else app.gui('config-popup')
|
||||||
|
|
||||||
|
|
||||||
<ReAnalyzeGamePopup>:
|
<ReAnalyzeGamePopup>:
|
||||||
visits: visits
|
visits: visits
|
||||||
@@ -803,15 +818,23 @@
|
|||||||
LabelledIntInput:
|
LabelledIntInput:
|
||||||
id: visits
|
id: visits
|
||||||
text: '2500'
|
text: '2500'
|
||||||
|
BoxLayout:
|
||||||
|
size_hint: 1, 1.5
|
||||||
|
orientation: 'horizontal'
|
||||||
|
DescriptionLabel:
|
||||||
|
text: i18n._('reanalyze mistakes only')
|
||||||
|
AnchorLayout:
|
||||||
|
LabelledCheckBox:
|
||||||
|
id: mistakes
|
||||||
AnchorLayout:
|
AnchorLayout:
|
||||||
size_hint: 1, 1
|
size_hint: 1, 1
|
||||||
AutoSizedRoundedRectangleButton:
|
AutoSizedRoundedRectangleButton:
|
||||||
id: button
|
id: button
|
||||||
padding_x: 15
|
padding_x: 15
|
||||||
size_hint: None,0.5
|
size_hint: None,0.66
|
||||||
text: i18n._("analysis:game") # re-use string
|
text: i18n._("analysis:game") # re-use string
|
||||||
on_press:
|
on_press:
|
||||||
root.katrain("analyze-extra", "game", visits=visits.input_value)
|
root.katrain("analyze-extra", "game", visits=visits.input_value, mistakes_only=mistakes.input_value)
|
||||||
root.popup.dismiss()
|
root.popup.dismiss()
|
||||||
|
|
||||||
<LoadSGFPopup>:
|
<LoadSGFPopup>:
|
||||||
@@ -858,4 +881,52 @@
|
|||||||
file_must_exist: False
|
file_must_exist: False
|
||||||
filters: ["*.sgf","*.gib","*.ngf"]
|
filters: ["*.sgf","*.gib","*.ngf"]
|
||||||
path: "."
|
path: "."
|
||||||
size_hint: 1,7
|
size_hint: 1,7
|
||||||
|
|
||||||
|
|
||||||
|
<GameReportPopup>
|
||||||
|
player_infos: {'B': player_b, 'W': player_w}
|
||||||
|
stats: stats
|
||||||
|
orientation: 'vertical'
|
||||||
|
padding: Theme.CP_PADDING
|
||||||
|
BoxLayout:
|
||||||
|
size_hint_y: 0.8
|
||||||
|
spacing: CP_SPACING
|
||||||
|
padding: CP_PADDING
|
||||||
|
SizedRectangleToggleButton:
|
||||||
|
text: i18n._('Entire Game')
|
||||||
|
allow_no_selection: False
|
||||||
|
size_hint: 1,1
|
||||||
|
group: 'stats_depth'
|
||||||
|
state: 'down'
|
||||||
|
on_press: root.set_depth_filter(None)
|
||||||
|
SizedRectangleToggleButton:
|
||||||
|
text: i18n._('Opening')
|
||||||
|
allow_no_selection: False
|
||||||
|
size_hint: 1,1
|
||||||
|
group: 'stats_depth'
|
||||||
|
on_press: root.set_depth_filter((0,0.14))
|
||||||
|
SizedRectangleToggleButton:
|
||||||
|
text: i18n._('Midgame')
|
||||||
|
allow_no_selection: False
|
||||||
|
size_hint: 1,1
|
||||||
|
group: 'stats_depth'
|
||||||
|
on_press: root.set_depth_filter((0.14,0.4))
|
||||||
|
SizedRectangleToggleButton:
|
||||||
|
text: i18n._('Endgame')
|
||||||
|
group: 'stats_depth'
|
||||||
|
allow_no_selection: False
|
||||||
|
size_hint: 1,1
|
||||||
|
on_press: root.set_depth_filter((0.4,10))
|
||||||
|
BoxLayout:
|
||||||
|
size_hint_y: 1.33
|
||||||
|
PlayerInfo:
|
||||||
|
player: 'B'
|
||||||
|
id: player_b
|
||||||
|
PlayerInfo:
|
||||||
|
player: 'W'
|
||||||
|
id: player_w
|
||||||
|
alignment: 'left'
|
||||||
|
BoxLayout:
|
||||||
|
id: stats
|
||||||
|
size_hint_y: 10
|
||||||
@@ -43,6 +43,7 @@ setup(
|
|||||||
"setuptools",
|
"setuptools",
|
||||||
"kivy[full]>=2.0.0",
|
"kivy[full]>=2.0.0",
|
||||||
"kivymd>=0.104.1",
|
"kivymd>=0.104.1",
|
||||||
|
"ffpyplayer",
|
||||||
"importlib_resources ;python_version<'3.7'",
|
"importlib_resources ;python_version<'3.7'",
|
||||||
"urllib3",
|
"urllib3",
|
||||||
"pygame;platform_system=='Darwin'", # some mac versions need this for kivy
|
"pygame;platform_system=='Darwin'", # some mac versions need this for kivy
|
||||||
|
|||||||
+107
@@ -0,0 +1,107 @@
|
|||||||
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
|
import pandas as pd
|
||||||
|
import numpy as np
|
||||||
|
from unidecode import unidecode
|
||||||
|
|
||||||
|
from katrain.core.ai import game_report
|
||||||
|
from katrain.core.base_katrain import KaTrainBase
|
||||||
|
from katrain.core.engine import KataGoEngine
|
||||||
|
from katrain.core.game import KaTrainSGF, Game
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
pd.set_option("display.max_rows", 5000)
|
||||||
|
|
||||||
|
settings = {
|
||||||
|
"fast_visits": 25,
|
||||||
|
"max_visits": 500,
|
||||||
|
"threads": 64,
|
||||||
|
"model": "C:\\Users\\sande\\.katrain\\kata1-b40c256-s7907049728-d1917596640.bin.gz",
|
||||||
|
}
|
||||||
|
# settings["model"] = "C:\\Users\\sande\\.katrain\\g170e-b20c256x2-s5303129600-d1228401921.bin.gz"
|
||||||
|
|
||||||
|
|
||||||
|
def dan(rank):
|
||||||
|
rank = rank.lower()
|
||||||
|
if rank[-1] in ["d", "p"]:
|
||||||
|
return int(rank[:-1])
|
||||||
|
elif rank == "?":
|
||||||
|
return np.nan
|
||||||
|
else:
|
||||||
|
assert rank[-1] == "k", f"unexpected rank {rank}"
|
||||||
|
return 1 - int(rank[:-1])
|
||||||
|
|
||||||
|
|
||||||
|
def polyfit(x, y, degree=1):
|
||||||
|
coeffs = np.polyfit(x, y, degree)
|
||||||
|
correlation = np.corrcoef(x, y)[0, 1]
|
||||||
|
results = {"coef": coeffs.tolist(), "r": correlation, "rsq": correlation ** 2}
|
||||||
|
return results
|
||||||
|
|
||||||
|
|
||||||
|
katrain = KaTrainBase(force_package_config=True, debug_level=0)
|
||||||
|
combined_settings = {**katrain.config("engine"), **settings}
|
||||||
|
engine = KataGoEngine(katrain, {**katrain.config("engine"), **settings})
|
||||||
|
thresholds = katrain.config("trainer/eval_thresholds")
|
||||||
|
|
||||||
|
games = []
|
||||||
|
n = 0
|
||||||
|
for sgf in os.listdir("sgftest/"):
|
||||||
|
if sgf.lower().endswith("sgf"):
|
||||||
|
print(sgf)
|
||||||
|
with open(os.path.join("sgftest", sgf)) as f:
|
||||||
|
move_tree = KaTrainSGF.parse_sgf(f.read())
|
||||||
|
games.append(Game(katrain, engine, move_tree=move_tree, analyze_fast=False))
|
||||||
|
n += 1
|
||||||
|
if n >= 30000: # small test=3
|
||||||
|
break
|
||||||
|
|
||||||
|
while not engine.is_idle():
|
||||||
|
print(f"waiting for engine to finish...{engine.queries_remaining()} queries left")
|
||||||
|
time.sleep(0.5)
|
||||||
|
engine.shutdown(finish=None)
|
||||||
|
|
||||||
|
reports = []
|
||||||
|
for game in games:
|
||||||
|
sum_stats, _, _ = game_report(game, thresholds=thresholds)
|
||||||
|
for bw in "BW":
|
||||||
|
oppbw = "B" if bw == "W" else "W"
|
||||||
|
info = {
|
||||||
|
f"name": game.root.get_property(f"P{bw}", "??"),
|
||||||
|
"rank": game.root.get_property(f"{bw}R", "9p"),
|
||||||
|
"opp_rank": game.root.get_property(f"{oppbw}R", "9p"),
|
||||||
|
**sum_stats[bw],
|
||||||
|
}
|
||||||
|
reports.append(info)
|
||||||
|
|
||||||
|
df = pd.DataFrame(reports).sort_values(by="accuracy", ascending=False).reset_index(drop=True)
|
||||||
|
df.name = [unidecode(n) for n in df.name]
|
||||||
|
print(df)
|
||||||
|
|
||||||
|
df["numrank"] = [dan(rank) for rank in df["rank"]]
|
||||||
|
|
||||||
|
|
||||||
|
def subplot(sp, ynames):
|
||||||
|
global df
|
||||||
|
plt.subplot(2, 2, sp)
|
||||||
|
legend = []
|
||||||
|
xfull = np.array(range(df["numrank"].min(), df["numrank"].max() + 1))
|
||||||
|
cols = "bgr"
|
||||||
|
for i, yname in enumerate(ynames):
|
||||||
|
plt.plot(df["numrank"], df[yname], cols[i] + "x")
|
||||||
|
for i, yname in enumerate(ynames):
|
||||||
|
fit = polyfit(df["numrank"], df[yname])
|
||||||
|
a, b = fit["coef"]
|
||||||
|
plt.plot(xfull, xfull * a + b, cols[i] + ":")
|
||||||
|
legend.append(f"{yname}: r^2 = {fit['rsq']:.3f}")
|
||||||
|
plt.xlabel("dan rank")
|
||||||
|
plt.legend(legend)
|
||||||
|
|
||||||
|
|
||||||
|
plt.subplots(2, 2)
|
||||||
|
subplot(1, ["accuracy"])
|
||||||
|
subplot(2, ["complexity"])
|
||||||
|
subplot(3, ["ai_top_move", "ai_top5_move"])
|
||||||
|
subplot(4, ["mean_ptloss", "weighted_ptloss"])
|
||||||
|
plt.show()
|
||||||
@@ -104,6 +104,17 @@ def test_old_server_style():
|
|||||||
SGF.parse_sgf(input_sgf)
|
SGF.parse_sgf(input_sgf)
|
||||||
|
|
||||||
|
|
||||||
|
def test_old_server_style_again():
|
||||||
|
input_sgf = """(;
|
||||||
|
SZ[19]TM[600]KM[0.500000]LT[]
|
||||||
|
|
||||||
|
;B[fp]BL[500];
|
||||||
|
|
||||||
|
)"""
|
||||||
|
tree = SGF.parse_sgf(input_sgf)
|
||||||
|
assert 2 == len(tree.nodes_in_tree)
|
||||||
|
|
||||||
|
|
||||||
def test_ogs():
|
def test_ogs():
|
||||||
file = os.path.join(os.path.dirname(__file__), "data/ogs.sgf")
|
file = os.path.join(os.path.dirname(__file__), "data/ogs.sgf")
|
||||||
SGF.parse_file(file)
|
SGF.parse_file(file)
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user