From 73be372a309b1af9c07a3eee5618f97628b3876e Mon Sep 17 00:00:00 2001 From: Bram van den Heuvel Date: Sun, 28 Jun 2026 23:09:55 +0200 Subject: [PATCH] Add improved documentation --- README.md | 27 +++++--- agents/README.md | 70 +++++++++++++++++++ agents/example.py | 104 +++++++++++++++++++++++++++++ client.py | 27 ++++++-- elo_tracker/README.md | 49 ++++++++++++++ known_players.json | 3 +- pyserver/README.md | 36 ++++++++++ spec/README.md => pyserver/spec.md | 14 ++++ server.py | 2 +- 9 files changed, 314 insertions(+), 18 deletions(-) create mode 100644 agents/README.md create mode 100644 agents/example.py create mode 100644 elo_tracker/README.md create mode 100644 pyserver/README.md rename spec/README.md => pyserver/spec.md (71%) diff --git a/README.md b/README.md index aaf82d5..f488db0 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,24 @@ # Bot-Man-Toe -Bot-Man-Toe is an attempt to create a way for players to play games against -themselves, other players, or self-trained AI players. +Write a script that plays tic-tac-toe, and see how well it performs against +other programs! -## Technology stack +- [🚀 Write your own agent](agents/README.md) +- [🖊 Compare how well your agent performs](/elo_tracker/README.md) +- [🌐 Publish your agent to the internet](/pyserver/README.md) -Counterintuitively, the **servers** are participants to a game. The **clients** -are programs or browsers that mediate matches between servers. +## Get started -## More +1. Clone this repository. +2. Run `python client.py` in the terminal and let two random agents play + against each other. +3. Copy the example agent and [create your own](agents/README.md). +4. Play against the AgentOfChaos while you improve your strategy. +5. Publish your agent. _(optional)_ +6. Compare it against others with the Elo tracker. _(optional)_ -- The discovery contract is documented in `spec/README.md`. -- Python client helpers live under `pyclient/`. -- Python server helpers live under `pyserver/`. + +## Links + +- [📜 API specification](pyserver/spec.md) +- [🏆 Online ELO tracker](https://elo.noordstar.me/) diff --git a/agents/README.md b/agents/README.md new file mode 100644 index 0000000..09e3fab --- /dev/null +++ b/agents/README.md @@ -0,0 +1,70 @@ +# 🚀 Write your own agent! + +An **agent** is a Python class that knows how to play one or more games. + +Don't worry about writing a perfect strategy. Start with something that works, +print the incoming game state, and improve it one step at a time. + +## Quick start + +1. Copy `example.py`. +2. Rename the file and the `ExampleAgent` class. +3. Update the agent's name, author and version. +4. Implement `play_tic_tac_toe()`. +5. Import your agent in `client.py` and let it play a game. + +That's enough to get started. + +## Understanding the game + +Whenever your agent has to make a move, it receives the current game state as +a Python dictionary. + +Start by printing it: + +```py +print(payload) +``` + +Run a few games and watch how the payload changes after every move. Once you +understand what you're receiving, you can start writing your own strategy! + +Your function should return: + +```py +{"move": 7} +``` + +where the number is the square you want to play. + +## Testing your agent + +Open [client.py](/client.py) and replace one of the players with your own agent. +For example: + +```py +from agents.my_agent import MyAgent +from agents.chaos import AgentOfChaos + +players = [ + MyAgent(), + AgentOfChaos(), +] +``` + +The repository includes `AgentOfChaos`, a very simple opponent that plays +random moves. It's useful for testing your own agent while you're developing it. + +> ⚠️ Don't try to build the perfect player immediately! Agents are easy to +> improve while you test against other agents. Plus, imperfect agents are +> typically the most interesting. + +Run the client, inspect the output, tweak your algorithm, and repeat. You don't +need to understand the rest of the project before you can start experimenting. + +## What's next? + +Once you're happy with your agent, you can: + +- [🖊 Compare your agent against other agents with the ELO tracker](/elo_tracker/README.md) +- [🌐 Publish your agent so other people can play against it](/pyserver/README.md) diff --git a/agents/example.py b/agents/example.py new file mode 100644 index 0000000..5659138 --- /dev/null +++ b/agents/example.py @@ -0,0 +1,104 @@ +""" + This module contains an example agent that you can use to create your own! + + Please copy this file, rename it, and then build it the way you see fit. +""" + +from __future__ import annotations + +import random + +from .agent import Agent, Payload + +class ExampleAgent(Agent): + """ + Describe here what your agent does and how it behaves! This will help + others understand how your agent works. + """ + + def __init__(self) -> None: + """ + Create a custom instance of your agent. This function allows you + to define which games your agent can play. + + You may add parameters to the function if your agent requires more + information to be able to operate. + """ + + super().__init__( + # Give your bot a name to display in leaderboards + name="MY super smart agent", + + # Your name, to give you credit + author="Unknown programmer", + + # Update the version to indicate the agent behaves differently. + # This will later allow you to compare different versions of your + # agent against one another. + version="1.0.0", + + # Add extra custom information about the agent to this dictionary. + profile={}, + ) + + # Indicate that you're willing to play tic-tac-toe + # Remove this if you don't want your bot to participate there. + self.add_tic_tac_toe(on_move=self.play_tic_tac_toe, profile={}) + + @staticmethod + def play_tic_tac_toe(payload : Payload) -> 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 + + :param payload: The incoming JSON that contains the game state. + :type payload: dict[str, Any] + :return: The move you wish to take. + :rtype: dict[str, Any] + """ + + # 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 algorithm. + return { "move": random.choice(options) } diff --git a/client.py b/client.py index 42cb17a..5fdc847 100644 --- a/client.py +++ b/client.py @@ -23,9 +23,14 @@ from __future__ import annotations import json import pyclient -from agents import Agent, RemoteAgent -from pyclient import PyClient +# Import your game(s) here from pyclient.games import TicTacToe +# ... + +# Import your agent(s) here +from agents import Agent, AgentOfChaos +# from agents.my_agent import MyAgent +# ... def main() -> int: """ @@ -34,22 +39,29 @@ def main() -> int: :return: Exit code :rtype: int """ - c = PyClient(debug=True) + # Start the game engine + c = pyclient.PyClient(debug=True) + + # Mix and match any agents you'd like. + # During development it's usually easiest to play against AgentOfChaos(). players : list[Agent] = [ - RemoteAgent(url="https://bmt001.noordstar.me/"), - RemoteAgent(url="https://bmt002.noordstar.me/"), + AgentOfChaos(), + AgentOfChaos(), ] - out = c.play_game( + # Play a given game with your players + result = c.play_game( players=players, start=TicTacToe.empty(), ) - inspect_game(out) + # Print the game results to the terminal! + inspect_game(result) return 0 + def inspect_game(game : pyclient.GameReplay) -> None: """ Print a diagnostic of a played game to the terminal. @@ -89,5 +101,6 @@ def inspect_game(game : pyclient.GameReplay) -> None: print(f"Total turns taken: {len(game.turns)}") print(f"Result: {final_state.winner()}") + if __name__ == "__main__": raise SystemExit(main()) diff --git a/elo_tracker/README.md b/elo_tracker/README.md new file mode 100644 index 0000000..1431dfb --- /dev/null +++ b/elo_tracker/README.md @@ -0,0 +1,49 @@ +# ELO tracker + +The ELO tracker lets your agent play many games and assigns every participant +an Elo rating. + +## Before you start + +The tracker only plays against **remote agents**. Remote agents are available +through a URL, and usually run on the internet. + +If your agent only exists as a local Python class, +[publish it first](/pyserver/README.md). + +## Add players + +Open `known_players.json` and add the URLs you want to include. + +For example: + +```json +{ + "players": [ + "https://my-agent.example", + "https://bmt001.noordstar.me" + ] +} +``` + +Every listed URL is considered a participant. The ELO tracker will compare +these players with one another. + +## Start the tracker + +Run: + +```sh +python elo.py +``` + +The tracker will continuously schedule new matches until you stop it. + +## Results + +Two files are updated while the tracker runs: + +* `games.jsonl` stores the played games. +* `known_players.json` stores the list of participants. + +You can stop the tracker with Ctrl+C. diff --git a/known_players.json b/known_players.json index 62f409e..de7befa 100644 --- a/known_players.json +++ b/known_players.json @@ -1,6 +1,7 @@ { "players": [ "https://bmt001.noordstar.me", - "https://bmt002.noordstar.me" + "https://bmt002.noordstar.me", + "https://bmt003.noordstar.me" ] } \ No newline at end of file diff --git a/pyserver/README.md b/pyserver/README.md new file mode 100644 index 0000000..9c7512e --- /dev/null +++ b/pyserver/README.md @@ -0,0 +1,36 @@ +# Publishing your agent + +Once your agent behaves the way you want, you can expose it over HTTP so other clients can play against it. + +You only need this if you want other programs to connect to your agent. + +## Use this repository + +1. Open `server.py`. +2. Replace the agent passed to `PyServer` with your own. For example: + +```py +from agents.my_agent import MyAgent + +player = PyServer(MyAgent()) +``` + +3. Start the server. Run: + +```sh +python server.py +``` + +By default, the server listens on port `5000`. + +4. Open your browser and visit `http://localhost:5000/`. You should receive +a JSON document describing your agent and the games it supports. +If that works, your agent is ready to receive game requests. + +## Writing your own server + +The included server is the easiest way to publish a Python agent. + +If you want to implement the protocol yourself +_(for example in another language)_ you can use the specification in +[the protocol specification](spec.md). diff --git a/spec/README.md b/pyserver/spec.md similarity index 71% rename from spec/README.md rename to pyserver/spec.md index 156d3b9..e918449 100644 --- a/spec/README.md +++ b/pyserver/spec.md @@ -3,6 +3,10 @@ This document describes how a Bot-Man-Toe server or client is supposed to behave. +**This document requires familiarity with setting up an HTTP server.** Please +use [the easy implementation](/pyserver/README.md) if you're building +[a simple agent](/agents/README.md) in this repository. + ## Terminology A **server** is a REST API server that hosts a player willing to play games. @@ -65,3 +69,13 @@ package namespace guidelines. } ``` +There's a few optional data fields that are specified here: + +| Field | Type | Description | +| ----- | ---- | ----------- | +| author | string | The name of the person who designed the agent. | +| version | string | The version of the agent, in case there's an update. | +| me.noordstar.peanuts.containerized | bool | Whether the agent is running in a container. | +| me.noordstar.peanuts.is_ai | bool | Whether the agent runs on a trained deep learning model or some artificial intelligence. | +| me.noordstar.peanuts.agent.version | string | **Deprecated.** Experimental value to demonstrate an agent's version. Please use `version` instead. | +| me.noordstar.peanuts.author | string | **Deprecated.** Experimental value to demonstrate an agent's author. Please use `author` instead. | diff --git a/server.py b/server.py index 9ed7be0..a3c7c5b 100644 --- a/server.py +++ b/server.py @@ -15,7 +15,7 @@ def main() -> int: :return: Exit code :rtype: int """ - player = PyServer(agents.RemoteAgent(url="https://bmt001.noordstar.me/")) + player = PyServer(agents.AgentOfChaos()) # Start listening for games player.start(