263 lines
6.4 KiB
Python
263 lines
6.4 KiB
Python
import re
|
|
|
|
|
|
def pre_engine(s):
|
|
s = re.sub("[^\t\n -~]", "", s)
|
|
s = s.split("#")[0]
|
|
s = s.replace("\t", " ")
|
|
return s
|
|
|
|
|
|
def pre_controller(s):
|
|
s = re.sub("[^\t\n -~]", "", s)
|
|
s = s.replace("\t", " ")
|
|
return s
|
|
|
|
|
|
def gtp_boolean(b):
|
|
return "true" if b else "false"
|
|
|
|
|
|
def gtp_list(l):
|
|
return "\n".join(l)
|
|
|
|
|
|
def gtp_color(color):
|
|
# an arbitrary choice amongst a number of possibilities
|
|
return {BLACK: "B", WHITE: "W"}[color]
|
|
|
|
|
|
def gtp_vertex(vertex):
|
|
if vertex == PASS:
|
|
return "pass"
|
|
elif vertex == RESIGN:
|
|
return "resign"
|
|
else:
|
|
x, y = vertex
|
|
return "{}{}".format("ABCDEFGHJKLMNOPQRSTYVWYZ"[x - 1], y)
|
|
|
|
|
|
def gtp_move(color, vertex):
|
|
return " ".join([gtp_color(color), gtp_vertex(vertex)])
|
|
|
|
|
|
def parse_message(message):
|
|
message = pre_engine(message).strip()
|
|
first, rest = (message.split(" ", 1) + [None])[:2]
|
|
if first.isdigit():
|
|
message_id = int(first)
|
|
if rest is not None:
|
|
command, arguments = (rest.split(" ", 1) + [None])[:2]
|
|
else:
|
|
command, arguments = None, None
|
|
else:
|
|
message_id = None
|
|
command, arguments = first, rest
|
|
|
|
return message_id, command, arguments
|
|
|
|
|
|
WHITE = -1
|
|
BLACK = +1
|
|
EMPTY = 0
|
|
|
|
PASS = (0, 0)
|
|
RESIGN = "resign"
|
|
|
|
|
|
def parse_color(color):
|
|
if color.lower() in ["b", "black"]:
|
|
return BLACK
|
|
elif color.lower() in ["w", "white"]:
|
|
return WHITE
|
|
else:
|
|
return False
|
|
|
|
|
|
def parse_vertex(vertex_string):
|
|
if vertex_string is None:
|
|
return False
|
|
elif vertex_string.lower() == "pass":
|
|
return PASS
|
|
elif len(vertex_string) > 1:
|
|
x = "abcdefghjklmnopqrstuvwxyz".find(vertex_string[0].lower()) + 1
|
|
if x == 0:
|
|
return False
|
|
if vertex_string[1:].isdigit():
|
|
y = int(vertex_string[1:])
|
|
else:
|
|
return False
|
|
else:
|
|
return False
|
|
return (x, y)
|
|
|
|
|
|
def parse_move(move_string):
|
|
color_string, vertex_string = (move_string.split(" ") + [None])[:2]
|
|
color = parse_color(color_string)
|
|
if color is False:
|
|
return False
|
|
vertex = parse_vertex(vertex_string)
|
|
if vertex is False:
|
|
return False
|
|
|
|
return color, vertex
|
|
|
|
|
|
MIN_BOARD_SIZE = 7
|
|
MAX_BOARD_SIZE = 19
|
|
|
|
|
|
def format_success(message_id, response=None):
|
|
if response is None:
|
|
response = ""
|
|
else:
|
|
response = " {}".format(response)
|
|
if message_id:
|
|
return "={}{}\n\n".format(message_id, response)
|
|
else:
|
|
return "={}\n\n".format(response)
|
|
|
|
|
|
def format_error(message_id, response):
|
|
if response:
|
|
response = " {}".format(response)
|
|
if message_id:
|
|
return "?{}{}\n\n".format(message_id, response)
|
|
else:
|
|
return "?{}\n\n".format(response)
|
|
|
|
|
|
class Engine(object):
|
|
def __init__(self, game_obj, name="gtp (python library)", version="0.2"):
|
|
|
|
self.size = 19
|
|
self.komi = 6.5
|
|
|
|
self._game = game_obj
|
|
self._game.clear()
|
|
|
|
self._name = name
|
|
self._version = version
|
|
|
|
self.disconnect = False
|
|
|
|
self.known_commands = [
|
|
field[4:] for field in dir(self) if field.startswith("cmd_")]
|
|
|
|
def send(self, message):
|
|
message_id, command, arguments = parse_message(message)
|
|
if command in self.known_commands:
|
|
try:
|
|
return format_success(
|
|
message_id, getattr(self, "cmd_" + command)(arguments))
|
|
except ValueError as exception:
|
|
return format_error(message_id, exception.args[0])
|
|
else:
|
|
return format_error(message_id, "unknown command")
|
|
|
|
def vertex_in_range(self, vertex):
|
|
if vertex == PASS:
|
|
return True
|
|
if 1 <= vertex[0] <= self.size and 1 <= vertex[1] <= self.size:
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
# commands
|
|
|
|
def cmd_protocol_version(self, arguments):
|
|
return 2
|
|
|
|
def cmd_name(self, arguments):
|
|
return self._name
|
|
|
|
def cmd_version(self, arguments):
|
|
return self._version
|
|
|
|
def cmd_known_command(self, arguments):
|
|
return gtp_boolean(arguments in self.known_commands)
|
|
|
|
def cmd_list_commands(self, arguments):
|
|
return gtp_list(self.known_commands)
|
|
|
|
def cmd_quit(self, arguments):
|
|
self.disconnect = True
|
|
|
|
def cmd_boardsize(self, arguments):
|
|
if arguments.isdigit():
|
|
size = int(arguments)
|
|
if MIN_BOARD_SIZE <= size <= MAX_BOARD_SIZE:
|
|
self.size = size
|
|
self._game.set_size(size)
|
|
else:
|
|
raise ValueError("unacceptable size")
|
|
else:
|
|
raise ValueError("non digit size")
|
|
|
|
def cmd_clear_board(self, arguments):
|
|
self._game.clear()
|
|
|
|
def cmd_komi(self, arguments):
|
|
try:
|
|
komi = float(arguments)
|
|
self.komi = komi
|
|
self._game.set_komi(komi)
|
|
except ValueError:
|
|
raise ValueError("syntax error")
|
|
|
|
def cmd_play(self, arguments):
|
|
move = parse_move(arguments)
|
|
if move:
|
|
color, vertex = move
|
|
if self.vertex_in_range(vertex):
|
|
if self._game.make_move(color, vertex):
|
|
return
|
|
raise ValueError("illegal move")
|
|
|
|
def cmd_genmove(self, arguments):
|
|
c = parse_color(arguments)
|
|
if c:
|
|
move = self._game.get_move(c)
|
|
self._game.make_move(c, move)
|
|
return gtp_vertex(move)
|
|
else:
|
|
raise ValueError("unknown player: {}".format(arguments))
|
|
|
|
|
|
class MinimalGame(object):
|
|
def __init__(self, size=19, komi=6.5):
|
|
self.size = size
|
|
self.komi = 6.5
|
|
self.board = [EMPTY] * (self.size * self.size)
|
|
|
|
def _flatten(self, vertex):
|
|
(x, y) = vertex
|
|
return (x - 1) * self.size + (y - 1)
|
|
|
|
def clear(self):
|
|
self.board = [EMPTY] * (self.size * self.size)
|
|
|
|
def make_move(self, color, vertex):
|
|
# no legality check other than the space being empty..
|
|
# no side-effects beyond placing the stone..
|
|
if vertex == PASS:
|
|
return True # noop
|
|
idx = self._flatten(vertex)
|
|
if self.board[idx] == EMPTY:
|
|
self.board[idx] = color
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def set_size(self, n):
|
|
self.size = n
|
|
self.clear()
|
|
|
|
def set_komi(self, k):
|
|
self.komi = k
|
|
|
|
def get_move(self, color):
|
|
# pass every time. At least it's legal
|
|
return (0, 0)
|