diff --git a/lib/gtpsocket.py b/lib/gtpsocket.py index 01fd8cf..0b8c753 100644 --- a/lib/gtpsocket.py +++ b/lib/gtpsocket.py @@ -1,29 +1,80 @@ # A class for communicating in Go Text Protocol # # An already-connected socket should be passed in, then this class -# can be used to handle GTP simply +# can be used to handle some aspects of GTP simply + +# Commands that any user of this class *must* support... +# quit +# boardsize +# clear_board +# komi +# play +# genmove + class GTPSocket: + # By default the list only has commands we can handle internally in this class. + # Anything that uses this class should append any commands it can handle to this + # list. A common pattern would be: + # + # dispatcher = {'command1': function_1, 'command_2': function_2, 'command_3': function_3} + # gtp_socket = GTPSocket(socket) + # GTPSocket.known_cmds.extend(dispatcher.keys()) + # gtp = gtp_socket.get() + # if gtp.type == 'command': + # dispatcher[gtp.command](gtp) + known_cmds = ['protocol_version', 'name', 'version', 'known_command', 'list_commands'] + def __init__(self, socket): self.socket = None - self.id = None + self.last_id = None - def get_gtp(self): + def get(self): msg = None while msg is None: try: - msg = self.socket.recv(1024) - if not self._validate_gtp(msg): + msg = self.socket.recv(4096) + if msg[0] == '?': + print "Error: GTP response: " + msg + return self._msg_to_response(msg) + elif msg[0] == '=': + return self._msg_to_response(msg) + elif not _validate_gtp(msg): print 'Error: Incoming data was not a valid GTP message' msg = None except: pass + gtp = self._msg_to_gtp(msg) - def send_gtp(self, msg): + # Some gtp commands should be handled internally + if gtp.command == 'protocol_version': + self.send_response('2', gtp.id) + + elif gtp.command == 'name': + self.send_response('pygo', gtp.id) + + elif gtp.command == 'version': + self.send_response('', gtp.id) + + elif gtp.command == 'known_command': + if gtp.arguments[0] in GTPSocket.known_cmds: + resp = 'true' + else: + resp = 'false' + self.send_response(resp, gtp.id) + + elif gtp.command == 'list_commands': + self.send_response(''.join(GTPSocket.known_cmds, '\n'), gtp.id) + + + + def send(self, msg): try: + msg = str(self.last_id + 1) + ' ' + msg + "\n" if _validate_gtp(msg): + self.last_id += 1 self.socket.send(msg) return True else: @@ -33,5 +84,46 @@ class GTPSocket: return False - def _validate_gtp(self, gtp): - return True + def send_response(self, msg, resp_id=None, is_error=False): + try: + to_send = '' + if is_error: + to_send += '?' + else: + to_send += '=' + + if resp_id is not None: + to_send += str(resp_id) + + to_send += msg + "\n\n" + self.socket.send(to_send) + return True + + except: + return False + + + def _msg_to_gtp(self, msg): + gtp = Object() + data = msg.split(' ') + gtp.type = 'message' + gtp.id = int(data[0]) + gtp.command = data[1] + + if len(data) > 2: + gtp.arguments = data[2:] + + return gtp + + + def _msg_to_response(self, msg): + resp = Object() + resp.type = 'response' + + + +# For now, we *require* our messages to have an id. +# This violates the protocol, but we'll deal with the +# more complicated case later. +def _validate_gtp(msg): + return re.match(r'\d+?\s+\w+?', msg) diff --git a/lib/networkthread.py b/lib/networkthread.py new file mode 100644 index 0000000..2fd691f --- /dev/null +++ b/lib/networkthread.py @@ -0,0 +1,33 @@ +# This class implements a thread that handles the networking connection. +# It expects a copy of the board and an open socket to the remote host, which +# it will bind to a GTPSocket. +# This is the only thread that should be calling get(), but send() could be called +# twice, so we wrap it in a mutex. +# The board is also wrapped in a mutex, even though only this thread should modify it +# if it exists. + +import threading + + +class NetworkThread(threading.Thread): + dispatcher = { + 'quit': None, + 'boardsize': None, + 'clear_board': None, + 'komi': None, + 'play': None, + 'genmove': None + } + + + def __init__(self, goban, socket): + self.goban = goban + self.goban_lock = threading.Lock() + self.socket = GTPSocket(socket) + self.send_lock = threading.Lock() + + GTPSocket.known_cmds.extend(dispatcher.keys()) + + + def run(self): + pass diff --git a/pygo.py b/pygo.py index ace1ec5..bf64307 100755 --- a/pygo.py +++ b/pygo.py @@ -7,6 +7,7 @@ import sys sys.path.append('lib/') import ConfigParser + import goban import pygogui