Merge pull request #33 from sanderland/animatepv

pv animations, tune settings
This commit is contained in:
sanderland authored and GitHub committed 2020-05-11 17:47:06 +02:00
commit c940e5201f
13 files changed
+319 -93

No files matched your search

+2 -2
View File
@@ -62,9 +62,9 @@ numSearchThreads = 6
# Maximum number of positions to send to GPU at once.
nnMaxBatchSize = 64
# Cache up to 2 ** this many neural net evaluations in case of transpositions in the tree.
nnCacheSizePowerOfTwo = 21
nnCacheSizePowerOfTwo = 19
# Size of mutex pool for nnCache is 2 ** this
nnMutexPoolSizePowerOfTwo = 17
nnMutexPoolSizePowerOfTwo = 16
# Randomize board orientation when running neural net evals?
nnRandomize = true
+1 -1
View File
@@ -181,5 +181,5 @@ If you ever need to reset to the original settings, simply re-download the `conf
* 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), [KakaoTalk](https://open.kakao.com/o/gTsMJCac) or [Reddit](http://reddit.com/u/sanderbaduk) to give feedback, or simply show your appreciation.
* You can also 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 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.
Binary file not shown.
+1 -1
View File
@@ -18,7 +18,7 @@ ENGINE_SETTINGS = {
"config": "KataGo/analysis_config.cfg",
"max_visits": 50,
"max_time": 1.0,
"enable_ownership": False,
"_enable_ownership": False,
"threads": 32,
}
+193
View File
@@ -0,0 +1,193 @@
# Example config for C++ (non-python) gtp bot
# SEE NOTES ABOUT PERFORMANCE AND MEMORY USAGE IN gtp_example.cfg
# Logs------------------------------------------------------------------------------------
# Where to output log?
logFile = gtp.log
# Controls the number of moves after the first move in a variation.
# analysisPVLen = 15
# Report winrates for analysis as (BLACK|WHITE|SIDETOMOVE).
reportAnalysisWinratesAs = BLACK
# Bot behavior---------------------------------------------------------------------------------------
# Handicap -------------
# Assume that if black makes many moves in a row right at the start of the game, then the game is a handicap game.
# This is necessary on some servers and for some GUIs and also when initializing from many SGF files, which may
# set up a handicap games using repeated GTP "play" commands for black rather than GTP "place_free_handicap" commands.
# However, it may also lead to incorrect undersanding of komi if whiteBonusPerHandicapStone = 1 and a server does NOT
# have such a practice.
# Defaults to true! Uncomment and set to false to disable this behavior.
# assumeMultipleStartingBlackMovesAreHandicap = true
# Passing and cleanup -------------
# Make the bot never assume that its pass will end the game, even if passing would end and "win" under Tromp-Taylor rules.
# Usually this is a good idea when using it for analysis or playing on servers where scoring may be implemented non-tromp-taylorly.
# Defaults to true! Uncomment and set to false to disable this.
conservativePass = true
# When using territory scoring, self-play games continue beyond two passes with special cleanup
# rules that may be confusing for human players. This option prevents the special cleanup phases from being
# reachable when using the bot for GTP play.
# Defaults to true! Uncomment and set to false if you want KataGo to be able to enter special cleanup.
# For example, if you are testing it against itself, or against another bot that has precisely implemented the rules
# documented at https://lightvector.github.io/KataGo/rules.html
# preventCleanupPhase = true
# Search limits-----------------------------------------------------------------------------------
# By default, if NOT specified in an individual request, limit maximum number of root visits per search to this much
maxVisits = 500
# If provided, cap search time at this many seconds
# maxTime = 60
# Number of threads to use in each search in parallel for any SINGLE position.
# NOTE: Analysis engine can specify number of POSITIONS to be able to search in parallel via command line argument
# so this number does not necessarily need to be larger than 1, although you can still set it larger if you prefer
# to analyze fewer positions in parallel but spend more threads on each position.
# Generally, having more threads on a single position will worsen the quality of search slightly, holding fixed the
# number of visits, and thread contention will reduce efficiency, so cross-position parallelization is preferable
# to numSearchThreads, but numSearchThreads is preferable if you want to reduce latency, and have individual
# searches complete faster by doing fewer of them at a time.
numSearchThreads = 2
# GPU Settings-------------------------------------------------------------------------------
# Maximum number of positions to send to GPU at once.
nnMaxBatchSize = 32
# Cache up to 2 ** this many neural net evaluations in case of transpositions in the tree.
nnCacheSizePowerOfTwo = 14
# Size of mutex pool for nnCache is 2 ** this
nnMutexPoolSizePowerOfTwo = 14
# Randomize board orientation when running neural net evals?
nnRandomize = true
# TO USE MULTIPLE GPUS:
# Set this to the number of GPUs you have and/or would like to use...
# AND if it is more than 1, uncomment the appropriate CUDA or OpenCL section below.
# numNNServerThreadsPerModel = 1
# CUDA GPU settings--------------------------------------
# These only apply when using the CUDA version of KataGo.
# IF USING ONE GPU: optionally uncomment and change this if the GPU you want to use turns out to be not device 0
# cudaDeviceToUse = 0
# IF USING TWO GPUS: Uncomment these two lines (AND set numNNServerThreadsPerModel above):
# cudaDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0
# cudaDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1
# IF USING THREE GPUS: Uncomment these three lines (AND set numNNServerThreadsPerModel above):
# cudaDeviceToUseThread0 = 0 # change this if the first GPU you want to use turns out to be not device 0
# cudaDeviceToUseThread1 = 1 # change this if the second GPU you want to use turns out to be not device 1
# cudaDeviceToUseThread2 = 2 # change this if the third GPU you want to use turns out to be not device 2
# You can probably guess the pattern if you have four, five, etc. GPUs.
# KataGo will automatically use FP16 or not based on the compute capability of your NVIDIA GPU. If you
# want to try to force a particular behavior though you can uncomment these lines and change them
# to "true" or "false". E.g. it's using FP16 but on your card that's giving an error, or it's not using
# FP16 but you think it should.
# cudaUseFP16 = auto
# cudaUseNHWC = auto
# OpenCL GPU settings--------------------------------------
# These only apply when using the OpenCL version of KataGo.
# Uncomment to tune OpenCL for every board size separately, rather than only the largest possible size
# openclReTunePerBoardSize = true
# IF USING ONE GPU: optionally uncomment and change this if the best device to use is guessed incorrectly.
# The default behavior tries to guess the 'best' GPU or device on your system to use, usually it will be a good guess.
# openclDeviceToUse = 0
# IF USING TWO GPUS: Uncomment these two lines and replace X and Y with the device ids of the devices you want to use.
# It might NOT be 0 and 1, some computers will have many OpenCL devices. You can see what the devices are when
# KataGo starts up - it should print or log all the devices it finds.
# (AND also set numNNServerThreadsPerModel above)
# openclDeviceToUseThread0 = X
# openclDeviceToUseThread1 = Y
# IF USING THREE GPUS: Uncomment these three lines and replace X and Y and Z with the device ids of the devices you want to use.
# It might NOT be 0 and 1 and 2, some computers will have many OpenCL devices. You can see what the devices are when
# KataGo starts up - it should print or log all the devices it finds.
# (AND also set numNNServerThreadsPerModel above)
# openclDeviceToUseThread0 = X
# openclDeviceToUseThread1 = Y
# openclDeviceToUseThread2 = Z
# You can probably guess the pattern if you have four, five, etc. GPUs.
# Root move selection and biases------------------------------------------------------------------------------
# Uncomment and edit any of the below values to change them from their default.
# Not all of these parameters are applicable to analysis, some are only used for actual play
# Temperature for the early game, randomize between chosen moves with this temperature
# chosenMoveTemperatureEarly = 0.5
# Decay temperature for the early game by 0.5 every this many moves, scaled with board size.
# chosenMoveTemperatureHalflife = 19
# At the end of search after the early game, randomize between chosen moves with this temperature
# chosenMoveTemperature = 0.10
# Subtract this many visits from each move prior to applying chosenMoveTemperature
# (unless all moves have too few visits) to downweight unlikely moves
# chosenMoveSubtract = 0
# The same as chosenMoveSubtract but only prunes moves that fall below the threshold, does not affect moves above
# chosenMovePrune = 1
# Number of symmetries to sample (WITH replacement) and average at the root
# rootNumSymmetriesToSample = 1
# Using LCB for move selection?
# useLcbForSelection = true
# How many stdevs a move needs to be better than another for LCB selection
# lcbStdevs = 5.0
# Only use LCB override when a move has this proportion of visits as the top move
# minVisitPropForLCB = 0.15
# Internal params------------------------------------------------------------------------------
# Uncomment and edit any of the below values to change them from their default.
# Scales the utility of winning/losing
# winLossUtilityFactor = 1.0
# Scales the utility for trying to maximize score
# staticScoreUtilityFactor = 0.10
# dynamicScoreUtilityFactor = 0.30
# Adjust dynamic score center this proportion of the way towards zero, capped at a reasonable amount.
# dynamicScoreCenterZeroWeight = 0.20
# dynamicScoreCenterScale = 0.75
# The utility of getting a "no result" due to triple ko or other long cycle in non-superko rulesets (-1 to 1)
# noResultUtilityForWhite = 0.0
# The number of wins that a draw counts as, for white. (0 to 1)
# drawEquivalentWinsForWhite = 0.5
# Exploration constant for mcts
# cpuctExploration = 0.9
# cpuctExplorationLog = 0.4
# FPU reduction constant for mcts
# fpuReductionMax = 0.2
# rootFpuReductionMax = 0.1
# Use parent average value for fpu base point instead of point value net estimate
# fpuUseParentAverage = true
# Amount to apply a downweighting of children with very bad values relative to good ones
# valueWeightExponent = 0.5
# Slight incentive for the bot to behave human-like with regard to passing at the end, filling the dame,
# not wasting time playing in its own territory, etc, and not play moves that are equivalent in terms of
# points but a bit more unfriendly to humans.
# rootEndingBonusPoints = 0.5
# Make the bot prune useless moves that are just prolonging the game to avoid losing yet
# rootPruneUselessMoves = true
# How big to make the mutex pool for search synchronization
# mutexPoolSize = 8192
# How many virtual losses to add when a thread descends through a node
# numVirtualLossesPerThread = 1
+24 -17
View File
@@ -16,7 +16,6 @@ import json
DB_FILENAME = "bots/ai_performance.pickle"
class Logger:
def log(self, msg, level):
if level <= OUTPUT_INFO:
@@ -36,12 +35,12 @@ class AI:
DEFAULT_ENGINE_SETTINGS = {
"katago": "KataGo/katago",
"model": "KataGo/models/b15-1.3.2.txt.gz",
"config": "KataGo/analysis_config.cfg",
"config": "bots/lowmem.cfg",
"max_visits": 1,
"max_time": 300.0,
"_enable_ownership": False,
}
NUM_THREADS = 32
NUM_THREADS = 8
IGNORE_SETTINGS_IN_TAG = {"threads", "_enable_ownership", "katago"} # katago for switching from/to bs version
ENGINES = []
LOCK = threading.Lock()
@@ -75,9 +74,14 @@ class AI:
try:
with open(DB_FILENAME, "rb") as f:
ai_database, all_results = pickle.load(f)
for ai in ai_database:
ai.fix_settings() # update as required
ai_database_loaded, all_results = pickle.load(f)
ai_database = []
for ai in ai_database_loaded:
try:
ai.fix_settings() # update as required
ai_database.append(ai)
except:
print("Error loading AI", ai.strategy)
except FileNotFoundError:
ai_database = []
all_results = []
@@ -96,25 +100,27 @@ def retrieve_ais(selected_ais):
test_ais = [
# AI("Jigo", {}, {"max_visits": 100}),
AI("Policy", {}, {"model": "my/model.bin.gz"}),
AI("Policy", {}, {"model": "KataGo/models/b10-1.3.txt.gz"}),
AI("Default", {}, {"model": "my/6b.bin.gz", "max_visits": 500}),
AI("Default", {}, {"model": "my/6bf104.txt.gz", "max_visits": 500}),
AI("Default", {}, {"model": "my/6b104-223.txt.gz", "max_visits": 500}),
AI("Default", {}, {"model": "my/6b104-423.txt.gz", "max_visits": 500}),
AI("Default", {}, {"model": "KataGo/models/b10-1.3.txt.gz","max_visits": 500}),
AI("Policy", {}),
AI("P:Local", {}),
AI("P:Pick", {}),
AI("P:Noise", {}),
AI("P:Tenuki", {}),
AI("P:Local", {}),
AI("P:Influence", {}),
AI("P:Territory", {}),
AI("P:Weighted", {}),
# AI("P:Pick", {}),
# AI("ScoreLoss", {"max_visits": 500}),
# AI("P:Tenuki", {}),
# AI("P:Local", {}),
# AI("P:Influence", {}),
# AI("P:Territory", {}),
]
for ai in test_ais:
add_ai(ai)
N_GAMES = 5
N_GAMES = 1
BOARDSIZE = 19
ais_to_test = retrieve_ais(test_ais)
@@ -133,7 +139,8 @@ def play_games(black: AI, white: AI):
start_time = time.time()
while not game.ended:
p = game.current_node.next_player
move = ai_move(game, players[p].strategy, players[p].ai_settings)
move, node = ai_move(game, players[p].strategy, players[p].ai_settings)
print(tag,move)
while not game.current_node.analysis_ready:
time.sleep(0.001)
game.game_id += f"_{game.current_node.format_score()}"
+1
View File
@@ -128,6 +128,7 @@
}
},
"board_ui": {
"anim_pv_time": 0.5,
"engine_down_col": [
0.8,
0,
+5 -2
View File
@@ -6,7 +6,7 @@ import threading
import time
from typing import Callable, Optional
from core.common import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_KATAGO_STDERR
from core.common import OUTPUT_DEBUG, OUTPUT_ERROR, OUTPUT_EXTRA_DEBUG, OUTPUT_KATAGO_STDERR, OUTPUT_INFO
from core.game_node import GameNode
@@ -103,13 +103,16 @@ class KataGoEngine:
continue
query_id = analysis["id"]
callback, error_callback, start_time, next_move = self.queries[query_id]
del self.queries[query_id]
if "error" in analysis:
del self.queries[query_id]
if error_callback:
error_callback(analysis)
elif not (next_move and "Illegal move" in analysis["error"]): # sweep
self.katrain.log(f"{analysis} received from KataGo", OUTPUT_ERROR)
elif "warning" in analysis:
self.katrain.log(f"{analysis} received from KataGo", OUTPUT_DEBUG)
else:
del self.queries[query_id]
time_taken = time.time() - start_time
self.katrain.log(f"[{time_taken:.1f}][{analysis['id']}] KataGo Analysis Received: {analysis.keys()}", OUTPUT_DEBUG)
self.katrain.log(line, OUTPUT_EXTRA_DEBUG)
+4 -4
View File
@@ -296,16 +296,16 @@ class Game:
while i < len(thresholds) and points_lost < thresholds[i]:
i += 1
num_undos = num_undo_prompts[i] if i < len(num_undo_prompts) else 0
xmsg = ". Please try again."
xmsg = ". Please try once more"
if num_undos == 0:
undo = False
elif num_undos < 1: # probability
undo = int(node.undo_threshold < num_undos) and len(node.parent.children) == 1
xmsg = f" (with {num_undos:.0%} probability at this level of mistake)" + xmsg
else:
undo = len(node.parent.children) <= num_undos
if len(node.parent.children) == num_undos:
xmsg = xmsg[:-1] + ", but note that this is your last try at this level of mistake."
if len(node.parent.children) < num_undos:
xmsg = ". Please try again (multiple tries remaining)."
node.auto_undo = undo
if undo:
self.undo(1)
+2 -2
View File
@@ -33,7 +33,7 @@ class GameNode(SGFNode):
properties["SQ"] = best_sq
if top_x and "MA" not in properties:
properties["MA"] = [top_x]
comment = self.comment(sgf=True)
comment = self.comment(sgf=True, interactive=False)
if comment:
properties["C"] = [properties.get("C", "") + comment]
if self.is_root:
@@ -92,7 +92,7 @@ class GameNode(SGFNode):
pvtext = f"[u][ref={pvtext}][color=#334466]{pvtext}[/color][/ref][/u]"
return pvtext
def comment(self, sgf=False, teach=False, hints=False, interactive=False):
def comment(self, sgf=False, teach=False, hints=False, interactive=True):
single_move = self.move
if not self.parent or not single_move: # root
return ""
+80 -61
View File
@@ -1,5 +1,6 @@
import copy
import math
import time
from kivy.clock import Clock
from kivy.graphics.context_instructions import Color
@@ -25,18 +26,17 @@ class BadukPanWidget(Widget):
self.grid_size = 0
self.stone_size = 0
self.active_pv_moves = []
self.show_pv_for = None
self.animating_pv = None
self.redraw_board_contents_trigger = Clock.create_trigger(self.draw_board_contents)
self.last_mouse_pos = (0, 0)
Window.bind(mouse_pos=self.on_mouse_pos)
Clock.schedule_interval(self.animate_pv, 0.25)
# stone placement functions
def _find_closest(self, pos, gridpos):
return sorted([(abs(p - pos), i) for i, p in enumerate(gridpos)])[0]
def on_touch_down(self, touch, mouse_move=False):
if touch.button != "left":
return
def check_next_move_ghost(self, touch):
if not self.gridpos_x:
return
xd, xp = self._find_closest(touch.x, self.gridpos_x)
@@ -46,33 +46,41 @@ class BadukPanWidget(Widget):
self.ghost_stone = (xp, yp)
else:
self.ghost_stone = None
cn = self.katrain.game.current_node
if prev_ghost != self.ghost_stone:
self.draw_hover_contents()
if not mouse_move and cn.move and (xp, yp) == cn.move.coords and cn.parent and cn.parent.analysis_ready:
self.show_pv_for_last_move(cn.parent.candidate_moves[0]["pv"])
def on_touch_down(self, touch):
self.animating_pv = None # any click kills PV from label/move
self.draw_hover_contents()
if touch.button != "left":
return
self.check_next_move_ghost(touch)
def on_touch_move(self, touch): # on_motion on_touch_move
return self.on_touch_down(touch, mouse_move=True)
def on_touch_move(self, touch):
return self.check_next_move_ghost(touch)
def on_mouse_pos(self, *args): # https://gist.github.com/opqopq/15c707dc4cffc2b6455f
last_show_pv = self.show_pv_for
self.show_pv_for = None
if self.get_root_window(): # do proceed if I'm not displayed <=> If have no parent
if self.get_root_window(): # don't proceed if I'm not displayed <=> If have no parent
pos = args[1]
rel_pos = self.to_widget(*pos) # compensate for relative layout
inside = self.collide_point(*rel_pos)
if inside and self.active_pv_moves:
near_move = [
move
for move in self.active_pv_moves
(move, pv)
for move, pv 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:
self.show_pv_for = near_move[0]
if self.show_pv_for != last_show_pv:
self.draw_hover_contents()
self.set_animating_pv(near_move[0][1], self.katrain.game.current_node)
else:
self.animating_pv = None
self.draw_hover_contents()
if inside and self.animating_pv is not None:
d_sq = (pos[0] - self.animating_pv[3][0]) ** 2 + (pos[1] - self.animating_pv[3][1])
if d_sq > 2 * self.stone_size ** 2: # move too far from where it was activated
self.animating_pv = None
self.draw_hover_contents()
self.last_mouse_pos = pos
def on_touch_up(self, touch):
if touch.button != "left" or not self.gridpos_x:
@@ -89,10 +97,13 @@ class BadukPanWidget(Widget):
if touch.is_double_tap: # navigate to move
katrain.game.set_current_node(nodes_here[-1])
katrain.update_state()
else: # load comments
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.controls.info.text = nodes_here[-1].comment(sgf=True, interactive=(nodes_here[-1] == katrain.game.current_node))
katrain.controls.info.text = nodes_here[-1].comment(sgf=True)
katrain.controls.active_comment_node = nodes_here[-1].parent
if nodes_here[-1].parent.analysis_ready:
self.set_animating_pv(nodes_here[-1].parent.candidate_moves[0]["pv"], nodes_here[-1].parent)
self.ghost_stone = None
self.draw_hover_contents() # remove ghost
@@ -300,12 +311,10 @@ class BadukPanWidget(Widget):
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)
if teaching and child_node.auto_undo and current_node.analysis_ready:
self.active_pv_moves.append((move.coords, current_node.candidate_moves[0]["pv"]))
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
if katrain.controls.hints.active and not game_ended and not lock_ai:
@@ -318,48 +327,58 @@ class BadukPanWidget(Widget):
alpha += self.ui_config["top_move_x_alpha"]
elif move_dict["visits"] < self.ui_config["visit_frac_small"] * hint_moves[0]["visits"]:
scale = 0.8
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(move_dict["pointsLost"])[:3], alpha], scale=scale)
self.active_pv_moves.append((move.coords, move_dict["pv"]))
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)
def animate_pv(self, _dt):
if not self.animating_pv:
return
pv, node, start_time, _ = self.animating_pv
delay = self.ui_config.get("anim_pv_time", 1)
up_to_move = (time.time() - start_time) / delay
self.draw_hover_contents()
self.draw_pv(pv, node, up_to_move)
def show_pv_for_last_move(self, pv):
cn = self.katrain.game.current_node
if isinstance(pv, str):
next_player = pv[0]
pv = pv[1:].split(" ")
else:
next_player = cn.player
next_last_player = [next_player, "W" if next_player == "B" else "B"]
def draw_pv(self, pv, node, up_to_move):
katrain = self.katrain
next_last_player = [node.next_player, node.player]
stone_color = self.ui_config["stones"]
cn = katrain.game.current_node
with self.canvas.after:
if cn.player == next_player and cn.move and not cn.move.is_pass:
self.draw_stone(*cn.move.coords, [0.85, 0.68, 0.40, 0.66])
self.draw_pv(self.katrain, pv, next_last_player)
if node != cn:
hide_node = cn
while hide_node and hide_node != node:
self.draw_stone(*hide_node.move.coords, [0.85, 0.68, 0.40, 0.66]) # board coloured dot
hide_node = hide_node.parent
for i, gtpmove in enumerate(pv):
if i > up_to_move:
return
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)
def set_animating_pv(self, pv, node):
if node is not None and (not self.animating_pv or not (self.animating_pv[0] == pv and self.animating_pv[1] == node)):
self.animating_pv = (pv, node, time.time(), self.last_mouse_pos)
def show_pv_from_comments(self, pv_str):
self.set_animating_pv(pv_str[1:].split(" "), self.katrain.controls.active_comment_node)
class BadukPanControls(BoxLayout):
+5 -2
View File
@@ -11,6 +11,7 @@ class Controls(BoxLayout):
self.status_node = None
self.ai_settings_popup = None
self.teacher_settings_popup = None
self.active_comment_node = None
def set_status(self, msg, at_node=None):
self.status = msg
@@ -59,9 +60,11 @@ class Controls(BoxLayout):
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)
if next_player_is_human_or_both_robots and not current_node.is_root and move:
info += current_node.comment(teach="undo" in self.player_mode(current_node.player), hints=self.hints.active, interactive=True)
info += current_node.comment(teach="undo" in self.player_mode(current_node.player), hints=self.hints.active)
self.active_comment_node = current_node
elif current_player_is_ai_playing_human and current_node.parent:
info += current_node.parent.comment(teach="undo" in self.player_mode(current_node.next_player), hints=self.hints.active, interactive=False)
info += current_node.parent.comment(teach="undo" in self.player_mode(current_node.next_player), hints=self.hints.active)
self.active_comment_node = current_node.parent
if current_node.analysis_ready:
self.score.text = current_node.format_score()
+1 -1
View File
@@ -621,7 +621,7 @@
ScrollableLabel:
id: info
markup: True
on_ref_press: root.katrain.board_gui.show_pv_for_last_move(args[1])
on_ref_press: root.katrain.board_gui.show_pv_from_comments(args[1])
size_hint: 1, None
height: self.parent.height - status_label.height - 1
BoxLayout: