Merge pull request #33 from sanderland/animatepv
pv animations, tune settings
This commit is contained in:
13 files changed
+319
-93
No files matched your search
@@ -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
|
||||
|
||||
|
||||
@@ -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.
@@ -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
@@ -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
@@ -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()}"
|
||||
|
||||
@@ -128,6 +128,7 @@
|
||||
}
|
||||
},
|
||||
"board_ui": {
|
||||
"anim_pv_time": 0.5,
|
||||
"engine_down_col": [
|
||||
0.8,
|
||||
0,
|
||||
|
||||
+5
-2
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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:
|
||||
|
||||
Reference in new issue
Block a user