如何通过已知协议将python程序与第三方程序连接?

时间:2017-07-05 21:52:20

标签: python interface

我尝试使用Go文本协议(GTP - https://sourceforge.net/projects/gogui/)与程序(GoGui - http://www.lysator.liu.se/~gunnar/gtp/)进行交互,该文档具有文档here。 (链接也可以在以前的网站上找到。)

所以我写了一些代码,至少让GoGui承认我的程序的存在:

import sys
import engine
import input_processing

for line in sys.stdin:
    if line == 'name\n':
        sys.stdout.write(' Muizz-Bot\n\n')
    if line == 'protocol_version\n':
        sys.stdout.write('2\n\n')
    if line == 'version\n':
        sys.stdout.write('')

现在这本身似乎不合理,但GoGui导致我出现以下错误: enter image description here

这当然是一个问题。所以我认为我在编程中的某个地方犯了一个错误,但是当我只是通过visual studio运行程序时,一切都按预期工作: http://i.imgur.com/JjDWgVD.png

这让我觉得问题在于连接两个应用程序,也许我应该看看除stdin和stdout之外的其他功能。有谁知道这里可能出了什么问题?

编辑评论:目前正在进行命令解析的代码(完整版)如下所示:

import sys

commands = ['protocol_version', 'name', 'version', 'list_commands', 'known_command', 'quit', 'boardsize', 
            'clear_board', 'komi', 'play', 'genmove']

pre_game_out = ['2','Muizz-Bot','']

# Define all output functions
def list_commands():
    out = '\n'.join(commands)
    return(out)
def known():
    return(True)
def quit():
    return(None)
def boardsize():
    return(None)
def clear_board():
    return(None)
def komi():
    return(None)
def play():
    return(None)
def genmove():
    return("A1")

# Create dictionary to point to all functions.
output = {'list_commands':list_commands, 'known_command':known, 'quit':quit, 'boardsize':boardsize, 
            'clear_board':clear_board, 'komi':komi, 'play':play, 'genmove':genmove}

# Define the function that will pass the commands and write outputs.
def parse(line):
    if line.strip() in commands:
        i = commands.index(line.strip())
        if i<3:
            sys.stdout.write('= '+ pre_game_out[i]+'\n\n')
            sys.stdout.flush()
        else:
            sys.stdout.write('= ' + output[line.strip()]() + '\n\n')
            sys.stdout.flush()

对于预处理:

def input(inp):
    # Remove control characters
    inp = inp.replace('\r', '')
    inp = inp.replace(' ', '')
    inp = inp.split('#', 1)[0]
    inp = inp.replace('\t', ' ')

    # Check if empty
    if inp.isspace() or inp==None or inp=='':
        return
    else:
        return(inp)

1 个答案:

答案 0 :(得分:1)

你没有刷新你的响应,所以没有任何东西被发送回调用者(因为命令不足以触发自动缓冲区刷新)。此外,漫步协议文件清楚地表明你的回复应该是= response\n\n的形式,所以即使你正在冲洗它可能仍然无法正常工作。

尝试使用以下内容:

import sys

for line in sys.stdin:
    if line.strip() == 'name':
        sys.stdout.write('= Muizz-Bot\n\n')
        sys.stdout.flush()
    elif line.strip() == 'protocol_version':
        sys.stdout.write('= 2\n\n')
        sys.stdout.flush()
    elif line.strip() == 'version':
        sys.stdout.write('=\n\n')
        sys.stdout.flush()

你可能想要创建一个简单的函数来解析命令/响应而不是重复代码。此外,这可能不会(完全)工作,因为协议文档声明您需要实现相当多的命令(6.1 Required Commands),但它应该让您开始。

更新 - 这是使其更易于管理且符合规范的一种方法 - 您可以为每个命令创建一个功能,以便您可以根据需要轻松添加/删除它们,例如:

def cmd_name(*args):
    return "Muizz-Bot"

def cmd_protocol_version(*args):
    return 2

def cmd_version(*args):
    return ""

def cmd_list_commands(*args):
    return " ".join(x[4:] for x in globals() if x[:4] == "cmd_")

def cmd_known_command(*args):
    commands = {x[4:] for x in globals() if x[:4] == "cmd_"}
    return "true" if args and args[0] in commands else "false"

# etc.

此处所有命令函数都以“cmd_”为前缀(cmd_list_commands()cmd_known_command()使用该事实检查全局命名空间中的命令函数)但您也可以将它们移动到另一个模块,然后“扫描”模块。使用这样的结构,添加新命令非常容易,例如添加所需的quit命令,只需要定义它:

def cmd_quit(*args):
    raise EOFError()  # we'll use EOFError to denote an exit state bellow

此外,当命令需要返回错误时,我们将处理以下情况 - 您需要执行的所有功能都是raise ValueError("error response"),并且它将作为错误发回。

一旦你将一组命令添加为函数,你只需要解析输入命令,用正确的参数调用正确的函数并打印回复:

def call_command(command):
    command = "".join(x for x in command if 31 < ord(x) < 127 or x == "\t")  # 3.1.1
    command = command.strip()  # 3.1.4
    if not command:  # ... return if there's nothing to do
        return
    command = command.split()  # split to get the [id], cmd, [arg1, arg2, ...] structure
    try:  # try to convert to int the first slice to check for command ID
        command_id = int(command[0])
        command_args = command[2:] if len(command) > 2 else []  # args or an empty list
        command = command[1]  # command name
    except ValueError:  # failed, no command ID present
        command_id = ""  # set it to blank
        command_args = command[1:] if len(command) > 1 else []  # args or an empty list
        command = command[0]  # command name
    # now, lets try to call our command as cmd_<command name> function and get its response
    try:
        response = globals()["cmd_" + command](*command_args)
        if response != "":  # response not empty, prepend it with space as per 3.4
            response = " {}".format(response)
        sys.stdout.write("={}{}\n\n".format(command_id, response))
    except KeyError:  # unknown command, return standard error as per 3.6
        sys.stdout.write("?{} unknown command\n\n".format(command_id))
    except ValueError as e:  # the called function raised a ValueError
        sys.stdout.write("?{} {}\n\n".format(command_id, e))
    except EOFError:  # a special case when we need to quit
        sys.stdout.write("={}\n\n".format(command_id))
        sys.stdout.flush()
        sys.exit(0)
    sys.stdout.flush()  # flush the STDOUT

最后,您只需要收听STDIN并将命令行转发到此功能即可完成繁重的工作。在这方面,我实际上明确地从你的STDIN逐行读取,而不是试图迭代它,因为它是一种更安全的方法,所以:

if __name__ == "__main__":  # make sure we're executing instead of importing this script
    while True:  # main loop
        try:
            line = sys.stdin.readline()  # read a line from STDIN
            if not line:  # reached the end of STDIN
                break  # exit the main loop
            call_command(line)  # call our command
        except Exception:  # too broad, but we don't care at this point as we're exiting
            break  # exit the main loop

当然,正如我之前提到的,将命令打包在一个单独的模块中可能更好一点,但这至少应该让您知道如何“分离关注点”,这样您就会担心回复命令而不是它们如何被调用以及它们如何响应调用者。