move bot code to sep repo

This commit is contained in:
Sander Land committed 2020-05-16 13:12:03 +02:00
1 parent d5a78e79dd
commit 545d273cb9
13 files changed
-750

No files matched your search

BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
# Bots
This directory contains the source code used to run the katrain-* bots on OGS,
and some code to test them. It is liable to break without warning.
View File
Whitespace-only changes.
-174
View File
@@ -1,174 +0,0 @@
# This is a script that turns a KaTrain AI into a sort-of GTP compatible bot
import json
import sys
import time
import random
from katrain.core.ai import ai_move
from katrain.core.common import OUTPUT_ERROR, OUTPUT_INFO
from bots.settings import bot_strategy_names
from katrain.core.engine import EngineDiedException, KataGoEngine
from katrain.core.game import Game
from katrain.core.sgf_parser import Move
if len(sys.argv) < 2:
bot = "dev"
else:
bot = sys.argv[1].strip()
port = int(sys.argv[2]) if len(sys.argv) > 2 else 8587
REPORT_SCORE_THRESHOLD = 1.5
MAX_WAIT_ANALYSIS = 10
MAX_PASS = 3 # after opponent passes this many times, we always pass
class Logger:
def log(self, msg, level):
if level <= OUTPUT_INFO:
print(msg, file=sys.stderr)
logger = Logger()
ENGINE_SETTINGS = {
"katago": f"python bots/engine_connector.py {port}", # actual engine settings in engine_server.py
"model": "models/b15-1.3.2.txt.gz",
"config": "KataGo/analysis_config.cfg",
"max_visits": 5,
"max_time": 5.0,
"_enable_ownership": False,
"threads": 1,
}
engine = KataGoEngine(logger, ENGINE_SETTINGS)
with open("katrain/config.json") as f:
settings = json.load(f)
all_ai_settings = settings["ai"]
if bot == "dev":
engine.override_settings["maxVisits"] = 500
all_ai_settings["dev"] = all_ai_settings["ScoreLoss"]
ai_strategy = bot_strategy_names[bot]
ai_settings = all_ai_settings[ai_strategy]
print(f"starting bot {bot} using server port {port}", file=sys.stderr)
print(ENGINE_SETTINGS, file=sys.stderr)
print(ai_strategy, ai_settings, file=sys.stderr)
logger.log(f"STARTED ENGINE", OUTPUT_ERROR)
game = Game(Logger(), engine, {})
def malkovich_analysis(cn):
start = time.time()
while not cn.analysis_ready:
time.sleep(0.001)
if engine.katago_process.poll() is not None: # TODO: clean up
raise EngineDiedException(f"Engine for {cn.next_player} ({engine.config}) died")
if time.time() - start > MAX_WAIT_ANALYSIS:
logger.log(f"Waiting for analysis timed out!", OUTPUT_ERROR)
return
if cn.analysis_ready and cn.parent and cn.parent.analysis_ready:
dscore = cn.analysis["root"]["scoreLead"] - cn.parent.analysis["root"]["scoreLead"]
logger.log(f"dscore {dscore} = {cn.analysis['root']['scoreLead']} {cn.parent.analysis['root']['scoreLead']} at {move}...", OUTPUT_ERROR)
if abs(dscore) > REPORT_SCORE_THRESHOLD and (cn.player == "B" and dscore < 0 or cn.player == "W" and dscore > 0): # relevant mistakes
favpl = "B" if dscore > 0 else "W"
msg = f"MALKOVICH:{cn.player} {cn.move.gtp()} caused a significant score change ({favpl} gained {abs(dscore):.1f} points)"
if cn.ai_thoughts:
msg += f" -> Win Rate {cn.format_win_rate()} Score {cn.format_score()} AI Thoughts: {cn.ai_thoughts}"
else:
comment = cn.comment(sgf=True).replace("\n", " ")
msg += f" -> Detailed move analysis: {comment}"
print(msg, file=sys.stderr)
sys.stderr.flush()
while True:
line = input()
logger.log(f"GOT INPUT {line}", OUTPUT_ERROR)
if "boardsize" in line:
_, *size = line.strip().split(" ")
if len(size) > 1:
size = f"{size[0]}:{size[1]}"
else:
size = int(size[0])
game = Game(Logger(), engine, {"init_size": size})
logger.log(f"Init game {game.root.properties}", OUTPUT_ERROR)
elif "komi" in line:
_, komi = line.split(" ")
game.root.set_property("KM", komi.strip())
game.root.set_property("RU", "chinese")
logger.log(f"Setting komi {game.root.properties}", OUTPUT_ERROR)
elif "place_free_handicap" in line:
_, n = line.split(" ")
n = int(n)
game.place_handicap_stones(n)
handicaps = set(game.root.get_list_property("AB"))
bx, by = game.board_size
while len(handicaps) < min(n, bx * by): # really obscure cases
handicaps.add(Move((random.randint(0, bx - 1), random.randint(0, by - 1)), player="B").sgf(board_size=game.board_size))
game.root.set_property("AB", list(handicaps))
game._calculate_groups()
gtp = [Move.from_sgf(m, game.board_size, "B").gtp() for m in handicaps]
logger.log(f"Chose handicap placements as {gtp}", OUTPUT_ERROR)
print(f"= {' '.join(gtp)}\n")
sys.stdout.flush()
game.analyze_all_nodes() # re-evaluate root
while engine.queries: # and make sure this gets processed
time.sleep(0.001)
continue
elif "set_free_handicap" in line:
_, *stones = line.split(" ")
game.root.set_property("AB", [Move.from_gtp(move.upper()).sgf(game.board_size) for move in stones])
game._calculate_groups()
game.analyze_all_nodes() # re-evaluate root
while engine.queries: # and make sure this gets processed
time.sleep(0.001)
logger.log(f"Set handicap placements to {game.root.get_list_property('AB')}", OUTPUT_ERROR)
elif "genmove" in line:
_, player = line.strip().split(" ")
if player[0].upper() != game.next_player:
logger.log(f"ERROR generating move: UNEXPECTED PLAYER {player} != {game.next_player}.", OUTPUT_ERROR)
print(f"= ??\n")
sys.stdout.flush()
continue
logger.log(f"{ai_strategy} generating move", OUTPUT_ERROR)
game.current_node.analyze(engine)
malkovich_analysis(game.current_node)
game.root.properties[f"P{game.current_node.next_player}"] = [f"KaTrain {ai_strategy}"]
num_passes = sum([int(n.is_pass or False) for n in game.current_node.nodes_from_root[::-1][0 : 2 * MAX_PASS : 2]])
bx, by = game.board_size
if num_passes >= MAX_PASS and game.current_node.depth - 2 * MAX_PASS >= bx + by:
logger.log(f"Forced pass as opponent is passing {MAX_PASS} times", OUTPUT_ERROR)
pol = game.current_node.policy
if not pol:
pol = ["??"]
print(f"DISCUSSION:OK, since you passed {MAX_PASS} times after the {bx+by}th move, I will pass as well [policy {pol[-1]:.3%}].", file=sys.stderr)
move = game.play(Move(None, player=game.next_player)).move
else:
move, node = ai_move(game, ai_strategy, ai_settings)
logger.log(f"Generated move {move}", OUTPUT_ERROR)
print(f"= {move.gtp()}\n")
sys.stdout.flush()
malkovich_analysis(game.current_node)
continue
elif "play" in line:
_, player, move = line.split(" ")
node = game.play(Move.from_gtp(move.upper(), player=player[0].upper()), analyze=False)
logger.log(f"played {player} {move}", OUTPUT_ERROR)
elif "final_score" in line:
score = game.current_node.format_score()
game.game_id += f"_{score}"
sgf = game.write_sgf("sgf_ogs/")
logger.log(f"Game ended. Score was {score} -> saved sgf to {sgf}", OUTPUT_ERROR)
print(f"= {score}\n")
sys.stdout.flush()
continue
elif "quit" in line:
print(f"= \n")
break
print(f"= \n")
sys.stdout.flush()
Binary file not shown.
-26
View File
@@ -1,26 +0,0 @@
# used to connect many bots to one kata engine
import socket
import sys
import time
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8587
restart = False
while True:
try:
sock = socket.create_connection(("localhost", PORT)).makefile(mode="rw")
while True:
line = input()
sock.write(line + "\n")
sock.flush()
response = sock.readline()
print(response.strip())
except Exception as e:
print(f"Error: {e}", file=sys.stderr)
if not restart:
break
print("Failed to connect or disconnected, waiting to reconnect", file=sys.stderr)
time.sleep(5)
-75
View File
@@ -1,75 +0,0 @@
# used to connect many bots to one kata engine
import json
import random
import socket
import sys
import threading
import traceback
from katrain.core.common import OUTPUT_INFO
from katrain.core.engine import KataGoEngine
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8587
ENGINE_SETTINGS = {
"katago": "my/katago25",
# "katago": "KataGo/katago",
"model": "katrain/models/g170e-b15c192-s1672170752-d466197061.bin.gz",
"config": "katrain/KataGo/analysis_config.cfg",
"max_visits": 50,
"max_time": 1.0,
"_enable_ownership": False,
"threads": 32,
}
class Logger:
def log(self, msg, level):
if level <= OUTPUT_INFO:
print(f"[{level} {msg}")
engine = KataGoEngine(Logger(), ENGINE_SETTINGS)
def engine_thread(conn, addr):
sockfile = conn.makefile(mode="rw")
try:
while True:
print(f"Waiting for input from {addr}")
line = sockfile.readline()
if not line:
break
query = {"id": "???"}
try:
query = json.loads(line)
tag = f"{int(random.random()*1000000000):09d}__"
query["id"] = tag + str(query["id"])
def callback(analysis, *args):
print(f"Returning {analysis['id']} for {addr} -> {len(engine.queries)} outstanding queries")
analysis["id"] = analysis["id"][len(tag) :]
sockfile.write(json.dumps(analysis) + "\n")
sockfile.flush()
engine.send_query(query, callback=callback, error_callback=callback)
except Exception as e:
print("Sent error to {addr}")
traceback.print_exc()
sockfile.write(json.dumps({"id": query["id"], "error": str(e)}) + "\n")
sockfile.flush()
except Exception as e:
traceback.print_exc()
print(f"Error: {e}")
print(f"Disconnected: {addr}")
conn.close()
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("localhost", PORT))
sock.listen(100)
print("Listening..")
while True:
conn, addr = sock.accept()
print(f"Accepted connection from {addr}")
threading.Thread(target=engine_thread, args=(conn, addr), daemon=True).start()
-193
View File
@@ -1,193 +0,0 @@
# 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
-205
View File
@@ -1,205 +0,0 @@
# This is a script I use to test the performance of AIs
import pickle
import sys
import threading
import time
import traceback
from collections import defaultdict
from concurrent.futures.thread import ThreadPoolExecutor
from katrain.core.ai import ai_move
from katrain.core.common import OUTPUT_ERROR, OUTPUT_INFO
from elote import EloCompetitor
from katrain.core.engine import KataGoEngine
from katrain.core.game import Game
import json
DB_FILENAME = "bots/ai_performance.pickle"
class Logger:
def log(self, msg, level):
if level <= OUTPUT_INFO:
print(msg)
if level <= OUTPUT_ERROR:
print(msg, file=sys.stderr)
logger = Logger()
with open("config.json") as f:
settings = json.load(f)
DEFAULT_AI_SETTINGS = settings["ai"]
class AI:
DEFAULT_ENGINE_SETTINGS = {
"katago": "KataGo/katago",
"model": "KataGo/models/b15-1.3.2.txt.gz",
"config": "bots/lowmem.cfg",
"max_visits": 1,
"max_time": 300.0,
"_enable_ownership": False,
}
NUM_THREADS = 32
IGNORE_SETTINGS_IN_TAG = {"threads", "_enable_ownership", "katago"} # katago for switching from/to bs version
ENGINES = []
LOCK = threading.Lock()
def __init__(self, strategy, ai_settings, engine_settings=None):
self.elo_comp = EloCompetitor(initial_rating=1000)
self.strategy = strategy
self.ai_settings = ai_settings
self.engine_settings = engine_settings or {}
fmt_settings = [f"{k}={v}" for k, v in {**self.ai_settings, **self.engine_settings}.items() if k not in AI.IGNORE_SETTINGS_IN_TAG]
self.name = f"{strategy}({ ','.join(fmt_settings) })"
self.fix_settings()
def fix_settings(self):
self.ai_settings = {**DEFAULT_AI_SETTINGS[self.strategy], **self.ai_settings}
self.engine_settings = {**AI.DEFAULT_ENGINE_SETTINGS, **self.engine_settings, "threads": AI.NUM_THREADS}
def get_engine(self): # factory
with AI.LOCK:
for existing_engine_settings, engine in AI.ENGINES:
if existing_engine_settings == self.engine_settings:
return engine
engine = KataGoEngine(logger, self.engine_settings)
AI.ENGINES.append((self.engine_settings, engine))
print("Creating new engine for", self.engine_settings, "now have", len(AI.ENGINES), "engines up")
return engine
def __eq__(self, other):
return self.name == other.name # should capture all relevant setting differences
try:
with open(DB_FILENAME, "rb") as f:
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 = []
def add_ai(ai):
if ai not in ai_database:
ai_database.append(ai)
print(f"Adding {ai.name}")
else:
print(f"AI {ai.name} already in DB")
def retrieve_ais(selected_ais):
return [ai for ai in ai_database if ai in selected_ais]
test_ais = [
AI("Default", {}, {"model": "bots/6b.bin.gz", "max_visits": 500}),
AI("Default", {}, {"model": "bots/6b104-s22347264.txt.gz", "max_visits": 500}),
AI("Default", {}, {"model": "bots/6b104-s42364928.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: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 = 1
BOARDSIZE = 19
ais_to_test = retrieve_ais(test_ais)
results = defaultdict(list)
def play_games(black: AI, white: AI):
players = {"B": black, "W": white}
engines = {"B": black.get_engine(), "W": white.get_engine()}
tag = f"{black.name} vs {white.name}"
try:
game = Game(Logger(), engines, {"init_size": BOARDSIZE})
game.root.add_list_property("PW", [white.name])
game.root.add_list_property("PB", [black.name])
start_time = time.time()
while not game.ended and game.current_node.depth < 300:
p = game.current_node.next_player
move, node = ai_move(game, players[p].strategy, players[p].ai_settings)
while not game.current_node.analysis_ready:
time.sleep(0.001)
game.game_id += f"_{game.current_node.format_score()}"
print(
f"{tag}\tGame finished in {time.time()-start_time:.1f}s @ move {game.current_node.depth} {game.current_node.format_score()} -> {game.write_sgf('sgf_selfplay/')}",
file=sys.stderr,
)
score = game.current_node.score
if score > 0.3:
black.elo_comp.beat(white.elo_comp)
elif score > -0.3:
black.elo_comp.tied(white.elo_comp)
results[tag].append(score)
all_results.append((black.name, white.name, score))
except Exception as e:
print(f"Exception in playing {tag}: {e}")
print(f"Exception in playing {tag}: {e}", file=sys.stderr)
traceback.print_exc()
traceback.print_exc(file=sys.stderr)
def fmt_score(score):
return f"{'B' if score >= 0 else 'W'}+{abs(score):.1f}"
print(len(ais_to_test), "ais to test")
global_start = time.time()
for n in range(N_GAMES):
for _, e in AI.ENGINES: # no caching/replays
e.shutdown()
AI.ENGINES = []
with ThreadPoolExecutor(max_workers=16) as threadpool:
for b in ais_to_test:
for w in ais_to_test:
if b is not w:
threadpool.submit(play_games, b, w)
print("POOL EXIT")
print(f"---- RESULTS ({n}) ----")
for k, v in results.items():
b_win = sum([s > 0.3 for s in v])
w_win = sum([s < -0.3 for s in v])
print(f"{b_win} {k} {w_win} : {list(map(fmt_score,v))}")
print("---- ELO ----")
for ai in sorted(ai_database, key=lambda a: -a.elo_comp.rating):
wins = [(b, w, s) for (b, w, s) in all_results if s > 0.3 and b == ai.name or w == ai.name and s < -0.3]
losses = [(b, w, s) for (b, w, s) in all_results if s < -0.3 and b == ai.name or w == ai.name and s > -0.3]
draws = [(b, w, s) for (b, w, s) in all_results if -0.3 <= s <= 0.3 and (b == ai.name or w == ai.name)]
out = f"{'*' if ai in ais_to_test else ' '} {ai.name}: ELO {ai.elo_comp.rating:.1f} WINS {len(wins)} LOSSES {len(losses)} DRAWS {len(draws)}"
# print("Wins:",wins)
print(out)
print(out, file=sys.stderr)
with open(DB_FILENAME, "wb") as f:
pickle.dump((ai_database, all_results), f)
print(f"Saving {len(all_results)} to pickle", file=sys.stderr)
print(f"Done!Time taken {time.time()-global_start:.1f}s", file=sys.stderr)
-26
View File
@@ -1,26 +0,0 @@
bot_strategy_names = {
# "dev": "P:Noise",
"dev": "ScoreLoss",
"dev-beta": "P:Weighted",
"strong": "Policy",
"influence": "P:Influence",
"territory": "P:Territory",
"balanced": "P:Pick",
"weighted": "P:Weighted",
"local": "P:Local",
"tenuki": "P:Tenuki",
}
greetings = {
# "dev": "Policy+Dirichlet noise.",
"dev": "Point loss-weighted random move.",
"dev-beta": "Play a policy-weighted move.",
"strong": "Play top policy move.",
"influence": "Play an influential style.",
"territory": "Play a territorial style.",
"balanced": "Play the best move out of a random selection.",
"weighted": "Play a policy-weighted move.",
"local": "Prefer local responses.",
"tenuki": "Prefer to tenuki.",
}
-48
View File
@@ -1,48 +0,0 @@
import json
import os
import sys
from bots.settings import bot_strategy_names, greetings
if len(sys.argv) < 2:
exit(0)
bot = sys.argv[1].strip()
port = int(sys.argv[2]) if len(sys.argv) > 2 else 8587
MAXGAMES = 10
if True or bot in ["dev", "local"]:
GTP2OGS = "node ../gtp2ogs"
else:
GTP2OGS = "node ../stable-gtp2ogs"
BOT_SETTINGS = f" --maxconnectedgames {MAXGAMES} --maxhandicapunranked 25 --maxhandicapranked 1 --boardsizesranked 19 --boardsizesunranked all --komisranked automatic,5.5,6.5,7.5 --komisunranked all"
if "beta" in bot:
BOT_SETTINGS += " --beta"
else:
BOT_SETTINGS += "" # --rankedonly"
username = f"katrain-{bot}"
with open("katrain/config.json") as f:
settings = json.load(f)
all_ai_settings = settings["ai"]
ai_settings = all_ai_settings[bot_strategy_names[bot]]
with open("my/apikey.json") as f:
apikeys = json.load(f)
if bot not in greetings or username not in apikeys:
print("BOT NOT FOUND")
exit(1)
APIKEY = apikeys[username]
settings_dump = ", ".join(f"{k}={v}" for k, v in ai_settings.items() if not k.startswith("_"))
print(settings_dump)
GREETING = f"Hello, play with these bots at any time by downloading KaTrain at github.com/sanderland/katrain - Current mode is {bot_strategy_names[bot]} ({greetings[bot]})"
if settings:
GREETING += f" Settings: {settings_dump}."
BYEMSG = "Thank you for playing. If you have any feedback, please message my admin! "
cmd = f'{GTP2OGS} --debug --apikey {APIKEY} --rejectnewfile ~/shutdown_bots --username {username} --greeting "{GREETING}" --farewell "{BYEMSG}" {BOT_SETTINGS} --farewellscore --aichat --noclock --nopause --speeds blitz,live --persist --minrank 25k -- python bots/ai2gtp.py {bot} {port}'
print(f"starting bot {username} using server port {port} --> {cmd}")
os.system(cmd)