38 files changed
+479
-205
No files matched your search
+2
-2
@@ -1,12 +1,12 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: sanderland # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
|
||||
patreon: # Replace with a single Patreon username
|
||||
patreon: katrainbaduk # Replace with a single Patreon username
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||
liberapay: sanderbaduk
|
||||
liberapay: # sanderbaduk
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||
+1
-1
@@ -59,7 +59,7 @@ Many thanks to the following contributors for translations.
|
||||
|
||||
* French: "Dontbtme" with contributions from "wonderingabout"
|
||||
* Korean: "isty2e"
|
||||
* German: "nimets123"
|
||||
* German: "nimets123" with contributions from "trohde"
|
||||
* Spanish: Sergio Villegas ("serpiente") with contributions from the Spanish OGS community
|
||||
* Russian: Dmitry Ivankov and Alexander Kiselev
|
||||
* Simplified Chinese: Qing Mu with contributions from "Medwin"
|
||||
|
||||
@@ -25,6 +25,8 @@ These commands install [Homebrew](https://brew.sh), which simplifies installing
|
||||
|
||||
To upgrade to a newer version, simply run `pip3 install -U katrain`
|
||||
|
||||
If you encounter an error about SDL being missing, try ` brew sdl sdl_image sdl_mixer sdl_ttf portmidi`
|
||||
|
||||
### <a name="MacSources"></a>Troubleshooting and Installation from sources
|
||||
|
||||
Installation from sources is essentially the same as for Linux, see [here](#LinuxSources),
|
||||
|
||||
@@ -154,23 +154,21 @@ Keyboard shortcuts are shown with **[key]**.
|
||||
wanting to guess the next move.
|
||||
* **[w]**: Show all dots: Show all evaluation dots instead of the last few.
|
||||
* You can configure how many are shown with this setting off, and whether they are shown for AIs under 'Configure Teacher'.
|
||||
* **[e]**: Top moves: Show the next moves KataGo considered, colored by their expected point loss. Small dots indicate high uncertainty. Hover over any of them to see the principal variation.
|
||||
* **[r]**: Policy moves: Show KataGo's policy network evaluation, i.e. where it thinks the best next move is purely from the position, and in the absence of any 'reading'.
|
||||
* **[e]**: Top moves: Show the next moves KataGo considered, colored by their expected point loss.
|
||||
Small/faint dots indicate high uncertainty and never show text (lower than your 'fast visits' setting).
|
||||
Hover over any of them to see the principal variation.
|
||||
* **[r]**: Policy moves: Show KataGo's policy network evaluation, i.e. where it thinks the best next move is purely from the position,
|
||||
and in the absence of any 'reading'. This turns off the 'top moves' setting as the overlap is often not useful.
|
||||
* **[t]**: Expected territory: Show expected ownership of each intersection.
|
||||
|
||||
* The analysis options available under the 'Analysis' button are used for deeper evaluation of the position:
|
||||
* **[a]**: Deeper analysis: Re-evaluate the position using more visits, usually resulting in a more accurate evaluation.
|
||||
* **[s]**: Equalize visits: Re-evaluate all currently shown next moves with the same visits as the current top move. Useful to increase confidence in the suggestions with high uncertainty.
|
||||
* **[d]**: Analyze all moves: Evaluate all possible next moves. This can take a bit of time even though 'fast_visits' is used, but can be useful to see how many reasonable next moves are available.
|
||||
* **[f]**: Find alternatives: Increases analysis of current candidate moves to at least the 'fast visits' level, and request a new query that excludes all current candidate moves.
|
||||
* **[spacebar]**: Turn continuous analysis on/off. This will continuously improve analysis of the current position, similar to Lizzie's 'pondering', but only when there are no other queries going on.
|
||||
* **[enter]** AI move. Makes the AI move for the current player regardless of current player selection.
|
||||
|
||||
### Rank Estimation
|
||||
|
||||
A new feature in v1.3 is the rank estimation panel. This adds an extra graph which uses a statistical model to estimate the playing strength
|
||||
of both players for every 80 move segment. It can be used for determining which part of your game needs extra attention,
|
||||
but keep in mind the estimation is based purely on how well moves correspond to the 'shape sense' of KataGo and can be very inaccurate at times.
|
||||
|
||||
## <a name="keyboard"></a> Keyboard and mouse shortcuts
|
||||
|
||||
In addition to shortcuts mentioned above and those shown in the main menu:
|
||||
@@ -179,8 +177,8 @@ In addition to shortcuts mentioned above and those shown in the main menu:
|
||||
* **[~]** or **[ ` ]** or **[m]**: Cycles through more minimalistic UI modes.
|
||||
* **[p]**: Pass
|
||||
* **[b]**: Pause/Resume timer
|
||||
* **[arrow left]** or **[z]**: Undo move. Hold shift for 10 moves at a time, or ctrl to skip to the start.
|
||||
* **[arrow right]** or **[x]**: Redo move. Hold shift for 10 moves at a time, or ctrl to skip to the start.
|
||||
* **[arrow left]** or **[z]**: Undo move. Hold alt for 10 moves at a time, or ctrl to skip to the start.
|
||||
* **[arrow right]** or **[x]**: Redo move. Hold alt for 10 moves at a time, or ctrl to skip to the start.
|
||||
* **[arrow up/down]** Switch branch, as would be expected from the move tree.
|
||||
* **[scroll up]**: Undo move. Only works when hovering the cursor over the board.
|
||||
* **[scroll down]**: Redo move. Only works when hovering the cursor over the board.
|
||||
@@ -197,15 +195,11 @@ In addition to shortcuts mentioned above and those shown in the main menu:
|
||||
* Try using a lower number for `nnMaxBatchSize` in `KataGo/analysis_config.cfg`, and avoid using versions compiled with large board sizes.
|
||||
* If still encountering problems, please start KataGo by itself to check for any errors it gives.
|
||||
* Note that if you don't have a GPU, or your GPU does not support OpenCL, you should use the 'eigen' binaries which run on CPU only.
|
||||
* How can I play on larger boards?
|
||||
* For windows, change the `katago` setting to `katrain\KataGo\katago-bs52.exe`. For other operating systems, you need to compile your own KataGo version with higher limits.
|
||||
|
||||
|
||||
## <a name="support"></a> Support / Contribute
|
||||
|
||||
[](https://github.com/sanderland/katrain/issues)
|
||||
[](CONTRIBUTIONS.md)
|
||||
[](https://liberapay.com/sanderbaduk/)
|
||||
[](https://github.com/sponsors/sanderland)
|
||||
|
||||
* Ideas, feedback, and contributions to code or translations are all very welcome.
|
||||
@@ -213,7 +207,7 @@ In addition to shortcuts mentioned above and those shown in the main menu:
|
||||
* I am looking for contributors of more translations of both this manual and the program itself. The best way to help with this is to contact me on discord.
|
||||
* You can contact me on [discord](https://discord.gg/AjTPFpN) (Sander#3278), [KakaoTalk](https://open.kakao.com/o/gTsMJCac)
|
||||
or [Reddit](http://reddit.com/u/sanderbaduk) to get help, discuss improvements, or simply show your appreciation.
|
||||
* You can also donate to the project through [Liberapay](https://liberapay.com/KaTrain/) or [Github Sponsors](https://github.com/sponsors/sanderland).
|
||||
* You can also donate to the project through [Github Sponsors](https://github.com/sponsors/sanderland).
|
||||
|
||||
|
||||
|
||||
+9
-5
@@ -196,14 +196,14 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
and cn.analysis_ready
|
||||
and cn.parent.analysis_ready
|
||||
and not cn.children
|
||||
and not self.game.ended
|
||||
and not self.game.end_result
|
||||
):
|
||||
self.game.analyze_undo(cn) # not via message loop
|
||||
if (
|
||||
cn.analysis_ready
|
||||
and next_player.ai
|
||||
and not cn.children
|
||||
and not self.game.ended
|
||||
and not self.game.end_result
|
||||
and not (teaching_undo and cn.auto_undo is None)
|
||||
): # cn mismatch stops this if undo fired. avoid message loop here or fires repeatedly.
|
||||
self._do_ai_move(cn)
|
||||
@@ -266,7 +266,7 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
player_info.name = move_tree.root.get_property("P" + bw)
|
||||
self.update_player(bw)
|
||||
self.controls.graph.initialize_from_game(self.game.root)
|
||||
self.controls.rank_graph.initialize_from_game(self.game.root)
|
||||
# self.controls.rank_graph.initialize_from_game(self.game.root)
|
||||
self.update_state(redraw_board=True)
|
||||
|
||||
def _do_ai_move(self, node=None):
|
||||
@@ -286,6 +286,9 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
self.board_gui.animating_pv = None
|
||||
self.game.undo(n_times)
|
||||
|
||||
def _do_resign(self):
|
||||
self.game.current_node.end_state = f"{self.game.current_node.player}+R"
|
||||
|
||||
def _do_redo(self, n_times=1):
|
||||
self.board_gui.animating_pv = None
|
||||
self.game.redo(n_times)
|
||||
@@ -436,6 +439,7 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
"a": ("analyze-extra", "extra"),
|
||||
"s": ("analyze-extra", "equalize"),
|
||||
"d": ("analyze-extra", "sweep"),
|
||||
"f": ("analyze-extra", "alternative"),
|
||||
"p": ("play", None),
|
||||
"down": ("switch-branch", 1),
|
||||
"up": ("switch-branch", -1),
|
||||
@@ -475,9 +479,9 @@ class KaTrainGui(Screen, KaTrainBase):
|
||||
elif keycode[1] in ["`", "~", "m"] and "ctrl" not in modifiers:
|
||||
self.zen = (self.zen + 1) % 3
|
||||
elif keycode[1] in ["left", "z"]:
|
||||
self("undo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999)
|
||||
self("undo", 1 + ("alt" in modifiers) * 9 + ("ctrl" in modifiers) * 999)
|
||||
elif keycode[1] in ["right", "x"]:
|
||||
self("redo", 1 + ("shift" in modifiers) * 9 + ("ctrl" in modifiers) * 999)
|
||||
self("redo", 1 + ("alt" in modifiers) * 9 + ("ctrl" in modifiers) * 999)
|
||||
elif keycode[1] == "n" and "ctrl" in modifiers:
|
||||
self("new-game-popup")
|
||||
elif keycode[1] == "l" and "ctrl" in modifiers:
|
||||
|
||||
+3
-2
@@ -17,7 +17,7 @@
|
||||
"anim_pv_time": 0.5,
|
||||
"debug_level": 0,
|
||||
"lang": "en",
|
||||
"version": "1.4.0"
|
||||
"version": "1.5.0"
|
||||
},
|
||||
"timer": {
|
||||
"byo_length": 30,
|
||||
@@ -65,8 +65,9 @@
|
||||
true,
|
||||
true
|
||||
],
|
||||
"low_visits": 25,
|
||||
"eval_off_show_last": 3,
|
||||
"text_point_loss": false,
|
||||
"text_point_loss": true,
|
||||
"eval_show_ai": true,
|
||||
"lock_ai": false
|
||||
},
|
||||
|
||||
@@ -61,8 +61,8 @@ class KaTrainBase:
|
||||
if self.debug_level >= OUTPUT_DEBUG:
|
||||
Config.set("kivy", "log_enable", 1)
|
||||
Config.set("kivy", "log_level", "debug")
|
||||
if self.debug_level >= OUTPUT_EXTRA_DEBUG:
|
||||
Config.set("kivy", "log_level", "trace")
|
||||
# if self.debug_level >= OUTPUT_EXTRA_DEBUG:
|
||||
# Config.set("kivy", "log_level", "trace")
|
||||
self.players_info = {"B": Player("B"), "W": Player("W")}
|
||||
self.reset_players()
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
VERSION = "1.4.0"
|
||||
VERSION = "1.5.0"
|
||||
HOMEPAGE = "https://github.com/sanderland/katrain"
|
||||
CONFIG_MIN_VERSION = "1.4.0" # keep config files from this version
|
||||
|
||||
|
||||
+24
-4
@@ -1,6 +1,7 @@
|
||||
import copy
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
@@ -41,10 +42,12 @@ class KataGoEngine:
|
||||
self._lock = threading.Lock()
|
||||
self.analysis_thread = None
|
||||
self.stderr_thread = None
|
||||
self.shell = False
|
||||
|
||||
exe = config.get("katago", "").strip()
|
||||
if config.get("altcommand", ""):
|
||||
self.command = config["altcommand"]
|
||||
self.shell = True
|
||||
else:
|
||||
if not exe:
|
||||
if platform == "win":
|
||||
@@ -74,14 +77,16 @@ class KataGoEngine:
|
||||
elif not os.path.isfile(cfg):
|
||||
self.katrain.log(i18n._("Kata config not found").format(config=cfg), OUTPUT_ERROR)
|
||||
return # don't start
|
||||
self.command = f'"{exe}" analysis -model "{model}" -config "{cfg}" -analysis-threads {config["threads"]}'
|
||||
self.command = shlex.split(
|
||||
f'"{exe}" analysis -model "{model}" -config "{cfg}" -analysis-threads {config["threads"]}'
|
||||
)
|
||||
self.start()
|
||||
|
||||
def start(self):
|
||||
try:
|
||||
self.katrain.log(f"Starting KataGo with {self.command}", OUTPUT_DEBUG)
|
||||
self.katago_process = subprocess.Popen(
|
||||
self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True
|
||||
self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=self.shell
|
||||
)
|
||||
except (FileNotFoundError, PermissionError, OSError) as e:
|
||||
self.katrain.log(
|
||||
@@ -221,13 +226,15 @@ class KataGoEngine:
|
||||
visits: int = None,
|
||||
analyze_fast: bool = False,
|
||||
time_limit=True,
|
||||
find_alternatives: bool = False,
|
||||
priority: int = 0,
|
||||
ownership: Optional[bool] = None,
|
||||
next_move: Optional[GameNode] = None,
|
||||
extra_settings: Optional[Dict] = None,
|
||||
):
|
||||
moves = [m for node in analysis_node.nodes_from_root for m in node.moves]
|
||||
initial_stones = analysis_node.root.placements
|
||||
nodes = analysis_node.nodes_from_root
|
||||
moves = [m for node in nodes for m in node.moves]
|
||||
initial_stones = [m for node in nodes for m in node.placements]
|
||||
if next_move:
|
||||
moves.append(next_move)
|
||||
if ownership is None:
|
||||
@@ -237,6 +244,17 @@ class KataGoEngine:
|
||||
if analyze_fast and self.config.get("fast_visits"):
|
||||
visits = self.config["fast_visits"]
|
||||
|
||||
if find_alternatives:
|
||||
avoid = [
|
||||
{
|
||||
"moves": list(analysis_node.analysis["moves"].keys()),
|
||||
"player": analysis_node.next_player,
|
||||
"untilDepth": 1,
|
||||
}
|
||||
]
|
||||
else:
|
||||
avoid = []
|
||||
|
||||
size_x, size_y = analysis_node.board_size
|
||||
settings = copy.copy(self.override_settings)
|
||||
if time_limit:
|
||||
@@ -250,11 +268,13 @@ class KataGoEngine:
|
||||
"analyzeTurns": [len(moves)],
|
||||
"maxVisits": visits,
|
||||
"komi": analysis_node.komi,
|
||||
"avoidMoves": avoid,
|
||||
"boardXSize": size_x,
|
||||
"boardYSize": size_y,
|
||||
"includeOwnership": ownership and not next_move,
|
||||
"includePolicy": not next_move,
|
||||
"initialStones": [[m.player, m.gtp()] for m in initial_stones],
|
||||
"initialPlayer": analysis_node.root.next_player,
|
||||
"moves": [[m.player, m.gtp()] for m in moves],
|
||||
"overrideSettings": {**settings, **(extra_settings or {})},
|
||||
}
|
||||
|
||||
+33
-13
@@ -55,7 +55,11 @@ class Game:
|
||||
self.root = move_tree
|
||||
self.komi = self.root.komi
|
||||
handicap = int(self.root.get_property("HA", 0))
|
||||
if handicap >= 2 and not self.root.placements:
|
||||
if (
|
||||
handicap >= 2
|
||||
and not self.root.placements
|
||||
and not (not self.root.move_with_placements and self.root.children and self.root.children[0].placements)
|
||||
): # 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")
|
||||
@@ -203,8 +207,11 @@ class Game:
|
||||
return sum(self.chains, [])
|
||||
|
||||
@property
|
||||
def ended(self):
|
||||
return self.current_node.parent and self.current_node.is_pass and self.current_node.parent.is_pass
|
||||
def end_result(self):
|
||||
if self.current_node.end_state:
|
||||
return self.current_node.end_state
|
||||
if self.current_node.parent and self.current_node.is_pass and self.current_node.parent.is_pass:
|
||||
return self.manual_score or i18n._("board-game-end")
|
||||
|
||||
@property
|
||||
def prisoner_count(
|
||||
@@ -279,13 +286,17 @@ class Game:
|
||||
def player_name(player_info):
|
||||
return f"{i18n._(player_info.player_type)} ({i18n._(player_info.player_subtype)})"
|
||||
|
||||
root_properties = self.root.properties
|
||||
x_properties = {}
|
||||
if "KaTrain" in self.root.get_property("AP", ""):
|
||||
for bw in "BW":
|
||||
self.root.set_property("P" + bw, player_name(self.katrain.players_info[bw]))
|
||||
x_properties["P" + bw] = player_name(self.katrain.players_info[bw])
|
||||
player_info = self.katrain.players_info[bw]
|
||||
if player_info.player_type == PLAYER_AI:
|
||||
self.root.set_property(bw + "R", rank_label(player_info.calculated_rank))
|
||||
|
||||
x_properties[bw + "R"] = rank_label(player_info.calculated_rank)
|
||||
if "+" in str(self.end_result):
|
||||
x_properties["RE"] = self.end_result
|
||||
self.root.properties = {**root_properties, **{k: [v] for k, v in x_properties.items()}}
|
||||
player_names = {bw: re.sub(r"['<>:\"/\\|?*]", "", self.root.get_property("P" + bw, bw)) for bw in "BW"}
|
||||
game_name = f"katrain_{player_names['B']} vs {player_names['W']} {self.game_id}"
|
||||
file_name = os.path.abspath(os.path.join(path, f"{game_name}.sgf"))
|
||||
@@ -299,6 +310,7 @@ class Game:
|
||||
)
|
||||
with open(file_name, "w", encoding="utf-8") as f:
|
||||
f.write(sgf)
|
||||
self.root.properties = root_properties
|
||||
return i18n._("sgf written").format(file_name=file_name)
|
||||
|
||||
def analyze_extra(self, mode, **kwargs):
|
||||
@@ -310,7 +322,9 @@ class Game:
|
||||
|
||||
if mode == "extra":
|
||||
if kwargs.get("continuous", False):
|
||||
visits = max(engine.config["max_visits"], math.ceil(cn.analysis_visits_requested * 1.25))
|
||||
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)
|
||||
@@ -355,21 +369,27 @@ class Game:
|
||||
visits = engine.config["fast_visits"]
|
||||
self.katrain.controls.set_status(i18n._("sweep analysis").format(visits=visits), STATUS_ANALYSIS)
|
||||
priority = -1_000_000_000
|
||||
elif mode == "equalize":
|
||||
elif mode in ["equalize", "alternative"]:
|
||||
if not cn.analysis_ready:
|
||||
self.katrain.controls.set_status(i18n._("wait-before-equalize"), STATUS_INFO, self.current_node)
|
||||
return
|
||||
|
||||
analyze_moves = [Move.from_gtp(gtp, player=cn.next_player) for gtp, _ in cn.analysis["moves"].items()]
|
||||
visits = max(d["visits"] for d in cn.analysis["moves"].values())
|
||||
self.katrain.controls.set_status(i18n._("equalizing analysis").format(visits=visits), STATUS_ANALYSIS)
|
||||
priority = -1_000
|
||||
if mode == "alternative": # also do a quick update on current candidates so it doesn't look too weird
|
||||
self.katrain.controls.set_status(i18n._("alternative analysis"), STATUS_ANALYSIS)
|
||||
cn.analyze(engine, priority=-500, time_limit=False, find_alternatives=True)
|
||||
visits = engine.config["fast_visits"]
|
||||
else:
|
||||
visits = max(d["visits"] for d in cn.analysis["moves"].values())
|
||||
self.katrain.controls.set_status(i18n._("equalizing analysis").format(visits=visits), STATUS_ANALYSIS)
|
||||
else:
|
||||
raise ValueError("Invalid analysis mode")
|
||||
for move in analyze_moves:
|
||||
cn.analyze(
|
||||
engine, priority, visits=visits, refine_move=move, time_limit=False
|
||||
) # explicitly requested so take as long as you need
|
||||
if cn.analysis["moves"].get(move.gtp(), {"visits": 0})["visits"] < visits:
|
||||
cn.analyze(
|
||||
engine, priority, visits=visits, refine_move=move, time_limit=False
|
||||
) # explicitly requested so take as long as you need
|
||||
|
||||
def analyze_undo(self, node):
|
||||
train_config = self.katrain.config("trainer")
|
||||
|
||||
@@ -23,6 +23,7 @@ class GameNode(SGFNode):
|
||||
self.time_used = 0
|
||||
self.analysis_visits_requested = 0
|
||||
self.undo_threshold = random.random() # for fractional undos
|
||||
self.end_state = None
|
||||
|
||||
def sgf_properties(self, save_comments_player=None, save_comments_class=None, eval_thresholds=None):
|
||||
properties = copy.copy(super().sgf_properties())
|
||||
@@ -67,15 +68,25 @@ class GameNode(SGFNode):
|
||||
) # analyzed/not undone main, non-teach second, undone last
|
||||
|
||||
# various analysis functions
|
||||
def analyze(self, engine, priority=0, visits=None, time_limit=True, refine_move=None, analyze_fast=False):
|
||||
def analyze(
|
||||
self,
|
||||
engine,
|
||||
priority=0,
|
||||
visits=None,
|
||||
time_limit=True,
|
||||
refine_move=None,
|
||||
analyze_fast=False,
|
||||
find_alternatives=False,
|
||||
):
|
||||
engine.request_analysis(
|
||||
self,
|
||||
lambda result: self.set_analysis(result, refine_move),
|
||||
lambda result: self.set_analysis(result, refine_move, find_alternatives),
|
||||
priority=priority,
|
||||
visits=visits,
|
||||
analyze_fast=analyze_fast,
|
||||
time_limit=time_limit,
|
||||
next_move=refine_move,
|
||||
find_alternatives=find_alternatives,
|
||||
)
|
||||
|
||||
def update_move_analysis(self, move_analysis, move_gtp):
|
||||
@@ -86,21 +97,30 @@ class GameNode(SGFNode):
|
||||
"order": 999,
|
||||
**move_analysis,
|
||||
} # some default values for keys missing in rootInfo
|
||||
elif cur["visits"] < move_analysis["visits"]:
|
||||
cur.update(move_analysis)
|
||||
else:
|
||||
cur["order"] = min(cur["order"], move_analysis.get("order", 999)) # parent arriving after child
|
||||
if cur["visits"] < move_analysis["visits"]:
|
||||
cur.update(move_analysis)
|
||||
|
||||
def set_analysis(self, analysis_json, refine_move):
|
||||
def set_analysis(self, analysis_json: Dict, refine_move: Optional[Move], alternatives_mode: bool):
|
||||
if refine_move:
|
||||
pvtail = analysis_json["moveInfos"][0]["pv"] if analysis_json["moveInfos"] else []
|
||||
self.update_move_analysis(
|
||||
{"pv": [refine_move.gtp()] + pvtail, **analysis_json["rootInfo"]}, refine_move.gtp()
|
||||
)
|
||||
else:
|
||||
if alternatives_mode:
|
||||
for m in analysis_json["moveInfos"]:
|
||||
m["order"] += 10 # offset for not making this top
|
||||
if refine_move is None and not alternatives_mode:
|
||||
for move_dict in self.analysis["moves"].values():
|
||||
move_dict["order"] = 999 # old moves to end
|
||||
for move_analysis in analysis_json["moveInfos"]:
|
||||
self.update_move_analysis(move_analysis, move_analysis["move"])
|
||||
self.ownership = analysis_json.get("ownership")
|
||||
self.policy = analysis_json.get("policy")
|
||||
self.analysis["root"] = analysis_json["rootInfo"]
|
||||
if not alternatives_mode:
|
||||
self.analysis["root"] = analysis_json["rootInfo"]
|
||||
if self.parent and self.move:
|
||||
analysis_json["rootInfo"]["pv"] = [self.move.gtp()] + (
|
||||
analysis_json["moveInfos"][0]["pv"] if analysis_json["moveInfos"] else []
|
||||
@@ -151,6 +171,8 @@ class GameNode(SGFNode):
|
||||
def comment(self, sgf=False, teach=False, details=False, interactive=True):
|
||||
single_move = self.move
|
||||
if not self.parent or not single_move: # root
|
||||
if self.root:
|
||||
return f"{i18n._('komi')}: {self.komi:.1f}\n{i18n._('ruleset')}: {i18n._(self.get_property('RU','Japanese').lower())}\n"
|
||||
return ""
|
||||
|
||||
text = i18n._("move").format(number=self.depth) + f": {single_move.player} {single_move.gtp()}\n"
|
||||
@@ -193,8 +215,10 @@ class GameNode(SGFNode):
|
||||
text += "\n" + i18n._("Info:AI thoughts").format(thoughts=self.ai_thoughts)
|
||||
else:
|
||||
text = i18n._("No analysis available") if sgf else i18n._("Analyzing move...")
|
||||
|
||||
if "C" in self.properties:
|
||||
text += "\n[u]SGF Comments:[/u]\n" + "\n".join(self.properties["C"])
|
||||
|
||||
return text
|
||||
|
||||
@property
|
||||
|
||||
@@ -275,7 +275,7 @@ class SGFNode:
|
||||
@property
|
||||
def next_player(self):
|
||||
"""Returns player to move"""
|
||||
if "B" in self.properties or "AB" in self.properties: # root or black moved
|
||||
if "B" in self.properties or ("AB" in self.properties and not "W" in self.properties): # root or black moved
|
||||
return "W"
|
||||
else:
|
||||
return "B"
|
||||
@@ -283,7 +283,7 @@ class SGFNode:
|
||||
@property
|
||||
def player(self):
|
||||
"""Returns player that moved last. nb root is considered white played if no handicap stones are placed"""
|
||||
if "B" in self.properties or "AB" in self.properties:
|
||||
if "B" in self.properties or ("AB" in self.properties and not "W" in self.properties):
|
||||
return "B"
|
||||
else:
|
||||
return "W"
|
||||
@@ -332,7 +332,17 @@ class SGF:
|
||||
"""Parse a string as SGF."""
|
||||
match = re.search(cls.SGF_PAT, input_str)
|
||||
clipped_str = match.group() if match else input_str
|
||||
return cls(clipped_str).root
|
||||
root = cls(clipped_str).root
|
||||
# Fix weird FoxGo server KM values
|
||||
if "foxwq" in root.get_list_property("AP", []):
|
||||
if int(root.get_property("HA", 0)) >= 1:
|
||||
corrected_komi = 0.5
|
||||
elif root.get_property("RU").lower() in ["chinese", "cn"]:
|
||||
corrected_komi = 7.5
|
||||
else:
|
||||
corrected_komi = 6.5
|
||||
root.set_property("KM", corrected_komi)
|
||||
return root
|
||||
|
||||
@classmethod
|
||||
def parse_file(cls, filename, encoding=None) -> SGFNode:
|
||||
@@ -343,7 +353,7 @@ class SGF:
|
||||
with open(filename, "rb") as f:
|
||||
bin_contents = f.read()
|
||||
if not encoding:
|
||||
if is_gib or is_ngf:
|
||||
if is_gib or is_ngf or b"AP[foxwq]" in bin_contents:
|
||||
encoding = "utf8"
|
||||
else: # sgf
|
||||
match = re.search(rb"CA\[(.*?)\]", bin_contents)
|
||||
|
||||
+16
-17
@@ -284,9 +284,9 @@
|
||||
outline_width: 2
|
||||
padding: [2*CP_PADDING,CP_PADDING,CP_PADDING,CP_PADDING]
|
||||
CircleWithText:
|
||||
size_hint: 0.32, 1
|
||||
player: root.player
|
||||
text: str(root.captures)
|
||||
size_hint: 0.32, 1
|
||||
BoxLayout:
|
||||
size_hint: 0.75, 1
|
||||
pos_hint: {'x':0,'y':0}
|
||||
@@ -511,7 +511,6 @@
|
||||
engine_status_pondering: -1
|
||||
timer:timer
|
||||
graph: graph
|
||||
rank_graph: rank_graph
|
||||
stats: stats
|
||||
info: info
|
||||
note: note
|
||||
@@ -551,41 +550,34 @@
|
||||
padding: 2*CP_PADDING,CP_PADDING
|
||||
spacing: CP_SPACING
|
||||
size_hint_x: 0.35
|
||||
SizedRoundedRectangleButton:
|
||||
text: i18n._('pass-button-text')
|
||||
size_hint: 1, 0.5
|
||||
on_left_release: root.katrain("play",None)
|
||||
SizedRoundedRectangleButton:
|
||||
text: i18n._('undo-button-text')
|
||||
size_hint: 1, 0.5
|
||||
on_left_release: root.katrain("undo","smart")
|
||||
SizedRoundedRectangleButton:
|
||||
text: i18n._('resign')
|
||||
size_hint: 1, 0.5
|
||||
on_left_release: root.katrain("resign")
|
||||
MoveTree:
|
||||
id: move_tree
|
||||
size_hint_x: EPSILON if self.parent.mode == MODE_PLAY else 1
|
||||
opacity: 0 if self.parent.mode == MODE_PLAY else 1
|
||||
CollapsablePanel:
|
||||
id: graph_panel
|
||||
content_height: graph.height + rank_graph.height
|
||||
content_height: graph.height # + rank_graph.height
|
||||
options_height: root.tab_option_height
|
||||
closed_label: 'closedlabel:scoregraph'
|
||||
options: ['score','winrate','rank_est']
|
||||
option_colors: [SCORE_COLOR,WINRATE_COLOR,POINTLOSS_COLOR]
|
||||
options: ['score','winrate']
|
||||
option_colors: [SCORE_COLOR,WINRATE_COLOR]
|
||||
option_active: [True,False,False]
|
||||
on_option_state:
|
||||
graph.show_graphs(args[1])
|
||||
graph.hidden = not (args[1]['score'] or args[1]['winrate'])
|
||||
rank_graph.hidden = not args[1]['rank_est']
|
||||
rank_graph.trigger_calculate()
|
||||
ScoreGraph:
|
||||
id: graph
|
||||
opacity: 0 if self.hidden else 1
|
||||
size_hint_y: None
|
||||
height: EPSILON if self.hidden else root.graph_height
|
||||
RankGraph:
|
||||
id: rank_graph
|
||||
opacity: 0 if self.hidden else 1
|
||||
size_hint_y: None
|
||||
height: EPSILON if self.hidden else root.graph_height
|
||||
CollapsablePanel:
|
||||
id: stats_panel
|
||||
num_active: 3
|
||||
@@ -705,12 +697,13 @@
|
||||
halign: 'center'
|
||||
font_size: root.height / 3
|
||||
font_name: root.font_name
|
||||
color: checkbox.selected_color if checkbox.active else checkbox.unselected_color
|
||||
color: checkbox.disabled_color if root.disabled else (checkbox.selected_color if checkbox.active else checkbox.unselected_color)
|
||||
size_hint_x: None
|
||||
width: self.texture_size[0] + 4
|
||||
on_left_press: checkbox.active = not checkbox.active
|
||||
MDCheckbox:
|
||||
id: checkbox
|
||||
disabled: root.disabled
|
||||
size_hint: None,0.5
|
||||
pos_hint: {'center_y': 0.5}
|
||||
width: self.height
|
||||
@@ -751,6 +744,11 @@
|
||||
icon: 'img/Sweep.png'
|
||||
shortcut: 'd'
|
||||
on_action: root.katrain("analyze-extra", "sweep")
|
||||
AnalysisMenuItem:
|
||||
text: i18n._("analysis:alternatives")
|
||||
icon: 'img/Alternative.png'
|
||||
shortcut: 'f'
|
||||
on_action: root.katrain("analyze-extra", "alternative")
|
||||
AnalysisMenuItem:
|
||||
text: i18n._("analysis:game")
|
||||
icon: 'img/analysis.png'
|
||||
@@ -797,6 +795,7 @@
|
||||
AnalysisToggle:
|
||||
id: hints
|
||||
text: i18n._("analysis:topmoves")
|
||||
disabled: policy.checkbox.active
|
||||
AnalysisToggle:
|
||||
id: policy
|
||||
text: i18n._("analysis:policy")
|
||||
|
||||
+89
-53
@@ -17,7 +17,7 @@ from kivymd.app import MDApp
|
||||
from kivymd.uix.boxlayout import MDBoxLayout
|
||||
from kivymd.uix.floatlayout import MDFloatLayout
|
||||
|
||||
from katrain.core.constants import MODE_PLAY, OUTPUT_DEBUG, STATUS_TEACHING
|
||||
from katrain.core.constants import MODE_PLAY, OUTPUT_DEBUG, STATUS_TEACHING, OUTPUT_EXTRA_DEBUG
|
||||
from katrain.core.game import Move
|
||||
from katrain.core.lang import i18n
|
||||
from katrain.core.utils import evaluation_class, var_to_grid
|
||||
@@ -120,8 +120,9 @@ class BadukPanWidget(Widget):
|
||||
katrain.game.set_current_node(nodes_here[-1].parent)
|
||||
katrain.update_state()
|
||||
else: # load comments & pv
|
||||
katrain.log(f"\nAnalysis:\n{nodes_here[-1].analysis}", OUTPUT_DEBUG)
|
||||
katrain.log(f"\nParent Analysis:\n{nodes_here[-1].parent.analysis}", OUTPUT_DEBUG)
|
||||
katrain.log(f"\nAnalysis:\n{nodes_here[-1].analysis}", OUTPUT_EXTRA_DEBUG)
|
||||
katrain.log(f"\nParent Analysis:\n{nodes_here[-1].parent.analysis}", OUTPUT_EXTRA_DEBUG)
|
||||
katrain.log(f"\nRoot Stats:\n{nodes_here[-1].analysis['root']}", OUTPUT_DEBUG)
|
||||
katrain.controls.info.text = nodes_here[-1].comment(sgf=True)
|
||||
katrain.controls.active_comment_node = nodes_here[-1]
|
||||
if nodes_here[-1].parent.analysis_ready:
|
||||
@@ -254,7 +255,7 @@ class BadukPanWidget(Widget):
|
||||
self.canvas.clear()
|
||||
# stones
|
||||
current_node = katrain.game.current_node
|
||||
game_ended = katrain.game.ended
|
||||
game_ended = katrain.game.end_result
|
||||
full_eval_on = katrain.analysis_controls.eval.active
|
||||
has_stone = {}
|
||||
drawn_stone = {}
|
||||
@@ -268,7 +269,6 @@ class BadukPanWidget(Widget):
|
||||
nodes = katrain.game.current_node.nodes_from_root
|
||||
realized_points_lost = None
|
||||
|
||||
katrain.config("trainer/show_dots")
|
||||
for i, node in enumerate(nodes[::-1]): # reverse order!
|
||||
points_lost = node.points_lost
|
||||
evalscale = 1
|
||||
@@ -362,7 +362,7 @@ class BadukPanWidget(Widget):
|
||||
polsize = 1.1 * math.sqrt(policy_grid[y][x])
|
||||
policy_circle_color = (
|
||||
*POLICY_COLOR,
|
||||
GHOST_ALPHA + TOP_MOVE_ALPHA * (policy_grid[y][x] == best_move_policy),
|
||||
POLICY_ALPHA + TOP_POLICY_ALPHA * (policy_grid[y][x] == best_move_policy),
|
||||
)
|
||||
draw_circle(
|
||||
(self.gridpos_x[x], self.gridpos_y[y]), polsize * self.stone_size, policy_circle_color
|
||||
@@ -377,15 +377,15 @@ class BadukPanWidget(Widget):
|
||||
|
||||
# pass circle
|
||||
passed = len(nodes) > 1 and current_node.is_pass
|
||||
if passed:
|
||||
if passed or game_ended:
|
||||
if game_ended:
|
||||
text = katrain.game.manual_score or i18n._("board-game-end")
|
||||
text = game_ended
|
||||
katrain.controls.timer.paused = True
|
||||
else:
|
||||
text = i18n._("board-pass")
|
||||
Color(0.45, 0.05, 0.45, 0.7)
|
||||
center = (self.gridpos_x[int(board_size_x / 2)], self.gridpos_y[int(board_size_y / 2)])
|
||||
size = min(self.width, self.height) * 0.22
|
||||
size = min(self.width, self.height) * 0.227
|
||||
Ellipse(pos=(center[0] - size / 2, center[1] - size / 2), size=(size, size))
|
||||
Color(0.85, 0.85, 0.85)
|
||||
draw_text(
|
||||
@@ -395,9 +395,9 @@ class BadukPanWidget(Widget):
|
||||
self.draw_hover_contents()
|
||||
|
||||
def draw_hover_contents(self, *_args):
|
||||
ghost_alpha = GHOST_ALPHA
|
||||
ghost_alpha = POLICY_ALPHA
|
||||
katrain = self.katrain
|
||||
game_ended = katrain.game.ended
|
||||
game_ended = katrain.game.end_result
|
||||
current_node = katrain.game.current_node
|
||||
player, next_player = current_node.player, current_node.next_player
|
||||
|
||||
@@ -409,52 +409,51 @@ class BadukPanWidget(Widget):
|
||||
self.canvas.after.clear()
|
||||
self.active_pv_moves = []
|
||||
|
||||
# children of current moves in undo / review
|
||||
alpha = GHOST_ALPHA
|
||||
if katrain.analysis_controls.show_children.active:
|
||||
for child_node in current_node.children:
|
||||
points_lost = child_node.points_lost
|
||||
move = child_node.move
|
||||
if move and move.coords is not None:
|
||||
if points_lost is None:
|
||||
evalcol = None
|
||||
else:
|
||||
evalcol = copy.copy(self.eval_color(points_lost))
|
||||
evalcol[3] = alpha
|
||||
if child_node.analysis_ready:
|
||||
self.active_pv_moves.append(
|
||||
(move.coords, [move.gtp()] + child_node.candidate_moves[0]["pv"], current_node)
|
||||
)
|
||||
scale = CHILD_SCALE
|
||||
self.draw_stone(
|
||||
move.coords[0],
|
||||
move.coords[1],
|
||||
move.player,
|
||||
alpha=alpha,
|
||||
evalcol=evalcol,
|
||||
evalscale=scale,
|
||||
scale=scale,
|
||||
)
|
||||
|
||||
# hints or PV
|
||||
if katrain.analysis_controls.hints.active and not game_ended:
|
||||
hint_moves = []
|
||||
if (
|
||||
katrain.analysis_controls.hints.active
|
||||
and not katrain.analysis_controls.policy.active
|
||||
and not game_ended
|
||||
):
|
||||
hint_moves = current_node.candidate_moves
|
||||
for i, move_dict in enumerate(hint_moves):
|
||||
elif katrain.controls.status_state[1] == STATUS_TEACHING: # show score hint for teaching undo
|
||||
hint_moves = [
|
||||
m
|
||||
for m in current_node.candidate_moves
|
||||
for c in current_node.children
|
||||
if c.move and c.auto_undo and c.move.gtp() == m["move"]
|
||||
]
|
||||
|
||||
top_move_coords = None
|
||||
if hint_moves:
|
||||
low_visits_threshold = katrain.config("trainer/low_visits", 25)
|
||||
for move_dict in hint_moves:
|
||||
move = Move.from_gtp(move_dict["move"])
|
||||
if move.coords is not None:
|
||||
alpha, scale = GHOST_ALPHA, 1.0
|
||||
if move_dict["visits"] < VISITS_FRAC_SMALL * hint_moves[0]["visits"]:
|
||||
scale = 0.8
|
||||
engine_best_move = move_dict.get("order", 99) == 0
|
||||
scale = HINT_SCALE
|
||||
text_on = True
|
||||
alpha = HINTS_ALPHA
|
||||
if move_dict["visits"] < low_visits_threshold and not engine_best_move:
|
||||
scale = UNCERTAIN_HINT_SCALE
|
||||
text_on = False
|
||||
alpha = HINTS_MIN_ALPHA + (HINTS_ALPHA - HINTS_MIN_ALPHA) * (
|
||||
move_dict["visits"] / low_visits_threshold
|
||||
)
|
||||
if "pv" in move_dict:
|
||||
self.active_pv_moves.append((move.coords, move_dict["pv"], current_node))
|
||||
else:
|
||||
katrain.log(f"PV missing for move_dict {move_dict}", OUTPUT_DEBUG)
|
||||
draw_circle(
|
||||
(self.gridpos_x[move.coords[0]], self.gridpos_y[move.coords[1]]),
|
||||
col=[*self.eval_color(move_dict["pointsLost"])[:3], alpha],
|
||||
r=self.stone_size * scale,
|
||||
evalsize = self.stone_size * scale
|
||||
evalcol = self.eval_color(move_dict["pointsLost"])
|
||||
Color(*evalcol[:3], alpha)
|
||||
Rectangle(
|
||||
pos=(self.gridpos_x[move.coords[0]] - evalsize, self.gridpos_y[move.coords[1]] - evalsize),
|
||||
size=(2 * evalsize, 2 * evalsize),
|
||||
source="img/topmove.png",
|
||||
)
|
||||
if self.trainer_config["text_point_loss"]:
|
||||
if self.trainer_config["text_point_loss"] and text_on:
|
||||
if move_dict["pointsLost"] < 0.05:
|
||||
ptloss_text = "0.0"
|
||||
else:
|
||||
@@ -468,13 +467,50 @@ class BadukPanWidget(Widget):
|
||||
font_name="Roboto",
|
||||
)
|
||||
|
||||
if i == 0:
|
||||
if engine_best_move:
|
||||
top_move_coords = move.coords
|
||||
Color(*TOP_MOVE_BORDER_COLOR)
|
||||
Line(
|
||||
circle=(
|
||||
self.gridpos_x[move.coords[0]],
|
||||
self.gridpos_y[move.coords[1]],
|
||||
self.stone_size * scale - 1.2,
|
||||
self.stone_size - dp(1.2),
|
||||
),
|
||||
width=dp(1.2),
|
||||
)
|
||||
|
||||
# children of current moves in undo / review
|
||||
if katrain.analysis_controls.show_children.active:
|
||||
for child_node in current_node.children:
|
||||
move = child_node.move
|
||||
if move and move.coords is not None:
|
||||
if child_node.analysis_ready:
|
||||
self.active_pv_moves.append(
|
||||
(move.coords, [move.gtp()] + child_node.candidate_moves[0]["pv"], current_node)
|
||||
)
|
||||
|
||||
if move.coords != top_move_coords: # for contrast
|
||||
dashed_width = 18
|
||||
Color(*STONE_CONTRAST_COLORS[child_node.player])
|
||||
Line(
|
||||
circle=(
|
||||
self.gridpos_x[move.coords[0]],
|
||||
self.gridpos_y[move.coords[1]],
|
||||
self.stone_size - dp(1.2),
|
||||
),
|
||||
width=dp(1.2),
|
||||
)
|
||||
else:
|
||||
dashed_width = 10
|
||||
Color(*STONE_COLORS[child_node.player])
|
||||
for s in range(0, 360, 30):
|
||||
Line(
|
||||
circle=(
|
||||
self.gridpos_x[move.coords[0]],
|
||||
self.gridpos_y[move.coords[1]],
|
||||
self.stone_size - dp(1.2),
|
||||
s,
|
||||
s + dashed_width,
|
||||
),
|
||||
width=dp(1.2),
|
||||
)
|
||||
@@ -524,9 +560,9 @@ class BadukPanWidget(Widget):
|
||||
|
||||
stone_size = self.stone_size * sizefac
|
||||
Color(1, 1, 1, 1)
|
||||
Rectangle(
|
||||
pos=(board_coords[0] - stone_size, board_coords[1] - stone_size),
|
||||
size=(2 * stone_size, 2 * stone_size),
|
||||
Rectangle( # not sure why the -1 here, but seems to center better
|
||||
pos=(board_coords[0] - stone_size - 1, board_coords[1] - stone_size),
|
||||
size=(2 * stone_size + 1, 2 * stone_size + 1),
|
||||
source=f"img/{move_player}_stone.png",
|
||||
)
|
||||
Color(*STONE_TEXT_COLORS[move_player])
|
||||
|
||||
@@ -127,7 +127,7 @@ class ControlsPanel(BoxLayout):
|
||||
lock_ai = katrain.config("trainer/lock_ai") and katrain.play_analyze_mode == MODE_PLAY
|
||||
details = self.info.detailed and not lock_ai
|
||||
info = ""
|
||||
if current_node.move and not current_node.is_root:
|
||||
if current_node.move or current_node.is_root:
|
||||
info = self.active_comment_node.comment(
|
||||
teach=katrain.players_info[self.active_comment_node.player].being_taught, details=details
|
||||
)
|
||||
@@ -144,7 +144,7 @@ class ControlsPanel(BoxLayout):
|
||||
self.stats.player = ""
|
||||
|
||||
self.graph.update_value(current_node)
|
||||
self.rank_graph.update_value(current_node)
|
||||
# self.rank_graph.update_value(current_node)
|
||||
self.note.text = current_node.note
|
||||
self.info.text = info
|
||||
|
||||
|
||||
@@ -205,7 +205,7 @@ class IMETextField(MDTextField):
|
||||
self._imo_cursor = self.cursor
|
||||
|
||||
|
||||
class I18NSpinner(Spinner):
|
||||
class KeyValueSpinner(Spinner):
|
||||
__events__ = ["on_select"]
|
||||
sync_height_frac = NumericProperty(1.0)
|
||||
value_refs = ListProperty()
|
||||
@@ -214,9 +214,8 @@ class I18NSpinner(Spinner):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.bind(size=self.update_dropdown_props, pos=self.update_dropdown_props, value_refs=self.i18n_values)
|
||||
self.i18n_values()
|
||||
MDApp.get_running_app().bind(language=self.i18n_values)
|
||||
self.build_values()
|
||||
self.bind(size=self.update_dropdown_props, pos=self.update_dropdown_props, value_refs=self.build_values)
|
||||
|
||||
@property
|
||||
def selected(self):
|
||||
@@ -245,9 +244,8 @@ class I18NSpinner(Spinner):
|
||||
except (ValueError, IndexError):
|
||||
pass
|
||||
|
||||
def i18n_values(self, *_args):
|
||||
if self.value_refs:
|
||||
self.values = [i18n._(ref) for ref in self.value_refs]
|
||||
def build_values(self, *_args):
|
||||
if self.value_refs and self.values:
|
||||
self.text = self.values[self.selected_index]
|
||||
self.font_name = i18n.font_name
|
||||
self.update_dropdown_props()
|
||||
@@ -269,6 +267,22 @@ class I18NSpinner(Spinner):
|
||||
item.font_name = self.font_name
|
||||
|
||||
|
||||
class I18NSpinner(KeyValueSpinner):
|
||||
__events__ = ["on_select"]
|
||||
sync_height_frac = NumericProperty(1.0)
|
||||
value_refs = ListProperty()
|
||||
selected_index = NumericProperty(0)
|
||||
font_name = StringProperty(DEFAULT_FONT)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
MDApp.get_running_app().bind(language=self.build_values)
|
||||
|
||||
def build_values(self, *_args):
|
||||
self.values = [i18n._(ref) for ref in self.value_refs]
|
||||
super().build_values()
|
||||
|
||||
|
||||
class PlayerSetup(MDBoxLayout):
|
||||
player = OptionProperty("B", options=["B", "W"])
|
||||
mode = StringProperty("")
|
||||
@@ -338,6 +352,7 @@ class PlayerInfo(MDBoxLayout, BackgroundMixin):
|
||||
captures = NumericProperty(0)
|
||||
player = OptionProperty("B", options=["B", "W"])
|
||||
player_type = StringProperty("Player")
|
||||
komi = NumericProperty(0)
|
||||
player_subtype = StringProperty("")
|
||||
name = StringProperty("", allownone=True)
|
||||
rank = StringProperty("", allownone=True)
|
||||
@@ -357,6 +372,7 @@ class AnalysisToggle(MDBoxLayout):
|
||||
text = StringProperty("")
|
||||
default_active = BooleanProperty(False)
|
||||
font_name = StringProperty(DEFAULT_FONT)
|
||||
disabled = BooleanProperty(False)
|
||||
|
||||
def trigger_action(self, *args, **kwargs):
|
||||
return self.checkbox.trigger_action(*args, **kwargs)
|
||||
|
||||
+49
-12
@@ -378,6 +378,7 @@ class ConfigPopup(QuickConfigGui):
|
||||
super().__init__(katrain)
|
||||
self.paths = [self.katrain.config("engine/model"), "katrain/models", "~/.katrain"]
|
||||
self.katago_paths = [self.katrain.config("engine/katago"), "~/.katrain"]
|
||||
Clock.schedule_once(self.check_katas)
|
||||
MDApp.get_running_app().bind(language=self.check_models)
|
||||
MDApp.get_running_app().bind(language=self.check_katas)
|
||||
|
||||
@@ -385,6 +386,18 @@ class ConfigPopup(QuickConfigGui):
|
||||
super().build_and_set_properties()
|
||||
|
||||
def check_models(self, *args):
|
||||
def find_description(path):
|
||||
file = os.path.split(path)[1]
|
||||
file_to_desc = {
|
||||
re.match(r".*/([^/]+)", model)[1].replace(".zip", ".bin.gz"): desc
|
||||
for mods in [self.MODELS, self.MODEL_DESC]
|
||||
for desc, model in mods.items()
|
||||
}
|
||||
if file in file_to_desc:
|
||||
return f"{file_to_desc[file]} - {path}"
|
||||
else:
|
||||
return path
|
||||
|
||||
done = set()
|
||||
model_files = []
|
||||
for path in self.paths + [self.model_path.text]:
|
||||
@@ -406,13 +419,28 @@ class ConfigPopup(QuickConfigGui):
|
||||
if files and path not in self.paths:
|
||||
self.paths.append(path) # persistent on paths with models found
|
||||
model_files += files
|
||||
|
||||
model_files = sorted([(find_description(path), path) for path in model_files])
|
||||
models_available_msg = i18n._("models available").format(num=len(model_files))
|
||||
self.model_files.values = [models_available_msg] + model_files
|
||||
self.model_files.values = [models_available_msg] + [desc for desc, path in model_files]
|
||||
self.model_files.value_keys = [""] + [path for desc, path in model_files]
|
||||
self.model_files.text = models_available_msg
|
||||
|
||||
def check_katas(self, *args):
|
||||
def find_description(path):
|
||||
file = os.path.split(path)[1].replace(".exe", "")
|
||||
file_to_desc = {
|
||||
re.match(r".*/([^/]+)", kg)[1].replace(".zip", ""): desc
|
||||
for _, kgs in self.KATAGOS.items()
|
||||
for desc, kg in kgs.items()
|
||||
}
|
||||
if file in file_to_desc:
|
||||
return f"{file_to_desc[file]} - {path}"
|
||||
else:
|
||||
return path
|
||||
|
||||
done = set()
|
||||
model_files = []
|
||||
kata_files = []
|
||||
for path in self.katago_paths + [self.katago_path.text]:
|
||||
path = path.rstrip("/\\")
|
||||
if path.startswith("katrain"):
|
||||
@@ -432,34 +460,43 @@ class ConfigPopup(QuickConfigGui):
|
||||
]
|
||||
if files and path not in self.paths:
|
||||
self.paths.append(path) # persistent on paths with models found
|
||||
model_files += files
|
||||
katas_available_msg = i18n._("katago binaries available").format(num=len(model_files))
|
||||
self.katago_files.values = [katas_available_msg, i18n._("default katago option")] + sorted(
|
||||
model_files, key=lambda f: "bs29" in f
|
||||
)
|
||||
kata_files += files
|
||||
|
||||
kata_files = [(path, find_description(path)) for path in sorted(kata_files, key=lambda f: "bs29" in f)]
|
||||
katas_available_msg = i18n._("katago binaries available").format(num=len(kata_files))
|
||||
self.katago_files.values = [katas_available_msg, i18n._("default katago option")] + [
|
||||
desc for path, desc in kata_files
|
||||
]
|
||||
self.katago_files.value_keys = ["", ""] + [path for path, desc in kata_files]
|
||||
self.katago_files.text = katas_available_msg
|
||||
|
||||
MODELS = {
|
||||
"latest 20b": "https://github.com/lightvector/KataGo/releases/download/v1.4.5/g170e-b20c256x2-s5303129600-d1228401921.bin.gz",
|
||||
"latest 30b": "https://github.com/lightvector/KataGo/releases/download/v1.4.5/g170-b30c320x2-s4824661760-d1229536699.bin.gz",
|
||||
"latest 40b": "https://github.com/lightvector/KataGo/releases/download/v1.4.5/g170-b40c256x2-s5095420928-d1229425124.bin.gz",
|
||||
"Latest 20 block model": "https://github.com/lightvector/KataGo/releases/download/v1.4.5/g170e-b20c256x2-s5303129600-d1228401921.bin.gz",
|
||||
"Latest 30 block model": "https://github.com/lightvector/KataGo/releases/download/v1.4.5/g170-b30c320x2-s4824661760-d1229536699.bin.gz",
|
||||
"Latest 40 block model": "https://github.com/lightvector/KataGo/releases/download/v1.4.5/g170-b40c256x2-s5095420928-d1229425124.bin.gz",
|
||||
}
|
||||
MODEL_DESC = {
|
||||
"Fat 40 block model": "https://d3dndmfyhecmj0.cloudfront.net/g170/neuralnets/g170e-b40c384x2-s2348692992-d1229892979.zip",
|
||||
"Latest 15 block model": "https://d3dndmfyhecmj0.cloudfront.net/g170/neuralnets/g170e-b15c192-s1672170752-d466197061.bin.gz",
|
||||
}
|
||||
|
||||
KATAGOS = {
|
||||
"win": {
|
||||
"OpenCL v1.6.1": "https://github.com/lightvector/KataGo/releases/download/v1.6.1/katago-v1.6.1-gpu-opencl-windows-x64.zip",
|
||||
"CUDA v1.6.1 (New NVIDIA cards)": "https://github.com/lightvector/KataGo/releases/download/v1.6.1/katago-v1.6.1-gpu-cuda10.2-windows-x64.zip",
|
||||
"Eigen AVX2 (Modern CPUs) v1.6.1": "https://github.com/lightvector/KataGo/releases/download/v1.6.1/katago-v1.6.1-cpu-eigen-avx2-windows-x64.zip",
|
||||
"Eigen (CPU, Non-optimized) v1.6.1": "https://github.com/lightvector/KataGo/releases/download/v1.6.1/katago-v1.6.1-cpu-eigen-windows-x64.zip",
|
||||
"OpenCL v1.6.1 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.6.1%2Bbs29/katago-v1.6.1+bs29-gpu-opencl-windows-x64.zip",
|
||||
},
|
||||
"linux": {
|
||||
"OpenCL v1.6.1": "https://github.com/lightvector/KataGo/releases/download/v1.6.1/katago-v1.6.1-gpu-opencl-linux-x64.zip",
|
||||
"CUDA v1.6.1 (New NVIDIA cards)": "https://github.com/lightvector/KataGo/releases/download/v1.6.1/katago-v1.6.1-gpu-cuda10.2-linux-x64.zip",
|
||||
"Eigen AVX2 (Modern CPUs) v1.6.1": "https://github.com/lightvector/KataGo/releases/download/v1.6.1/katago-v1.6.1-cpu-eigen-avx2-linux-x64.zip",
|
||||
"Eigen (CPU, Non-optimized) v1.6.1": "https://github.com/lightvector/KataGo/releases/download/v1.6.1/katago-v1.6.1-cpu-eigen-linux-x64.zip",
|
||||
"OpenCL v1.6.1 (bigger boards)": "https://github.com/lightvector/KataGo/releases/download/v1.6.1%2Bbs29/katago-v1.6.1+bs29-gpu-opencl-linux-x64.zip",
|
||||
},
|
||||
"just-descriptions": {
|
||||
"CUDA v1.6.1 (Windows)": "https://github.com/lightvector/KataGo/releases/download/v1.6.1/katago-v1.6.1-gpu-cuda10.2-windows-x64.zip",
|
||||
"CUDA v1.6.1 (Linux)": "https://github.com/lightvector/KataGo/releases/download/v1.6.1/katago-v1.6.1-gpu-cuda10.2-linux-x64.zip",
|
||||
},
|
||||
}
|
||||
|
||||
def download_models(self, *_largs):
|
||||
|
||||
+13
-6
@@ -8,6 +8,8 @@ DEFAULT_FONT = "fonts/NotoSansCJKsc-Regular.otf"
|
||||
WHITE = [0.95, 0.95, 0.95, 1]
|
||||
BLACK = [0.05, 0.05, 0.05, 1]
|
||||
LIGHTGREY = [0.7, 0.7, 0.7, 1]
|
||||
GREY = [0.5, 0.5, 0.5, 1]
|
||||
LIGHTERGREY = [0.85, 0.85, 0.85, 1]
|
||||
|
||||
RED = [0.8, 0.1, 0.1, 1]
|
||||
GREEN = [0.1, 0.8, 0.1, 1]
|
||||
@@ -31,7 +33,7 @@ EVAL_COLORS = {
|
||||
[0.447, 0.129, 0.42, 1],
|
||||
[0.8, 0, 0, 1],
|
||||
[0.9, 0.4, 0.1, 1],
|
||||
[0.85, 0.89, 0.3, 1],
|
||||
[0.95, 0.95, 0, 1],
|
||||
[0.67, 0.9, 0.18, 1],
|
||||
[0.117, 0.588, 0, 1],
|
||||
],
|
||||
@@ -39,7 +41,7 @@ EVAL_COLORS = {
|
||||
[1, 0, 1, 1],
|
||||
[1, 0, 0, 1],
|
||||
[1, 0.5, 0, 1],
|
||||
[0.85, 0.89, 0.3, 1],
|
||||
[1, 1, 0, 1],
|
||||
[0, 1, 1, 1],
|
||||
[0, 0, 1, 1],
|
||||
],
|
||||
@@ -50,6 +52,7 @@ EVAL_DOT_MAX_SIZE = 0.5
|
||||
EVAL_DOT_MIN_SIZE = 0.25
|
||||
|
||||
STONE_COLORS = {"B": BLACK, "W": WHITE}
|
||||
STONE_CONTRAST_COLORS = {"B": LIGHTERGREY, "W": GREY}
|
||||
OUTLINE_COLORS = {"B": [0.3, 0.3, 0.3, 0.5], "W": [0.7, 0.7, 0.7, 0.5]}
|
||||
STONE_TEXT_COLORS = {"W": BLACK, "B": WHITE}
|
||||
|
||||
@@ -59,12 +62,16 @@ POLICY_COLOR = [0.9, 0.2, 0.8]
|
||||
STARPOINT_SIZE = 0.1
|
||||
BOARD_COLOR = [0.85, 0.68, 0.40, 1]
|
||||
STONE_SIZE = 0.505 # texture edge is transparent
|
||||
VISITS_FRAC_SMALL = 0.1
|
||||
|
||||
GHOST_ALPHA = 0.5
|
||||
TOP_MOVE_ALPHA = 0.4
|
||||
TOP_MOVE_BORDER_COLOR = [46 / 255, 65 / 255, 88 / 255, 0.9]
|
||||
HINTS_MIN_ALPHA = 0.2 # single visit
|
||||
HINTS_ALPHA = 0.8
|
||||
POLICY_ALPHA = 0.6
|
||||
TOP_POLICY_ALPHA = 0.3
|
||||
TOP_MOVE_BORDER_COLOR = [10 / 255, 200 / 255, 250 / 255, 1.0]
|
||||
CHILD_SCALE = 0.95
|
||||
HINT_SCALE = 0.975
|
||||
UNCERTAIN_HINT_SCALE = 0.7
|
||||
|
||||
|
||||
# ponder light
|
||||
ENGINE_DOWN_COL = EVAL_COLORS["theme:normal"][1]
|
||||
|
||||
Binary file not shown.
@@ -521,6 +521,7 @@ msgstr "读秒声音开启"
|
||||
msgid "tab:info-details"
|
||||
msgstr "详细"
|
||||
|
||||
#. TODO: updated
|
||||
msgid "teaching undo message"
|
||||
msgstr "撤销第 {move} 手棋因为损失超过 {points_lost:.1f} 目. 悬停在该手棋 上方查看信息. 请再次尝试."
|
||||
|
||||
@@ -627,3 +628,15 @@ msgstr "所有可用的exe文件已下载"
|
||||
|
||||
msgid "Engine missing DLL"
|
||||
msgstr "启动katago失败因缺失dll文件. 请检查运行要求, 或尝试让exe文件自行运行并检查细节. "
|
||||
|
||||
#. resign the game
|
||||
msgid "resign"
|
||||
msgstr "认输"
|
||||
|
||||
#. Analyze again but exclude all currently considered moves
|
||||
msgid "analysis:alternatives"
|
||||
msgstr "寻找其他走法"
|
||||
|
||||
#. status message
|
||||
msgid "alternative analysis"
|
||||
msgstr "正在搜索其他走法"
|
||||
Binary file not shown.
@@ -105,12 +105,6 @@ msgstr "AI Zug erzwingen"
|
||||
msgid "pass-button-text"
|
||||
msgstr "Passen"
|
||||
|
||||
msgid "teaching undo message"
|
||||
msgstr ""
|
||||
"Zug {move} zurückgenommen, da er {points_lost:.1f} Punkte verliert. Bewege "
|
||||
"Mauszeiger über den Zug,um die erwartete Widerlegung zu sehen. Bitte "
|
||||
"versuche es erneut."
|
||||
|
||||
msgid "ai-ponder"
|
||||
msgstr ""
|
||||
"Zeigt den Enginestatus an von grün (Leerlauf) bis orange(Zugberechnung). Ist"
|
||||
@@ -636,55 +630,62 @@ msgstr "Geschätzte Spielstärke"
|
||||
msgid "reanalyze max visits"
|
||||
msgstr "Anzahl der Visits pro Zug"
|
||||
|
||||
#. TODO - on move tree editing
|
||||
msgid "Delete Node"
|
||||
msgstr "Delete Node"
|
||||
msgstr "Node löschen"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:red-green-colourblind"
|
||||
msgstr "Red-Green Colourblind"
|
||||
msgstr "Rot-Grün-farbenblind"
|
||||
|
||||
#. TODO - theme name
|
||||
msgid "theme:normal"
|
||||
msgstr "Default Theme"
|
||||
msgstr "Standard-Farbschema"
|
||||
|
||||
#. TODO - theme label
|
||||
msgid "theme"
|
||||
msgstr "Colour Theme"
|
||||
msgstr "Farbschema"
|
||||
|
||||
#. TODO
|
||||
msgid "engine:altcommand:hint"
|
||||
msgstr ""
|
||||
"Full command used to start the engine. All other model options will be "
|
||||
"ignored."
|
||||
"Kompletter Befehl um die Engine zu starten. Alle anderen Modelloptionen "
|
||||
"werden ignoriert."
|
||||
|
||||
#. TODO
|
||||
msgid "engine:altcommand"
|
||||
msgstr "Override Engine Command"
|
||||
msgstr "Engine-Befehl überschreiben"
|
||||
|
||||
#. TODO - button in general settings for downloading katago executables
|
||||
msgid "download katago button"
|
||||
msgstr "Download KataGo versions"
|
||||
msgstr "KataGo-Versionen herunterladen"
|
||||
|
||||
#. TODO - shown on on the binaries select dropdown
|
||||
msgid "katago binaries available"
|
||||
msgstr "{num} available katago versions found"
|
||||
msgstr "{num} KataGo-Programme verfügbar"
|
||||
|
||||
#. TODO - shown on on the katago binary select dropdown
|
||||
msgid "default katago option"
|
||||
msgstr "Detect platform and use provided OpenCL KataGo binary"
|
||||
msgstr "Plattform erkennen und angebotenes Programm benutzen"
|
||||
|
||||
#. TODO - error message on trying to download models when already done
|
||||
msgid "All binaries downloaded"
|
||||
msgstr "All available binaries already downloaded"
|
||||
msgstr "Alle verfügbaren Programme wurden bereits heruntergeladen"
|
||||
|
||||
#. TODO
|
||||
msgid "Engine missing DLL"
|
||||
msgstr ""
|
||||
"Failed to start KataGo due to missing DLL. Check the requirements in the "
|
||||
"KataGo documentation, or try running the binary by itself for details. "
|
||||
"Konnte KataGo wegen fehlender DLL nicht starten. Überprüf die Anforderungen "
|
||||
"in der KataGo-Dokumentation oder versuch, KataGo alleine zu starten, um "
|
||||
"Einzelheiten zu erfahren."
|
||||
|
||||
#. TODO - in addition to colour, show point loss as -0.1 etc on hints/top
|
||||
#. moves
|
||||
msgid "show point loss as text"
|
||||
msgstr "Show point loss on top moves"
|
||||
msgstr "Zeig den Punkteverlust auf Beste Züge"
|
||||
|
||||
#. TODO - updated
|
||||
msgid "teaching undo message"
|
||||
msgstr ""
|
||||
"Zug {move} zurückgenommen, da er {points_lost:.1f} Punkte verliert. Bewege "
|
||||
"Mauszeiger über den Zug,um die erwartete Widerlegung zu sehen. Bitte "
|
||||
"versuche es erneut."
|
||||
|
||||
#. resign the game
|
||||
msgid "resign"
|
||||
msgstr "Aufgeben"
|
||||
|
||||
#. - Analyze again but exclude all currently considered moves
|
||||
msgid "analysis:alternatives"
|
||||
msgstr "Finde alternative Züge"
|
||||
|
||||
#. - status message
|
||||
msgid "alternative analysis"
|
||||
msgstr "Suche nach alternativen Zügen"
|
||||
Binary file not shown.
@@ -102,6 +102,10 @@ msgstr "Fast analysis of all moves"
|
||||
msgid "analysis:game"
|
||||
msgstr "Deeper full game analysis"
|
||||
|
||||
#. Analyze again but exclude all currently considered moves
|
||||
msgid "analysis:alternatives"
|
||||
msgstr "Find alternative moves"
|
||||
|
||||
#. Keep improving current position analysis when there is nothing else to do
|
||||
msgid "analysis:continuous"
|
||||
msgstr "Toggle continuous analysis"
|
||||
@@ -117,6 +121,10 @@ msgstr "Performing additional analysis to {visits} visits"
|
||||
msgid "sweep analysis"
|
||||
msgstr "Analyzing entire board using {visits} visits/move"
|
||||
|
||||
#. status message
|
||||
msgid "alternative analysis"
|
||||
msgstr "Searching for alternative moves"
|
||||
|
||||
#. status message
|
||||
msgid "equalizing analysis"
|
||||
msgstr "Equalizing analysis of candidate moves to {visits} visits"
|
||||
@@ -129,10 +137,16 @@ msgstr "Re-analyzing entire game using {visits} visits"
|
||||
msgid "pass-button-text"
|
||||
msgstr "Pass"
|
||||
|
||||
#. resign the game
|
||||
msgid "resign"
|
||||
msgstr "Resign"
|
||||
|
||||
#. Shown when a move is undone in teaching mode.
|
||||
msgid "teaching undo message"
|
||||
msgstr ""
|
||||
"Undid move {move} as it lost {points_lost:.1f} points. Hover over the move "
|
||||
"to see expected refutation. Please try again."
|
||||
"Undid move {move} as it lost {points_lost:.1f} points. Please try again. For"
|
||||
" a hint, hover over the move to see expected refutation, or check expected "
|
||||
"territory (t) to see the areas of the board where point loss is expected."
|
||||
|
||||
msgid "ai-ponder"
|
||||
msgstr ""
|
||||
|
||||
Binary file not shown.
@@ -105,6 +105,7 @@ msgstr "Forzar jugada IA"
|
||||
msgid "pass-button-text"
|
||||
msgstr "Pasar"
|
||||
|
||||
#. TODO: updated
|
||||
msgid "teaching undo message"
|
||||
msgstr ""
|
||||
"Deshice jugada {move} ya que perdía {points_lost:.1f} puntos. Coloca el "
|
||||
@@ -684,3 +685,15 @@ msgstr ""
|
||||
#. moves
|
||||
msgid "show point loss as text"
|
||||
msgstr "Show point loss on top moves"
|
||||
|
||||
#. TODO - resign the game
|
||||
msgid "resign"
|
||||
msgstr "Resign"
|
||||
|
||||
#. TODO - Analyze again but exclude all currently considered moves
|
||||
msgid "analysis:alternatives"
|
||||
msgstr "Find alternative moves"
|
||||
|
||||
#. TODO - status message
|
||||
msgid "alternative analysis"
|
||||
msgstr "Searching for alternative moves"
|
||||
Binary file not shown.
@@ -522,6 +522,7 @@ msgstr ""
|
||||
"possiblement pour cause de \n"
|
||||
"mémoire insuffisante : {error}"
|
||||
|
||||
#. TODO: updated
|
||||
msgid "teaching undo message"
|
||||
msgstr ""
|
||||
"Le coup {move} a été annulé car il perdait {points_lost:.1f} points. Vous "
|
||||
@@ -683,8 +684,8 @@ msgstr "Palette de couleurs"
|
||||
|
||||
msgid "engine:altcommand:hint"
|
||||
msgstr ""
|
||||
"Commande complète exécutée par le moteur de jeu, outrepassant les "
|
||||
"réseaux/fichiers sélectionnés plus haut."
|
||||
"Commande personnelle outrepassant l'exécutable, la configuration et le "
|
||||
"réseau sélectionnés plus haut."
|
||||
|
||||
msgid "engine:altcommand"
|
||||
msgstr "Redéfinir la commande"
|
||||
@@ -705,9 +706,18 @@ msgstr "Pas de nouveaux exécutables disponibles"
|
||||
|
||||
msgid "Engine missing DLL"
|
||||
msgstr ""
|
||||
"Impossible de démarrer KataGo à cause d'un DLL manquant. Veuillez répondre "
|
||||
"aux conditions requises, ou bien essayez l'exécutable seul pour plus de "
|
||||
"Impossible de démarrer KataGo à cause d'un DLL manquant. Veuillez vérifier "
|
||||
"votre configuration système, ou bien essayez l'exécutable seul pour plus de "
|
||||
"détails."
|
||||
|
||||
msgid "show point loss as text"
|
||||
msgstr "Indiquer les points perdus"
|
||||
|
||||
msgid "resign"
|
||||
msgstr "Abandon"
|
||||
|
||||
msgid "analysis:alternatives"
|
||||
msgstr "Proposer des coups différents"
|
||||
|
||||
msgid "alternative analysis"
|
||||
msgstr "Recherche de coups différents."
|
||||
Binary file not shown.
@@ -129,10 +129,6 @@ msgstr "一局全体を再分析中(各{visits}手)"
|
||||
msgid "pass-button-text"
|
||||
msgstr "パス"
|
||||
|
||||
msgid "teaching undo message"
|
||||
msgstr ""
|
||||
"待った。{move}は{points_lost:.1f}目損です(マウスカーソルをこの手にかざせばとがめる手順が表示されます)。打ち直してください。"
|
||||
|
||||
msgid "ai-ponder"
|
||||
msgstr "この印はエンジンの状態をシンプルに表します(緑=空き、オレンジ=処理中)。エンジンが止まったり起動できなかったりすると赤くなります。"
|
||||
|
||||
@@ -647,11 +643,11 @@ msgstr "別版KataGoをダウンロード"
|
||||
|
||||
#. shown on on the binaries select dropdown
|
||||
msgid "katago binaries available"
|
||||
msgstr "入手済KataGo実行ファイルの一覧({num}個みつかりました)"
|
||||
msgstr "入手済KataGoの一覧({num}個みつかりました)"
|
||||
|
||||
#. shown on on the katago binary select dropdown
|
||||
msgid "default katago option"
|
||||
msgstr "内蔵KataGo"
|
||||
msgstr "内蔵KataGo(OpenCL版)"
|
||||
|
||||
#. error message on trying to download models when already done
|
||||
msgid "All binaries downloaded"
|
||||
@@ -666,3 +662,20 @@ msgstr ""
|
||||
#. moves
|
||||
msgid "show point loss as text"
|
||||
msgstr "損失目数を候補手上に表示"
|
||||
|
||||
#. resign the game
|
||||
msgid "resign"
|
||||
msgstr "投了"
|
||||
|
||||
#. Analyze again but exclude all currently considered moves
|
||||
msgid "analysis:alternatives"
|
||||
msgstr "他の候補手を分析"
|
||||
|
||||
#. message shown when move is undone
|
||||
msgid "teaching undo message"
|
||||
msgstr ""
|
||||
"待った。{move}は{points_lost:.1f}目損です。打ち直してください。ヒント:マウスカーソルをこの手にかざせばとがめる手順が表示されます。あるいは、地合推定をオンに(tキー)すれば盤上どのあたりで損をしたかが見られます。"
|
||||
|
||||
#. status message
|
||||
msgid "alternative analysis"
|
||||
msgstr "他の候補手を分析中"
|
||||
Binary file not shown.
@@ -546,6 +546,7 @@ msgstr ""
|
||||
"`threshold`선 밑의 수를 더 많이 뽑은 후 그 중 최선의 수를 둡니다. `line_weight`을 증가시키면 바둑판 중앙에 있는"
|
||||
" 수를 더욱 꺼리게 됩니다. 판이 `endgame` 비율만큼 차면 평범하게 둡니다."
|
||||
|
||||
#. TODO: updated
|
||||
msgid "teaching undo message"
|
||||
msgstr ""
|
||||
"{points_lost:.1f}집 손해이므로 {move}을/를 물렀습니다. 커서를 올려 상대의 대응책을 보십시오. 다시 두어 주세요."
|
||||
@@ -644,3 +645,12 @@ msgstr ""
|
||||
|
||||
msgid "show point loss as text"
|
||||
msgstr "돌 위에 집 손해 표시하기"
|
||||
|
||||
msgid "resign"
|
||||
msgstr "기권"
|
||||
|
||||
msgid "analysis:alternatives"
|
||||
msgstr "다른 수 찾기"
|
||||
|
||||
msgid "alternative analysis"
|
||||
msgstr "다른 수를 찾는 중입니다"
|
||||
Binary file not shown.
@@ -117,10 +117,13 @@ msgstr "Дополнительный анализ всех ходов-канди
|
||||
msgid "pass-button-text"
|
||||
msgstr "Пас"
|
||||
|
||||
#. Shown when a move is undone in teaching mode.
|
||||
msgid "teaching undo message"
|
||||
msgstr ""
|
||||
"Ход {move} отменён, т.к. это потеря {points_lost:.1f} очков. Наведите на "
|
||||
"этот ход чтобы увидеть опровергающее продолжение. Попытайтесь снова."
|
||||
"Ход {move} отменён, т.к. это потеря {points_lost:.1f} очков. Попытайтесь "
|
||||
"снова. Наведите на этот ход, чтобы увидеть опровергающее продолжение, или "
|
||||
"посмотрите на ожидаемую территорию (t), чтобы увидеть, где на доске "
|
||||
"прогнозируется потеря очков."
|
||||
|
||||
msgid "ai-ponder"
|
||||
msgstr ""
|
||||
@@ -672,3 +675,12 @@ msgstr ""
|
||||
|
||||
msgid "show point loss as text"
|
||||
msgstr "Показывать потерянные очки текстом"
|
||||
|
||||
msgid "resign"
|
||||
msgstr "Сдаться"
|
||||
|
||||
msgid "analysis:alternatives"
|
||||
msgstr "Поискать альтернативные ходы"
|
||||
|
||||
msgid "alternative analysis"
|
||||
msgstr "Поиск альтернативных ходов"
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
+16
-6
@@ -109,12 +109,16 @@
|
||||
on_press: root.download_katas()
|
||||
size_hint_y: 0.7
|
||||
AnchorLayout:
|
||||
Spinner:
|
||||
KeyValueSpinner:
|
||||
id: katago_files
|
||||
text: ''
|
||||
on_text:
|
||||
if self.text and self.text == self.values[1]: katago_path.text = ''
|
||||
elif self.text and self.text != self.values[0]: katago_path.text = args[1]
|
||||
text_autoupdate: True
|
||||
-text_size: self.width, None
|
||||
-halign: 'center'
|
||||
-shorten: True
|
||||
-shorten_from: 'right'
|
||||
on_select:
|
||||
if self.selected_index != 0: katago_path.text = self.value_keys[self.selected_index]
|
||||
size_hint_y: 0.7
|
||||
sync_height_frac: 1.0
|
||||
-font_size: self.height * 0.5
|
||||
@@ -140,10 +144,16 @@
|
||||
on_press: root.download_models()
|
||||
size_hint_y: 0.7
|
||||
AnchorLayout:
|
||||
Spinner:
|
||||
KeyValueSpinner:
|
||||
id: model_files
|
||||
text: ''
|
||||
on_text: if self.text and self.text != self.values[0]: model_path.text = args[1]
|
||||
text_autoupdate: True
|
||||
-text_size: self.width, None
|
||||
-halign: 'center'
|
||||
-shorten: True
|
||||
-shorten_from: 'right'
|
||||
on_select:
|
||||
if self.selected_index != 0: model_path.text = self.value_keys[self.selected_index]
|
||||
size_hint_y: 0.7
|
||||
sync_height_frac: 1.0
|
||||
-font_size: self.height * 0.5
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- mode: python ; coding: utf-8 -*-
|
||||
from kivy_deps import sdl2, glew
|
||||
from kivymd import hooks_path as kivymd_hooks_path
|
||||
import subprocess
|
||||
|
||||
block_cipher = None
|
||||
|
||||
@@ -45,6 +46,8 @@ print("DATA FILTERED", len(a.datas))
|
||||
|
||||
console_names = {True:"DebugKaTrain",False:"KaTrain"}
|
||||
|
||||
powershell = subprocess.Popen(["powershell"], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
|
||||
|
||||
for console, name in console_names.items():
|
||||
|
||||
|
||||
@@ -89,4 +92,9 @@ for console, name in console_names.items():
|
||||
console=console,
|
||||
icon="C:\\icon.ico",
|
||||
)
|
||||
powershell.stdin.write(f"Set-AuthenticodeSignature dist/{name}.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert)\n".encode('ascii'))
|
||||
powershell.stdin.write(f"Set-AuthenticodeSignature dist/{name}/{name}.exe -Certificate (Get-ChildItem Cert:\CurrentUser\My -CodeSigningCert)\n".encode('ascii'))
|
||||
powershell.stdin.flush()
|
||||
|
||||
while True:
|
||||
print(powershell.stdout.readline())
|
||||
Reference in new issue
Block a user