1.10 (#481)
* edit katrain/__main__.py * delay / 0 * edit katrain/core/constants.py * edit ENGINE.md * fix bug * fix bug * fix scroll variations * edit katrain/core/sgf_parser.py, edit tests/test_parser.py and 4 other changes * fix touchscreen * antimirror * openssl dir try * openssl dir try * fix log * openssl * auto version in osx * edit .github/workflows/osxbuild.yaml * edit .github/workflows/osxbuild.yaml * edit katrain/KataGo/katago * better pondering * new downloads * middle click add variation * change middle click pv * more tweaks * black Co-authored-by: Sander Land <sander.land@futurefacts.nl> Co-authored-by: Sander Land <sander.land@cognite.com> Co-authored-by: Sander Land <sander@chatdesk.com>
This commit is contained in:
38 files changed
+249
-86
No files matched your search
@@ -17,7 +17,7 @@ jobs:
|
||||
python-version: 3.8
|
||||
- name: Build KataGo
|
||||
run: |
|
||||
export OPENSSL_ROOT_DIR=$(brew --prefix openssl)
|
||||
export OPENSSL_ROOT_DIR=$(brew --prefix openssl@1.1)
|
||||
export OPENSSL_LIBRARIES="${OPENSSL_ROOT_DIR}/lib"
|
||||
echo $OPENSSL_ROOT_DIR
|
||||
ls $OPENSSL_ROOT_DIR
|
||||
@@ -48,11 +48,13 @@ jobs:
|
||||
python -m pip install .
|
||||
- name: Finalize KaTrain bundle
|
||||
run: |
|
||||
export KATRAIN_VERSION=`python -c 'from katrain.core.constants import VERSION;print(VERSION)' `
|
||||
echo "Setting version to ${KATRAIN_VERSION}"
|
||||
cd ../kivy-sdk-packager/osx
|
||||
pushd KaTrain.app/Contents/Resources/
|
||||
ln -s ./venv/bin/KaTrain yourapp
|
||||
popd
|
||||
./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"
|
||||
./fix-bundle-metadata.sh KaTrain.app -n KaTrain -v "${KATRAIN_VERSION}" -a "Sander Land" -o "org.katrain.KaTrain" -i "../../katrain/katrain/img/icon.ico"
|
||||
./cleanup-app.sh KaTrain.app -g 1
|
||||
./relocate.sh KaTrain.app
|
||||
- name: Create dmg
|
||||
|
||||
@@ -62,13 +62,16 @@ Run DebugKaTrain.exe, which is released in the .zip file distributable in releas
|
||||
|
||||
### 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.
|
||||
The most common KataGo issue relates to different libzip versions in the provided executables,
|
||||
leading to an "Error 127".
|
||||
Although the provided executables should work on the latest versions of Ubuntu with libzip installed,
|
||||
various other versions and distros differ in their libzip version.
|
||||
|
||||
* First, try `sudo apt-get install libzip-dev` to
|
||||
* First, try `****sudo apt-get install libzip-dev****` to get this library on recent systems.
|
||||
* 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.
|
||||
* Check the terminal output around startup time.
|
||||
* Start KataGo by itself using `katrain/KataGo/katago` when running from source and check output.
|
||||
@@ -6,7 +6,6 @@
|
||||
# Logs------------------------------------------------------------------------------------
|
||||
|
||||
# Where to output log?
|
||||
logFile = "~/.katrain/katago_log.txt"
|
||||
# logFile = analysis.log # Use this instead of logDir to just specify a single file directly
|
||||
# logToStderr = true # Echo everything output to log file to stderr as well
|
||||
# logAllRequests = false # Log all input lines received to the analysis engine.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
+23
-24
@@ -47,6 +47,7 @@ from kivy.properties import NumericProperty, ObjectProperty, StringProperty
|
||||
from kivy.clock import Clock
|
||||
from kivy.metrics import dp
|
||||
from katrain.core.ai import generate_ai_move
|
||||
from kivy.utils import platform as kivy_platform
|
||||
|
||||
from katrain.core.lang import DEFAULT_LANGUAGE, i18n
|
||||
from katrain.core.constants import (
|
||||
@@ -111,7 +112,7 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
self.timer_settings_popup = None
|
||||
self.contribute_popup = None
|
||||
|
||||
self.idle_analysis = False
|
||||
self.pondering = False
|
||||
self.animate_contributing = False
|
||||
self.message_queue = Queue()
|
||||
|
||||
@@ -141,7 +142,7 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
def handle_animations(self, *_args):
|
||||
if self.contributing and self.animate_contributing:
|
||||
self.engine.advance_showing_game()
|
||||
if (self.contributing and self.animate_contributing) or self.idle_analysis:
|
||||
if (self.contributing and self.animate_contributing) or self.pondering:
|
||||
self.board_controls.engine_status_pondering += 5
|
||||
else:
|
||||
self.board_controls.engine_status_pondering = -1
|
||||
@@ -154,9 +155,9 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
if self.contributing:
|
||||
self.animate_contributing = not self.animate_contributing
|
||||
else:
|
||||
if self.idle_analysis:
|
||||
if self.pondering:
|
||||
self.controls.set_status("", STATUS_INFO)
|
||||
self.idle_analysis = not self.idle_analysis
|
||||
self.pondering = not self.pondering
|
||||
self.update_state()
|
||||
|
||||
def start(self):
|
||||
@@ -257,8 +258,11 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
): # cn mismatch stops this if undo fired. avoid message loop here or fires repeatedly.
|
||||
self._do_ai_move(cn)
|
||||
Clock.schedule_once(self.board_gui.play_stone_sound, 0.25)
|
||||
if self.engine.is_idle() and self.idle_analysis:
|
||||
self("analyze-extra", "extra", continuous=True)
|
||||
if self.engine:
|
||||
if self.pondering:
|
||||
self.game.analyze_extra("ponder")
|
||||
else:
|
||||
self.engine.stop_pondering()
|
||||
Clock.schedule_once(lambda _dt: self.update_gui(cn, redraw_board=redraw_board), -1) # trigger?
|
||||
|
||||
def update_player(self, bw, **kwargs):
|
||||
@@ -322,7 +326,7 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
self.message_queue.put([self.game.game_id, message, args, kwargs])
|
||||
|
||||
def _do_new_game(self, move_tree=None, analyze_fast=False, sgf_filename=None):
|
||||
self.idle_analysis = False
|
||||
self.pondering = False
|
||||
mode = self.play_analyze_mode
|
||||
if (move_tree is not None and mode == MODE_PLAY) or (move_tree is None and mode == MODE_ANALYZE):
|
||||
self.play_mode.switch_ui_mode() # for new game, go to play, for loaded, analyze
|
||||
@@ -351,7 +355,7 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
self.contributing = self.animate_contributing = True # special mode
|
||||
if self.play_analyze_mode == MODE_PLAY: # switch to analysis view
|
||||
self.play_mode.switch_ui_mode()
|
||||
self.idle_analysis = False
|
||||
self.pondering = False
|
||||
self.board_gui.animating_pv = None
|
||||
for bw, player_info in self.players_info.items():
|
||||
self.update_player(bw, player_type=PLAYER_AI, player_subtype=AI_DEFAULT)
|
||||
@@ -395,10 +399,6 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
self.board_gui.animating_pv = None
|
||||
getattr(self.game, fn)(9999, stop_on_mistake=self.config("trainer/eval_thresholds")[-4])
|
||||
|
||||
def _do_cycle_children(self, *args):
|
||||
self.board_gui.animating_pv = None
|
||||
self.game.cycle_children(*args)
|
||||
|
||||
def _do_switch_branch(self, *args):
|
||||
self.board_gui.animating_pv = None
|
||||
self.controls.move_tree.switch_branch(*args)
|
||||
@@ -593,22 +593,21 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
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)
|
||||
or self.controls.move_tree.collide_point(*touch.pos)
|
||||
):
|
||||
if touch.is_mouse_scrolling:
|
||||
if self.board_gui.animating_pv is None:
|
||||
if touch.button == "scrollup":
|
||||
self("redo")
|
||||
elif touch.button == "scrolldown":
|
||||
self("undo")
|
||||
else:
|
||||
touching_board = self.board_gui.collide_point(*touch.pos) or self.board_controls.collide_point(*touch.pos)
|
||||
touching_control_nonscroll = self.controls.collide_point(
|
||||
*touch.pos
|
||||
) and not self.controls.notes_panel.collide_point(*touch.pos)
|
||||
if self.board_gui.animating_pv is not None and touching_board:
|
||||
if touch.button == "scrollup":
|
||||
self.board_gui.adjust_animate_pv_index(1)
|
||||
elif touch.button == "scrolldown":
|
||||
self.board_gui.adjust_animate_pv_index(-1)
|
||||
elif touching_board or touching_control_nonscroll: # scroll through moves
|
||||
if touch.button == "scrollup":
|
||||
self("redo")
|
||||
elif touch.button == "scrolldown":
|
||||
self("undo")
|
||||
return super().on_touch_up(touch)
|
||||
|
||||
@property
|
||||
@@ -653,7 +652,7 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
|
||||
def _on_keyboard_down(self, _keyboard, keycode, _text, modifiers):
|
||||
self.last_key_down = keycode
|
||||
ctrl_pressed = "ctrl" in modifiers
|
||||
ctrl_pressed = "ctrl" in modifiers or ("meta" in modifiers and kivy_platform == "macosx")
|
||||
shift_pressed = "shift" in modifiers
|
||||
if self.controls.note.focus:
|
||||
return # when making notes, don't allow keyboard shortcuts
|
||||
|
||||
+2
-1
@@ -28,7 +28,7 @@
|
||||
"anim_pv_time": 0.5,
|
||||
"debug_level": 0,
|
||||
"lang": "en",
|
||||
"version": "1.9.0",
|
||||
"version": "1.10.0",
|
||||
"load_fast_analysis": false,
|
||||
"load_sgf_rewind": true
|
||||
},
|
||||
@@ -93,6 +93,7 @@
|
||||
},
|
||||
"ai": {
|
||||
"ai:default": {},
|
||||
"ai:antimirror": {},
|
||||
"ai:handicap": {
|
||||
"automatic": true,
|
||||
"pda": 0
|
||||
|
||||
+11
-1
@@ -10,6 +10,7 @@ from katrain.core.constants import (
|
||||
AI_INFLUENCE,
|
||||
AI_INFLUENCE_ELO_GRID,
|
||||
AI_JIGO,
|
||||
AI_ANTIMIRROR,
|
||||
AI_LOCAL,
|
||||
AI_LOCAL_ELO_GRID,
|
||||
AI_PICK,
|
||||
@@ -264,6 +265,11 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move,
|
||||
if not handicap_analysis:
|
||||
game.katrain.log("Error getting handicap-based move", OUTPUT_ERROR)
|
||||
ai_mode = AI_DEFAULT
|
||||
elif ai_mode == AI_ANTIMIRROR:
|
||||
antimirror_analysis = request_ai_analysis(game, cn, {"antiMirror": True})
|
||||
if not antimirror_analysis:
|
||||
game.katrain.log("Error getting antimirror move", OUTPUT_ERROR)
|
||||
ai_mode = AI_DEFAULT
|
||||
|
||||
while not cn.analysis_complete:
|
||||
time.sleep(0.01)
|
||||
@@ -388,6 +394,8 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move,
|
||||
candidate_ai_moves = cn.candidate_moves
|
||||
if ai_mode == AI_HANDICAP:
|
||||
candidate_ai_moves = handicap_analysis["moveInfos"]
|
||||
elif ai_mode == AI_ANTIMIRROR:
|
||||
candidate_ai_moves = antimirror_analysis["moveInfos"]
|
||||
|
||||
top_cand = Move.from_gtp(candidate_ai_moves[0]["move"], player=cn.next_player)
|
||||
if top_cand.is_pass and ai_mode not in [
|
||||
@@ -492,12 +500,14 @@ def generate_ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move,
|
||||
else:
|
||||
raise (Exception("No moves found - are you using an older KataGo with no per-move ownership info?"))
|
||||
else:
|
||||
if ai_mode not in [AI_DEFAULT, AI_HANDICAP]:
|
||||
if ai_mode not in [AI_DEFAULT, AI_HANDICAP, AI_ANTIMIRROR]:
|
||||
game.katrain.log(f"Unknown AI mode {ai_mode} or policy missing, using default.", OUTPUT_INFO)
|
||||
ai_thoughts += f"Strategy {ai_mode} not found or unexpected fallback."
|
||||
aimove = top_cand
|
||||
if ai_mode == AI_HANDICAP:
|
||||
ai_thoughts += f"Handicap strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move. PDA based score {cn.format_score(handicap_analysis['rootInfo']['scoreLead'])} and win rate {cn.format_winrate(handicap_analysis['rootInfo']['winrate'])}"
|
||||
if ai_mode == AI_ANTIMIRROR:
|
||||
ai_thoughts += f"AntiMirror strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move. antiMirror based score {cn.format_score(antimirror_analysis['rootInfo']['scoreLead'])} and win rate {cn.format_winrate(antimirror_analysis['rootInfo']['winrate'])}"
|
||||
else:
|
||||
ai_thoughts += f"Default strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move"
|
||||
game.katrain.log(f"AI thoughts: {ai_thoughts}", OUTPUT_DEBUG)
|
||||
|
||||
@@ -54,6 +54,13 @@ class Player:
|
||||
return f"{self.player_type} ({self.player_subtype})"
|
||||
|
||||
|
||||
def parse_version(s):
|
||||
parts = [int(p) for p in s.split(".")]
|
||||
while len(parts) < 3:
|
||||
parts.append(0)
|
||||
return parts
|
||||
|
||||
|
||||
class KaTrainBase:
|
||||
USER_CONFIG_FILE = os.path.expanduser(os.path.join(DATA_FOLDER, "config.json"))
|
||||
PACKAGE_CONFIG_FILE = "katrain/config.json"
|
||||
@@ -101,11 +108,13 @@ class KaTrainBase:
|
||||
self.log(f"Copied package config to local file {config_file}", OUTPUT_INFO)
|
||||
else: # user file exists
|
||||
try:
|
||||
version = JsonStore(user_config_file).get("general")["version"]
|
||||
version_str = JsonStore(user_config_file).get("general")["version"]
|
||||
version = parse_version(version_str)
|
||||
except Exception: # noqa E722 broken file etc
|
||||
version = "0.0.0"
|
||||
if version < CONFIG_MIN_VERSION:
|
||||
backup = user_config_file + f".{version}.backup"
|
||||
version_str = "0.0.0"
|
||||
version = [0, 0, 0]
|
||||
if version < parse_version(CONFIG_MIN_VERSION):
|
||||
backup = f"{user_config_file}.{version_str}.backup"
|
||||
shutil.copyfile(user_config_file, backup)
|
||||
shutil.copyfile(package_config_file, user_config_file)
|
||||
self.log(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
PROGRAM_NAME = "KaTrain"
|
||||
VERSION = "1.9.3"
|
||||
VERSION = "1.10.0"
|
||||
HOMEPAGE = "https://github.com/sanderland/katrain"
|
||||
CONFIG_MIN_VERSION = "1.9.0" # keep config files from this version
|
||||
CONFIG_MIN_VERSION = "1.10.0" # keep config files from this version
|
||||
ANALYSIS_FORMAT_VERSION = "1.0"
|
||||
DATA_FOLDER = "~/.katrain"
|
||||
|
||||
@@ -42,6 +42,7 @@ AI_HANDICAP = "ai:handicap"
|
||||
AI_SCORELOSS = "ai:scoreloss"
|
||||
AI_WEIGHTED = "ai:p:weighted"
|
||||
AI_JIGO = "ai:jigo"
|
||||
AI_ANTIMIRROR = "ai:antimirror"
|
||||
AI_POLICY = "ai:policy"
|
||||
AI_PICK = "ai:p:pick"
|
||||
AI_LOCAL = "ai:p:local"
|
||||
@@ -54,7 +55,7 @@ AI_SETTLE_STONES = "ai:settle"
|
||||
|
||||
AI_CONFIG_DEFAULT = AI_RANK
|
||||
|
||||
AI_STRATEGIES_ENGINE = [AI_DEFAULT, AI_HANDICAP, AI_SCORELOSS, AI_SIMPLE_OWNERSHIP, AI_JIGO] # AI_SETTLE_STONES
|
||||
AI_STRATEGIES_ENGINE = [AI_DEFAULT, AI_HANDICAP, AI_SCORELOSS, AI_SIMPLE_OWNERSHIP, AI_JIGO, AI_ANTIMIRROR]
|
||||
AI_STRATEGIES_PICK = [AI_PICK, AI_LOCAL, AI_TENUKI, AI_INFLUENCE, AI_TERRITORY, AI_RANK]
|
||||
AI_STRATEGIES_POLICY = [AI_WEIGHTED, AI_POLICY] + AI_STRATEGIES_PICK
|
||||
AI_STRATEGIES = AI_STRATEGIES_ENGINE + AI_STRATEGIES_POLICY
|
||||
@@ -63,20 +64,21 @@ AI_STRATEGIES_RECOMMENDED_ORDER = [
|
||||
AI_RANK,
|
||||
AI_HANDICAP,
|
||||
AI_SIMPLE_OWNERSHIP,
|
||||
# AI_SETTLE_STONES,
|
||||
AI_SCORELOSS,
|
||||
AI_POLICY,
|
||||
AI_WEIGHTED,
|
||||
AI_JIGO,
|
||||
AI_ANTIMIRROR,
|
||||
AI_PICK,
|
||||
AI_LOCAL,
|
||||
AI_TENUKI,
|
||||
AI_TERRITORY,
|
||||
AI_INFLUENCE,
|
||||
AI_JIGO,
|
||||
]
|
||||
|
||||
AI_STRENGTH = { # dan ranks, backup if model is missing. TODO: remove some?
|
||||
AI_DEFAULT: 9,
|
||||
AI_ANTIMIRROR: 9,
|
||||
AI_POLICY: 5,
|
||||
AI_JIGO: float("nan"),
|
||||
AI_SCORELOSS: -4,
|
||||
@@ -260,7 +262,8 @@ TOP_MOVE_OPTIONS = [
|
||||
# TOP_MOVE_UTILITY,
|
||||
# TOP_MOVE_UTILITYLCB
|
||||
]
|
||||
REPORT_DT = 0.25
|
||||
REPORT_DT = 1
|
||||
PONDERING_REPORT_DT = 0.25
|
||||
|
||||
SGF_INTERNAL_COMMENTS_MARKER = "\u3164\u200b"
|
||||
SGF_SEPARATOR_MARKER = "\u3164\u3164"
|
||||
@@ -243,7 +243,9 @@ class KataGoContributeEngine(BaseEngine):
|
||||
game_properties["RU"] = json.dumps(analysis["rules"])
|
||||
game_properties["PB"] = analysis["blackPlayer"]
|
||||
game_properties["PW"] = analysis["whitePlayer"]
|
||||
current_game = BaseGame(self.katrain, game_properties=game_properties)
|
||||
current_game = BaseGame(
|
||||
self.katrain, game_properties=game_properties, bypass_config=True
|
||||
)
|
||||
self.active_games[game_id] = current_game
|
||||
last_node = current_game.sync_branch(
|
||||
[Move.from_gtp(coord, pl) for pl, coord in analysis["moves"]]
|
||||
|
||||
+47
-3
@@ -19,6 +19,7 @@ from katrain.core.constants import (
|
||||
OUTPUT_KATAGO_STDERR,
|
||||
DATA_FOLDER,
|
||||
KATAGO_EXCEPTION,
|
||||
PONDERING_REPORT_DT,
|
||||
)
|
||||
from katrain.core.game_node import GameNode
|
||||
from katrain.core.lang import i18n
|
||||
@@ -93,11 +94,14 @@ class BaseEngine: # some common elements between analysis and contribute engine
|
||||
class KataGoEngine(BaseEngine):
|
||||
"""Starts and communicates with the KataGO analysis engine"""
|
||||
|
||||
PONDER_KEY = "_kt_continuous"
|
||||
|
||||
def __init__(self, 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.ponder_query = None
|
||||
self.query_counter = 0
|
||||
self.katago_process = None
|
||||
self.base_priority = 0
|
||||
@@ -166,6 +170,7 @@ class KataGoEngine(BaseEngine):
|
||||
with self.thread_lock:
|
||||
self.write_queue = queue.Queue()
|
||||
self.terminate_queries(only_for_node=None, lock=False)
|
||||
self.ponder_query = None
|
||||
self.queries = {}
|
||||
|
||||
def terminate_queries(self, only_for_node=None, lock=True):
|
||||
@@ -176,9 +181,18 @@ class KataGoEngine(BaseEngine):
|
||||
if only_for_node is None or only_for_node is node:
|
||||
self.terminate_query(query_id)
|
||||
|
||||
def terminate_query(self, query_id):
|
||||
def stop_pondering(self):
|
||||
pq = self.ponder_query
|
||||
if pq:
|
||||
self.terminate_query(pq["id"], ignore_further_results=False)
|
||||
self.ponder_query = None
|
||||
|
||||
def terminate_query(self, query_id, ignore_further_results=True):
|
||||
self.katrain.log(f"Terminating query {query_id}", OUTPUT_DEBUG)
|
||||
|
||||
if query_id is not None:
|
||||
self.send_query({"action": "terminate", "terminateId": query_id}, None, None)
|
||||
if ignore_further_results:
|
||||
self.queries.pop(query_id, None)
|
||||
|
||||
def restart(self):
|
||||
@@ -269,6 +283,7 @@ class KataGoEngine(BaseEngine):
|
||||
continue
|
||||
query_id = analysis["id"]
|
||||
if query_id not in self.queries:
|
||||
if analysis.get("action") != "terminate":
|
||||
self.katrain.log(
|
||||
f"Query result {query_id} discarded -- recent new game or node reset?", OUTPUT_DEBUG
|
||||
)
|
||||
@@ -314,12 +329,38 @@ class KataGoEngine(BaseEngine):
|
||||
except queue.Empty:
|
||||
continue
|
||||
with self.thread_lock:
|
||||
self.katrain.log(f"GOT QUERY: {query}", OUTPUT_DEBUG)
|
||||
if "id" not in query:
|
||||
self.query_counter += 1
|
||||
query["id"] = f"QUERY:{str(self.query_counter)}"
|
||||
if query.get("action") != "terminate":
|
||||
|
||||
ponder = query.pop(self.PONDER_KEY, False)
|
||||
if ponder: # handle pondering in here to be in lock and such
|
||||
pq = self.ponder_query or {}
|
||||
# basically we handle pondering by just asking for these queries a lot and ignoring duplicates
|
||||
# when a different ponder query comes in, e.g. due to selecting a roi or different node, switch
|
||||
differences = {
|
||||
k: (pq.get(k), query.get(k))
|
||||
for k in (query.keys() | pq.keys()) - {"id", "maxVisits", "reportDuringSearchEvery"}
|
||||
if pq.get(k) != query.get(k)
|
||||
}
|
||||
if differences:
|
||||
# TODO:remove
|
||||
self.katrain.log(f"Found differences in ponder check: {differences}", OUTPUT_EXTRA_DEBUG)
|
||||
self.stop_pondering()
|
||||
query["maxVisits"] = 1_000_000
|
||||
query["reportDuringSearchEvery"] = PONDERING_REPORT_DT
|
||||
self.ponder_query = query
|
||||
else:
|
||||
# TODO:remove
|
||||
self.katrain.log("Found no differences in ponder check, skipping", OUTPUT_EXTRA_DEBUG)
|
||||
continue
|
||||
|
||||
terminate = query.get("action") == "terminate"
|
||||
if not terminate:
|
||||
self.queries[query["id"]] = (callback, error_callback, time.time(), next_move, node)
|
||||
self.katrain.log(f"Sending query {query['id']}: {json.dumps(query)}", OUTPUT_DEBUG)
|
||||
tag = "ponder " if ponder else ("terminate " if terminate else "")
|
||||
self.katrain.log(f"Sending {tag}query {query['id']}: {json.dumps(query)}", OUTPUT_DEBUG)
|
||||
try:
|
||||
self.katago_process.stdin.write((json.dumps(query) + "\n").encode())
|
||||
self.katago_process.stdin.flush()
|
||||
@@ -341,6 +382,7 @@ class KataGoEngine(BaseEngine):
|
||||
find_alternatives: bool = False,
|
||||
region_of_interest: Optional[List] = None,
|
||||
priority: int = 0,
|
||||
ponder=False, # infinite visits, cancellable
|
||||
ownership: Optional[bool] = None,
|
||||
next_move: Optional[GameNode] = None,
|
||||
extra_settings: Optional[Dict] = None,
|
||||
@@ -358,6 +400,7 @@ class KataGoEngine(BaseEngine):
|
||||
moves.append(next_move)
|
||||
if ownership is None:
|
||||
ownership = self.config["_enable_ownership"] and not next_move
|
||||
|
||||
if visits is None:
|
||||
visits = self.config["max_visits"]
|
||||
if analyze_fast and self.config.get("fast_visits"):
|
||||
@@ -411,6 +454,7 @@ class KataGoEngine(BaseEngine):
|
||||
"initialPlayer": analysis_node.initial_player,
|
||||
"moves": [[m.player, m.gtp()] for m in moves],
|
||||
"overrideSettings": {**settings, **(extra_settings or {})},
|
||||
self.PONDER_KEY: ponder,
|
||||
}
|
||||
if report_every is not None:
|
||||
query["reportDuringSearchEvery"] = report_every
|
||||
|
||||
+26
-13
@@ -53,6 +53,7 @@ class BaseGame:
|
||||
move_tree: GameNode = None,
|
||||
game_properties: Optional[Dict] = None,
|
||||
sgf_filename=None,
|
||||
bypass_config=False, # TODO: refactor?
|
||||
):
|
||||
self.katrain = katrain
|
||||
self._lock = threading.Lock()
|
||||
@@ -83,18 +84,24 @@ class BaseGame:
|
||||
): # not really according to sgf, and not sure if still needed, last clause for fox
|
||||
self.root.place_handicap_stones(handicap)
|
||||
else:
|
||||
board_size = katrain.config("game/size")
|
||||
rules = katrain.config("game/rules")
|
||||
self.komi = katrain.config("game/komi")
|
||||
default_properties = {**Game.DEFAULT_PROPERTIES, "DT": self.game_id}
|
||||
if not bypass_config:
|
||||
default_properties.update(
|
||||
{
|
||||
"SZ": katrain.config("game/size"),
|
||||
"KM": katrain.config("game/komi"),
|
||||
"RU": katrain.config("game/rules"),
|
||||
}
|
||||
)
|
||||
self.root = GameNode(
|
||||
properties={
|
||||
**Game.DEFAULT_PROPERTIES,
|
||||
**{"SZ": board_size, "KM": self.komi, "DT": self.game_id, "RU": rules},
|
||||
**default_properties,
|
||||
**(game_properties or {}),
|
||||
}
|
||||
)
|
||||
self.komi = self.root.komi
|
||||
handicap = katrain.config("game/handicap")
|
||||
if handicap:
|
||||
if not bypass_config and handicap:
|
||||
self.root.place_handicap_stones(handicap)
|
||||
|
||||
if not self.root.get_property("RU"): # if rules missing in sgf, inherit current
|
||||
@@ -558,23 +565,28 @@ class Game(BaseGame):
|
||||
cn = self.current_node
|
||||
|
||||
if mode == "stop":
|
||||
self.katrain.pondering = False
|
||||
for e in set(self.engines.values()):
|
||||
e.stop_pondering()
|
||||
e.terminate_queries()
|
||||
self.katrain.idle_analysis = False
|
||||
return
|
||||
|
||||
engine = self.engines[cn.next_player]
|
||||
Clock.schedule_once(self.katrain.analysis_controls.hints.activate, 0)
|
||||
|
||||
if mode == "extra":
|
||||
if kwargs.get("continuous", False):
|
||||
visits = min(
|
||||
1_000_000_000, max(engine.config["max_visits"], math.ceil(cn.analysis_visits_requested * 1.25))
|
||||
if mode == "ponder":
|
||||
cn.analyze(
|
||||
engine,
|
||||
ponder=True,
|
||||
priority=PRIORITY_EXTRA_ANALYSIS,
|
||||
region_of_interest=self.region_of_interest,
|
||||
time_limit=False,
|
||||
)
|
||||
else:
|
||||
return
|
||||
|
||||
if mode == "extra":
|
||||
visits = cn.analysis_visits_requested + engine.config["max_visits"]
|
||||
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(
|
||||
engine,
|
||||
visits=visits,
|
||||
@@ -583,6 +595,7 @@ class Game(BaseGame):
|
||||
time_limit=False,
|
||||
)
|
||||
return
|
||||
|
||||
if mode == "game":
|
||||
nodes = self.root.nodes_in_tree
|
||||
only_mistakes = kwargs.get("mistakes_only", False)
|
||||
|
||||
@@ -187,6 +187,7 @@ class GameNode(SGFNode):
|
||||
engine,
|
||||
priority=PRIORITY_DEFAULT,
|
||||
visits=None,
|
||||
ponder=False,
|
||||
time_limit=True,
|
||||
refine_move=None,
|
||||
analyze_fast=False,
|
||||
@@ -201,6 +202,7 @@ class GameNode(SGFNode):
|
||||
),
|
||||
priority=priority,
|
||||
visits=visits,
|
||||
ponder=ponder,
|
||||
analyze_fast=analyze_fast,
|
||||
time_limit=time_limit,
|
||||
next_move=refine_move,
|
||||
@@ -278,6 +280,10 @@ class GameNode(SGFNode):
|
||||
def analysis_complete(self):
|
||||
return self.analysis["completed"] and self.analysis["root"] is not None
|
||||
|
||||
@property
|
||||
def root_visits(self):
|
||||
return ((self.analysis or {}).get("root") or {}).get("visits", 0)
|
||||
|
||||
@property
|
||||
def score(self) -> Optional[float]:
|
||||
if self.analysis_exists:
|
||||
|
||||
+26
-10
@@ -45,7 +45,7 @@ class Move:
|
||||
self.coords = coords
|
||||
|
||||
def __repr__(self):
|
||||
return f"Move({self.player}{self.gtp()})"
|
||||
return f"Move({self.player or ''}{self.gtp()})"
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.coords == other.coords and self.player == other.player
|
||||
@@ -247,22 +247,38 @@ class SGFNode:
|
||||
]
|
||||
return self.moves_cache
|
||||
|
||||
def _expanded_placements(self, player):
|
||||
sgf_pl = player if player is not None else "E" # AE
|
||||
placements = self.get_list_property("A" + sgf_pl, [])
|
||||
if not placements:
|
||||
return []
|
||||
to_be_expanded = [p for p in placements if ":" in p]
|
||||
board_size = self.board_size
|
||||
if to_be_expanded:
|
||||
coords = {
|
||||
Move.from_sgf(sgf_coord, player=player, board_size=board_size)
|
||||
for sgf_coord in placements
|
||||
if ":" not in sgf_coord
|
||||
}
|
||||
for p in to_be_expanded:
|
||||
from_coord, to_coord = [Move.from_sgf(c, board_size=board_size) for c in p.split(":")[:2]]
|
||||
for x in range(from_coord.coords[0], to_coord.coords[0] + 1):
|
||||
for y in range(to_coord.coords[1], from_coord.coords[1] + 1): # sgf upside dn
|
||||
if 0 <= x < board_size[0] and 0 <= y < board_size[1]:
|
||||
coords.add(Move((x, y), player=player))
|
||||
return list(coords)
|
||||
else:
|
||||
return [Move.from_sgf(sgf_coord, player=player, board_size=board_size) for sgf_coord in placements]
|
||||
|
||||
@property
|
||||
def placements(self) -> List[Move]:
|
||||
"""Returns all placements (AB/AW) in the node."""
|
||||
return [
|
||||
Move.from_sgf(sgf_coords, player=pl, board_size=self.board_size)
|
||||
for pl in Move.PLAYERS
|
||||
for sgf_coords in self.get_list_property("A" + pl, [])
|
||||
]
|
||||
return [coord for pl in Move.PLAYERS for coord in self._expanded_placements(pl)]
|
||||
|
||||
@property
|
||||
def clear_placements(self) -> List[Move]:
|
||||
"""Returns all AE clear square commends in the node."""
|
||||
return [
|
||||
Move.from_sgf(sgf_coords, player=None, board_size=self.board_size)
|
||||
for sgf_coords in self.get_list_property("AE", [])
|
||||
]
|
||||
return self._expanded_placements(None)
|
||||
|
||||
@property
|
||||
def move_with_placements(self) -> List[Move]:
|
||||
|
||||
@@ -550,6 +550,7 @@
|
||||
timer:timer
|
||||
graph: graph
|
||||
stats: stats
|
||||
notes_panel: notes_panel
|
||||
info: info
|
||||
note: note
|
||||
status: status
|
||||
|
||||
+15
-5
@@ -103,14 +103,24 @@ class BadukPanWidget(Widget):
|
||||
self.redraw_hover_contents_trigger()
|
||||
|
||||
def on_touch_down(self, touch):
|
||||
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":
|
||||
return
|
||||
animating_pv = self.animating_pv
|
||||
if "button" in touch.profile:
|
||||
if touch.button == "left":
|
||||
if self.selecting_region_of_interest:
|
||||
self.update_box_selection(touch, second_point=False)
|
||||
else:
|
||||
self.check_next_move_ghost(touch)
|
||||
elif touch.button == "middle" and animating_pv:
|
||||
pv, node, _, _ = animating_pv
|
||||
upto = self.animating_pv_index or 1e9
|
||||
for i, gtpmove in enumerate(pv):
|
||||
if i <= upto: # up to move when scrolling, or all
|
||||
node = node.play(Move.from_gtp(gtpmove, node.next_player))
|
||||
node.analyze(self.katrain.engine, analyze_fast=True)
|
||||
self.katrain.controls.move_tree.redraw_tree_trigger()
|
||||
|
||||
if ("button" not in touch.profile) or (touch.button not in ["scrollup", "scrolldown", "middle"]):
|
||||
self.set_animating_pv(None, None) # any click/touch kills PV from label/move
|
||||
|
||||
def on_touch_move(self, touch):
|
||||
if "button" in touch.profile and touch.button != "left":
|
||||
@@ -762,7 +772,7 @@ class BadukPanWidget(Widget):
|
||||
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
|
||||
return (time.time() - start_time) / max(delay, 0.1)
|
||||
else:
|
||||
return 0
|
||||
|
||||
|
||||
+12
-10
@@ -479,20 +479,22 @@ class BaseConfigPopup(QuickConfigGui):
|
||||
|
||||
KATAGOS = {
|
||||
"win": {
|
||||
"OpenCL v1.9.1": "https://github.com/lightvector/KataGo/releases/download/v1.9.1/katago-v1.9.1-opencl-windows-x64.zip",
|
||||
"Eigen AVX2 (Modern CPUs) v1.9.1": "https://github.com/lightvector/KataGo/releases/download/v1.9.1/katago-v1.9.1-eigenavx2-windows-x64.zip",
|
||||
"Eigen (CPU, Non-optimized) v1.9.1": "https://github.com/lightvector/KataGo/releases/download/v1.9.1/katago-v1.9.1-eigen-windows-x64.zip",
|
||||
"OpenCL v1.9 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.9.1/katago-v1.9.1-opencl-windows-x64+bs29.zip",
|
||||
"OpenCL v1.10.0": "https://github.com/lightvector/KataGo/releases/download/v1.10.0/katago-v1.10.0-opencl-windows-x64.zip",
|
||||
"Eigen AVX2 (Modern CPUs) v1.10.0": "https://github.com/lightvector/KataGo/releases/download/v1.10.0/katago-v1.10.0-eigenavx2-windows-x64.zip",
|
||||
"Eigen (CPU, Non-optimized) v1.10.0": "https://github.com/lightvector/KataGo/releases/download/v1.10.0/katago-v1.10.0-eigen-windows-x64.zip",
|
||||
"OpenCL v1.10.0 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.10.0/katago-v1.10.0-opencl-windows-x64+bs29.zip",
|
||||
},
|
||||
"linux": {
|
||||
"OpenCL v1.9.1": "https://github.com/lightvector/KataGo/releases/download/v1.9.1/katago-v1.9.1-opencl-linux-x64.zip",
|
||||
"Eigen AVX2 (Modern CPUs) v1.9.1": "https://github.com/lightvector/KataGo/releases/download/v1.9.1/katago-v1.9.1-eigenavx2-linux-x64.zip",
|
||||
"Eigen (CPU, Non-optimized) v1.9.1": "https://github.com/lightvector/KataGo/releases/download/v1.9.1/katago-v1.9.1-eigen-linux-x64.zip",
|
||||
"OpenCL v1.9 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.9.1/katago-v1.9.1-opencl-linux-x64+bs29.zip",
|
||||
"OpenCL v1.10.0": "https://github.com/lightvector/KataGo/releases/download/v1.10.0/katago-v1.10.0-opencl-linux-x64.zip",
|
||||
"Eigen AVX2 (Modern CPUs) v1.10.0": "https://github.com/lightvector/KataGo/releases/download/v1.10.0/katago-v1.10.0-eigenavx2-linux-x64.zip",
|
||||
"Eigen (CPU, Non-optimized) v1.10.0": "https://github.com/lightvector/KataGo/releases/download/v1.10.0/katago-v1.10.0-eigen-linux-x64.zip",
|
||||
"OpenCL v1.10.0 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.10.0/katago-v1.10.0-opencl-linux-x64+bs29.zip",
|
||||
},
|
||||
"just-descriptions": {
|
||||
"CUDA v1.9.1 (Windows)": "https://github.com/lightvector/KataGo/releases/download/v1.9.1/katago-v1.9.1-cuda11.2-windows-x64.zip",
|
||||
"CUDA v1.9.1 (Linux)": "https://github.com/lightvector/KataGo/releases/download/v1.9.1/katago-v1.9.1-cuda11.1-linux-x64.zip",
|
||||
"CUDA v1.10.0 (Windows)": "https://github.com/lightvector/KataGo/releases/download/v1.10.0/katago-v1.10.0-cuda11.2-windows-x64.zip",
|
||||
"CUDA v1.10.0 (Linux)": "https://github.com/lightvector/KataGo/releases/download/v1.10.0/katago-v1.10.0-cuda11.1-linux-x64.zip",
|
||||
"Cuda/TensorRT v1.10.0 (Windows)": "https://github.com/lightvector/KataGo/releases/download/v1.10.0/katago-v1.10.0-trt8.2-cuda11.2-windows-x64.zip",
|
||||
"Cuda/TensorRT v1.10.0 (Linux)": "https://github.com/lightvector/KataGo/releases/download/v1.10.0/katago-v1.10.0-trt8.2-cuda11.1-linux-x64.zip",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ 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 in ["win",'macosx'] else -2), ("sdl", -1), ("gst", 99), ("", 0)]
|
||||
ranking = [("ffpy", 98 if platform in ["win", "macosx"] 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:
|
||||
|
||||
Binary file not shown.
@@ -915,3 +915,7 @@ msgstr "AI最佳选点重合度 %"
|
||||
#. game filter in performance report
|
||||
msgid "Entire Game"
|
||||
msgstr "全局"
|
||||
|
||||
#. TODO
|
||||
msgid "ai:antimirror"
|
||||
msgstr "KataAntiMirror"
|
||||
Binary file not shown.
@@ -984,3 +984,7 @@ msgstr "Bester KI Zug Übereinstimmung %"
|
||||
#. - game filter in performance report
|
||||
msgid "Entire Game"
|
||||
msgstr "Gesamtes Spiel"
|
||||
|
||||
#. TODO
|
||||
msgid "ai:antimirror"
|
||||
msgstr "KataAntiMirror"
|
||||
Binary file not shown.
@@ -772,6 +772,9 @@ msgstr ""
|
||||
msgid "ai:jigo"
|
||||
msgstr "KataJigo"
|
||||
|
||||
msgid "ai:antimirror"
|
||||
msgstr "KataAntiMirror"
|
||||
|
||||
msgid "aihelp:jigo"
|
||||
msgstr ""
|
||||
"Will try to win by a set amount of points (default 0.5), without further "
|
||||
|
||||
Binary file not shown.
@@ -999,3 +999,7 @@ msgstr "AI Best Move Match %"
|
||||
#. TODO - game filter in performance report
|
||||
msgid "Entire Game"
|
||||
msgstr "Entire Game"
|
||||
|
||||
#. TODO
|
||||
msgid "ai:antimirror"
|
||||
msgstr "KataAntiMirror"
|
||||
Binary file not shown.
@@ -960,3 +960,7 @@ msgstr "Ce que jouerait KataGo"
|
||||
|
||||
msgid "Entire Game"
|
||||
msgstr "Partie entière"
|
||||
|
||||
#. TODO
|
||||
msgid "ai:antimirror"
|
||||
msgstr "AntiMirrorGo"
|
||||
Binary file not shown.
@@ -959,3 +959,7 @@ msgstr "AIの最善手との一致率"
|
||||
#. game filter in performance report
|
||||
msgid "Entire Game"
|
||||
msgstr "全局"
|
||||
|
||||
#. TODO
|
||||
msgid "ai:antimirror"
|
||||
msgstr "KataAntiMirror"
|
||||
Binary file not shown.
@@ -898,3 +898,7 @@ msgstr "인공지능 최선수 일치율(%)"
|
||||
|
||||
msgid "Entire Game"
|
||||
msgstr "모두"
|
||||
|
||||
#. TODO
|
||||
msgid "ai:antimirror"
|
||||
msgstr "KataAntiMirror"
|
||||
Binary file not shown.
@@ -924,3 +924,7 @@ msgstr ""
|
||||
|
||||
msgid "Entire Game"
|
||||
msgstr "Полная игра"
|
||||
|
||||
#. TODO
|
||||
msgid "ai:antimirror"
|
||||
msgstr "KataAntiMirror"
|
||||
Binary file not shown.
@@ -941,3 +941,7 @@ msgstr "AI最佳選點重合度 %"
|
||||
#. game filter in performance report
|
||||
msgid "Entire Game"
|
||||
msgstr "全局"
|
||||
|
||||
#. TODO
|
||||
msgid "ai:antimirror"
|
||||
msgstr "KataAntiMirror"
|
||||
@@ -187,3 +187,11 @@ def test_next_player():
|
||||
input_sgf = "(;GM[1]FF[4]AB[aa];W[dd])" # branch exists
|
||||
assert "W" == SGF.parse_sgf(input_sgf).next_player
|
||||
assert "W" == SGF.parse_sgf(input_sgf).initial_player
|
||||
|
||||
|
||||
def test_placements():
|
||||
input_sgf = "(;GM[1]FF[4]SZ[19]DT[2020-04-12]AB[dd][aa:ee]AW[ff:zz]AE[aa][bb][cc:dd])"
|
||||
root = SGF.parse_sgf(input_sgf)
|
||||
print(root.properties)
|
||||
assert 6 == len(root.clear_placements)
|
||||
assert 25 + 14 * 14 == len(root.placements)
|
||||
Reference in new issue
Block a user