optimization, bot fixes

This commit is contained in:
Sander Land committed 2020-05-03 16:22:16 +02:00
1 parent de0d99e11a
commit b9aec4b6dd
12 files changed
+69325 -37

No files matched your search

+21 -3
View File
@@ -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
@@ -103,8 +104,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 +122,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 +143,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)
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+26 -24
View File
@@ -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
+3 -5
View File
@@ -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):
+4 -3
View File
@@ -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)]
+2 -2
View File
@@ -133,7 +133,7 @@ class ConfigPopup(QuickConfigGui):
col_container.add_widget(cols[0]) col_container.add_widget(cols[0])
col_container.add_widget(cols[1]) col_container.add_widget(cols[1])
self.add_widget(col_container) self.add_widget(col_container)
self.info_label = Label(halign='center') self.info_label = Label(halign="center")
self.apply_button = StyledButton(text="Apply", on_press=lambda _: self.update_config()) self.apply_button = StyledButton(text="Apply", on_press=lambda _: self.update_config())
self.save_button = StyledButton(text="Apply and Save", on_press=lambda _: self.update_config(save_to_file=True)) self.save_button = StyledButton(text="Apply and Save", on_press=lambda _: self.update_config(save_to_file=True))
btn_container = BoxLayout(orientation="horizontal", size_hint=(1, 0.1), spacing=1, padding=1) btn_container = BoxLayout(orientation="horizontal", size_hint=(1, 0.1), spacing=1, padding=1)
@@ -170,7 +170,7 @@ class ConfigPopup(QuickConfigGui):
def restart_engine(_dt): def restart_engine(_dt):
old_engine = self.katrain.engine # type: KataGoEngine old_engine = self.katrain.engine # type: KataGoEngine
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)