* 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:
authored and GitHub committed 2021-10-31 19:54:19 +01:00
1 parent f368f1dcfa
commit 6005579c53
38 files changed
+263 -100

No files matched your search

+4 -2
View File
@@ -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
+7 -4
View File
@@ -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.
-1
View File
@@ -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.
+28 -29
View File
@@ -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:
if touch.button == "scrollup":
self.board_gui.adjust_animate_pv_index(1)
elif touch.button == "scrolldown":
self.board_gui.adjust_animate_pv_index(-1)
if touch.is_mouse_scrolling:
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
View File
@@ -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
View File
@@ -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)
+13 -4
View File
@@ -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(
+9 -6
View File
@@ -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"
+3 -1
View File
@@ -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"]]
+51 -7
View File
@@ -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,10 +181,19 @@ 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)
self.queries.pop(query_id, None)
if ignore_further_results:
self.queries.pop(query_id, None)
def restart(self):
self.queries = {}
@@ -269,9 +283,10 @@ class KataGoEngine(BaseEngine):
continue
query_id = analysis["id"]
if query_id not in self.queries:
self.katrain.log(
f"Query result {query_id} discarded -- recent new game or node reset?", OUTPUT_DEBUG
)
if analysis.get("action") != "terminate":
self.katrain.log(
f"Query result {query_id} discarded -- recent new game or node reset?", OUTPUT_DEBUG
)
continue
callback, error_callback, start_time, next_move, _ = self.queries[query_id]
if "error" in analysis:
@@ -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
+27 -14
View File
@@ -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,22 +565,27 @@ 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 == "ponder":
cn.analyze(
engine,
ponder=True,
priority=PRIORITY_EXTRA_ANALYSIS,
region_of_interest=self.region_of_interest,
time_limit=False,
)
return
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))
)
else:
visits = cn.analysis_visits_requested + engine.config["max_visits"]
self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS)
visits = cn.analysis_visits_requested + engine.config["max_visits"]
self.katrain.controls.set_status(i18n._("extra analysis").format(visits=visits), STATUS_ANALYSIS)
cn.analyze(
engine,
@@ -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)
+6
View File
@@ -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
View File
@@ -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]:
+1
View File
@@ -550,6 +550,7 @@
timer:timer
graph: graph
stats: stats
notes_panel: notes_panel
info: info
note: note
status: status
+19 -9
View File
@@ -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
if self.selecting_region_of_interest:
self.update_box_selection(touch, second_point=False)
else:
self.check_next_move_ghost(touch)
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
View File
@@ -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",
},
}
+1 -1
View File
@@ -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"
+8
View File
@@ -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)