working version with json analysis engine

This commit is contained in:
Sander Land committed 2020-01-25 16:51:27 +01:00
1 parent ff1db55a97
commit da5d99194b
9 files changed
+72 -707

No files matched your search

-305
View File
@@ -1,305 +0,0 @@
# Example config for C++ (non-python) gtp bot
# RUNNING ON AN ONLINE SERVER OR IN A REAL TOURNAMENT OR MATCH:
# If you plan to do so, you may want to read through the "Rules" section
# below carefully for proper handling of komi and handicap games and end-of-game cleanup
# and various other details.
# NOTES ABOUT PERFORMANCE AND MEMORY USAGE:
# You will likely want to tune one or more the following:
#
# numSearchThreads:
# The number of CPU threads to use. If your GPU is powerful, it can actually be much higher than
# the number of cores on your processor because you will need many threads to feed large enough
# batches to make good use of the GPU.
#
# The "./katago benchmark" command can help you tune this parameter, as well as to test out the effect
# of changes to any of the other parameters below!
#
# nnMaxBatchSize:
# The maximum GPU batch size. Should often be at least as large as numSearchThreads.
# Larger won't do anything, but also won't hurt except use a little bit more GPU memory.
# Smaller can be fine if you have more than one GPU, since the GPUs will be sharing the work
# of servicing the CPU threads.
#
# cudaUseFP16 and cudaUseNHWC:
# These have a good chance of improving peformance at larger threads/batch sizes if
# you are using the CUDA implementation with an NVIDIA GPU with FP16 tensor cores.
#
# nnCacheSizePowerOfTwo:
# This controls the NN Cache size, which is the primary RAM/memory use.
# Increase this if you don't mind the memory use and want better performance for searches with
# tens of thousands of visits or more. Decrease this if you want to limit memory usage.
#
# If you're someone who is happy to do a bit of math - each neural net entry takes very
# approximately 1.5KB, except when using whole-board ownership/territory visualizations, each
# entry will take very approximately 3KB. The number of entries is (2 ** nnCacheSizePowerOfTwo),
# for example 2 ** 18 = 262144.
#
# OTHER NOTES:
# If you have more than one GPU, take a look at "OpenCL GPU settings" or "CUDA GPU settings" below.
#
# If using OpenCL, you will want to verify that KataGo is picking up the correct device!
# (e.g. some systems may have both an Intel CPU OpenCL and GPU OpenCL, if KataGo appears to pick
# the wrong one, you correct this by specifying "openclGpuToUse" below).
#
# You may also want to adjust "maxVisits", "ponderingEnabled", "resignThreshold", and possibly
# other parameters depending on your intended usage.
# Logs------------------------------------------------------------------------------------
# Where to output log?
logFile = gtp.log
# Logging options
logAllGTPCommunication = true
logSearchInfo = true
logToStderr = false
# KataGo will display some info to stderr on GTP startup
# Uncomment this to suppress that and remain silent
# startupPrintMessageToStderr = false
# Chat some stuff to stderr, for use in things like malkovich chat to OGS.
# ogsChatToStderr = true
# Configure the maximum length of analysis printed out by lz-analyze and other places.
# Controls the number of moves after the first move in a variation.
# analysisPVLen = 9
# Report winrates for chat and analysis as (BLACK|WHITE|SIDETOMOVE).
# Default is SIDETOMOVE, which is what tools that use LZ probably also expect
# reportAnalysisWinratesAs = SIDETOMOVE
# Default rules------------------------------------------------------------------------------------
# See https://lightvector.github.io/KataGo/rules.html for a description of the rules.
# These rules are defaults and can be changed mid-run by several custom GTP commands.
# See https://github.com/lightvector/KataGo/blob/master/docs/GTP_Extensions.md for those commands.
# koRule = SIMPLE # Simple ko rules (triple ko = no result)
koRule = POSITIONAL # Positional superko
# koRule = SITUATIONAL # Situational superko
scoringRule = AREA # Area scoring
# scoringRule = TERRITORY # Territory scoring (uses a sort of special computer-friendly territory ruleset)
taxRule = NONE # All surrounded empty points are scored
# taxRule = SEKI # Eyes in seki do NOT count as points
# taxRule = ALL # All groups are taxed up to 2 points for the two eyes needed to live
multiStoneSuicideLegal = true #Is multiple-stone suicide legal? (Single-stone suicide is always illegal).
hasButton = false # Set to true when area scoring to award 0.5 points to the first pass.
whiteHandicapBonus = 0 # In handicap games, give white no compensation for black's handicap stones (Tromp-taylor, NZ, JP)
# whiteHandicapBonus = N-1 # In handicap games, give white N-1 points for black's handicap stones (AGA)
# whiteHandicapBonus = N # In handicap games, give white N points for black's handicap stones (Chinese)
# Bot behavior---------------------------------------------------------------------------------------
# Resignation -------------
# Resignation occurs if for at least resignConsecTurns in a row,
# the winLossUtility (which is on a [-1,1] scale) is below resignThreshold.
allowResignation = true
resignThreshold = -0.98
resignConsecTurns = 3
# 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 understanding of komi if whiteHandicapBonus is used and a server does NOT
# have such a practice.
# Defaults to true! Uncomment and set to false to disable this behavior.
# assumeMultipleStartingBlackMovesAreHandicap = true
# Makes katago dynamically adjust to play more aggressively in handicap games based on the handicap and the current state of the game.
# Comment to disable this and make KataGo play the same always.
dynamicPlayoutDoublingAdvantageCapPerOppLead = 0.04
# Instead of setting dynamicPlayoutDoublingAdvantageCapPerOppLead, you can uncomment these and set this to a value from -2.0 to 2.0
# to set KataGo's aggression to a FIXED level.
# Negative makes KataGo behave as if it is much weaker than the opponent, preferring to play defensively
# Positive makes KataGo behave as if it is much stronger than the opponent, prefering to play aggressively or even overplay slightly.
# playoutDoublingAdvantage = 0.0
# Controls which side dynamicPlayoutDoublingAdvantageCapPerOppLead or playoutDoublingAdvantage applies to.
playoutDoublingAdvantagePla = WHITE
# 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 playing under territory scoring, encourage the bot to fill dame before passing.
# This is NOT absolutely guaranteed to work, and possibly in some rare pathological situations will make the bot
# play a bad move, losing points. However, it also acts as a safeguard against things like a situation when the opponent must
# eventually make a protective move and lose 1 point, where the bot might otherwise assume that the score would be counted as such,
# yet without filling the dame to force the opponent to actually make the move.
# Defaults to true! Uncomment and set to false to disable this.
# fillDameBeforePass = 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-----------------------------------------------------------------------------------
# If provided, limit maximum number of root visits per search to this much. (With tree reuse, visits do count earlier search)
maxVisits = 500
# If provided, limit maximum number of new playouts per search to this much. (With tree reuse, playouts do not count earlier search)
# maxPlayouts = 300
# If provided, cap search time at this many seconds (search will still try to follow GTP time controls)
# maxTime = 60
# Ponder on the opponent's turn?
ponderingEnabled = false
# Same limits but for ponder searches if pondering is enabled
# maxVisitsPondering = 1000
# maxPlayoutsPondering = 1000
# maxTimePondering = 60
# Number of seconds to buffer for lag for GTP time controls
lagBuffer = 1.0
# Number of threads to use in search
numSearchThreads = 1
# Play a little faster if the opponent is passing, for friendliness
searchFactorAfterOnePass = 0.50
searchFactorAfterTwoPass = 0.25
# Play a little faster if super-winning, for friendliess
searchFactorWhenWinning = 0.40
searchFactorWhenWinningThreshold = 0.95
# GPU Settings-------------------------------------------------------------------------------
# Maximum number of positions to send to GPU at once. Note that you will also need to increase numSearchThreads
# to make use of this, as every thread in KataGo is synchronous, so with 1 thread max batch will only be 1 anyways.
nnMaxBatchSize = 16
# Cache up to 2 ** this many neural net evaluations in case of transpositions in the tree.
nnCacheSizePowerOfTwo = 19
# Size of mutex pool for nnCache is 2 ** this
nnMutexPoolSizePowerOfTwo = 15
# Randomize board orientation when running neural net evals?
nnRandomize = true
# If provided, force usage of a specific seed for nnRandomize instead of randomizing
# nnRandSeed = abcdefg
# How many threads should there be to feed positions to the neural net?
# Server threads are indexed 0,1,...(n-1) for the purposes of the below GPU settings arguments
# that specify which threads should use which GPUs.
# NOTE: This parameter is probably ONLY useful if you have multiple GPUs, since each GPU will need a thread.
# If you're tuning single-GPU performance, use numSearchThreads instead.
numNNServerThreadsPerModel = 1
# CUDA GPU settings--------------------------------------
# These only apply when using CUDA as the backend for inference.
# (For GTP, we only ever have one model, when playing matches, we might have more than one, see match_example.cfg)
# Default behavior tries to guess the 'best' GPU or device
# You will want to uncomment and adjust one or more of these lines to take advantage of a multi-gpu machine
# cudaDeviceToUse = 0 #use device 0 for all server threads (numNNServerThreadsPerModel) unless otherwise specified per-model or per-thread-per-model
# cudaDeviceToUseModel0 = 3 #use device 3 for model 0 for all threads unless otherwise specified per-thread for this model
# cudaDeviceToUseModel1 = 2 #use device 2 for model 1 for all threads unless otherwise specified per-thread for this model
# cudaDeviceToUseModel0Thread0 = 3 #use device 3 for model 0, server thread 0
# cudaDeviceToUseModel0Thread1 = 2 #use device 2 for model 0, server thread 1
# Uncomment these on NVIDIA devices with FP16 tensor cores for probably a speedup, at the cost of introducing some precision loss in the nn calculation.
# cudaUseFP16 = true
# cudaUseNHWC = true
# OpenCL GPU settings--------------------------------------
# These only apply when using OpenCL as the backend for inference.
# (For GTP, we only ever have one model, when playing matches, we might have more than one, see match_example.cfg)
# Default behavior tries to guess the 'best' GPU or device
# You will want to uncomment and adjust one or more of these lines to take advantage of a multi-gpu machine
# openclDeviceToUse = 0 #use device 0 for all server threads (numNNServerThreadsPerModel) unless otherwise specified per-model or per-thread-per-model
# openclDeviceToUseModel0 = 3 #use device 3 for model 0 for all threads unless otherwise specified per-thread for this model
# openclDeviceToUseModel1 = 2 #use device 2 for model 1 for all threads unless otherwise specified per-thread for this model
# openclDeviceToUseModel0Thread0 = 3 #use device 3 for model 0, server thread 0
# openclDeviceToUseModel0Thread1 = 2 #use device 2 for model 0, server thread 1
# Uncomment to tune OpenCL for every board size separately, rather than only the largest possible size
# openclReTunePerBoardSize = true
# Root move selection and biases------------------------------------------------------------------------------
# If provided, force usage of a specific seed for various things in the search instead of randomizing
# searchRandSeed = hijklmn
# 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
# Use dirichlet noise for the root node policy?
rootNoiseEnabled = false
# Dirichlet noise alpha is set to this divided by number of legal moves. 10.83 produces an alpha of 0.03 on an empty 19x19 board.
rootDirichletNoiseTotalConcentration = 10.83
# Proportion of root policy that is noise
rootDirichletNoiseWeight = 0.25
# 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------------------------------------------------------------------------------
# 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.6
# 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
-305
View File
@@ -1,305 +0,0 @@
# Example config for C++ (non-python) gtp bot
# RUNNING ON AN ONLINE SERVER OR IN A REAL TOURNAMENT OR MATCH:
# If you plan to do so, you may want to read through the "Rules" section
# below carefully for proper handling of komi and handicap games and end-of-game cleanup
# and various other details.
# NOTES ABOUT PERFORMANCE AND MEMORY USAGE:
# You will likely want to tune one or more the following:
#
# numSearchThreads:
# The number of CPU threads to use. If your GPU is powerful, it can actually be much higher than
# the number of cores on your processor because you will need many threads to feed large enough
# batches to make good use of the GPU.
#
# The "./katago benchmark" command can help you tune this parameter, as well as to test out the effect
# of changes to any of the other parameters below!
#
# nnMaxBatchSize:
# The maximum GPU batch size. Should often be at least as large as numSearchThreads.
# Larger won't do anything, but also won't hurt except use a little bit more GPU memory.
# Smaller can be fine if you have more than one GPU, since the GPUs will be sharing the work
# of servicing the CPU threads.
#
# cudaUseFP16 and cudaUseNHWC:
# These have a good chance of improving peformance at larger threads/batch sizes if
# you are using the CUDA implementation with an NVIDIA GPU with FP16 tensor cores.
#
# nnCacheSizePowerOfTwo:
# This controls the NN Cache size, which is the primary RAM/memory use.
# Increase this if you don't mind the memory use and want better performance for searches with
# tens of thousands of visits or more. Decrease this if you want to limit memory usage.
#
# If you're someone who is happy to do a bit of math - each neural net entry takes very
# approximately 1.5KB, except when using whole-board ownership/territory visualizations, each
# entry will take very approximately 3KB. The number of entries is (2 ** nnCacheSizePowerOfTwo),
# for example 2 ** 18 = 262144.
#
# OTHER NOTES:
# If you have more than one GPU, take a look at "OpenCL GPU settings" or "CUDA GPU settings" below.
#
# If using OpenCL, you will want to verify that KataGo is picking up the correct device!
# (e.g. some systems may have both an Intel CPU OpenCL and GPU OpenCL, if KataGo appears to pick
# the wrong one, you correct this by specifying "openclGpuToUse" below).
#
# You may also want to adjust "maxVisits", "ponderingEnabled", "resignThreshold", and possibly
# other parameters depending on your intended usage.
# Logs------------------------------------------------------------------------------------
# Where to output log?
logFile = gtp.log
# Logging options
logAllGTPCommunication = true
logSearchInfo = true
logToStderr = false
# KataGo will display some info to stderr on GTP startup
# Uncomment this to suppress that and remain silent
# startupPrintMessageToStderr = false
# Chat some stuff to stderr, for use in things like malkovich chat to OGS.
# ogsChatToStderr = true
# Configure the maximum length of analysis printed out by lz-analyze and other places.
# Controls the number of moves after the first move in a variation.
# analysisPVLen = 9
# Report winrates for chat and analysis as (BLACK|WHITE|SIDETOMOVE).
# Default is SIDETOMOVE, which is what tools that use LZ probably also expect
# reportAnalysisWinratesAs = SIDETOMOVE
# Default rules------------------------------------------------------------------------------------
# See https://lightvector.github.io/KataGo/rules.html for a description of the rules.
# These rules are defaults and can be changed mid-run by several custom GTP commands.
# See https://github.com/lightvector/KataGo/blob/master/docs/GTP_Extensions.md for those commands.
koRule = SIMPLE # Simple ko rules (triple ko = no result)
# koRule = POSITIONAL # Positional superko
# koRule = SITUATIONAL # Situational superko
# scoringRule = AREA # Area scoring
scoringRule = TERRITORY # Territory scoring (uses a sort of special computer-friendly territory ruleset)
# taxRule = NONE # All surrounded empty points are scored
taxRule = SEKI # Eyes in seki do NOT count as points
# taxRule = ALL # All groups are taxed up to 2 points for the two eyes needed to live
multiStoneSuicideLegal = false #Is multiple-stone suicide legal? (Single-stone suicide is always illegal).
hasButton = false # Set to true when area scoring to award 0.5 points to the first pass.
whiteHandicapBonus = 0 # In handicap games, give white no compensation for black's handicap stones (Tromp-taylor, NZ, JP)
# whiteHandicapBonus = N-1 # In handicap games, give white N-1 points for black's handicap stones (AGA)
# whiteHandicapBonus = N # In handicap games, give white N points for black's handicap stones (Chinese)
# Bot behavior---------------------------------------------------------------------------------------
# Resignation -------------
# Resignation occurs if for at least resignConsecTurns in a row,
# the winLossUtility (which is on a [-1,1] scale) is below resignThreshold.
allowResignation = true
resignThreshold = -0.999
resignConsecTurns = 3
# 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 understanding of komi if whiteHandicapBonus is used and a server does NOT
# have such a practice.
# Defaults to true! Uncomment and set to false to disable this behavior.
# assumeMultipleStartingBlackMovesAreHandicap = true
# Makes katago dynamically adjust to play more aggressively in handicap games based on the handicap and the current state of the game.
# Comment to disable this and make KataGo play the same always.
dynamicPlayoutDoublingAdvantageCapPerOppLead = 0.04
# Instead of setting dynamicPlayoutDoublingAdvantageCapPerOppLead, you can uncomment these and set this to a value from -2.0 to 2.0
# to set KataGo's aggression to a FIXED level.
# Negative makes KataGo behave as if it is much weaker than the opponent, preferring to play defensively
# Positive makes KataGo behave as if it is much stronger than the opponent, prefering to play aggressively or even overplay slightly.
# playoutDoublingAdvantage = 0.0
# Controls which side dynamicPlayoutDoublingAdvantageCapPerOppLead or playoutDoublingAdvantage applies to.
playoutDoublingAdvantagePla = WHITE
# 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 playing under territory scoring, encourage the bot to fill dame before passing.
# This is NOT absolutely guaranteed to work, and possibly in some rare pathological situations will make the bot
# play a bad move, losing points. However, it also acts as a safeguard against things like a situation when the opponent must
# eventually make a protective move and lose 1 point, where the bot might otherwise assume that the score would be counted as such,
# yet without filling the dame to force the opponent to actually make the move.
# Defaults to true! Uncomment and set to false to disable this.
# fillDameBeforePass = 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-----------------------------------------------------------------------------------
# If provided, limit maximum number of root visits per search to this much. (With tree reuse, visits do count earlier search)
maxVisits = 2500
# If provided, limit maximum number of new playouts per search to this much. (With tree reuse, playouts do not count earlier search)
# maxPlayouts = 300
# If provided, cap search time at this many seconds (search will still try to follow GTP time controls)
# maxTime = 60
# Ponder on the opponent's turn?
ponderingEnabled = false
# Same limits but for ponder searches if pondering is enabled
# maxVisitsPondering = 1000
# maxPlayoutsPondering = 1000
# maxTimePondering = 60
# Number of seconds to buffer for lag for GTP time controls
lagBuffer = 1.0
# Number of threads to use in search
numSearchThreads = 4
# Play a little faster if the opponent is passing, for friendliness
searchFactorAfterOnePass = 0.50
searchFactorAfterTwoPass = 0.25
# Play a little faster if super-winning, for friendliess
searchFactorWhenWinning = 0.40
searchFactorWhenWinningThreshold = 0.95
# GPU Settings-------------------------------------------------------------------------------
# Maximum number of positions to send to GPU at once. Note that you will also need to increase numSearchThreads
# to make use of this, as every thread in KataGo is synchronous, so with 1 thread max batch will only be 1 anyways.
nnMaxBatchSize = 16
# Cache up to 2 ** this many neural net evaluations in case of transpositions in the tree.
nnCacheSizePowerOfTwo = 20
# Size of mutex pool for nnCache is 2 ** this
nnMutexPoolSizePowerOfTwo = 16
# Randomize board orientation when running neural net evals?
nnRandomize = true
# If provided, force usage of a specific seed for nnRandomize instead of randomizing
# nnRandSeed = abcdefg
# How many threads should there be to feed positions to the neural net?
# Server threads are indexed 0,1,...(n-1) for the purposes of the below GPU settings arguments
# that specify which threads should use which GPUs.
# NOTE: This parameter is probably ONLY useful if you have multiple GPUs, since each GPU will need a thread.
# If you're tuning single-GPU performance, use numSearchThreads instead.
numNNServerThreadsPerModel = 1
# CUDA GPU settings--------------------------------------
# These only apply when using CUDA as the backend for inference.
# (For GTP, we only ever have one model, when playing matches, we might have more than one, see match_example.cfg)
# Default behavior tries to guess the 'best' GPU or device
# You will want to uncomment and adjust one or more of these lines to take advantage of a multi-gpu machine
# cudaDeviceToUse = 0 #use device 0 for all server threads (numNNServerThreadsPerModel) unless otherwise specified per-model or per-thread-per-model
# cudaDeviceToUseModel0 = 3 #use device 3 for model 0 for all threads unless otherwise specified per-thread for this model
# cudaDeviceToUseModel1 = 2 #use device 2 for model 1 for all threads unless otherwise specified per-thread for this model
# cudaDeviceToUseModel0Thread0 = 3 #use device 3 for model 0, server thread 0
# cudaDeviceToUseModel0Thread1 = 2 #use device 2 for model 0, server thread 1
# Uncomment these on NVIDIA devices with FP16 tensor cores for probably a speedup, at the cost of introducing some precision loss in the nn calculation.
# cudaUseFP16 = true
# cudaUseNHWC = true
# OpenCL GPU settings--------------------------------------
# These only apply when using OpenCL as the backend for inference.
# (For GTP, we only ever have one model, when playing matches, we might have more than one, see match_example.cfg)
# Default behavior tries to guess the 'best' GPU or device
# You will want to uncomment and adjust one or more of these lines to take advantage of a multi-gpu machine
# openclDeviceToUse = 0 #use device 0 for all server threads (numNNServerThreadsPerModel) unless otherwise specified per-model or per-thread-per-model
# openclDeviceToUseModel0 = 3 #use device 3 for model 0 for all threads unless otherwise specified per-thread for this model
# openclDeviceToUseModel1 = 2 #use device 2 for model 1 for all threads unless otherwise specified per-thread for this model
# openclDeviceToUseModel0Thread0 = 3 #use device 3 for model 0, server thread 0
# openclDeviceToUseModel0Thread1 = 2 #use device 2 for model 0, server thread 1
# Uncomment to tune OpenCL for every board size separately, rather than only the largest possible size
# openclReTunePerBoardSize = true
# Root move selection and biases------------------------------------------------------------------------------
# If provided, force usage of a specific seed for various things in the search instead of randomizing
# searchRandSeed = hijklmn
# 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
# Use dirichlet noise for the root node policy?
rootNoiseEnabled = false
# Dirichlet noise alpha is set to this divided by number of legal moves. 10.83 produces an alpha of 0.03 on an empty 19x19 board.
rootDirichletNoiseTotalConcentration = 10.83
# Proportion of root policy that is noise
rootDirichletNoiseWeight = 0.25
# 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------------------------------------------------------------------------------
# Scales the utility of winning/losing
winLossUtilityFactor = 0.4
# Scales the utility for trying to maximize score
staticScoreUtilityFactor = 0.2
dynamicScoreUtilityFactor = 0.4
# 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 = 2.0
cpuctExplorationLog = 0.8
# 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
BIN
View File
Binary file not shown.
+11 -11
View File
@@ -4,36 +4,38 @@ Manual
Installation Installation
------------ ------------
* pip install kivy * pip install kivy
* change the `engine.command` field in `config.json` to your kata installation (for example, to `path/to/lizzie/katago/katago.exe`) * On linux, change the `engine.command` field in `config.json` to your kata v1.3+ installation.
* start the app by running `python katrain.py` (or `python3` if needed) * start the app by running `python katrain.py`
Options Options
------- -------
* Top row options * Check box options
* Eval: show the coloured dots on the moves for this player. * Eval: show the coloured dots on the moves for this player.
* Hints: show suggested moves for this player. * Hints: show suggested moves for this player.
* Undo: automatically undo poor moves for this player and make them try again. * Undo: automatically undo poor moves for this player and make them try again.
* AI: let the AI control this player. Check both for self-play.
* Show owner: show expected control of territory.
* Lock AI: disallow extra undos, changing hints options, changing auto move, or AI move. * Lock AI: disallow extra undos, changing hints options, changing auto move, or AI move.
* Show owner: show expected control of territory. * Fast: use a lower number of max visits for evaluation/AI move.
* Balance score: Deliberately make sub-optimal moves as the AI in an attempt to balance the score towawrds a slight win.
* Temperature/Evaluation/Score: Not that these fields can be hidden by clicking on the text. * Temperature/Evaluation/Score: Not that these fields can be hidden by clicking on the text.
* Temperature is the point difference between passing and the best move. * Temperature is the point difference between passing and the best move.
* Evaluation is where on this scale the last move was, from 0% (equivalent to a pass) to 100% (best move). * Evaluation is where on this scale the last move was, from 0% (equivalent to a pass) to 100% (best move).
This can be < 0% in case of suicidal moves, or >100% when Kata did not consider the move before, or further analysis shows it to be better than the best one considered. This can be < 0% in case of suicidal moves, or >100% when Kata did not consider the move before, or further analysis shows it to be better than the best one considered.
* Score: Expected score. * Score: How far one player is ahead.
Play Play
---- ----
* Play against the AI * Play against the AI
* Turn on auto move. * Turn on AI for the chosen player.
* Choose whether to turn on `balance score` to make the AI play slack moves. * Choose whether to turn on `balance score` to make the AI play slack moves.
* Choose whether to turn on `undo` for your colour to be prompted to re-try poor moves. * Choose whether to turn on `undo` for your colour to be prompted to re-try poor moves.
* Choose whether or not to turn on `fast` to make the AI play faster but read less deeply (NB: with balance score, faster AI can be a stronger opponent, as there are fewer mediocre moves considered). * Choose whether or not to turn on `fast` to make the AI play faster but read less deeply (NB: with balance score, faster AI can be a stronger opponent, as there are fewer mediocre moves considered).
* Possibly lock AI to prevent yourself from peeking at hints, etc. * Possibly lock AI to prevent yourself from peeking at hints, etc.
* Possibly hide score or temperature. * Possibly hide score or temperature.
* Possibly hide evaluation for the AI player. * If you chose AI to play black, click AI move for the first move.
* Play by playing a move or clicking AI move if you want white.
* Engine-assisted play * Engine-assisted play
* Turn off auto move. * Turn off auto move.
@@ -43,7 +45,7 @@ Play
* Play with a friend with instant feedback and/or undos for both, or see how many stones stronger you are with one undo. (But please play unranked and be honest to your opponent on what you're doing) * Play with a friend with instant feedback and/or undos for both, or see how many stones stronger you are with one undo. (But please play unranked and be honest to your opponent on what you're doing)
* Analysis * Analysis
* Copy the SGF into the text box * Copy the SGF into the text box. Note that branches are not supported and will lead to strange results.
* Choose whether or not to turn on `fast` to make the AI weaker but analyze faster. * Choose whether or not to turn on `fast` to make the AI weaker but analyze faster.
* Click `Analyze` * Click `Analyze`
@@ -61,9 +63,7 @@ The `trainer` block has the following options to tweak:
* `balance_play_min_eval`: when needing to balance score, the AI will pick a move which is at least this good. * `balance_play_min_eval`: when needing to balance score, the AI will pick a move which is at least this good.
* `balance_play_min_visits`: never pick a move with fewer playouts than this. * `balance_play_min_visits`: never pick a move with fewer playouts than this.
* `undo_eval_threshold`, `undo_point_threshold`: prompt player to undo if move is worse than this in terms of points AND evaluation. * `undo_eval_threshold`, `undo_point_threshold`: prompt player to undo if move is worse than this in terms of points AND evaluation.
* `undo_outdated_eval_threshold`: don't prompt undo if last move's evaluation is >= `undo_eval_threshold` and the NEW evaluation is greater than this. (this decreases frustration when hints are on, or when kata over-estimates the best move).
* `num_undo_prompts`: automatically undo bad moves when `undo` is on at most this many times. * `num_undo_prompts`: automatically undo bad moves when `undo` is on at most this many times.
* `show_ai_options`: show which moves the AI considered.
The cfg file has additional configuration for kata. In particular, it changes the default to being more exploratory and score-based (and therefore nicer as an opponent, but weaker as analysis tool). The cfg file has additional configuration for kata. In particular, it changes the default to being more exploratory and score-based (and therefore nicer as an opponent, but weaker as analysis tool).
+14 -9
View File
@@ -17,6 +17,8 @@ class Move:
self.analysis = None self.analysis = None
self.pass_analysis = None self.pass_analysis = None
self.ownership = None self.ownership = None
self.x_comment = ""
self.auto_undid = False
self.move_number = 0 self.move_number = 0
def __repr__(self): def __repr__(self):
@@ -61,11 +63,13 @@ class Move:
score = score or self.score score = score or self.score
return f"{'B' if score >= 0 else 'W'}+{abs(score):.1f}" return f"{'B' if score >= 0 else 'W'}+{abs(score):.1f}"
@property def comment(self,sgf=False, eval=False, hints=False):
def comment(self,sgf=False):
if not self.parent: # root if not self.parent: # root
return "" return ""
text = f"Move {self.move_number}: {self.bw_player()} @ {self.gtp()} {'(AI Move)' if self.robot else ''}\n" text = f"Move {self.move_number}: {self.bw_player()} {self.gtp()} {'(AI Move)' if self.robot else ''}\n"
text += self.x_comment
text += "".join(f"Auto undid move {m.gtp()} ({m.evaluation*100:.1f}% efficient)\n" for m in self.children if m.auto_undid)
if self.analysis_ready: if self.analysis_ready:
score, _, temperature = self.temperature_stats score, _, temperature = self.temperature_stats
if sgf: if sgf:
@@ -73,13 +77,14 @@ class Move:
text += f"Temperature: {temperature:.1f}\n" text += f"Temperature: {temperature:.1f}\n"
if self.parent and self.parent.analysis_ready: if self.parent and self.parent.analysis_ready:
prev_best_score, prev_worst_score, prev_temperature = self.parent.temperature_stats prev_best_score, prev_worst_score, prev_temperature = self.parent.temperature_stats
text += f"Top move was {self.format_score(prev_best_score)} @ {self.parent.analysis[0]['move']}\n" if sgf or hints:
text += f"Pass score was {self.format_score(prev_worst_score)}\n" text += f"Top move was {self.parent.analysis[0]['move']} ({self.format_score(prev_best_score)})\n"
text += f"Pass score was {self.format_score(prev_worst_score)}\n"
if prev_temperature < 0.5: if prev_temperature < 0.5:
text += f"Previous temperature ({prev_temperature}) too low for evaluation\n" text += f"Previous temperature ({prev_temperature}) too low for evaluation\n"
else: else:
if eval: if sgf or eval:
text += f"Evaluation: {100*self.evaluation:.1f}%\n" text += f"Evaluation: {100*self.evaluation:.1f}% efficient\n"
outdated_evaluation = self.outdated_evaluation outdated_evaluation = self.outdated_evaluation
if outdated_evaluation and outdated_evaluation > self.evaluation and outdated_evaluation > self.evaluation + 0.01: if outdated_evaluation and outdated_evaluation > self.evaluation and outdated_evaluation > self.evaluation + 0.01:
text += f"(Was considered last move as: {100 * outdated_evaluation :.1f}%)\n" text += f"(Was considered last move as: {100 * outdated_evaluation :.1f}%)\n"
@@ -94,7 +99,7 @@ class Move:
@property @property
def evaluation_info(self): def evaluation_info(self):
if self.parent and self.parent.analysis_ready and self.analysis_ready: if self.parent and self.parent.analysis_ready and self.analysis_ready:
return self.evaluation,self.parent.temperature_stats[2] return self.evaluation, self.parent.temperature_stats[2]
else: else:
return None,None return None,None
@@ -264,7 +269,7 @@ class Board:
def moves(self) -> list: # flat list of moves to current def moves(self) -> list: # flat list of moves to current
moves = [] moves = []
p = self.current_move p = self.current_move
while p != self.root: while p is not self.root: # NB == is wrong here
moves.append(p) moves.append(p)
p = p.parent p = p.parent
return moves[::-1] return moves[::-1]
+2 -4
View File
@@ -3,12 +3,11 @@
"pass_visits": 200, "pass_visits": 200,
"pass_visits_fast": 50, "pass_visits_fast": 50,
"visits": 3500, "visits": 3500,
"visits_fast": 1500, "visits_fast": 1500
"nopass_visits": 10
}, },
"board": { "board": {
"size": 19, "size": 19,
"komi": 7.5 "komi": 6.5
}, },
"ui": { "ui": {
"size_min": 1, "size_min": 1,
@@ -35,7 +34,6 @@
"balance_play_min_eval": 0.875, "balance_play_min_eval": 0.875,
"balance_play_min_visits": 20, "balance_play_min_visits": 20,
"undo_eval_threshold": 0.875, "undo_eval_threshold": 0.875,
"undo_outdated_eval_threshold": 0.8,
"undo_point_threshold": 1, "undo_point_threshold": 1,
"num_undo_prompts": 1 "num_undo_prompts": 1
}, },
+27 -61
View File
@@ -26,7 +26,6 @@ class EngineControls(GridLayout):
[analysis_settings["pass_visits"], analysis_settings["visits"]], [analysis_settings["pass_visits"], analysis_settings["visits"]],
[analysis_settings["pass_visits_fast"], analysis_settings["visits_fast"]], [analysis_settings["pass_visits_fast"], analysis_settings["visits_fast"]],
] ]
self.min_nopass_visits = analysis_settings["nopass_visits"]
self.train_settings = Config.get("trainer") self.train_settings = Config.get("trainer")
self.debug = Config.get("debug")["level"] self.debug = Config.get("debug")["level"]
self.board_size = Config.get("board")["size"] self.board_size = Config.get("board")["size"]
@@ -78,13 +77,11 @@ class EngineControls(GridLayout):
print(str(e)) print(str(e))
self.info.text = f"Illegal move: {str(e)}" self.info.text = f"Illegal move: {str(e)}"
return return
print("PLAYED", move, self.board.stones)
self._request_analysis(mr) self._request_analysis(mr)
return mr return mr
# engine action functions # engine action functions
def _do_play(self, *args): def _do_play(self, *args):
print("CURRENT PLAYER", self.board.current_player)
move = Move(player=self.board.current_player, coords=args[0]) move = Move(player=self.board.current_player, coords=args[0])
self.play(move) self.play(move)
# mr.waiting_for_analysis # mr.waiting_for_analysis
@@ -93,66 +90,34 @@ class EngineControls(GridLayout):
def update_evaluation(self): def update_evaluation(self):
current_move = self.board.current_move current_move = self.board.current_move
if self.eval.active(current_move.player): if self.eval.active(current_move.player):
self.info.text = current_move.comment self.info.text = current_move.comment(eval=self.eval.active(current_move.player), hints=self.hints.active(current_move.player))
self.evaluation.text = '' self.evaluation.text = ''
if current_move.analysis_ready: if current_move.analysis_ready and self.eval.active(current_move.player):
self.score.text = current_move.format_score().replace("-", "\u2013") self.score.text = current_move.format_score().replace("-", "\u2013")
self.temperature.text = f"{current_move.temperature_stats[2]:.1f}" self.temperature.text = f"{current_move.temperature_stats[2]:.1f}"
if current_move.parent and current_move.parent.analysis_ready: if current_move.parent and current_move.parent.analysis_ready:
self.evaluation.text = f"{100 * current_move.evaluation:.1f}%" self.evaluation.text = f"{100 * current_move.evaluation:.1f}%"
# when to trigger auto undo? if current_move.analysis_ready and current_move.parent and current_move.parent.analysis_ready and not current_move.children:
# self.undo.disabled = True # undo while waiting for this does weird things # handle automatic undo
# undid = False if self.auto_undo.active(current_move.player) and not self.ai_auto.active(current_move.player) and not current_move.auto_undid:
# self.info.text = "" ts = self.train_settings
# if self.auto_undo.active(1 - self.board.current_player): # TODO: is this overly generous wrt low visit outdated evaluations?
# undid = self._auto_undo(move) eval = max(current_move.evaluation, current_move.outdated_evaluation or 0)
# if self.ai_auto.active and not undid: points_lost = (current_move.parent or current_move).temperature_stats[2] * (1 - eval)
# self._do_aimove(move, True) if eval < ts["undo_eval_threshold"] and points_lost >= ts["undo_point_threshold"]:
# self.undo.disabled = False current_move.auto_undid = True
def _auto_undo(self, move):
ts = self.train_settings
self.info.text = "Evaluating..."
if (
move.evaluation
and move.evaluation < ts["undo_eval_threshold"]
and move.points_lost >= ts["undo_point_threshold"]
and ts["num_undo_prompts"] > 0
):
if move.outdated_evaluation:
outdated_points_lost = (1 - move.outdated_evaluation) * move.points_lost / (1 - move.evaluation)
# so if the move was not that far off (>undo_outdated_eval_threshold) and according to last move's analysis it was fine, don't undo.
if (
move.outdated_evaluation
and (
move.outdated_evaluation >= ts["undo_eval_threshold"]
or outdated_points_lost < ts["undo_point_threshold"]
)
and (
move.evaluation > ts["undo_outdated_eval_threshold"]
or outdated_points_lost < ts["undo_point_threshold"]
)
):
self.info.text += f"\nBut according to my previous evaluation it was {move.outdated_evaluation*100:.1f}% effective and lost {outdated_points_lost:.1f} point(s), so let's continue anyway.\n"
else:
if len(self.board.current_move.parent.children) <= ts["num_undo_prompts"]:
self.info.text += f"\nLet's try again.\n"
self.board.undo() self.board.undo()
return True if len(current_move.parent.children) >= ts["num_undo_prompts"] + 1:
else: best_move = sorted([m for m in current_move.parent.children], key=lambda m: -(m.evaluation_info[0] or 0) )[0]
evaled_moves = sorted( best_move.x_comment = f"Automatically played as best option after max. {ts['num_undo_prompts']} undo(s).\n"
[m for m in self.board.current_move.parent.children if m.evaluation], self.board.play(best_move)
key=lambda m: -m.evaluation, self.update_evaluation()
) # ai player doesn't technically need parent ready, but don't want to override waiting for undo
if evaled_moves and evaled_moves[0].coords != move.coords: elif self.ai_auto.active(1 - current_move.player) and not current_move.children:
self.board.undo() self._do_aimove()
self.board.play(evaled_moves[0])
summary = "\n".join(f"{m.gtp()}: {100*m.evaluation:.1f}% effective" for m in evaled_moves)
self.info.text += f"\nYour moves:\n{summary}.\nLet's continue with {evaled_moves[0].gtp()}.\n"
return False
def _do_aimove(self, auto=False): def _do_aimove(self):
ts = self.train_settings ts = self.train_settings
while not self.board.current_move.analysis_ready: while not self.board.current_move.analysis_ready:
self.info.text = "Thinking..." self.info.text = "Thinking..."
@@ -204,9 +169,9 @@ class EngineControls(GridLayout):
sgfmoves = re.findall(r"([BW])\[([a-z]{2})\]", sgf) sgfmoves = re.findall(r"([BW])\[([a-z]{2})\]", sgf)
moves = [Move(player=Move.PLAYERS.index(p.upper()), sgfcoords=(mv, self.board_size)) for p, mv in sgfmoves] moves = [Move(player=Move.PLAYERS.index(p.upper()), sgfcoords=(mv, self.board_size)) for p, mv in sgfmoves]
for move in moves: for move in moves:
self.board.play(move) self.play(move)
while not all(m.analysis for m in moves): while not all(m.analysis for m in moves):
time.sleep(0.01) time.sleep(0.05)
self.info.text = f"{sum([1 if m.analysis else 0 for m in moves])}/{len(moves)} analyzed" self.info.text = f"{sum([1 if m.analysis else 0 for m in moves])}/{len(moves)} analyzed"
# analysis thread # analysis thread
@@ -215,7 +180,8 @@ class EngineControls(GridLayout):
while self.outstanding_analysis_queries: while self.outstanding_analysis_queries:
self._send_analysis_query(self.outstanding_analysis_queries.pop(0)) self._send_analysis_query(self.outstanding_analysis_queries.pop(0))
line = self.kata.stdout.readline() line = self.kata.stdout.readline()
print("KATA ANALYSIS RECEIVED:", line[:50]) if self.debug:
print("KATA ANALYSIS RECEIVED:", line[:50])
self.board.store_analysis(json.loads(line)) self.board.store_analysis(json.loads(line))
self.update_evaluation() self.update_evaluation()
self.redraw(include_board=False) self.redraw(include_board=False)
@@ -242,14 +208,14 @@ class EngineControls(GridLayout):
"includeOwnership": True, "includeOwnership": True,
"maxVisits": self.visits[fast][1], "maxVisits": self.visits[fast][1],
} }
print("query", query) if self.debug:
print("query", query)
self._send_analysis_query(query) self._send_analysis_query(query)
query.update( query.update(
{"id": f"PASS_{move_id}", "maxVisits": self.visits[fast][0], "includeOwnership": False} {"id": f"PASS_{move_id}", "maxVisits": self.visits[fast][0], "includeOwnership": False}
) # TODO: merge? )
query["moves"] += [[move.bw_player(next_move=True), "pass"]] query["moves"] += [[move.bw_player(next_move=True), "pass"]]
query["analyzeTurns"][0] += 1 query["analyzeTurns"][0] += 1
print("pass-query", query)
self._send_analysis_query(query) self._send_analysis_query(query)
+12 -12
View File
@@ -168,11 +168,11 @@
id: auto_undo id: auto_undo
text: 'undo' text: 'undo'
on_active: root.parent.board.redraw() on_active: root.parent.board.redraw()
CheckBoxHint: BWCheckBoxHint:
size_hint: 0.2, 0.5 size_hint: 0.166, 0.5
text: 'lock\nai' text: 'ai'
id: ai_lock id: ai_auto
on_active: self.checkbox.disabled = hints.black.disabled = hints.white.disabled = ai_move.disabled = auto_undo.black.disabled = auto_undo.white.disabled = ai_auto.checkbox.disabled = True default_active: False
CheckBoxHint: CheckBoxHint:
size_hint: 0.2, 0.5 size_hint: 0.2, 0.5
id: ownership id: ownership
@@ -189,19 +189,19 @@
on_press: root.action("aimove") on_press: root.action("aimove")
CheckBoxHint: CheckBoxHint:
size_hint: 0.166, 0.5 size_hint: 0.166, 0.5
text: 'auto\nmove' text: 'fast'
id: ai_auto id: ai_fast
default_active: False default_active: True
CheckBoxHint: CheckBoxHint:
size_hint: 0.166, 0.5 size_hint: 0.166, 0.5
text: 'balance\nscore' text: 'balance\nscore'
id: ai_balance id: ai_balance
default_active: False default_active: False
CheckBoxHint: CheckBoxHint:
size_hint: 0.166, 0.5 size_hint: 0.2, 0.5
text: 'fast' text: 'lock\nai'
id: ai_fast id: ai_lock
default_active: True on_active: self.checkbox.disabled = hints.black.disabled = hints.white.disabled = ai_move.disabled = auto_undo.black.disabled = auto_undo.white.disabled = ai_auto.checkbox.disabled = True
GridLayout: GridLayout:
cols: 2 cols: 2
rows: 1 rows: 1
+6
View File
@@ -47,6 +47,12 @@ class BadukPanWidget(Widget):
def on_touch_up(self, touch): def on_touch_up(self, touch):
if self.ghost_stone: if self.ghost_stone:
self.engine.action("play", self.ghost_stone) self.engine.action("play", self.ghost_stone)
else:
xd, xp = self._find_closest(touch.x)
yd, yp = self._find_closest(touch.y)
stones_here = [m for m in self.engine.board.stones if m.coords == (xp,yp)]
if stones_here and max(yd, xd) < self.grid_size / 2: # load old comment
self.engine.info.text = stones_here[-1].comment(sgf=True)
self.ghost_stone = None self.ghost_stone = None
self.redraw() # remove ghost self.redraw() # remove ghost