Building a Simple AI with the Sente GTP Module

One of the required GTP commands that all programs that implement GTP are required to implement is the genmove command. This command asks the associated engine to make a move. Sente provides utilities that allow you to change the functionality of the genmove command with custom code.

In this tutorial, we will learn how to override genmove and use it to create a simple GTP AI that plays random moves using sente.

Setting Up the Python file

To begin, let’s set up a GTP interpreter in a python file. We will copy most of the code from the previous section Creating an Interactive GTP Shell.

 1"""
 2
 3Author: Arthur Wesley
 4
 5"""
 6
 7from sente import GTP
 8
 9
10def main():
11    """
12
13    main method
14
15    """
16
17    session = GTP.Session("random_move", "0.0.1")
18
19    while session.active():
20
21        response = session.interpret(input(""))
22        print(response)
23
24
25if __name__ == "__main__":
26    main()

The above is equivalent to the code in the aforementioned section, only with proper python formatting. Running the file will open a GTP session.

(venv) $ python random_move.py
# begin executing commands

However, so far we haven’t changed the functionality of the genmove command. Trying to call the genmove command with a GTP session where no GenMove has been registered will cause an exception:

(venv) $ python random_move.py
genmove B
Traceback (most recent call last):
  File "random_move.py", line 26, in <module>
    main()
  File "random_move.py", line 21, in main
    response = session.interpret(input(""))
RuntimeError: genmove has not been implemented by this engine, please register a valid function

Overwriting Genmove

The genmove command is overwritten by adding the Session.GenMove decorator to the function, which registers it internally to be called whenever the GTP interpreter receives the genmove commmand.

To begin, we define a function that returns a random legal move. The sente.game object has a handy method that generates a list of all legal moves. We can obtain the current game object the GTP Engine uses and then use the random.choice function to choose a random move.

 1"""
 2
 3Author: Arthur Wesley
 4
 5"""
 6
 7import random
 8
 9from sente import GTP
10
11
12def main():
13    """
14
15    main method
16
17    """
18
19    session = GTP.Session("random_move", "0.0.1")
20
21    def gen_move():
22        """
23
24        generates a random move
25
26        :return: random move
27        """
28
29        return random.choice(session.game.get_legal_moves())
30
31    while session.active():
32
33        response = session.interpret(input(""))
34        print(response)
35
36
37if __name__ == "__main__":
38    main()

Next, we need to add the decorator to the gen_move method to register it and override the default genmove command.

Importantly, the Session.GenMove decorator only accepts functions that have typing hints. This is because GTP is a strongly typed protocol meaning that only predefined data types can be transferred. Additionally, section 6.3.3 of the GTP spec requires that the genmove command the following signature:

param color

Color of the player to generate a move for

return

Move object containing the desired move

Note

the color argument is ignored here for the sake of simplicity, but in general it should not be ignored.

Adding the decorators and type hints we get the following code:

 1"""
 2
 3Author: Arthur Wesley
 4
 5"""
 6
 7import random
 8
 9import sente
10from sente import GTP
11
12
13def main():
14    """
15
16    main method
17
18    """
19
20    session = GTP.Session("random_move", "0.0.1")
21
22    @session.GenMove
23    def gen_move(color: sente.stone) -> sente.Move:
24        """
25
26        generates a random move
27
28        :param color: Color of the player to generate a move for
29        :return: Move object containing the desired move
30        """
31
32        return random.choice(session.game.get_legal_moves())
33
34    while session.active():
35
36        response = session.interpret(input(""))
37        print(response)
38
39
40if __name__ == "__main__":
41    main()

We can now run our program and watch our AI generate hillariously awful moves

(venv) $ python random_move.py
genmove B
= K18

Note

you do not need to play the generated move into the session’s sente.Game object. Sente does this automatically

Connecting the AI to Sabaki

Note

the following instructions are based on a Unix environment. If using windows, adapt accordingly (use backslashes, batch files, etc. instead)

In order for another Go program to talk to this AI, the host program needs to spawn our program as a subprocess. In this example, we will see how this is done for Sabaki, a popular Go GUI program.

Sabaki only executes one system command to spawn the engine subprocess. Unfortunately, if your engine’s libraries are stored in a virtual environment it will take multiple system commands to activate the environment and run the program. a simple way around this is to write a short shell script that activates and runs the engine.

1#!/bin/bash
2
3cd "~/path/to/your/project" # absolute path, since the current directory of Sabaki is arbitrary
4source venv/bin/activate
5python random_move.py

With this shell script created, the steps to run the engine in Sabaki are as follows.

  1. Open the Engines Sidebar from the Engines Menu.

../_images/Open Engines Sidebar.png
  1. Click on the Attach Engine... button and select “Manage Engines”.

../_images/Open Manage Engines.png
  1. Click “Add”.

../_images/Add Engine.png
  1. Name the AI Random Move and for the path, put the path to your shell script.

../_images/Add Random Move.png
  1. Close the “Manage Engines” Window. “Random Move” should now appear under the options for Attach Engine.

../_images/Random Move Under Attatch Engine.png
  1. Run “Random Move”.

../_images/Run Random Move.png
  1. Assign Random Move to be the White Player.

../_images/Set as White Player.png
  1. Begin Play.

../_images/Play.png