From 78c461d9b2f6fe510e75e657dd08ec5fac4bdcb2 Mon Sep 17 00:00:00 2001 From: Bram van den Heuvel Date: Fri, 19 Jun 2026 15:46:03 +0200 Subject: [PATCH] Create client script with diagnostics --- client.py | 87 ++++++++++++++++++++++++++++++++++++++ main.py | 89 --------------------------------------- requirements-pyclient.txt | 5 +++ requirements.txt | 2 +- 4 files changed, 93 insertions(+), 90 deletions(-) create mode 100644 client.py delete mode 100644 main.py create mode 100644 requirements-pyclient.txt diff --git a/client.py b/client.py new file mode 100644 index 0000000..45a968d --- /dev/null +++ b/client.py @@ -0,0 +1,87 @@ +""" + This module offers a small place to start building game clients from. + + You can use this for: + + - Learning how the system works. + - Debugging a game server. + - Debugging a game that behaves weirdly. + + This module lets you host a game, let online servers participate in it, + and then analyze the outcome. + + + You can build several things with this: + + 1. You can build an AI trainer that trains on existing players. + 2. You can build an ELO evaluator that compares the performance of various + strategies. +""" + +from __future__ import annotations + +import json + +from pyclient import PyClient +from pyclient.games.tic_tac_toe import TicTacToe +from typing import Any + +def main() -> int: + """ + Start a client, then use it to analyze one or more matches. + + :return: Exit code + :rtype: int + """ + c = PyClient([ + "https://bmt001.noordstar.me", + "https://bmt001.noordstar.me", + ], debug=False) + + out = c.play_game("tic-tac-toe", TicTacToe) + + inspect_game(out) + + return 0 + +def inspect_game(game : list[tuple[int, dict[str, Any], Any]]) -> None: + """ + Print a diagnostic of a played game to the terminal. + + :param game: The results of a played game. + :type game: list[typle[int, dict[str, Any], Any]] + """ + max_width = 40 + hbar = max_width * '%' + + def title(s : str) -> None: + """ + Show a title nice and clean in the middle + + :param s: The title to display + :type s: str + """ + w = (max_width - len(s)) // 2 + + print(hbar) + print((w * ' ') + s.upper() + (w * ' ')) + print(hbar) + + # Show all moves made throughout the game + title("Turns taken") + for player, action, _ in game: + print(f"Player {player} : " + json.dumps(action)[:max_width-12]) + + # Show all remaining variables in the finishing state of the game + title("Final state") + final_state = game[-1][2] + for k, v in final_state.to_dict().items(): + print(f"{k} => {json.dumps(v)}") + + # Some final (usually the most relevant) statistics + title("Result") + print(f"Total turns taken: {len(game)}") + print("Winner: " + ("0 (tie)" if final_state.winner() == 0 else f"player {final_state.winner()}")) + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/main.py b/main.py deleted file mode 100644 index b550462..0000000 --- a/main.py +++ /dev/null @@ -1,89 +0,0 @@ -""" - This module enables a user to host a server that is able to play games. -""" - -import random - -from pyserver import PyServer -from pyclient import PyClient -from pyclient.games.tic_tac_toe import TicTacToe - -def main(): - player = PyServer( - # Customize this to whatever you'd like to call your player - name="My super smart robot player", - - # Custom information that you can use to tell people about this player - profile={}, - - # Unless you know what you're doing, don't touch this. - import_name=__name__, - ) - - player.add_tic_tac_toe(on_move=play_tic_tac_toe, profile={}) - - player.start(port=5001) - - return 0 - -def play_tic_tac_toe(payload): - """ - Play a game of tic-tac-toe. - - You receive a payload that looks like this: - - { - "1": "X", "2": "", "3": "O", - "4": "X", "5": "O", "6": "", - "7": "", "8": "", "9": "", - "your_token": "X" - } - - And you're expected to return a response of which field you'd like to - place your piece in. For example, if you wish to place your token in - field 7, your response should look like this: - - { "move": 7 } - - The board is arranged as follows: - - 1 | 2 | 3 - ---+---+--- - 4 | 5 | 6 - ---+---+--- - 7 | 8 | 9 - """ - - # Try printing the payload to see what it looks like! - print(payload) - - options = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, ] - - # 1. Try filtering out the impossible moves! - # If an X or O was already placed at a field, remove it from the options - - # - - # 2. Try finding two in a row! If possible, you can try to place the third - # item on the board and get 3 in a row. - - # - - # 3. Perhaps you can block the opponent from getting 3 in a row? - - # - - # Now, pick any of the remaining options. - # This is just a simple implementation. Naturally, you're welcome to try - # your own logic. - return { "move": random.choice(options) } - -if __name__ == "__main__": - raise SystemExit(main()) - - c = PyClient([ - "http://127.0.0.1:5001", - "http://127.0.0.1:5002", - ], debug=True) - - out = c.play_game("tic-tac-toe", TicTacToe) diff --git a/requirements-pyclient.txt b/requirements-pyclient.txt new file mode 100644 index 0000000..58746c1 --- /dev/null +++ b/requirements-pyclient.txt @@ -0,0 +1,5 @@ +certifi==2026.6.17 +charset-normalizer==3.4.7 +idna==3.18 +requests==2.34.2 +urllib3==2.7.0 diff --git a/requirements.txt b/requirements.txt index ceebf86..3c1e47d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ blinker==1.9.0 -certifi==2026.5.20 +certifi==2026.6.17 charset-normalizer==3.4.7 click==8.4.1 colorama==0.4.6