Merge branch 'master' of www.github.com:sanderland/katrain
This commit is contained in:
commit
e0d800895f
7 files changed
+66
-45
No files matched your search
@@ -13,6 +13,8 @@ sgf_ogs
|
|||||||
log*
|
log*
|
||||||
tmp.pickle
|
tmp.pickle
|
||||||
my
|
my
|
||||||
|
logs
|
||||||
|
callgrind.*
|
||||||
|
|
||||||
# debug
|
# debug
|
||||||
outdated_log.txt
|
outdated_log.txt
|
||||||
|
|||||||
+21
-4
@@ -2,6 +2,7 @@
|
|||||||
import json
|
import json
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import random
|
||||||
|
|
||||||
from core.ai import ai_move
|
from core.ai import ai_move
|
||||||
from core.common import OUTPUT_ERROR, OUTPUT_INFO
|
from core.common import OUTPUT_ERROR, OUTPUT_INFO
|
||||||
@@ -85,7 +86,6 @@ def malkovich_analysis(cn):
|
|||||||
|
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
p = game.current_node.next_player
|
|
||||||
line = input()
|
line = input()
|
||||||
logger.log(f"GOT INPUT {line}", OUTPUT_ERROR)
|
logger.log(f"GOT INPUT {line}", OUTPUT_ERROR)
|
||||||
if "boardsize" in line:
|
if "boardsize" in line:
|
||||||
@@ -103,8 +103,14 @@ while True:
|
|||||||
logger.log(f"Setting komi {game.root.properties}", OUTPUT_ERROR)
|
logger.log(f"Setting komi {game.root.properties}", OUTPUT_ERROR)
|
||||||
elif "place_free_handicap" in line:
|
elif "place_free_handicap" in line:
|
||||||
_, n = line.split(" ")
|
_, n = line.split(" ")
|
||||||
game.place_handicap_stones(int(n))
|
n = int(n)
|
||||||
gtp = [Move.from_sgf(m, game.board_size, "B").gtp() for m in game.root.get_list_property("AB")]
|
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))
|
||||||
|
gtp = [Move.from_sgf(m, game.board_size, "B").gtp() for m in handicaps]
|
||||||
logger.log(f"Chose handicap placements as {gtp}", OUTPUT_ERROR)
|
logger.log(f"Chose handicap placements as {gtp}", OUTPUT_ERROR)
|
||||||
print(f"= {' '.join(gtp)}\n")
|
print(f"= {' '.join(gtp)}\n")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
@@ -115,6 +121,12 @@ while True:
|
|||||||
game.root.set_property("AB", [Move.from_gtp(move.upper()).sgf(game.board_size) for move in stones])
|
game.root.set_property("AB", [Move.from_gtp(move.upper()).sgf(game.board_size) for move in stones])
|
||||||
logger.log(f"Set handicap placements to {game.root.get_list_property('AB')}", OUTPUT_ERROR)
|
logger.log(f"Set handicap placements to {game.root.get_list_property('AB')}", OUTPUT_ERROR)
|
||||||
elif "genmove" in line:
|
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)
|
logger.log(f"{ai_strategy} generating move", OUTPUT_ERROR)
|
||||||
game.current_node.analyze(engine)
|
game.current_node.analyze(engine)
|
||||||
malkovich_analysis(game.current_node)
|
malkovich_analysis(game.current_node)
|
||||||
@@ -130,7 +142,12 @@ while True:
|
|||||||
move = game.play(Move(None, player=game.next_player)).single_move
|
move = game.play(Move(None, player=game.next_player)).single_move
|
||||||
else:
|
else:
|
||||||
move, node = ai_move(game, ai_strategy, ai_settings)
|
move, node = ai_move(game, ai_strategy, ai_settings)
|
||||||
logger.log(f"Generated move {move}", OUTPUT_ERROR)
|
if node is None:
|
||||||
|
while node is None:
|
||||||
|
logger.log(f"ERROR generating move, backing up with weighted.", OUTPUT_ERROR)
|
||||||
|
move, node = ai_move(game, "p:weighted", {"pick_override": 1.0, "lower_bound": 0.001, "weaken_fac": 1})
|
||||||
|
else:
|
||||||
|
logger.log(f"Generated move {move}", OUTPUT_ERROR)
|
||||||
print(f"= {move.gtp()}\n")
|
print(f"= {move.gtp()}\n")
|
||||||
sys.stdout.flush()
|
sys.stdout.flush()
|
||||||
malkovich_analysis(game.current_node)
|
malkovich_analysis(game.current_node)
|
||||||
|
|||||||
+26
-24
@@ -34,8 +34,7 @@ def ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, GameNode
|
|||||||
raise EngineDiedException(f"Engine for {cn.next_player} ({engine.config}) died")
|
raise EngineDiedException(f"Engine for {cn.next_player} ({engine.config}) died")
|
||||||
ai_mode = ai_mode.lower()
|
ai_mode = ai_mode.lower()
|
||||||
ai_thoughts = ""
|
ai_thoughts = ""
|
||||||
candidate_ai_moves = cn.candidate_moves
|
if ("policy" in ai_mode or "p:" in ai_mode) and cn.policy: # pure policy based move
|
||||||
if ("policy" in ai_mode or "p:" in ai_mode) and cn.policy:
|
|
||||||
policy_moves = cn.policy_ranking
|
policy_moves = cn.policy_ranking
|
||||||
pass_policy = cn.policy[-1]
|
pass_policy = cn.policy[-1]
|
||||||
top_5_pass = any([polmove[1].is_pass for polmove in policy_moves[:5]]) # dont make it jump around for the last few sensible non pass moves
|
top_5_pass = any([polmove[1].is_pass for polmove in policy_moves[:5]]) # dont make it jump around for the last few sensible non pass moves
|
||||||
@@ -131,28 +130,30 @@ def ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, GameNode
|
|||||||
ai_thoughts += f"Pick policy strategy {ai_mode} failed to find legal moves, so is playing top policy move {aimove.gtp()}."
|
ai_thoughts += f"Pick policy strategy {ai_mode} failed to find legal moves, so is playing top policy move {aimove.gtp()}."
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unknown AI mode {ai_mode}")
|
raise ValueError(f"Unknown AI mode {ai_mode}")
|
||||||
elif "balance" in ai_mode and candidate_ai_moves[0]["move"] != "pass": # don't play suicidal to balance score - pass when it's best
|
else: # Engine based move
|
||||||
sign = cn.player_sign(cn.next_player)
|
candidate_ai_moves = cn.candidate_moves
|
||||||
sel_moves = [ # top move, or anything not too bad, or anything that makes you still ahead
|
if "balance" in ai_mode and candidate_ai_moves[0]["move"] != "pass": # don't play suicidal to balance score - pass when it's best
|
||||||
move
|
sign = cn.player_sign(cn.next_player)
|
||||||
for i, move in enumerate(candidate_ai_moves)
|
sel_moves = [ # top move, or anything not too bad, or anything that makes you still ahead
|
||||||
if i == 0
|
move
|
||||||
or move["visits"] >= ai_settings["min_visits"]
|
for i, move in enumerate(candidate_ai_moves)
|
||||||
and (move["pointsLost"] < ai_settings["random_loss"] or move["pointsLost"] < ai_settings["max_loss"] and sign * move["scoreLead"] > ai_settings["target_score"])
|
if i == 0
|
||||||
]
|
or move["visits"] >= ai_settings["min_visits"]
|
||||||
aimove = Move.from_gtp(random.choice(sel_moves)["move"], player=cn.next_player)
|
and (move["pointsLost"] < ai_settings["random_loss"] or move["pointsLost"] < ai_settings["max_loss"] and sign * move["scoreLead"] > ai_settings["target_score"])
|
||||||
ai_thoughts += f"Balance strategy selected moves {sel_moves} based on target score and max points lost, and randomly chose {aimove.gtp()}."
|
]
|
||||||
elif "jigo" in ai_mode and candidate_ai_moves[0]["move"] != "pass":
|
aimove = Move.from_gtp(random.choice(sel_moves)["move"], player=cn.next_player)
|
||||||
sign = cn.player_sign(cn.next_player)
|
ai_thoughts += f"Balance strategy selected moves {sel_moves} based on target score and max points lost, and randomly chose {aimove.gtp()}."
|
||||||
jigo_move = min(candidate_ai_moves, key=lambda move: abs(sign * move["scoreLead"] - ai_settings["target_score"]))
|
elif "jigo" in ai_mode and candidate_ai_moves[0]["move"] != "pass":
|
||||||
aimove = Move.from_gtp(jigo_move["move"], player=cn.next_player)
|
sign = cn.player_sign(cn.next_player)
|
||||||
ai_thoughts += f"Jigo strategy found candidate moves {candidate_ai_moves} moves and chose {aimove.gtp()} as closest to 0.5 point win"
|
jigo_move = min(candidate_ai_moves, key=lambda move: abs(sign * move["scoreLead"] - ai_settings["target_score"]))
|
||||||
else:
|
aimove = Move.from_gtp(jigo_move["move"], player=cn.next_player)
|
||||||
if "default" not in ai_mode and "katago" not in ai_mode:
|
ai_thoughts += f"Jigo strategy found candidate moves {candidate_ai_moves} moves and chose {aimove.gtp()} as closest to 0.5 point win"
|
||||||
game.katrain.log(f"Unknown AI mode {ai_mode} or policy missing, using default.", OUTPUT_INFO)
|
else:
|
||||||
ai_thoughts += f"Strategy {ai_mode} not found or unexpected fallback."
|
if "default" not in ai_mode and "katago" not in ai_mode:
|
||||||
aimove = Move.from_gtp(candidate_ai_moves[0]["move"], player=cn.next_player)
|
game.katrain.log(f"Unknown AI mode {ai_mode} or policy missing, using default.", OUTPUT_INFO)
|
||||||
ai_thoughts += f"Default strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move"
|
ai_thoughts += f"Strategy {ai_mode} not found or unexpected fallback."
|
||||||
|
aimove = Move.from_gtp(candidate_ai_moves[0]["move"], player=cn.next_player)
|
||||||
|
ai_thoughts += f"Default strategy found {len(candidate_ai_moves)} moves returned from the engine and chose {aimove.gtp()} as top move"
|
||||||
game.katrain.log(f"AI thoughts: {ai_thoughts}", OUTPUT_DEBUG)
|
game.katrain.log(f"AI thoughts: {ai_thoughts}", OUTPUT_DEBUG)
|
||||||
try:
|
try:
|
||||||
played_node = game.play(aimove)
|
played_node = game.play(aimove)
|
||||||
@@ -160,3 +161,4 @@ def ai_move(game: Game, ai_mode: str, ai_settings: Dict) -> Tuple[Move, GameNode
|
|||||||
return aimove, played_node
|
return aimove, played_node
|
||||||
except IllegalMoveException as e:
|
except IllegalMoveException as e:
|
||||||
game.katrain.log(f"AI Strategy {ai_mode} generated illegal move {aimove.gtp()}: {e}", OUTPUT_ERROR)
|
game.katrain.log(f"AI Strategy {ai_mode} generated illegal move {aimove.gtp()}: {e}", OUTPUT_ERROR)
|
||||||
|
return None, None
|
||||||
+1
-1
@@ -113,7 +113,7 @@ class KataGoEngine:
|
|||||||
query["id"] = f"QUERY:{str(self.query_counter)}"
|
query["id"] = f"QUERY:{str(self.query_counter)}"
|
||||||
self.queries[query["id"]] = (callback, error_callback, time.time(), next_move)
|
self.queries[query["id"]] = (callback, error_callback, time.time(), next_move)
|
||||||
if self.katago_process:
|
if self.katago_process:
|
||||||
self.katrain.log(f"Sending query {query['id']}: {str(query)}", OUTPUT_DEBUG)
|
self.katrain.log(f"Sending query {query['id']}: {json.dumps(query)}", OUTPUT_DEBUG)
|
||||||
try:
|
try:
|
||||||
self.katago_process.stdin.write((json.dumps(query) + "\n").encode())
|
self.katago_process.stdin.write((json.dumps(query) + "\n").encode())
|
||||||
self.katago_process.stdin.flush()
|
self.katago_process.stdin.flush()
|
||||||
|
|||||||
+3
-5
@@ -154,10 +154,8 @@ class Game:
|
|||||||
|
|
||||||
def place_handicap_stones(self, n_handicaps):
|
def place_handicap_stones(self, n_handicaps):
|
||||||
board_size_x, board_size_y = self.board_size
|
board_size_x, board_size_y = self.board_size
|
||||||
near_x = 3 if board_size_x >= 13 else 2
|
near_x = 3 if board_size_x >= 13 else min(2, board_size_x - 1)
|
||||||
near_y = 3 if board_size_y >= 13 else 2
|
near_y = 3 if board_size_y >= 13 else min(2, board_size_y - 1)
|
||||||
if board_size_x < 3 or board_size_y < 3:
|
|
||||||
return
|
|
||||||
far_x = board_size_x - 1 - near_x
|
far_x = board_size_x - 1 - near_x
|
||||||
far_y = board_size_y - 1 - near_y
|
far_y = board_size_y - 1 - near_y
|
||||||
middle_x = board_size_x // 2 # what for even sizes?
|
middle_x = board_size_x // 2 # what for even sizes?
|
||||||
@@ -176,7 +174,7 @@ class Game:
|
|||||||
if n_handicaps % 2 == 1:
|
if n_handicaps % 2 == 1:
|
||||||
stones.append((middle_x, middle_y))
|
stones.append((middle_x, middle_y))
|
||||||
stones += [(near_x, middle_y), (far_x, middle_y), (middle_x, near_y), (middle_x, far_y)]
|
stones += [(near_x, middle_y), (far_x, middle_y), (middle_x, near_y), (middle_x, far_y)]
|
||||||
self.root.set_property("AB", [Move(stone).sgf(board_size=(board_size_x, board_size_y)) for stone in stones[:n_handicaps]])
|
self.root.set_property("AB", list({Move(stone).sgf(board_size=(board_size_x, board_size_y)) for stone in stones[:n_handicaps]}))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def board_size(self):
|
def board_size(self):
|
||||||
|
|||||||
+12
-9
@@ -9,7 +9,7 @@ class ParseError(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class Move:
|
class Move:
|
||||||
GTP_COORD = list("ABCDEFGHJKLMNOPQRSTUVWXYZ") + ["A" + c for c in "ABCDEFGHJKLMNOPQRSTUVWXYZ"] # kata board size 29 support
|
GTP_COORD = list("ABCDEFGHJKLMNOPQRSTUVWXYZ") + [xa + c for xa in "AB" for c in "ABCDEFGHJKLMNOPQRSTUVWXYZ"] # kata board size 29 support
|
||||||
PLAYERS = "BW"
|
PLAYERS = "BW"
|
||||||
SGF_COORD = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ".lower()) + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
SGF_COORD = list("ABCDEFGHIJKLMNOPQRSTUVWXYZ".lower()) + list("ABCDEFGHIJKLMNOPQRSTUVWXYZ")
|
||||||
|
|
||||||
@@ -79,10 +79,11 @@ class SGFNode:
|
|||||||
|
|
||||||
def sgf(self, **xargs) -> str:
|
def sgf(self, **xargs) -> str:
|
||||||
"""Generates an SGF, calling sgf_properties on each node with the given xargs, so it can filter relevant properties if needed."""
|
"""Generates an SGF, calling sgf_properties on each node with the given xargs, so it can filter relevant properties if needed."""
|
||||||
import sys
|
if self.is_root:
|
||||||
|
import sys
|
||||||
|
|
||||||
bszx, bszy = self.board_size
|
bszx, bszy = self.board_size
|
||||||
sys.setrecursionlimit(max(sys.getrecursionlimit(), 3 * bszx * bszy)) # thanks to lightvector for causing stack overflows ;)
|
sys.setrecursionlimit(max(sys.getrecursionlimit(), 4 * bszx * bszy))
|
||||||
sgf_str = "".join([prop + "".join(f"[{v}]" for v in values) for prop, values in self.sgf_properties(**xargs).items() if values])
|
sgf_str = "".join([prop + "".join(f"[{v}]" for v in values) for prop, values in self.sgf_properties(**xargs).items() if values])
|
||||||
if self.children:
|
if self.children:
|
||||||
children = [c.sgf(**xargs) for c in self.order_children(self.children)]
|
children = [c.sgf(**xargs) for c in self.order_children(self.children)]
|
||||||
@@ -211,15 +212,17 @@ class SGFNode:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def next_player(self):
|
def next_player(self):
|
||||||
if "B" in self.properties or "AB" in self.properties:
|
if "B" in self.properties or "AB" in self.properties: # root or black moved
|
||||||
return "W"
|
return "W"
|
||||||
return "B"
|
else:
|
||||||
|
return "B"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def player(self):
|
def player(self):
|
||||||
if "W" in self.properties:
|
if "B" in self.properties or "AB" in self.properties:
|
||||||
return "W"
|
return "B"
|
||||||
return "B"
|
else:
|
||||||
|
return "W" # nb root is considered white played if no handicap stones are placed
|
||||||
|
|
||||||
|
|
||||||
class SGF:
|
class SGF:
|
||||||
|
|||||||
+1
-2
@@ -173,13 +173,12 @@ class ConfigPopup(QuickConfigGui):
|
|||||||
old_proc = old_engine.katago_process
|
old_proc = old_engine.katago_process
|
||||||
if old_proc:
|
if old_proc:
|
||||||
old_engine.shutdown(finish=True)
|
old_engine.shutdown(finish=True)
|
||||||
|
|
||||||
new_engine = KataGoEngine(self.katrain, self.config["engine"])
|
new_engine = KataGoEngine(self.katrain, self.config["engine"])
|
||||||
self.katrain.engine = new_engine
|
self.katrain.engine = new_engine
|
||||||
self.katrain.game.engines = {"B": new_engine, "W": new_engine}
|
self.katrain.game.engines = {"B": new_engine, "W": new_engine}
|
||||||
if not old_proc:
|
if not old_proc:
|
||||||
self.katrain.game.analyze_all_nodes() # old engine was broken, so make sure we redo any failures
|
self.katrain.game.analyze_all_nodes() # old engine was broken, so make sure we redo any failures
|
||||||
|
self.katrain.update_state()
|
||||||
Clock.schedule_once(restart_engine, 0)
|
Clock.schedule_once(restart_engine, 0)
|
||||||
|
|
||||||
self.katrain.debug_level = self.config["debug"]["level"]
|
self.katrain.debug_level = self.config["debug"]["level"]
|
||||||
|
|||||||
Reference in new issue
Block a user