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
>