refutation PV / refactor
This commit is contained in:
1 parent
82ded97058
commit
0972cba8d3
10 files changed
+127
-81
No files matched your search
@@ -173,11 +173,11 @@ If you ever need to reset to the original settings, simply re-download the `conf
|
||||
* The program is running too slowly. How can I speed it up?
|
||||
* Adjust the number of visits or maximum time allowed in the settings.
|
||||
* KataGo crashes with out of memory errors, how can I prevent this?
|
||||
* Try using a lower number for `nnMaxBatchSize` in `KataGo/analysis_config.cfg`, and avoid using the board size 29 version.
|
||||
* Try using a lower number for `nnMaxBatchSize` in `KataGo/analysis_config.cfg`, and avoid using versions compiled with large board sizes.
|
||||
|
||||
## Contributing
|
||||
|
||||
* Feedback and pull requests are both very welcome.
|
||||
* Feedback and pull requests are both very welcome. I would also be happy to host translations of this manual into languages where English fluency is typically lower.
|
||||
* For suggestions and planned improvements, see the 'issues' tab on github.
|
||||
* You can also contact me on discord (Sander#3278) or [reddit](http://reddit.com/u/sanderbaduk) to give feedback, or simply show your appreciation.
|
||||
|
||||
* You can also contact me on discord (Sander#3278), [KakaoTalk](https://open.kakao.com/o/gTsMJCac) or [Reddit](http://reddit.com/u/sanderbaduk) to give feedback, or simply show your appreciation.
|
||||
* Some people have also asked me how to donate. Something go-related such as a book or teaching time is highly appreciated.
|
||||
+2
-2
@@ -75,7 +75,7 @@ def malkovich_analysis(cn):
|
||||
logger.log(f"dscore {dscore} = {cn.analysis['root']['scoreLead']} {cn.parent.analysis['root']['scoreLead']} at {move}...", OUTPUT_ERROR)
|
||||
if abs(dscore) > REPORT_SCORE_THRESHOLD and (cn.player == "B" and dscore < 0 or cn.player == "W" and dscore > 0): # relevant mistakes
|
||||
favpl = "B" if dscore > 0 else "W"
|
||||
msg = f"MALKOVICH:{cn.player} {cn.single_move.gtp()} caused a significant score change ({favpl} gained {abs(dscore):.1f} points)"
|
||||
msg = f"MALKOVICH:{cn.player} {cn.move.gtp()} caused a significant score change ({favpl} gained {abs(dscore):.1f} points)"
|
||||
if cn.ai_thoughts:
|
||||
msg += f" -> Win Rate {cn.format_win_rate()} Score {cn.format_score()} AI Thoughts: {cn.ai_thoughts}"
|
||||
else:
|
||||
@@ -139,7 +139,7 @@ while True:
|
||||
if not pol:
|
||||
pol = ["??"]
|
||||
print(f"DISCUSSION:OK, since you passed {MAX_PASS} times after the {bx+by}th move, I will pass as well [policy {pol[-1]:.3%}].", file=sys.stderr)
|
||||
move = game.play(Move(None, player=game.next_player)).single_move
|
||||
move = game.play(Move(None, player=game.next_player)).move
|
||||
else:
|
||||
move, node = ai_move(game, ai_strategy, ai_settings)
|
||||
if node is None:
|
||||
|
||||
+2
-2
@@ -99,11 +99,11 @@ def ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, GameNode
|
||||
ai_thoughts += f"Generated weights for {ai_mode} according to weight factor {ai_settings['line_weight']} and distance from {thr_line+1}th line. "
|
||||
elif "local" in ai_mode or "tenuki" in ai_mode:
|
||||
var = ai_settings["stddev"] ** 2
|
||||
if not cn.single_move or cn.single_move.coords is None:
|
||||
if not cn.move or cn.move.coords is None:
|
||||
weighted_coords = [(1, 1, *top_policy_move.coords)] # if "pick" in ai_mode -> even
|
||||
ai_thoughts += f"No previous non-pass move, faking weights to play top policy move. "
|
||||
else:
|
||||
mx, my = cn.single_move.coords
|
||||
mx, my = cn.move.coords
|
||||
weighted_coords = [
|
||||
(policy_grid[y][x], math.exp(-0.5 * ((x - mx) ** 2 + (y - my) ** 2) / var), x, y) for x in range(size[0]) for y in range(size[1]) if policy_grid[y][x] > 0
|
||||
]
|
||||
|
||||
+2
-2
@@ -286,7 +286,7 @@ class Game:
|
||||
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):
|
||||
move = node.single_move
|
||||
move = node.move
|
||||
if node != self.current_node or node.auto_undo is not None or not node.analysis_ready or not move:
|
||||
return
|
||||
points_lost = node.points_lost
|
||||
@@ -309,5 +309,5 @@ class Game:
|
||||
node.auto_undo = undo
|
||||
if undo:
|
||||
self.undo(1)
|
||||
self.katrain.controls.set_status(f"Undid move {move.gtp()} as it lost {points_lost:.1f} points{xmsg}")
|
||||
self.katrain.controls.set_status(f"Undid move {move.gtp()} as it lost {points_lost:.1f} points{xmsg}. Hover over the move to see expected refutation.")
|
||||
self.katrain.update_state()
|
||||
+11
-5
@@ -86,8 +86,14 @@ class GameNode(SGFNode):
|
||||
if win_rate is not None:
|
||||
return f"{'B' if win_rate > 0.5 else 'W'} {max(win_rate,1-win_rate):.1%}"
|
||||
|
||||
def comment(self, sgf=False, teach=False, hints=False):
|
||||
single_move = self.single_move
|
||||
def make_pv(self, player, pv, interactive):
|
||||
pvtext = f"{player}{' '.join(pv)}"
|
||||
# if interactive:
|
||||
# pvtext = f"[u][ref={pvtext}][color=#334466]{pvtext}[/color][/ref][/u]"
|
||||
return pvtext
|
||||
|
||||
def comment(self, sgf=False, teach=False, hints=False, interactive=False):
|
||||
single_move = self.move
|
||||
if not self.parent or not single_move: # root
|
||||
return ""
|
||||
|
||||
@@ -109,7 +115,7 @@ class GameNode(SGFNode):
|
||||
text += f"Move was predicted best move.\n"
|
||||
if sgf:
|
||||
if previous_top_move.get("pv") and (sgf or hints):
|
||||
text += f"PV: {single_move.player}{' '.join(previous_top_move['pv'])}\n"
|
||||
text += f"PV: {self.make_pv(single_move.player,previous_top_move['pv'],interactive)}\n"
|
||||
|
||||
if sgf or hints or teach:
|
||||
policy_ranking = self.parent.policy_ranking
|
||||
@@ -131,7 +137,7 @@ class GameNode(SGFNode):
|
||||
|
||||
@property
|
||||
def points_lost(self) -> Optional[float]:
|
||||
single_move = self.single_move
|
||||
single_move = self.move
|
||||
if single_move and self.parent and self.analysis_ready and self.parent.analysis_ready:
|
||||
parent_score = self.parent.score
|
||||
score = self.score
|
||||
@@ -139,7 +145,7 @@ class GameNode(SGFNode):
|
||||
|
||||
@property
|
||||
def parent_realized_points_lost(self) -> Optional[float]:
|
||||
single_move = self.single_move
|
||||
single_move = self.move
|
||||
if single_move and self.parent and self.parent.parent and self.analysis_ready and self.parent.parent.analysis_ready:
|
||||
parent_parent_score = self.parent.parent.score
|
||||
score = self.score
|
||||
|
||||
+38
-13
@@ -5,16 +5,19 @@ from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
|
||||
class ParseError(Exception):
|
||||
"""Exception raised on a parse error"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
class Move:
|
||||
GTP_COORD = list("ABCDEFGHJKLMNOPQRSTUVWXYZ") + [xa + c for xa in "AB" for c in "ABCDEFGHJKLMNOPQRSTUVWXYZ"] # kata board size 29 support
|
||||
GTP_COORD = list("ABCDEFGHJKLMNOPQRSTUVWXYZ") + [xa + c for xa in "AB" for c in "ABCDEFGHJKLMNOPQRSTUVWXYZ"] # board size 52+ support
|
||||
PLAYERS = "BW"
|
||||
SGF_COORD = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ".lower()) + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||
SGF_COORD = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ".lower()) + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ") # sgf goes to 52
|
||||
|
||||
@classmethod
|
||||
def from_gtp(cls, gtp_coords, player="B"):
|
||||
"""Initialize a move from GTP coordinates and player"""
|
||||
if "pass" in gtp_coords.lower():
|
||||
return cls(coords=None, player=player)
|
||||
match = re.match(r"([A-Z]+)(\d+)", gtp_coords)
|
||||
@@ -22,11 +25,13 @@ class Move:
|
||||
|
||||
@classmethod
|
||||
def from_sgf(cls, sgf_coords, board_size, player="B"):
|
||||
"""Initialize a move from SGF coordinates and player"""
|
||||
if sgf_coords == "" or Move.SGF_COORD.index(sgf_coords[0]) == board_size[0]: # some servers use [tt] for pass
|
||||
return cls(coords=None, player=player)
|
||||
return cls(coords=(Move.SGF_COORD.index(sgf_coords[0]), board_size[1] - Move.SGF_COORD.index(sgf_coords[1]) - 1), player=player)
|
||||
return cls(coords=(Move.SGF_COORD.index(sgf_coords[0]), board_size[1] - Move.SGF_COORD.index(sgf_coords[1]) - 1), player=player,)
|
||||
|
||||
def __init__(self, coords: Optional[Tuple[int, int]] = None, player: str = "B"):
|
||||
"""Initialize a move from zero-based coordinates and player"""
|
||||
self.player = player
|
||||
self.coords = coords
|
||||
|
||||
@@ -37,21 +42,25 @@ class Move:
|
||||
return self.coords == other.coords and self.player == other.player
|
||||
|
||||
def gtp(self):
|
||||
"""Returns GTP coordinates of the move"""
|
||||
if self.is_pass:
|
||||
return "pass"
|
||||
return Move.GTP_COORD[self.coords[0]] + str(self.coords[1] + 1)
|
||||
|
||||
def sgf(self, board_size):
|
||||
"""Returns SGF coordinates of the move"""
|
||||
if self.is_pass:
|
||||
return ""
|
||||
return f"{Move.SGF_COORD[self.coords[0]]}{Move.SGF_COORD[board_size[1] - self.coords[1] - 1]}"
|
||||
|
||||
@property
|
||||
def is_pass(self):
|
||||
"""Returns True if the move is a pass"""
|
||||
return self.coords is None
|
||||
|
||||
@property
|
||||
def opponent(self):
|
||||
"""Returns the opposing player, i.e. W <-> B"""
|
||||
return "W" if self.player == "B" else "B"
|
||||
|
||||
|
||||
@@ -114,6 +123,7 @@ class SGFNode:
|
||||
|
||||
@property
|
||||
def parent(self) -> Optional["SGFNode"]:
|
||||
"""Returns the parent node"""
|
||||
return self._parent
|
||||
|
||||
@parent.setter
|
||||
@@ -123,13 +133,15 @@ class SGFNode:
|
||||
self._depth = None
|
||||
|
||||
@property
|
||||
def root(self) -> "SGFNode": # cached root property
|
||||
def root(self) -> "SGFNode":
|
||||
"""Returns the root of the tree, cached for speed"""
|
||||
if self._root is None:
|
||||
self._root = self.parent.root if self.parent else self
|
||||
return self._root
|
||||
|
||||
@property
|
||||
def depth(self) -> int: # cached depth property
|
||||
def depth(self) -> int:
|
||||
"""Returns the depth of this node, where root is 0, cached for speed"""
|
||||
if self._depth is None:
|
||||
if self.is_root:
|
||||
self._depth = 0
|
||||
@@ -137,9 +149,9 @@ class SGFNode:
|
||||
self._depth = self.parent.depth + 1
|
||||
return self._depth
|
||||
|
||||
# some root properties are available on any node
|
||||
@property
|
||||
def board_size(self) -> Tuple[int, int]:
|
||||
"""Retrieves the root's SZ property, or 19 if missing. Parses it, and returns board size as a tuple x,y"""
|
||||
size = str(self.root.get_property("SZ", "19"))
|
||||
if ":" in size:
|
||||
x, y = map(int, size.split(":"))
|
||||
@@ -150,15 +162,17 @@ class SGFNode:
|
||||
|
||||
@property
|
||||
def komi(self) -> float:
|
||||
"""Retrieves the root's KM property, or 6.5 if missing"""
|
||||
return float(self.root.get_property("KM", 6.5))
|
||||
|
||||
@property
|
||||
def ruleset(self) -> str:
|
||||
return self.root.get_property("RU")
|
||||
"""Retrieves the root's RU property, or 'japanese' if missing"""
|
||||
return self.root.get_property("RU", "japanese")
|
||||
|
||||
@property
|
||||
def moves(self) -> List[Move]:
|
||||
"""Returns all moves in the node."""
|
||||
"""Returns all moves in the node - typically 'move' will be better."""
|
||||
return [Move.from_sgf(move, player=pl, board_size=self.board_size) for pl in Move.PLAYERS for move in self.get_list_property(pl, [])]
|
||||
|
||||
@property
|
||||
@@ -172,30 +186,35 @@ class SGFNode:
|
||||
return self.placements + self.moves
|
||||
|
||||
@property
|
||||
def single_move(self) -> Optional[Move]:
|
||||
def move(self) -> Optional[Move]:
|
||||
"""Returns the single move for the node if one exists, or None if no moves (or multiple ones) exist."""
|
||||
moves = self.moves
|
||||
if len(moves) == 1: # TODO: and not placements?
|
||||
if len(moves) == 1:
|
||||
return moves[0]
|
||||
|
||||
@property
|
||||
def is_root(self) -> bool:
|
||||
"""Returns true if node is a root"""
|
||||
return self.parent is None
|
||||
|
||||
@property
|
||||
def is_pass(self) -> bool:
|
||||
return not self.placements and self.single_move and self.single_move.is_pass
|
||||
"""Returns true if associated move is pass"""
|
||||
return not self.placements and self.move and self.move.is_pass
|
||||
|
||||
@property
|
||||
def empty(self) -> bool:
|
||||
"""Returns true if node has no children or properties"""
|
||||
return not self.children and not self.properties
|
||||
|
||||
@property
|
||||
def nodes_in_tree(self) -> List:
|
||||
"""Returns all nodes in the tree rooted at this node"""
|
||||
return [self] + sum([c.nodes_in_tree for c in self.children], [])
|
||||
|
||||
@property
|
||||
def nodes_from_root(self) -> List:
|
||||
"""Returns all nodes from the root up to this node, i.e. the moves played in the current branch of the game"""
|
||||
nodes = [self]
|
||||
n = self
|
||||
while not n.is_root:
|
||||
@@ -206,12 +225,13 @@ class SGFNode:
|
||||
def play(self, move) -> "SGFNode":
|
||||
"""Either find an existing child or create a new one with the given move."""
|
||||
for c in self.children:
|
||||
if c.single_move == move:
|
||||
if c.move == move:
|
||||
return c
|
||||
return self.__class__(parent=self, move=move)
|
||||
|
||||
@property
|
||||
def next_player(self):
|
||||
"""Returns player to move"""
|
||||
if "B" in self.properties or "AB" in self.properties: # root or black moved
|
||||
return "W"
|
||||
else:
|
||||
@@ -219,21 +239,26 @@ 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:
|
||||
return "B"
|
||||
else:
|
||||
return "W" # nb root is considered white played if no handicap stones are placed
|
||||
return "W"
|
||||
|
||||
|
||||
class SGF:
|
||||
"""Class used for SGF Nodes, can change this to something that inherits from SGFNode"""
|
||||
|
||||
_NODE_CLASS = SGFNode
|
||||
|
||||
@classmethod
|
||||
def parse(cls, input_str) -> SGFNode:
|
||||
"""Parse a string as SGF."""
|
||||
return cls(input_str).root
|
||||
|
||||
@classmethod
|
||||
def parse_file(cls, filename, encoding=None) -> SGFNode:
|
||||
"""Parse a file as SGF, encoding will be detected if not given."""
|
||||
with open(filename, "rb") as f:
|
||||
bin_contents = f.read()
|
||||
if not encoding:
|
||||
|
||||
+56
-50
@@ -25,7 +25,7 @@ class BadukPanWidget(Widget):
|
||||
self.grid_size = 0
|
||||
self.stone_size = 0
|
||||
self.last_eval = 0
|
||||
self.active_hints = []
|
||||
self.active_pv_moves = []
|
||||
self.show_pv_for = None
|
||||
self.redraw_board_contents_trigger = Clock.create_trigger(self.draw_board_contents)
|
||||
Window.bind(mouse_pos=self.on_mouse_pos)
|
||||
@@ -60,10 +60,10 @@ class BadukPanWidget(Widget):
|
||||
pos = args[1]
|
||||
rel_pos = self.to_widget(*pos) # compensate for relative layout
|
||||
inside = self.collide_point(*rel_pos)
|
||||
if inside and self.active_hints:
|
||||
if inside and self.active_pv_moves:
|
||||
near_move = [
|
||||
move
|
||||
for move in self.active_hints
|
||||
for move in self.active_pv_moves
|
||||
if abs(rel_pos[0] - self.gridpos_x[move[0]]) < self.grid_size / 2 and abs(rel_pos[1] - self.gridpos_y[move[1]]) < self.grid_size / 2
|
||||
]
|
||||
if near_move:
|
||||
@@ -81,7 +81,7 @@ class BadukPanWidget(Widget):
|
||||
xd, xp = self._find_closest(touch.x, self.gridpos_x)
|
||||
yd, yp = self._find_closest(touch.y, self.gridpos_y)
|
||||
|
||||
nodes_here = [node for node in katrain.game.current_node.nodes_from_root if node.single_move and node.single_move.coords == (xp, yp)]
|
||||
nodes_here = [node for node in katrain.game.current_node.nodes_from_root if node.move and node.move.coords == (xp, yp)]
|
||||
if nodes_here and max(yd, xd) < self.grid_size / 2: # load old comment
|
||||
if touch.is_double_tap: # navigate to move
|
||||
katrain.game.set_current_node(nodes_here[-1])
|
||||
@@ -89,8 +89,7 @@ class BadukPanWidget(Widget):
|
||||
else: # load comments
|
||||
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.controls.info.text = nodes_here[-1].comment(sgf=True)
|
||||
katrain.controls.info.text = nodes_here[-1].comment(sgf=True)
|
||||
katrain.controls.info.text = nodes_here[-1].comment(sgf=True, interactive=True)
|
||||
|
||||
self.ghost_stone = None
|
||||
self.draw_hover_contents() # remove ghost
|
||||
@@ -256,23 +255,6 @@ class BadukPanWidget(Widget):
|
||||
with pass_btn.canvas.after:
|
||||
draw_circle((pass_btn.pos[0] + pass_btn.width / 2, pass_btn.pos[1] + pass_btn.height / 2), polsize * pass_btn.height / 2, self.ui_config["policy_color"])
|
||||
|
||||
# children of current moves in undo / review
|
||||
undo_coords = set()
|
||||
if katrain.controls.show_children.active:
|
||||
alpha = self.ui_config["ghost_alpha"]
|
||||
for child_node in current_node.children:
|
||||
points_lost = child_node.points_lost
|
||||
m = child_node.single_move
|
||||
if m and m.coords is not None:
|
||||
undo_coords.add(m.coords)
|
||||
if points_lost is None:
|
||||
evalcol = None
|
||||
else:
|
||||
evalcol = copy.copy(self.eval_color(points_lost))
|
||||
evalcol[3] = alpha
|
||||
scale = self.ui_config["child_scale"]
|
||||
self.draw_stone(m.coords[0], m.coords[1], (*stone_color[m.player][:3], alpha), None, None, evalcol, evalscale=scale, scale=scale)
|
||||
|
||||
# pass circle
|
||||
passed = len(nodes) > 1 and current_node.is_pass
|
||||
if passed:
|
||||
@@ -296,49 +278,73 @@ class BadukPanWidget(Widget):
|
||||
current_node = katrain.game.current_node
|
||||
player, next_player = current_node.player, current_node.next_player
|
||||
stone_color = self.ui_config["stones"]
|
||||
lock_ai = self.trainer_config.get("lock_ai", False) and katrain.controls.teaching_mode_enabled()
|
||||
teaching = katrain.controls.teaching_mode_enabled()
|
||||
lock_ai = self.trainer_config.get("lock_ai", False) and teaching
|
||||
|
||||
self.canvas.after.clear()
|
||||
with self.canvas.after:
|
||||
self.active_pv_moves = []
|
||||
|
||||
# children of current moves in undo / review
|
||||
if katrain.controls.show_children.active:
|
||||
alpha = self.ui_config["ghost_alpha"]
|
||||
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
|
||||
self.active_pv_moves.append(move.coords)
|
||||
if teaching and move.coords == self.show_pv_for and child_node.analysis_ready:
|
||||
self.draw_pv(katrain, [move.gtp()] + child_node.candidate_moves[0].get("pv", []), [next_player, player])
|
||||
else:
|
||||
scale = self.ui_config["child_scale"]
|
||||
self.draw_stone(move.coords[0], move.coords[1], (*stone_color[move.player][:3], alpha), None, None, evalcol, evalscale=scale, scale=scale)
|
||||
|
||||
# hints or PV
|
||||
self.active_hints = []
|
||||
if katrain.controls.hints.active and not game_ended and not lock_ai:
|
||||
hint_moves = current_node.candidate_moves
|
||||
for i, d in enumerate(hint_moves):
|
||||
move = Move.from_gtp(d["move"])
|
||||
if move.coords is not None: # and move.coords not in undo_coords:
|
||||
for i, move_dict in enumerate(hint_moves):
|
||||
move = Move.from_gtp(move_dict["move"])
|
||||
if move.coords is not None:
|
||||
alpha, scale = self.ui_config["ghost_alpha"], 1.0
|
||||
if i == 0:
|
||||
alpha += self.ui_config["top_move_x_alpha"]
|
||||
elif d["visits"] < self.ui_config["visit_frac_small"] * hint_moves[0]["visits"]:
|
||||
elif move_dict["visits"] < self.ui_config["visit_frac_small"] * hint_moves[0]["visits"]:
|
||||
scale = 0.8
|
||||
self.active_hints.append(move.coords)
|
||||
|
||||
if move.coords == self.show_pv_for: # TODO: overlapping moves
|
||||
pv = d.get("pv", [move.gtp()]) # if empty, show current move at least
|
||||
for i, gtpmove in enumerate(pv):
|
||||
move_player = [next_player, player][i % 2]
|
||||
opp_player = [next_player, player][1 - i % 2]
|
||||
coords = Move.from_gtp(gtpmove).coords
|
||||
if coords is None: # tee-hee
|
||||
sizefac = katrain.board_controls.pass_btn.size[1] / 2 / self.stone_size
|
||||
board_coords = [
|
||||
katrain.board_controls.pass_btn.pos[0] + katrain.board_controls.pass_btn.size[0] + self.stone_size * sizefac,
|
||||
katrain.board_controls.pass_btn.pos[1] + katrain.board_controls.pass_btn.size[1] / 2,
|
||||
]
|
||||
else:
|
||||
board_coords = (self.gridpos_x[coords[0]], self.gridpos_y[coords[1]])
|
||||
sizefac = 0.95
|
||||
draw_circle(board_coords, self.stone_size * sizefac, stone_color[move_player])
|
||||
Color(*stone_color[opp_player])
|
||||
draw_text(pos=board_coords, text=str(i + 1), font_size=sizefac * self.grid_size / 1.45)
|
||||
self.active_pv_moves.append(move.coords)
|
||||
if move.coords == self.show_pv_for:
|
||||
self.draw_pv(katrain, move_dict.get("pv", [move.gtp()]), [next_player, player]) # if empty, show current move at least
|
||||
elif not self.show_pv_for:
|
||||
self.draw_stone(move.coords[0], move.coords[1], [*self.eval_color(d["pointsLost"])[:3], alpha], scale=scale)
|
||||
self.draw_stone(move.coords[0], move.coords[1], [*self.eval_color(move_dict["pointsLost"])[:3], alpha], scale=scale)
|
||||
|
||||
# hover next move ghost stone
|
||||
if self.ghost_stone:
|
||||
self.draw_stone(*self.ghost_stone, (*stone_color[next_player], ghost_alpha))
|
||||
|
||||
def draw_pv(self, katrain, pv, next_last_player):
|
||||
# TODO: overlapping moves
|
||||
stone_color = self.ui_config["stones"]
|
||||
for i, gtpmove in enumerate(pv):
|
||||
move_player = next_last_player[i % 2]
|
||||
opp_player = next_last_player[1 - i % 2]
|
||||
coords = Move.from_gtp(gtpmove).coords
|
||||
if coords is None: # tee-hee
|
||||
sizefac = katrain.board_controls.pass_btn.size[1] / 2 / self.stone_size
|
||||
board_coords = [
|
||||
katrain.board_controls.pass_btn.pos[0] + katrain.board_controls.pass_btn.size[0] + self.stone_size * sizefac,
|
||||
katrain.board_controls.pass_btn.pos[1] + katrain.board_controls.pass_btn.size[1] / 2,
|
||||
]
|
||||
else:
|
||||
board_coords = (self.gridpos_x[coords[0]], self.gridpos_y[coords[1]])
|
||||
sizefac = 0.95
|
||||
draw_circle(board_coords, self.stone_size * sizefac, stone_color[move_player])
|
||||
Color(*stone_color[opp_player])
|
||||
draw_text(pos=board_coords, text=str(i + 1), font_size=sizefac * self.grid_size / 1.45)
|
||||
|
||||
|
||||
class BadukPanControls(BoxLayout):
|
||||
pass
|
||||
+3
-3
@@ -54,7 +54,7 @@ class Controls(BoxLayout):
|
||||
info = ""
|
||||
|
||||
if current_node:
|
||||
move = current_node.single_move
|
||||
move = current_node.move
|
||||
both_players_are_robots = "ai" in self.player_mode(current_node.player) and "ai" in self.player_mode(current_node.next_player)
|
||||
next_player_is_human_or_both_robots = current_node.player and ("ai" not in self.player_mode(current_node.player) or both_players_are_robots)
|
||||
current_player_is_ai_playing_human = current_node.player and "ai" in self.player_mode(current_node.player) and "ai" not in self.player_mode(current_node.next_player)
|
||||
@@ -73,10 +73,10 @@ class Controls(BoxLayout):
|
||||
elif not current_player_is_ai_playing_human:
|
||||
self.score_change.label = f"Points lost"
|
||||
self.score_change.text = ""
|
||||
elif current_player_is_ai_playing_human and current_node.parent and current_node.parent.single_move:
|
||||
elif current_player_is_ai_playing_human and current_node.parent and current_node.parent.move:
|
||||
points_lost = current_node.parent.points_lost
|
||||
self.score_change.label = f"Points lost" if points_lost and points_lost > 0 else f"Points gained"
|
||||
self.score_change.text = f"{current_node.parent.single_move.player}: {abs(points_lost):.1f}" if points_lost else "..."
|
||||
self.score_change.text = f"{current_node.parent.move.player}: {abs(points_lost):.1f}" if points_lost else "..."
|
||||
elif both_players_are_robots and current_node.parent and current_node.parent.analysis_ready:
|
||||
self.score.text = current_node.parent.format_score()
|
||||
self.win_rate.text = current_node.parent.format_win_rate()
|
||||
|
||||
@@ -80,9 +80,14 @@ class BackgroundLabel(Label, BackgroundColor):
|
||||
|
||||
|
||||
class ScrollableLabel(ScrollView):
|
||||
__events__ = ["on_ref_press"]
|
||||
text = StringProperty("")
|
||||
markup = BooleanProperty(False)
|
||||
border_color = ListProperty([0, 0, 0, 1])
|
||||
|
||||
def on_ref_press(self, ref):
|
||||
pass
|
||||
|
||||
|
||||
class StyledButton(Button, ToolTipBehavior):
|
||||
button_color = ListProperty([])
|
||||
|
||||
@@ -402,6 +402,7 @@
|
||||
width:1
|
||||
Label:
|
||||
id: label
|
||||
markup: root.markup
|
||||
padding: 5, 5
|
||||
font_size: dp(18)
|
||||
color: BLACK
|
||||
@@ -409,6 +410,7 @@
|
||||
height: self.texture_size[1]
|
||||
text_size: self.width, None
|
||||
text: root.text
|
||||
on_ref_press: root.dispatch('on_ref_press',args[1])
|
||||
|
||||
<Controls>:
|
||||
orientation: 'vertical'
|
||||
@@ -626,6 +628,8 @@
|
||||
height: 0.00001 if not self.text else min(0.66*self.parent.height,self.label.texture_size[1])
|
||||
ScrollableLabel:
|
||||
id: info
|
||||
# markup: True
|
||||
# on_ref_press: root.katrain.board_gui.show_pv_from_label(args[1])
|
||||
size_hint: 1, None
|
||||
height: self.parent.height - status_label.height - 1
|
||||
BoxLayout:
|
||||
|
||||
Reference in new issue
Block a user