0

I'm trying to interface with a program (GoGui - https://sourceforge.net/projects/gogui/) using the Go Text Protocol (GTP - http://www.lysator.liu.se/~gunnar/gtp/) which has its documentation here. (Link can also be found on the previous website.)

So I've written a bit of code to at least get GoGui to acknowledge the existence of my program:

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('')

Now this doesn't seem unreasonable per se, but results in GoGui giving me the following error: enter image description here

Which is of course a problem. So I figured that I made a mistake somewhere in my programming, but when I simply run the program through visual studio, everything works as expected: http://i.imgur.com/JjDWgVD.png

This makes me think that the problem lies in interfacing the two applications, and maybe I should be looking at other functions than stdin and stdout. Does anyone know what may be going wrong here?

EDIT FOR COMMENTS: The code I'm currently working on for command parsing (in its entirety) looks like this:

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()

For pre processing:

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)
2
  • Maybe use sockets?? docs.python.org/3/library/… Commented Jul 5, 2017 at 22:39
  • Both programs run on the same machine, and the protocol is expected to talk over stdin/out, so that's almost certainly what the third party program does. Note that I'm pretty new to programming and python in particular, so I may require a little more hand holding than usual when trying to explain things, sorry. Commented Jul 5, 2017 at 22:58

1 Answer 1

1

You're not flushing your response so nothing gets sent back to the caller (as the command is not big enough to trigger auto buffer flush). Also, strolling through the protocol document it clearly says that your response should be in the form of = response\n\n so even if you were flushing it probably still wouldn't work.

Try with something like:

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()

You might want to create a simple function for parsing commands / responding back instead of repeating the code, tho. Also, this probably won't (fully) work either as the protocol document states that you need to implement quite a number of commands (6.1 Required Commands) but it should get you started.

UPDATE - Here's one way to make it more manageable and in line with the specs - you can create a function for each command so you can easily add/remove them as you please, for example:

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.

Here all the command functions are prefixed with "cmd_" (and cmd_list_commands() and cmd_known_command() use that fact to check for the command functions in the global namespace) but you can also move them to a different module and then 'scan' the module instead. With such structure it's very easy to add a new command, for example to add the required quit command all you need is to define it:

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

Also, we'll deal bellow with the situation when a command needs to return an error - all you need to do from your functions is to raise ValueError("error response") and it will be sent back as an error.

Once you have your set of commands added as functions all you need is to parse the input command, call the right function with the right arguments and print back the 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

Finally, all you need is to listen to your STDIN and forward the command lines to this function to do the heavy lifting. In that regard, I'd actually explicitly read line-by-line from your STDIN rather than trying to iterate over it as it's a safer approach so:

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

Of course, as I mentioned earlier, it might be a better idea to pack your commands in a separate module, but this should at least give you an idea how to do 'separation of concerns' so you worry about responding to your commands rather than on how they get called and how they respond back to the caller.

Sign up to request clarification or add additional context in comments.

3 Comments

I totally missed the =... (was looking at 3.4 instead) But it works great like this! I did have 2 questions though: you use line.strip() in the if statements, is that to increase readability, or does it have some more important use too? And you use elif statements as opposed to if statements. Now this may be a very theoretical question, but my reasoning for using if statements was that only one equality would have to be checked, as opposed to multiple; which would increase efficiency. Does this reasoning have any merit, or what benefits does elif have?
@MitchellFaas - str.strip() clears out the whitespace on both ends. However, if the caller sent a command like: 14 name\n (a perfectly valid command) it wouldn't work as it doesn't deal with the case when an ID is present - check the update above as an example on how to properly deal with the case described in the docs. As for the if statements, you got it the wrong way around - elif/else statements evaluate only if the previous ones in the same chain didn't evaluate as True. Your approach would have all of them evaluate on each loop whether some of them evaluated as True or not.
The code you've written currently goes above my head. What my plan was is was to use a command parsing module (all the code can be found in the original post), and use a dictionary to recognise which command needs to be executed, then run that function. Today I'll be working on getting arguments set up, which I'll be attempting to split out in pre-processing.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.