* 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

-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"