package up

This commit is contained in:
Sander Land committed 2020-05-13 17:23:54 +02:00
1 parent 3e04ac2942
commit f8341801ee
61 files changed
+693 -435

No files matched your search

+3 -1
View File
@@ -1,8 +1,10 @@
# additions # additions
KataGoData KataGoData
KataGo/KataGoData katrain/KataGo/KataGoData
experiments experiments
.idea .idea
.buildozer
bin
gtp.log gtp.log
*.zip *.zip
log.txt log.txt
+31 -41
View File
@@ -1,55 +1,45 @@
# Installation from source for Windows users
* Download the repository by clicking the green *Clone or download* on this page and *Download zip*. Extract the contents.
* Make sure you have a python installation, I will assume Anaconda (Python 3.7), available [here](https://www.anaconda.com/products/individual#download-section).
* Open 'Anaconda prompt' from the start menu and navigate to where you extracted the zip file using the `cd <folder>` command.
* Execute the command `pip install kivy_deps.glew kivy_deps.sdl2 kivy_deps.gstreamer kivy`
* Start the app by running `python katrain.py` in the directory where you downloaded the scripts.
* Note that the program can be slow to initialize the first time, due to KataGo's gpu tuning.
# Installation for Linux users
* This assumed you have a working Python 3.6/3.7 installation as a default. If your default is python 2, use pip3/python3.
Kivy currently does not have a release for Python 3.8.
* Open a terminal.
* Run the command `git clone https://github.com/sanderland/katrain.git` to download the repository.
* Run the command `pip install kivy`.
* A binary for KataGo is included, but if you have compiled your own, point the 'engine/katago' setting to the relevant KataGo v1.4+ binary.
* Start the app by changing directory using `cd katrain` and running `python katrain.py`.
* Note that the program can be slow to initialize the first time, due to KataGo's GPU tuning.
# Installation for MacOS users # Installation for MacOS users
## Installation pre-requisites ## <a name="MacPrereq"></a>Installation pre-requisites
* Download and install [Python 3.7.5](https://www.python.org/downloads/release/python-375/) * Download and install [Python 3.7.5](https://www.python.org/downloads/release/python-375/)
* Install [Homebrew](https://brew.sh) by running the following command in terminal: * Install [Homebrew](https://brew.sh) by running the following command in terminal:
* ``` * ```
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)" /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
``` ```
* Run the command `pip3 install kivy` in the terminal. * Install Katago using [Homebrew](https://brew.sh/) by executing `brew install katago`
* Install Katago using [Homebrew](https://brew.sh/) * You can also follow instructions [here](https://github.com/lightvector/KataGo) to compile KataGo yourself.
* Note that the version required for KaTrain is currently too new so we need to update the Homebrew script.
* Run the command `brew edit katago` and replace lines 4-5 with
* ```
url "https://github.com/lightvector/KataGo/archive/v1.4.1.tar.gz"
sha256 "b408086c7c973ddc6144e16156907556ae5f42921b9f29dc13e6909a9e9a4787"
```
* You can also follow instructions [here](https://github.com/lightvector/KataGo) to compile KataGo yourself.
## Installation and running KaTrain ## Installation and running KaTrain from PyPi
* Run `pip3 install katrain`
* Now that the dependencies are installed its time to Git clone or download the KaTrain repository
* Run the command `git clone https://github.com/sanderland/katrain.git` this will clone KaTrain to your home folder.
* To run Katrain you need to first access the KaTrain folder.
* If you used the 'git clone' command to download the repository then its located in your home folder.
You can access it by typing `cd katrain` in the terminal.
* If you've moved the folder to another location the easiest way to navigate to it in terminal is to type `cd` and drag
the KaTrain folder from the finder window into terminal. This will copy its full path to the command line.
* Now that we're in the KaTrain folder run the following command. `python3 katrain.py`
* The first time you run KaTrain you will see an error about initializing KataGo. * The first time you run KaTrain you will see an error about initializing KataGo.
* Open the settings dialog by clicking on the gear icon at the bottom right of the window and change the path of the 'katago' * Open the settings dialog by clicking on the gear icon at the bottom right of the window and change the path of the 'katago'
setting to `/usr/local/bin/katago` (or the path where you compiled KataGo) then click 'Apply and Save'. setting under 'engine' to `katago` and then click 'Apply and Save'.
* If katago is not in your path, try `/usr/local/bin/katago`, or the path where you compiled KataGo.
* Run the program by typing `katrain` in the terminal.
## Installation from sources
* This is largely the same as for linux, see [here](#LinuxSources).
# Installation from sources for Windows users
* Download the repository by clicking the green *Clone or download* on this page and *Download zip*. Extract the contents.
* Make sure you have a python installation, I will assume Anaconda (Python 3.7), available [here](https://www.anaconda.com/products/individual#download-section).
* Open 'Anaconda prompt' from the start menu and navigate to where you extracted the zip file using the `cd <folder>` command.
* Execute the command `python setup.py install`
* Start the app by running `katrain` in the directory where you downloaded the scripts.
# <a name="LinuxSources"></a>Installation from sources for Linux users
* This assumed you have a working Python 3.6/3.7 installation as a default. If your default is python 2, use pip3/python3.
Kivy currently does not have a release for Python 3.8.
* Open a terminal.
* Run the command `git clone https://github.com/sanderland/katrain.git` to download the repository.
* Changing directory using `cd katrain`.
* Run the command `python3 setup.py install`.
* A binary for KataGo is included, but if you have compiled your own, point the 'engine/katago' setting to the relevant KataGo v1.4+ binary.
* Run the program by typing `katrain` in the terminal.
# Configuring the GPU(s) KataGo uses # Configuring the GPU(s) KataGo uses
+13
View File
@@ -0,0 +1,13 @@
[[source]]
name = "pypi"
url = "https://pypi.org/simple"
verify_ssl = true
[dev-packages]
pytest = "*"
[packages]
kivy = ">=1.11"
[requires]
python_version = "3.7"
Generated
+178
View File
@@ -0,0 +1,178 @@
{
"_meta": {
"hash": {
"sha256": "63ca72eff43ccb8467fc921871c0438382996aa6fc1642e955cf1432c90ce520"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.7"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"certifi": {
"hashes": [
"sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304",
"sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519"
],
"version": "==2020.4.5.1"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"docutils": {
"hashes": [
"sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af",
"sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc"
],
"version": "==0.16"
},
"idna": {
"hashes": [
"sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb",
"sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa"
],
"version": "==2.9"
},
"kivy": {
"hashes": [
"sha256:090d3ded9835a17477cd93fbdaf0a7c42ff2218981cf198ded5ad8795bc74391",
"sha256:11e85eaf6efbfa2362a3334ffdad179a1b0ca8d255cca79eaa6a2765560d4982",
"sha256:1a1ff32f8a95f1e175198cbab81fcd2596783b180d4eafe63e87d171aa7fdb5e",
"sha256:1d28b198a64c30db8d94a0488e85f3037af60d514ab0d7ad5ab45add3ab77090",
"sha256:4a5480cbf837d3780c77a4f61b32b56d22ae9f03845e7a89dd3eaef1ae5fd037",
"sha256:4d0e596f74271e901b551f77661dde238df4765484fce9f5d1c72e8022984e84",
"sha256:5c3d0f2749522d62e9cce09cd54b2d823bf1b6b644ff1f627be49de6f3e3cba0",
"sha256:815a5c0b3b72fcd81ca7b2aa0744087163ed03e4cf9ab4e7c9733cea99fc1571",
"sha256:8819a27a09871af451760cb69486ced52e830c8a0a37480f22ef5e692f12c05b",
"sha256:a687602d90c4629dd036f577ca39acb76ba581370f9d915f3cab99be818ba8ad",
"sha256:b7ef6aad43a86d8df3fb865db864e354f2155a748019f8517f69f65c1a29cb64",
"sha256:b85ccf165050cbf2ee8447671eebbc222b369b40f0e0038dd9547d49a5e37373",
"sha256:c36652caa7f6c327dee834cfc699d5962d346b7a53e54bd81abc17c314226d89",
"sha256:ece170514db3f49844a41e4c910ad9ce9bc46da6f47a49158e11266bdcc6e479",
"sha256:f3bea6e4a21991827885d04127fc6d09a0e974ecfa12da7bf5faae93562ea102",
"sha256:f835462dd9aa491272552ef079b948a088598e2e95d68bb1d885d2c3f3d4e2c3"
],
"index": "pypi",
"version": "==1.11.1"
},
"kivy-garden": {
"hashes": [
"sha256:c256f42788421273a08fbb0a228f0fb0e80dd86b629fb8c0920507f645be6c72"
],
"version": "==0.1.4"
},
"pygments": {
"hashes": [
"sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44",
"sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324"
],
"version": "==2.6.1"
},
"requests": {
"hashes": [
"sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee",
"sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6"
],
"version": "==2.23.0"
},
"urllib3": {
"hashes": [
"sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527",
"sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115"
],
"version": "==1.25.9"
}
},
"develop": {
"attrs": {
"hashes": [
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
],
"version": "==19.3.0"
},
"importlib-metadata": {
"hashes": [
"sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f",
"sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e"
],
"markers": "python_version < '3.8'",
"version": "==1.6.0"
},
"more-itertools": {
"hashes": [
"sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c",
"sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"
],
"version": "==8.2.0"
},
"packaging": {
"hashes": [
"sha256:3c292b474fda1671ec57d46d739d072bfd495a4f51ad01a055121d81e952b7a3",
"sha256:82f77b9bee21c1bafbf35a84905d604d5d1223801d639cf3ed140bd651c08752"
],
"version": "==20.3"
},
"pluggy": {
"hashes": [
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
],
"version": "==0.13.1"
},
"py": {
"hashes": [
"sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa",
"sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"
],
"version": "==1.8.1"
},
"pyparsing": {
"hashes": [
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"version": "==2.4.7"
},
"pytest": {
"hashes": [
"sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3",
"sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698"
],
"index": "pypi",
"version": "==5.4.2"
},
"six": {
"hashes": [
"sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a",
"sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"
],
"version": "==1.14.0"
},
"wcwidth": {
"hashes": [
"sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1",
"sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1"
],
"version": "==0.1.9"
},
"zipp": {
"hashes": [
"sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b",
"sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96"
],
"version": "==3.1.0"
}
}
}
+4 -2
View File
@@ -15,7 +15,7 @@ but has since grown to include a wide range of features, including:
| Analyze games | Play against an AI Teacher | | Analyze games | Play against an AI Teacher |
| ------------- | ------------- | | ------------- | ------------- |
| ![screenshot](img/anim_analyze.gif) | ![screenshot](img/anim_teach.gif) | | ![screenshot](katrain/img/anim_analyze.gif) | ![screenshot](katrain/img/anim_teach.gif) |
## Quickstart ## Quickstart
@@ -26,7 +26,9 @@ but has since grown to include a wide range of features, including:
## Installation ## Installation
* See the [releases tab](https://github.com/sanderland/katrain/releases) for pre-built installers for windows. * See the [releases tab](https://github.com/sanderland/katrain/releases) for pre-built installers for windows.
* See [here](INSTALL.md) for detailed instructions for running from source files on Window, Linux and MacOS, * Alternatively use `pip3 install -U katrain` to install the latest version from PyPI on any OS.
* Note that on MacOS you will need to set up KataGo using brew, as described [here](INSTALL.md).
* See [here](INSTALL.md#MacPrereq) for detailed instructions for running from source files on Window, Linux and MacOS,
as well as setting up KataGo to use multiple GPUs. as well as setting up KataGo to use multiple GPUs.
## Manual ## Manual
+6 -6
View File
@@ -4,12 +4,12 @@ import sys
import time import time
import random import random
from core.ai import ai_move from katrain.core.ai import ai_move
from core.common import OUTPUT_ERROR, OUTPUT_INFO from katrain.core.common import OUTPUT_ERROR, OUTPUT_INFO
from bots.settings import bot_strategy_names from bots.settings import bot_strategy_names
from core.engine import EngineDiedException, KataGoEngine from katrain.core.engine import EngineDiedException, KataGoEngine
from core.game import Game from katrain.core.game import Game
from core.sgf_parser import Move from katrain.core.sgf_parser import Move
if len(sys.argv) < 2: if len(sys.argv) < 2:
bot = "dev" bot = "dev"
@@ -42,7 +42,7 @@ ENGINE_SETTINGS = {
engine = KataGoEngine(logger, ENGINE_SETTINGS) engine = KataGoEngine(logger, ENGINE_SETTINGS)
with open("config.json") as f: with open("katrain/config.json") as f:
settings = json.load(f) settings = json.load(f)
all_ai_settings = settings["ai"] all_ai_settings = settings["ai"]
+2 -2
View File
@@ -6,8 +6,8 @@ import sys
import threading import threading
import traceback import traceback
from core.common import OUTPUT_INFO from katrain.core.common import OUTPUT_INFO
from core.engine import KataGoEngine from katrain.core.engine import KataGoEngine
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8587 PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8587
+4 -4
View File
@@ -7,11 +7,11 @@ import traceback
from collections import defaultdict from collections import defaultdict
from concurrent.futures.thread import ThreadPoolExecutor from concurrent.futures.thread import ThreadPoolExecutor
from core.ai import ai_move from katrain.core.ai import ai_move
from core.common import OUTPUT_ERROR, OUTPUT_INFO from katrain.core.common import OUTPUT_ERROR, OUTPUT_INFO
from elote import EloCompetitor from elote import EloCompetitor
from core.engine import KataGoEngine from katrain.core.engine import KataGoEngine
from core.game import Game from katrain.core.game import Game
import json import json
DB_FILENAME = "bots/ai_performance.pickle" DB_FILENAME = "bots/ai_performance.pickle"
+1 -1
View File
@@ -22,7 +22,7 @@ else:
username = f"katrain-{bot}" username = f"katrain-{bot}"
with open("config.json") as f: with open("katrain/config.json") as f:
settings = json.load(f) settings = json.load(f)
all_ai_settings = settings["ai"] all_ai_settings = settings["ai"]
-4
View File
@@ -1,4 +0,0 @@
from gui.badukpan import BadukPanControls, BadukPanWidget
from gui.controls import Controls
from gui.kivyutils import *
from gui.popups import LoadSGFPopup, NewGamePopup, ConfigAIPopup, ConfigTeacherPopup, ConfigPopup
+3 -347
View File
@@ -1,347 +1,3 @@
from kivy.config import Config # isort:skip # for backward compatibility
from katrain.__main__ import run_app
Config.set("input", "mouse", "mouse,multitouch_on_demand") # isort:skip # no red dots on right click run_app()
ICON = "img/icon.png"
Config.set("kivy", "window_icon", ICON) # isort:skip # set icon before Window is imported
import signal
import os
import sys
import threading
import traceback
from queue import Queue
from kivy.app import App
from kivy.core.clipboard import Clipboard
from kivy.storage.jsonstore import JsonStore
from kivy.uix.popup import Popup
from core.ai import ai_move
from core.common import OUTPUT_INFO, OUTPUT_ERROR, OUTPUT_DEBUG, OUTPUT_EXTRA_DEBUG, OUTPUT_KATAGO_STDERR
from core.engine import KataGoEngine
from core.game import Game, IllegalMoveException, KaTrainSGF
from core.sgf_parser import Move, ParseError
from gui import *
class KaTrainGui(BoxLayout):
"""Top level class responsible for tying everything together"""
def __init__(self, **kwargs):
super(KaTrainGui, self).__init__(**kwargs)
self.debug_level = 0
self.engine = None
self.game = None
self.new_game_popup = None
self.fileselect_popup = None
self.config_popup = None
self.logger = lambda message, level=OUTPUT_INFO: self.log(message, level)
self._load_config()
self.debug_level = self.config("debug/level", OUTPUT_INFO)
self.controls.ai_mode_groups["W"].values = self.controls.ai_mode_groups["B"].values = list(self.config("ai").keys())
self.message_queue = Queue()
self._keyboard = Window.request_keyboard(None, self, "")
self._keyboard.bind(on_key_down=self._on_keyboard_down)
def log(self, message, level=OUTPUT_INFO):
if level == OUTPUT_KATAGO_STDERR:
if "starting" in message.lower():
self.controls.set_status(f"KataGo engine starting...")
if message.startswith("Tuning"):
self.controls.set_status(f"KataGo is tuning settings for first startup, please wait." + message)
if "ready" in message.lower():
self.controls.set_status(f"KataGo engine ready.")
print(f"[KG:STDERR]{message.strip()}")
elif level == OUTPUT_ERROR:
self.controls.set_status(f"ERROR: {message}")
print(f"ERROR: {message}")
elif self.debug_level >= level:
print(message)
def _load_config(self):
base_path = getattr(sys, "_MEIPASS", os.getcwd()) # for pyinstaller
config_file = sys.argv[1] if len(sys.argv) > 1 else os.path.join(base_path, "config.json")
try:
self.log(f"Using config file {config_file}", OUTPUT_INFO)
self._config_store = JsonStore(config_file, indent=4)
self._config = dict(self._config_store)
except Exception as e:
self.log(f"Failed to load config {config_file}: {e}", OUTPUT_ERROR)
sys.exit(1)
def save_config(self):
for k, v in self._config.items():
self._config_store.put(k, **v)
def config(self, setting, default=None):
try:
if "/" in setting:
cat, key = setting.split("/")
return self._config[cat].get(key, default)
else:
return self._config[setting]
except KeyError:
self.log(f"Missing configuration option {setting}", OUTPUT_ERROR)
def start(self):
if self.engine:
return
self.board_gui.trainer_config = self.config("trainer")
self.board_gui.ui_config = self.config("board_ui")
self.engine = KataGoEngine(self, self.config("engine"))
threading.Thread(target=self._message_loop_thread, daemon=True).start()
self._do_new_game()
def update_state(self, redraw_board=False): # is called after every message and on receiving analyses and config changes
# AI and Trainer/auto-undo handlers
cn = self.game.current_node
auto_undo = cn.player and "undo" in self.controls.player_mode(cn.player)
if auto_undo and cn.analysis_ready and cn.parent and cn.parent.analysis_ready and not cn.children and not self.game.ended:
self.game.analyze_undo(cn, self.config("trainer")) # not via message loop
if cn.analysis_ready and "ai" in self.controls.player_mode(cn.next_player).lower() and not cn.children and not self.game.ended and not (auto_undo and cn.auto_undo is None):
self._do_ai_move(cn) # cn mismatch stops this if undo fired. avoid message loop here or fires repeatedly.
# Handle prisoners and next player display
prisoners = self.game.prisoner_count
top, bot = self.board_controls.black_prisoners.__self__, self.board_controls.white_prisoners.__self__ # no weakref
if self.game.next_player == "W":
top, bot = bot, top
self.board_controls.mid_circles_container.clear_widgets()
self.board_controls.mid_circles_container.add_widget(bot)
self.board_controls.mid_circles_container.add_widget(top)
self.board_controls.black_prisoners.text = str(prisoners["W"])
self.board_controls.white_prisoners.text = str(prisoners["B"])
# update engine status dot
if not self.engine or not self.engine.katago_process or self.engine.katago_process.poll() is not None:
self.board_controls.engine_status_col = self.config("board_ui/engine_down_col")
elif len(self.engine.queries) >= 4:
self.board_controls.engine_status_col = self.config("board_ui/engine_busy_col")
elif len(self.engine.queries) >= 2:
self.board_controls.engine_status_col = self.config("board_ui/engine_little_busy_col")
elif len(self.engine.queries) == 0:
self.board_controls.engine_status_col = self.config("board_ui/engine_ready_col")
else:
self.board_controls.engine_status_col = self.config("board_ui/engine_almost_done_col")
# redraw
if redraw_board:
Clock.schedule_once(self.board_gui.draw_board, -1)
self.board_gui.redraw_board_contents_trigger()
self.controls.update_evaluation()
def _message_loop_thread(self):
while True:
game, msg, *args = self.message_queue.get()
try:
self.log(f"Message Loop Received {msg}: {args} for Game {game}", OUTPUT_EXTRA_DEBUG)
if game != self.game.game_id:
self.log(f"Message skipped as it is outdated (current game is {self.game.game_id}", OUTPUT_EXTRA_DEBUG)
continue
getattr(self, f"_do_{msg.replace('-','_')}")(*args)
self.update_state()
except Exception as e:
self.log(f"Exception in processing message {msg} {args}: {e}", OUTPUT_ERROR)
traceback.print_exc()
def __call__(self, message, *args):
if self.game:
self.message_queue.put([self.game.game_id, message, *args])
def _do_new_game(self, move_tree=None, analyze_fast=False):
self.board_gui.animating_pv = None
self.engine.on_new_game() # clear queries
self.game = Game(self, self.engine, self.config("game"), move_tree=move_tree, analyze_fast=analyze_fast)
self.controls.select_mode("analyze" if move_tree and len(move_tree.nodes_in_tree) > 1 else "play")
self.controls.graph.initialize_from_game(self.game.root)
self.update_state(redraw_board=True)
def _do_ai_move(self, node=None):
if node is None or self.game.current_node == node:
mode = self.controls.ai_mode(self.game.current_node.next_player)
settings = self.config(f"ai/{mode}")
if settings:
ai_move(self.game, mode, settings)
def _do_undo(self, n_times=1):
self.board_gui.animating_pv = None
self.game.undo(n_times)
def _do_redo(self, n_times=1):
self.board_gui.animating_pv = None
self.game.redo(n_times)
def _do_switch_branch(self, direction):
self.game.switch_branch(direction)
def _do_play(self, coords):
self.board_gui.animating_pv = None
try:
self.game.play(Move(coords, player=self.game.next_player))
except IllegalMoveException as e:
self.controls.set_status(f"Illegal Move: {str(e)}")
def _do_analyze_extra(self, mode):
self.game.analyze_extra(mode)
def _do_analyze_sgf_popup(self):
if not self.fileselect_popup:
self.fileselect_popup = Popup(title="Double Click SGF file to analyze", size_hint=(0.8, 0.8)).__self__
popup_contents = LoadSGFPopup()
self.fileselect_popup.add_widget(popup_contents)
popup_contents.filesel.path = os.path.abspath(os.path.expanduser(self.config("sgf/sgf_load")))
def readfile(files, _mouse):
self.fileselect_popup.dismiss()
try:
move_tree = KaTrainSGF.parse_file(files[0])
except ParseError as e:
self.log(f"Failed to load SGF. Parse Error: {e}", OUTPUT_ERROR)
return
self._do_new_game(move_tree=move_tree, analyze_fast=popup_contents.fast.active)
if not popup_contents.rewind.active:
self.game.redo(999)
popup_contents.filesel.on_submit = readfile
self.fileselect_popup.open()
def _do_new_game_popup(self):
if not self.new_game_popup:
self.new_game_popup = Popup(title="New Game", size_hint=(0.5, 0.6)).__self__
popup_contents = NewGamePopup(self, self.new_game_popup, {k: v[0] for k, v in self.game.root.properties.items() if len(v) == 1})
self.new_game_popup.add_widget(popup_contents)
self.new_game_popup.open()
def _do_config_popup(self):
if not self.config_popup:
self.config_popup = Popup(title="Edit Settings", size_hint=(0.9, 0.9)).__self__
popup_contents = ConfigPopup(self, self.config_popup, dict(self._config), ignore_cats=("trainer", "ai"))
self.config_popup.add_widget(popup_contents)
self.config_popup.open()
def _do_output_sgf(self):
for pl in Move.PLAYERS:
if not self.game.root.get_property(f"P{pl}"):
_, model_file = os.path.split(self.engine.config["model"])
self.game.root.set_property(
f"P{pl}", f"AI {self.controls.ai_mode(pl)} (KataGo { os.path.splitext(model_file)[0]})" if "ai" in self.controls.player_mode(pl) else "Player"
)
msg = self.game.write_sgf(
self.config("sgf/sgf_save"),
trainer_config=self.config("trainer"),
save_feedback=self.config("sgf/save_feedback"),
eval_thresholds=self.config("trainer/eval_thresholds"),
)
self.log(msg, OUTPUT_INFO)
self.controls.set_status(msg)
def load_sgf_from_clipboard(self):
clipboard = Clipboard.paste()
if not clipboard:
self.controls.set_status(f"Ctrl-V pressed but clipboard is empty.")
return
try:
move_tree = KaTrainSGF.parse(clipboard)
except Exception as e:
self.controls.set_status(f"Failed to imported game from clipboard: {e}\nClipboard contents: {clipboard[:50]}...")
return
move_tree.nodes_in_tree[-1].analyze(self.engine, analyze_fast=False) # speed up result for looking at end of game
self._do_new_game(move_tree=move_tree, analyze_fast=True)
self("redo", 999)
self.log("Imported game from clipboard.", OUTPUT_INFO)
def on_touch_up(self, touch):
if self.board_gui.collide_point(*touch.pos) or self.board_controls.collide_point(*touch.pos):
if touch.button == "scrollup":
self("redo")
elif touch.button == "scrolldown":
self("undo")
return super().on_touch_up(touch)
def _on_keyboard_down(self, _keyboard, keycode, _text, modifiers):
if isinstance(App.get_running_app().root_window.children[0], Popup):
return # if in new game or load, don't allow keyboard shortcuts
shortcuts = {
"q": self.controls.show_children,
"w": self.controls.eval,
"e": self.controls.hints,
"r": self.controls.ownership,
"t": self.controls.policy,
"enter": ("ai-move",),
"a": self.controls.analyze_extra,
"s": self.controls.analyze_equalize,
"d": self.controls.analyze_sweep,
"right": ("switch-branch", 1),
"left": ("switch-branch", -1),
}
if keycode[1] in shortcuts.keys():
shortcut = shortcuts[keycode[1]]
if isinstance(shortcut, Widget):
shortcut.trigger_action(duration=0)
else:
self(*shortcut)
elif keycode[1] == "tab":
self.controls.switch_mode()
elif keycode[1] == "spacebar":
self("play", None) # pass
elif keycode[1] in ["`", "~", "p"]:
self.controls_box.hidden = not self.controls_box.hidden
elif keycode[1] in ["up", "z"]:
self("undo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999)
elif keycode[1] in ["down", "x"]:
self("redo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999)
elif keycode[1] == "n" and "ctrl" in modifiers:
self("new-game-popup")
elif keycode[1] == "l" and "ctrl" in modifiers:
self("analyze-sgf-popup")
elif keycode[1] == "s" and "ctrl" in modifiers:
self("output-sgf")
elif keycode[1] == "c" and "ctrl" in modifiers:
Clipboard.copy(self.game.root.sgf())
self.controls.set_status("Copied SGF to clipboard.")
elif keycode[1] == "v" and "ctrl" in modifiers:
self.load_sgf_from_clipboard()
return True
class KaTrainApp(App):
gui = ObjectProperty(None)
def build(self):
self.icon = ICON # how you're supposed to set an icon
self.gui = KaTrainGui()
print(self.get_application_icon())
Window.bind(on_request_close=self.on_request_close)
return self.gui
def on_start(self):
self.gui.start()
def on_request_close(self, *args):
if getattr(self, "gui", None) and self.gui.engine:
self.gui.engine.shutdown()
def signal_handler(self, *args):
if self.gui.debug_level >= OUTPUT_DEBUG:
print("TRACEBACKS")
for threadId, stack in sys._current_frames().items():
print(f"\n# ThreadID: {threadId}")
for filename, lineno, name, line in traceback.extract_stack(stack):
print(f"\tFile: {filename}, line {lineno}, in {name}")
if line:
print(f"\t\t{line.strip()}")
self.on_request_close()
sys.exit(0)
if __name__ == "__main__":
app = KaTrainApp()
signal.signal(signal.SIGINT, app.signal_handler)
try:
app.run()
except Exception:
app.on_request_close()
raise
File renamed without changes.
File renamed without changes.
File renamed without changes.
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
View File
Whitespace-only changes.
+360
View File
@@ -0,0 +1,360 @@
from kivy.config import Config # isort:skip
from kivy.lang import Builder
from kivy.resources import resource_add_path
Config.set("input", "mouse", "mouse,multitouch_on_demand") # isort:skip # no red dots on right click
ICON = "img/icon.png"
Config.set("kivy", "window_icon", ICON) # isort:skip # set icon before Window is imported
import signal
import os
import sys
import threading
import traceback
from queue import Queue
from kivy.app import App
from kivy.core.clipboard import Clipboard
from kivy.storage.jsonstore import JsonStore
from kivy.uix.popup import Popup
from katrain.core.ai import ai_move
from katrain.core.common import OUTPUT_INFO, OUTPUT_ERROR, OUTPUT_DEBUG, OUTPUT_EXTRA_DEBUG, OUTPUT_KATAGO_STDERR, find_package_resource
from katrain.core.engine import KataGoEngine
from katrain.core.game import Game, IllegalMoveException, KaTrainSGF
from katrain.core.sgf_parser import Move, ParseError
from katrain.gui.kivyutils import *
from katrain.gui.badukpan import BadukPanWidget
from katrain.gui.controls import Controls
from katrain.gui.popups import NewGamePopup, ConfigPopup, LoadSGFPopup
__version__ = "1.0.5"
class KaTrainGui(BoxLayout):
"""Top level class responsible for tying everything together"""
def __init__(self, **kwargs):
super(KaTrainGui, self).__init__(**kwargs)
self.debug_level = 0
self.engine = None
self.game = None
self.new_game_popup = None
self.fileselect_popup = None
self.config_popup = None
self.logger = lambda message, level=OUTPUT_INFO: self.log(message, level)
self.config_file = self._load_config()
self.debug_level = self.config("debug/level", OUTPUT_INFO)
self.controls.ai_mode_groups["W"].values = self.controls.ai_mode_groups["B"].values = list(self.config("ai").keys())
self.message_queue = Queue()
self._keyboard = Window.request_keyboard(None, self, "")
self._keyboard.bind(on_key_down=self._on_keyboard_down)
def log(self, message, level=OUTPUT_INFO):
if level == OUTPUT_KATAGO_STDERR:
if "starting" in message.lower():
self.controls.set_status(f"KataGo engine starting...")
if message.startswith("Tuning"):
self.controls.set_status(f"KataGo is tuning settings for first startup, please wait." + message)
if "ready" in message.lower():
self.controls.set_status(f"KataGo engine ready.")
print(f"[KG:STDERR]{message.strip()}")
elif level == OUTPUT_ERROR:
self.controls.set_status(f"ERROR: {message}")
print(f"ERROR: {message}")
elif self.debug_level >= level:
print(message)
def _load_config(self):
config_file = os.path.abspath(sys.argv[1] if len(sys.argv) > 1 else find_package_resource("katrain/config.json"))
try:
self.log(f"Using config file {config_file}", OUTPUT_INFO)
self._config_store = JsonStore(config_file, indent=4)
self._config = dict(self._config_store)
return config_file
except Exception as e:
self.log(f"Failed to load config {config_file}: {e}", OUTPUT_ERROR)
sys.exit(1)
def save_config(self):
for k, v in self._config.items():
self._config_store.put(k, **v)
def config(self, setting, default=None):
try:
if "/" in setting:
cat, key = setting.split("/")
return self._config[cat].get(key, default)
else:
return self._config[setting]
except KeyError:
self.log(f"Missing configuration option {setting}", OUTPUT_ERROR)
def start(self):
if self.engine:
return
self.board_gui.trainer_config = self.config("trainer")
self.board_gui.ui_config = self.config("board_ui")
self.engine = KataGoEngine(self, self.config("engine"))
threading.Thread(target=self._message_loop_thread, daemon=True).start()
self._do_new_game()
def update_state(self, redraw_board=False): # is called after every message and on receiving analyses and config changes
# AI and Trainer/auto-undo handlers
cn = self.game.current_node
auto_undo = cn.player and "undo" in self.controls.player_mode(cn.player)
if auto_undo and cn.analysis_ready and cn.parent and cn.parent.analysis_ready and not cn.children and not self.game.ended:
self.game.analyze_undo(cn, self.config("trainer")) # not via message loop
if cn.analysis_ready and "ai" in self.controls.player_mode(cn.next_player).lower() and not cn.children and not self.game.ended and not (auto_undo and cn.auto_undo is None):
self._do_ai_move(cn) # cn mismatch stops this if undo fired. avoid message loop here or fires repeatedly.
# Handle prisoners and next player display
prisoners = self.game.prisoner_count
top, bot = self.board_controls.black_prisoners.__self__, self.board_controls.white_prisoners.__self__ # no weakref
if self.game.next_player == "W":
top, bot = bot, top
self.board_controls.mid_circles_container.clear_widgets()
self.board_controls.mid_circles_container.add_widget(bot)
self.board_controls.mid_circles_container.add_widget(top)
self.board_controls.black_prisoners.text = str(prisoners["W"])
self.board_controls.white_prisoners.text = str(prisoners["B"])
# update engine status dot
if not self.engine or not self.engine.katago_process or self.engine.katago_process.poll() is not None:
self.board_controls.engine_status_col = self.config("board_ui/engine_down_col")
elif len(self.engine.queries) >= 4:
self.board_controls.engine_status_col = self.config("board_ui/engine_busy_col")
elif len(self.engine.queries) >= 2:
self.board_controls.engine_status_col = self.config("board_ui/engine_little_busy_col")
elif len(self.engine.queries) == 0:
self.board_controls.engine_status_col = self.config("board_ui/engine_ready_col")
else:
self.board_controls.engine_status_col = self.config("board_ui/engine_almost_done_col")
# redraw
if redraw_board:
Clock.schedule_once(self.board_gui.draw_board, -1)
self.board_gui.redraw_board_contents_trigger()
self.controls.update_evaluation()
def _message_loop_thread(self):
while True:
game, msg, *args = self.message_queue.get()
try:
self.log(f"Message Loop Received {msg}: {args} for Game {game}", OUTPUT_EXTRA_DEBUG)
if game != self.game.game_id:
self.log(f"Message skipped as it is outdated (current game is {self.game.game_id}", OUTPUT_EXTRA_DEBUG)
continue
getattr(self, f"_do_{msg.replace('-','_')}")(*args)
self.update_state()
except Exception as e:
self.log(f"Exception in processing message {msg} {args}: {e}", OUTPUT_ERROR)
traceback.print_exc()
def __call__(self, message, *args):
if self.game:
self.message_queue.put([self.game.game_id, message, *args])
def _do_new_game(self, move_tree=None, analyze_fast=False):
self.board_gui.animating_pv = None
self.engine.on_new_game() # clear queries
self.game = Game(self, self.engine, self.config("game"), move_tree=move_tree, analyze_fast=analyze_fast)
self.controls.select_mode("analyze" if move_tree and len(move_tree.nodes_in_tree) > 1 else "play")
self.controls.graph.initialize_from_game(self.game.root)
self.update_state(redraw_board=True)
def _do_ai_move(self, node=None):
if node is None or self.game.current_node == node:
mode = self.controls.ai_mode(self.game.current_node.next_player)
settings = self.config(f"ai/{mode}")
if settings:
ai_move(self.game, mode, settings)
def _do_undo(self, n_times=1):
self.board_gui.animating_pv = None
self.game.undo(n_times)
def _do_redo(self, n_times=1):
self.board_gui.animating_pv = None
self.game.redo(n_times)
def _do_switch_branch(self, direction):
self.game.switch_branch(direction)
def _do_play(self, coords):
self.board_gui.animating_pv = None
try:
self.game.play(Move(coords, player=self.game.next_player))
except IllegalMoveException as e:
self.controls.set_status(f"Illegal Move: {str(e)}")
def _do_analyze_extra(self, mode):
self.game.analyze_extra(mode)
def _do_analyze_sgf_popup(self):
if not self.fileselect_popup:
self.fileselect_popup = Popup(title="Double Click SGF file to analyze", size_hint=(0.8, 0.8)).__self__
popup_contents = LoadSGFPopup()
self.fileselect_popup.add_widget(popup_contents)
popup_contents.filesel.path = os.path.abspath(os.path.expanduser(self.config("sgf/sgf_load")))
def readfile(files, _mouse):
self.fileselect_popup.dismiss()
try:
move_tree = KaTrainSGF.parse_file(files[0])
except ParseError as e:
self.log(f"Failed to load SGF. Parse Error: {e}", OUTPUT_ERROR)
return
self._do_new_game(move_tree=move_tree, analyze_fast=popup_contents.fast.active)
if not popup_contents.rewind.active:
self.game.redo(999)
popup_contents.filesel.on_submit = readfile
self.fileselect_popup.open()
def _do_new_game_popup(self):
if not self.new_game_popup:
self.new_game_popup = Popup(title="New Game", size_hint=(0.5, 0.6)).__self__
popup_contents = NewGamePopup(self, self.new_game_popup, {k: v[0] for k, v in self.game.root.properties.items() if len(v) == 1})
self.new_game_popup.add_widget(popup_contents)
self.new_game_popup.open()
def _do_config_popup(self):
if not self.config_popup:
self.config_popup = Popup(title=f"Edit Settings - {self.config_file}", size_hint=(0.9, 0.9)).__self__
popup_contents = ConfigPopup(self, self.config_popup, dict(self._config), ignore_cats=("trainer", "ai"))
self.config_popup.add_widget(popup_contents)
self.config_popup.open()
def _do_output_sgf(self):
for pl in Move.PLAYERS:
if not self.game.root.get_property(f"P{pl}"):
_, model_file = os.path.split(self.engine.config["model"])
self.game.root.set_property(
f"P{pl}", f"AI {self.controls.ai_mode(pl)} (KataGo { os.path.splitext(model_file)[0]})" if "ai" in self.controls.player_mode(pl) else "Player"
)
msg = self.game.write_sgf(
self.config("sgf/sgf_save"),
trainer_config=self.config("trainer"),
save_feedback=self.config("sgf/save_feedback"),
eval_thresholds=self.config("trainer/eval_thresholds"),
)
self.log(msg, OUTPUT_INFO)
self.controls.set_status(msg)
def load_sgf_from_clipboard(self):
clipboard = Clipboard.paste()
if not clipboard:
self.controls.set_status(f"Ctrl-V pressed but clipboard is empty.")
return
try:
move_tree = KaTrainSGF.parse(clipboard)
except Exception as e:
self.controls.set_status(f"Failed to imported game from clipboard: {e}\nClipboard contents: {clipboard[:50]}...")
return
move_tree.nodes_in_tree[-1].analyze(self.engine, analyze_fast=False) # speed up result for looking at end of game
self._do_new_game(move_tree=move_tree, analyze_fast=True)
self("redo", 999)
self.log("Imported game from clipboard.", OUTPUT_INFO)
def on_touch_up(self, touch):
if self.board_gui.collide_point(*touch.pos) or self.board_controls.collide_point(*touch.pos):
if touch.button == "scrollup":
self("redo")
elif touch.button == "scrolldown":
self("undo")
return super().on_touch_up(touch)
def _on_keyboard_down(self, _keyboard, keycode, _text, modifiers):
if isinstance(App.get_running_app().root_window.children[0], Popup):
return # if in new game or load, don't allow keyboard shortcuts
shortcuts = {
"q": self.controls.show_children,
"w": self.controls.eval,
"e": self.controls.hints,
"r": self.controls.ownership,
"t": self.controls.policy,
"enter": ("ai-move",),
"a": self.controls.analyze_extra,
"s": self.controls.analyze_equalize,
"d": self.controls.analyze_sweep,
"right": ("switch-branch", 1),
"left": ("switch-branch", -1),
}
if keycode[1] in shortcuts.keys():
shortcut = shortcuts[keycode[1]]
if isinstance(shortcut, Widget):
shortcut.trigger_action(duration=0)
else:
self(*shortcut)
elif keycode[1] == "tab":
self.controls.switch_mode()
elif keycode[1] == "spacebar":
self("play", None) # pass
elif keycode[1] in ["`", "~", "p"]:
self.controls_box.hidden = not self.controls_box.hidden
elif keycode[1] in ["up", "z"]:
self("undo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999)
elif keycode[1] in ["down", "x"]:
self("redo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999)
elif keycode[1] == "n" and "ctrl" in modifiers:
self("new-game-popup")
elif keycode[1] == "l" and "ctrl" in modifiers:
self("analyze-sgf-popup")
elif keycode[1] == "s" and "ctrl" in modifiers:
self("output-sgf")
elif keycode[1] == "c" and "ctrl" in modifiers:
Clipboard.copy(self.game.root.sgf())
self.controls.set_status("Copied SGF to clipboard.")
elif keycode[1] == "v" and "ctrl" in modifiers:
self.load_sgf_from_clipboard()
return True
class KaTrainApp(App):
gui = ObjectProperty(None)
def build(self):
self.icon = ICON # how you're supposed to set an icon
self.gui = KaTrainGui()
self.title = f"KaTrain v{__version__}"
print(self.get_application_icon())
Window.bind(on_request_close=self.on_request_close)
return self.gui
def on_start(self):
self.gui.start()
def on_request_close(self, *args):
if getattr(self, "gui", None) and self.gui.engine:
self.gui.engine.shutdown()
def signal_handler(self, *args):
if self.gui.debug_level >= OUTPUT_DEBUG:
print("TRACEBACKS")
for threadId, stack in sys._current_frames().items():
print(f"\n# ThreadID: {threadId}")
for filename, lineno, name, line in traceback.extract_stack(stack):
print(f"\tFile: {filename}, line {lineno}, in {name}")
if line:
print(f"\t\t{line.strip()}")
self.on_request_close()
sys.exit(0)
def run_app():
kv_file = find_package_resource("katrain/gui.kv")
resource_add_path(os.path.split(kv_file)[0])
Builder.load_file(kv_file)
app = KaTrainApp()
signal.signal(signal.SIGINT, app.signal_handler)
try:
app.run()
except Exception:
app.on_request_close()
raise
if __name__ == "__main__":
run_app()
+3 -3
View File
@@ -1,8 +1,8 @@
{ {
"engine": { "engine": {
"katago": "KataGo/katago", "katago": "katrain/KataGo/katago",
"model": "KataGo/models/b15-1.3.2.txt.gz", "model": "katrain/KataGo/models/b15-1.3.2.txt.gz",
"config": "KataGo/analysis_config.cfg", "config": "katrain/KataGo/analysis_config.cfg",
"threads": 16, "threads": 16,
"max_visits": 500, "max_visits": 500,
"fast_visits": 50, "fast_visits": 50,
View File
Whitespace-only changes.
+3 -3
View File
@@ -4,9 +4,9 @@ import random
import time import time
from typing import Dict, List, Tuple from typing import Dict, List, Tuple
from core.common import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_INFO, var_to_grid from katrain.core.common import OUTPUT_DEBUG, OUTPUT_INFO, var_to_grid
from core.engine import EngineDiedException from katrain.core.engine import EngineDiedException
from core.game import Game, GameNode, IllegalMoveException, Move from katrain.core.game import Game, GameNode, Move
def weighted_selection_without_replacement(items: List[Tuple], pick_n: int) -> List[Tuple]: def weighted_selection_without_replacement(items: List[Tuple], pick_n: int) -> List[Tuple]:
+16
View File
@@ -1,4 +1,10 @@
from typing import Any, List, Tuple from typing import Any, List, Tuple
import os
try:
import importlib.resources as pkg_resources
except:
import importlib_resources as pkg_resources
OUTPUT_ERROR = -1 OUTPUT_ERROR = -1
OUTPUT_KATAGO_STDERR = -0.5 OUTPUT_KATAGO_STDERR = -0.5
@@ -22,3 +28,13 @@ def evaluation_class(points_lost: float, eval_thresholds: List[float]):
while i < len(eval_thresholds) - 1 and points_lost < eval_thresholds[i]: while i < len(eval_thresholds) - 1 and points_lost < eval_thresholds[i]:
i += 1 i += 1
return i return i
def find_package_resource(path):
if path.startswith("katrain"):
parts = path.replace("\\", "/").split("/")
with pkg_resources.path(".".join(parts[:-1]), parts[-1]) as path_obj:
found_path = str(path_obj)
return found_path
else:
return path # absolute path
+6 -4
View File
@@ -5,9 +5,10 @@ import sys
import threading import threading
import time import time
from typing import Callable, Optional from typing import Callable, Optional
import importlib.resources as pkg_resources
from core.common import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_KATAGO_STDERR, OUTPUT_INFO from katrain.core.common import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_KATAGO_STDERR, find_package_resource
from core.game_node import GameNode from katrain.core.game_node import GameNode
class EngineDiedException(Exception): class EngineDiedException(Exception):
@@ -27,7 +28,8 @@ class KataGoEngine:
def __init__(self, katrain, config): def __init__(self, katrain, config):
self.katrain = katrain self.katrain = katrain
self.command = f"{config['katago']} analysis -model {config['model']} -config {config['config']} -analysis-threads {config['threads']}"
self.command = f"{find_package_resource(config['katago'])} analysis -model {config['model']} -config {config['config']} -analysis-threads {config['threads']}"
if not sys.platform.startswith("win"): if not sys.platform.startswith("win"):
self.command = shlex.split(self.command) self.command = shlex.split(self.command)
self.queries = {} # outstanding query id -> start time and callback self.queries = {} # outstanding query id -> start time and callback
@@ -45,7 +47,7 @@ class KataGoEngine:
try: try:
self.katrain.log(f"Starting KataGo with {self.command}", OUTPUT_DEBUG) self.katrain.log(f"Starting KataGo with {self.command}", OUTPUT_DEBUG)
self.katago_process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) self.katago_process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except FileNotFoundError as e: except (FileNotFoundError, PermissionError) as e:
self.katrain.log( self.katrain.log(
f"Starting kata with command '{self.command}' failed with error {e}. Please make sure the 'katago' value under 'engine' in settings points to the correct KataGo executable.", f"Starting kata with command '{self.command}' failed with error {e}. Please make sure the 'katago' value under 'engine' in settings points to the correct KataGo executable.",
OUTPUT_ERROR, OUTPUT_ERROR,
+4 -4
View File
@@ -5,10 +5,10 @@ import threading
from datetime import datetime from datetime import datetime
from typing import Dict, List, Union from typing import Dict, List, Union
from core.common import var_to_grid, OUTPUT_INFO, OUTPUT_DEBUG from katrain.core.common import var_to_grid, OUTPUT_INFO, OUTPUT_DEBUG
from core.engine import KataGoEngine from katrain.core.engine import KataGoEngine
from core.game_node import GameNode from katrain.core.game_node import GameNode
from core.sgf_parser import SGF, Move from katrain.core.sgf_parser import SGF, Move
class IllegalMoveException(Exception): class IllegalMoveException(Exception):
@@ -2,8 +2,8 @@ import copy
import random import random
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
from core.common import evaluation_class, var_to_grid from katrain.core.common import evaluation_class, var_to_grid
from core.sgf_parser import Move, SGFNode from katrain.core.sgf_parser import Move, SGFNode
class GameNode(SGFNode): class GameNode(SGFNode):
File renamed without changes.
View File
File renamed without changes.
View File
Whitespace-only changes.
+4 -4
View File
@@ -8,10 +8,10 @@ from kivy.graphics.vertex_instructions import Ellipse, Line, Rectangle
from kivy.uix.boxlayout import BoxLayout from kivy.uix.boxlayout import BoxLayout
from kivy.uix.widget import Widget from kivy.uix.widget import Widget
from core.common import OUTPUT_DEBUG, evaluation_class from katrain.core.common import OUTPUT_DEBUG, evaluation_class
from core.game import Move from katrain.core.game import Move
from gui.kivyutils import draw_circle, draw_text from katrain.gui.kivyutils import draw_circle, draw_text
from core.common import var_to_grid from katrain.core.common import var_to_grid
from kivy.core.window import Window from kivy.core.window import Window
+1 -1
View File
@@ -1,7 +1,7 @@
from kivy.uix.boxlayout import BoxLayout from kivy.uix.boxlayout import BoxLayout
from kivy.uix.popup import Popup from kivy.uix.popup import Popup
from gui.popups import ConfigAIPopup, ConfigTeacherPopup from katrain.gui.popups import ConfigAIPopup, ConfigTeacherPopup
class Controls(BoxLayout): class Controls(BoxLayout):
File renamed without changes.
+4 -4
View File
@@ -7,10 +7,10 @@ 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 core.common import OUTPUT_DEBUG, OUTPUT_ERROR from katrain.core.common import OUTPUT_DEBUG, OUTPUT_ERROR
from core.engine import KataGoEngine from katrain.core.engine import KataGoEngine
from core.game import Game, GameNode from katrain.core.game import Game, GameNode
from gui.kivyutils import ( from katrain.gui.kivyutils import (
BackgroundLabel, BackgroundLabel,
LabelledCheckBox, LabelledCheckBox,
LabelledFloatInput, LabelledFloatInput,
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+43
View File
@@ -0,0 +1,43 @@
import re
import os
from setuptools import find_packages, setup
package_data = {"": ["*.json", "*.kv"], "katrain": [], "tests": []}
packages = find_packages(exclude=["bots","tests"])
version = re.search('^__version__\s*=\s*"(.*)"', open("katrain/__main__.py").read(), re.M).group(1)
def include_data_files(directory):
for root, subfolders, files in os.walk(directory):
for fn in files:
filename = os.path.join(root, fn)
parts = filename.split(os.path.sep)
package_data[parts[0]].append(os.path.join(*parts[1:]))
include_data_files("katrain/KataGo")
include_data_files("katrain/img/")
include_data_files("katrain/img/flaticon")
print(packages,package_data)
with open("README.md", "r") as fh:
long_description = fh.read()
setup(
name="KaTrain",
version=version,
description="Go/Baduk/Weiqi playing and teaching app with a variety of AIs",
long_description=long_description,
long_description_content_type="text/markdown",
author="Sander Land",
author_email="sander.land@gmail.com",
url="https://github.com/sanderland/katrain",
license="MIT",
install_requires=["kivy"],
python_requires=">=3.6, <3.8",
entry_points={"console_scripts": ["katrain=katrain.__main__:run_app"]},
classifiers=["Development Status :: 4 - Beta", "Operating System :: Microsoft :: Windows", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3",],
packages=packages,
package_data=package_data,
)
+1 -1
View File
@@ -1,6 +1,6 @@
import pytest import pytest
from core.game import Game, IllegalMoveException, Move from katrain.core.game import Game, IllegalMoveException, Move
class MockKaTrain: class MockKaTrain:
+1 -1
View File
@@ -1,6 +1,6 @@
import os import os
from core.sgf_parser import SGF from katrain.core.sgf_parser import SGF
def test_simple(): def test_simple():