Add improved documentation
parent
87525238ba
commit
73be372a30
27
README.md
27
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/)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -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) }
|
||||
27
client.py
27
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())
|
||||
|
|
|
|||
|
|
@ -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>.
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"players": [
|
||||
"https://bmt001.noordstar.me",
|
||||
"https://bmt002.noordstar.me"
|
||||
"https://bmt002.noordstar.me",
|
||||
"https://bmt003.noordstar.me"
|
||||
]
|
||||
}
|
||||
|
|
@ -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).
|
||||
|
|
@ -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. |
|
||||
Loading…
Reference in New Issue