Merge pull request #195 from sanderland/1.5

1.5
This commit is contained in:
sanderland authored and GitHub committed 2020-09-18 18:55:02 +02:00
commit a29ad6c3e8
38 files changed
+479 -205

No files matched your search

+2 -2
View File
@@ -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
View File
@@ -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"
+2
View File
@@ -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),
+9 -15
View File
@@ -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
[![GitHub issues](https://img.shields.io/github/issues/sanderland/katrain)](https://github.com/sanderland/katrain/issues)
[![Contributors](https://img.shields.io/static/v1?label=contributors&message=19&color=dcb424)](CONTRIBUTIONS.md)
[![Liberapay patrons](https://img.shields.io/liberapay/patrons/sanderbaduk)](https://liberapay.com/sanderbaduk/)
[![Github sponsors](https://img.shields.io/static/v1?label=sponsor&message=7&logo=GitHub&color=dcb424&link=https://github.com/sponsors/sanderland/)](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
View File
@@ -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
View File
@@ -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
},
+2 -2
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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")
+30 -6
View File
@@ -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
+14 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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])
+2 -2
View File
@@ -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
+23 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+34 -33
View File
@@ -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.
+16 -2
View File
@@ -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.
+14 -4
View File
@@ -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.
+19 -6
View File
@@ -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.
+14 -2
View File
@@ -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
View File
@@ -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
+8
View File
@@ -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())