Dear Phillippe,

Thank you for your email, this was very helpful. I managed to get something
working although it has been hacked together:

I make a subprocess call to the gnubg exe:










































*def call_gnubg(action: str = "session", match_ref: str = str(uuid4()),
match_length: str = "1"):    """    Calls the GNU Backgammon (gnubg)
program from Python using the subprocess module.    Adjust the command and
arguments according to your specific requirements.    """    # Create a
copy of the current environment variables    env = os.environ.copy()    #
Set the custom environment variable    env["MATCH_REF"] = str(match_ref)
env["MATCH_LENGTH"] = str(match_length)    env["ACTION"] = str(action)
env["HOME"] = "/tmp" # required as gnubg needs to create .gnubg to run    #
Command to run gnubg. Modify the command according to your use case.    #
For example, you might want to run a specific command or script within
gnubg.    command = ["/usr/games/gnubg", "-q", "-t", "-p", "libgnubg.py"]
  # If you have specific arguments or need to interact with gnubg,    # add
them to the command list. For example: ["gnubg", "--some-argument",
"value"]    try:        # Run the command and wait for it to complete
  result = subprocess.run(            command,            check=True,
      stdout=subprocess.PIPE,            stderr=subprocess.PIPE,
text=True,            env=env,        )        # Output the result
(stdout)        print("GNU Backgammon output: %s", result.stdout)        #
If needed, you can also handle errors using result.stderr        if
result.stderr:            print("GNU Backgammon error output: %s",
result.stderr)    except subprocess.CalledProcessError as e:
print("Error running GNU Backgammon: %s", e)        raise    except
Exception as e:        print("An unexpected error occurred: %s", e)
raise*


Which then calls a libgnubg from gnubg:




















































































































































































*import base64import gzipimport osimport jsonimport gnubgdef
compress_data(data):    """Compress data using gzip and encode it with
base64."""    compressed_data = gzip.compress(data.encode("utf-8"))
return base64.b64encode(compressed_data).decode("utf-8")def
decompress_data(data):    """Decode from base64 and decompress data using
gzip."""    decoded_data = base64.b64decode(data)    decompressed_data =
gzip.decompress(decoded_data)    return
decompressed_data.decode("utf-8")def
read_game_content_from_file(file_path):    """Read game content from a
local file."""    with open(file_path, "r", encoding="utf-8") as file:
  return file.read()def delete_local_file(file_path):    """Delete the
specified file."""    try:        os.remove(file_path)        #
print(f"File '{file_path}' was successfully deleted.")    except OSError as
e:        print(f"Error: {e.strerror} - {e.filename}")def
write_dict_to_json_file(data_dict, match_ref):    """    Writes the
provided dictionary to a JSON file named after the match reference.
Parameters:    - data_dict (dict): The dictionary to be converted into a
JSON string.    - match_ref (str): The reference name for the match, which
is used as the filename.    The function writes the JSON representation of
`data_dict` to a file named `{match_ref}.json`.    """    # Define the
filename based on match_ref    filename =
f"/tmp/{match_ref}/{match_ref}.json"    # Write the dictionary to a JSON
file    with open(filename, "w") as json_file:        json.dump(data_dict,
json_file, indent=4)def new_gnubg_match(match_length: str, match_ref: str)
-> str:    """    Creates a new GNU Backgammon match and saves it to a
specified directory.    Parameters:    - match_length (int): The length of
the backgammon match.    - match_ref (str): The reference name for the
match, which is used to name the directory and match file.    Returns:    -
None: The function prints the match details.    """    # Create the
directory for the match    match_dir = f"/tmp/{match_ref}"
os.makedirs(match_dir, exist_ok=True)    # Create a new match and save it
  gnubg.command(f"new match {match_length}")    gnubg.command(f"save match
{match_dir}/{match_ref}.sgf")def new_gnubg_session(match_ref: str) -> str:
  """    Creates a new GNU Backgammon match and saves it to a specified
directory.    Parameters:    - match_length (int): The length of the
backgammon match.    - match_ref (str): The reference name for the match,
which is used to name the directory and match file.    Returns:    - None:
The function prints the match details.    """    # Create the directory for
the match    match_dir = f"/tmp/{match_ref}"    os.makedirs(match_dir,
exist_ok=True)    # Create a new match and save it    gnubg.command(f"new
session")    gnubg.command(f"save match {match_dir}/{match_ref}.sgf")def
load_match(match_ref: str):    gnubg.command(f"load match
{match_ref}.sgf")#  print(dir(gnubg))"""        data_dict = {
"sgf": compressed_data,            # board            # "board":
gnubg.board(),            # calcgammonprice            #  "gammonprice":
gnubg.calcgammonprice(),            # cfevaluate            # "cfevaluate":
gnubg.cfevaluate(),            # classifypos            # command
  # cubeinfo            # dicerolls            # eq2mwc            #
eq2mwc_stderr            # errorrating            # evalcontext
# evaluate            # findbestmove            # getevalhintfilter
    # gnubgid            "gnubgid": gnubg.gnubgid(),            # hint
      # luckrating            # match            "match": gnubg.match(),
        # matchchecksum            "matchchecksum": gnubg.matchchecksum(),
          # matchid            "matchid": gnubg.matchid(),            #
met            # "met": gnubg.met(),            # movetupletostring
    # mwc2eq            # "mwc2eq": gnubg.mwc2eq(),            #
mwc2eq_stderr            # "mwc2eq_stderr": gnubg.mwc2eq_stderr(),
  # navigate            # nextturn            # parsemove            #
posinfo            # "posinfo": gnubg.posinfo(),            #
positionbearoff            # positionfrombearoff            #
positionfromid            # positionfromkey            # positionid
    "positionid": gnubg.positionid(),            # positionkey            #
rolloutcontext            # setevalhintfilter            # updateui"""if
__name__ == "__main__":    match_ref = os.environ["MATCH_REF"]
match_length = os.environ["MATCH_LENGTH"]    action = os.environ["ACTION"]
  if action == "new_match":        new_gnubg_match(match_ref,
match_length)    if action == "session":
new_gnubg_session(match_ref)    # Create game data for a log of the game
game_data =
read_game_content_from_file(f"/tmp/{match_ref}/{match_ref}.sgf")
compressed_data = compress_data(game_data)    # Combine into dictionary
data_dict = {        "sgf": compressed_data,        "gnubgid":
gnubg.gnubgid(),        "match": gnubg.match(),        "matchchecksum":
gnubg.matchchecksum(),        "matchid": gnubg.matchid(),
"positionid": gnubg.positionid(),    }    # Write to file system
write_dict_to_json_file(data_dict, match_ref)*

I have to write to the data to the file system, and use environment
variables to pass in variables to the subprocess, but it seems to work.

Regards,


David.


On Mon, 1 Apr 2024 at 13:25, Philippe Michel <[email protected]>
wrote:

> On Wed, Mar 27, 2024 at 03:00:06PM +0000, David Reay wrote:
>
> > I hope this message finds you well. I am currently developing a RESTful
> API
> > using Python, specifically with frameworks such as FastAPI/Flask, and I
> am
> > keen on integrating GNU Backgammon (gnubg) into this project. The primary
> > function I aim to achieve is to send a match and position ID to gnubg,
> have
> > it load the match, analyze the position, and if it is gnubg's turn, to
> also
> > receive the recommended move.
> >
> > The end goal is to facilitate this interaction through JSON for both the
> > requests and responses, ensuring seamless integration within a Python
> > environment. This setup is intended to serve a broader application that
> > leverages gnubg's capabilities for analysis and decision-making in
> > backgammon matches.
> >
> > Could you please provide guidance or point me towards documentation on
> how
> > to achieve the following using gnubg?
> >
> > 1. Sending a match and position ID to gnubg, preferably in a JSON format,
> > for it to load and analyze the match.
> > 2. Receiving gnubg's analysis and recommended move in a JSON response,
> > which is crucial for the integration with my Python-based API.
> > 3. Any examples or best practices for integrating gnubg with Python,
> > especially in a serverless architecture or a containerized environment.
>
> I'm not familiar with cient-server programming and json encoding in
> python, but gnubg includes an embedded python interpreter, so anything
> that works with regular python should (with Linux where the system
> python libraries are used) or could (with Windows where a limited set of
> modules are included in gnubg's installer and you may need to add some
> missing ones).
>
> For instance the following short examples (picked from searches, I can't
> explain how they work exactly or how much oversimplified they are but
> someone more familiar with what you want to do should be able to) can be
> launched from gnubg. Or more probably the server part from gnubg and the
> client part from regular python.
>
> server.py:
>
> import socketserver
> import json
>
> class MyTCPHandler(socketserver.StreamRequestHandler):
>
>     def handle(self):
>         self.data = self.rfile.readline().strip()
>         print("{} wrote:".format(self.client_address[0]))
>         print(self.data)
>
>         j = json.loads(self.data)
>
>         self.wfile.write(str(j).format().encode('utf-8'))
>
> if __name__ == "__main__":
>     HOST, PORT = "localhost", 9999
>
>     with socketserver.TCPServer((HOST, PORT), MyTCPHandler) as server:
>         server.serve_forever()
>
>
> client.py:
>
> import socket
> import sys
> import json
>
> HOST, PORT = "localhost", 9999
> data = json.dumps(sys.argv[1:])
>
> with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
>     sock.connect((HOST, PORT))
>     sock.sendall(bytes(data + "\n", "utf-8"))
>
>     received = str(sock.recv(1024), "utf-8")
>
> print("Sent:     {}".format(data))
> print("Received: {}".format(received))
>
>
> To do something useful, the server part's handler can use the following
> functions:
>
> gnubg.setgnubgid() to set the position
> and then either
> gnubg.findbestmove() to find the best checker play
> or
> gnubg.cfevaluate() to evaluate a cube decision
>
>
> >>> gnubg.setgnubgid("cC/kAVDatg0iAA:QYkVAAAAAAAA")
> Setting GNUbg ID cC/kAVDatg0iAA:QYkVAAAAAAAA
> >>> gnubg.findbestmove()
> (8, 5, 7, 2)
> >>> mt = gnubg.findbestmove()
> >>> gnubg.movetupletostring(mt, gnubg.board())
> '8/5 7/2'
>
> (the latter needs gnubg.board() to distinguish 8/5 from 8/5* for
> instance...)
>
> or
>
> >>> gnubg.setgnubgid("4HPwA0AzTvABMA:cAkAAAAAAAAA")
> Setting GNUbg ID 4HPwA0AzTvABMA:cAkAAAAAAAAA
> >>> gnubg.cfevaluate()
> (0.5438268780708313, 0.5438268780708313, 0.5045644044876099, 1.0, 2, 'No
> double, take')
>
> The result tuple is:
> estimated cubeful equity after the best decision
> ND equity
> D/T equity
> D/P equity
> index of the best decision (from the cubedecision enum in eval.h)
> description of the best decision
>

Reply via email to