Add improved documentation

sam/sneaky
Bram van den Heuvel 2026-06-28 23:09:55 +02:00
parent 87525238ba
commit 73be372a30
9 changed files with 314 additions and 18 deletions

View File

@ -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/)

70
agents/README.md Normal file
View File

@ -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)

104
agents/example.py Normal file
View File

@ -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) }

View File

@ -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())

49
elo_tracker/README.md Normal file
View File

@ -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 <kbd>Ctrl</kbd>+<kbd>C</kbd>.

View File

@ -1,6 +1,7 @@
{
"players": [
"https://bmt001.noordstar.me",
"https://bmt002.noordstar.me"
"https://bmt002.noordstar.me",
"https://bmt003.noordstar.me"
]
}

36
pyserver/README.md Normal file
View File

@ -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).

View File

@ -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. |

View File

@ -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(