Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a5bec37
Format README
bcollazo Dec 21, 2025
34e2a9b
Address Ruff Warnings
bcollazo Dec 21, 2025
2e4014d
Make CatanatronEnv action space dynamic based on map_type
bcollazo Dec 21, 2025
d69a124
Add action as reward function parameter
bcollazo Dec 21, 2025
dd4eb0f
PyGame render method for CatanatronEnv Gym
bcollazo Dec 21, 2025
cbc4a0d
Blue BG
bcollazo Dec 21, 2025
2cae38f
Watermark Simple
bcollazo Dec 21, 2025
aee5f8b
Update Color
bcollazo Dec 21, 2025
a016ba0
Fix warning
bcollazo Dec 21, 2025
b5cfa9c
Update PyGame Render UI
bcollazo Dec 21, 2025
1f5b254
Render Mode DB
bcollazo Dec 22, 2025
eea2f41
Add parquet test
bcollazo Dec 22, 2025
92f0e82
Test Gym Render Method
bcollazo Dec 22, 2025
1adc165
Add pygame dependency to gym
bcollazo Dec 22, 2025
ada3bcd
Improve Reproducibility in Gym
bcollazo Dec 22, 2025
3062041
Update DOCS
bcollazo Dec 22, 2025
6719444
PPO Example
bcollazo Dec 22, 2025
d5f44e3
n_epochs
bcollazo Dec 22, 2025
bab515c
Wandb first integration
bcollazo Dec 22, 2025
acd2bc8
Upload Videos to WANDB
bcollazo Dec 22, 2025
28f57e9
Integrate Wandb Sweep Hyperparam Search
bcollazo Dec 22, 2025
61a758d
Increase Searchable Space
bcollazo Dec 22, 2025
d9683eb
Random Grid Search
bcollazo Dec 22, 2025
68a6464
Faster action_masks method
bcollazo Dec 23, 2025
7132537
Remove Video Recording to Scale Up Training
bcollazo Dec 23, 2025
6d58e25
Change 1
bcollazo Dec 23, 2025
0051c49
Fixup
bcollazo Dec 24, 2025
f8d986e
Allow users to specify Observation Encoder
bcollazo Dec 24, 2025
e419a76
python evaluate.py script
bcollazo Dec 24, 2025
4a94f74
Share Code
bcollazo Dec 24, 2025
b5b6056
record_video script
bcollazo Dec 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ data/
profile.pstats
catanatron-venv
.DS_Store
wandb
videos
models
runs

# Byte-compiled / optimized / DLL files
__pycache__/
Expand Down
57 changes: 32 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
![Discord](https://img.shields.io/discord/1385302652014825552)
[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/bcollazo/catanatron/blob/master/examples/Overview.ipynb)

Catanatron is a high-performance simulator and strong AI player for Settlers of Catan. You can run thousands of games in the order of seconds. The goal is to find the strongest Settlers of Catan bot possible.
Catanatron is a high-performance simulator and strong AI player for Settlers of Catan. You can run thousands of games in the order of seconds. The goal is to find the strongest Settlers of Catan bot possible.

Get Started with the Full Documentation: https://docs.catanatron.com

Join our Discord: https://discord.gg/FgFmb75TWd!

## Command Line Interface

Catanatron provides a `catanatron-play` CLI tool to run large scale simulations.

<p align="left">
Expand All @@ -22,26 +23,30 @@ Catanatron provides a `catanatron-play` CLI tool to run large scale simulations.

1. Clone the repository:

```bash
git clone git@github.com:bcollazo/catanatron.git
cd catanatron/
```
2. Create a virtual environment (requires Python 3.11 or higher)
```bash
git clone git@github.com:bcollazo/catanatron.git
cd catanatron/
```

2. Create a virtual environment (requires Python 3.11 or higher)

```bash
python -m venv venv
source ./venv/bin/activate
# ./venv/Scripts/Activate.ps1 (on windows)
```

```bash
python -m venv venv
source ./venv/bin/activate
```
3. Install dependencies

```bash
pip install -e .
```
4. (Optional) Install developer and advanced dependencies
```bash
pip install -e .
```

4. (Optional) Install developer and advanced dependencies

```bash
pip install -e ".[web,gym,dev]"
```
```bash
pip install -e ".[web,gym,dev]"
```

### Usage

Expand All @@ -52,13 +57,13 @@ catanatron-play --players=R,R,R,W --num=100
```

Generate datasets from the games to analyze:

```bash
catanatron-play --num 100 --output my-data-path/ --output-format json
```

See more examples at https://docs.catanatron.com.


## Graphical User Interface

We provide Docker images so that you can watch, inspect, and play games against Catanatron via a web UI!
Expand All @@ -67,15 +72,15 @@ We provide Docker images so that you can watch, inspect, and play games against
<img src="https://raw.githubusercontent.com/bcollazo/catanatron/master/docs/source/_static/CatanatronUI.png">
</p>


### Installation

1. Ensure you have Docker installed (https://docs.docker.com/engine/install/)
2. Run the `docker-compose.yaml` in the root folder of the repo:

```bash
docker compose up
```
```bash
docker compose up
```

3. Visit http://localhost:3000 in your browser!

## Python Library
Expand All @@ -100,14 +105,17 @@ print(game.play()) # returns winning color
See more at http://docs.catanatron.com

## Gymnasium Interface

For Reinforcement Learning, catanatron provides an Open AI / Gymnasium Environment.

Install it with:

```bash
pip install -e .[gym]
```

and use it like:

```python
import random
import gymnasium
Expand All @@ -128,8 +136,8 @@ env.close()

See more at: https://docs.catanatron.com


## Documentation

Full documentation here: https://docs.catanatron.com

## Contributing
Expand All @@ -144,6 +152,5 @@ coverage run --source=catanatron -m pytest tests/ && coverage report
See more at: https://docs.catanatron.com

## Appendix
See the motivation of the project here: [5 Ways NOT to Build a Catan AI](https://medium.com/@bcollazo2010/5-ways-not-to-build-a-catan-ai-e01bc491af17).


See the motivation of the project here: [5 Ways NOT to Build a Catan AI](https://medium.com/@bcollazo2010/5-ways-not-to-build-a-catan-ai-e01bc491af17).
17 changes: 11 additions & 6 deletions catanatron/catanatron/cli/play.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@
from rich.console import Console
from rich.table import Table
from rich.progress import Progress
from rich.progress import Progress, BarColumn, TimeRemainingColumn
from rich.progress import BarColumn, TimeRemainingColumn
from rich import box
from rich.console import Console
from rich.theme import Theme
from rich.text import Text

Expand Down Expand Up @@ -207,7 +206,7 @@ class OutputOptions:
class GameConfigOptions:
discard_limit: int = 7
vps_to_win: int = 10
catan_map: Literal["BASE", "TOURNAMENT", "MINI"] = "BASE"
map_type: Literal["BASE", "TOURNAMENT", "MINI"] = "BASE"


COLOR_TO_RICH_STYLE = {
Expand Down Expand Up @@ -238,7 +237,7 @@ def play_batch_core(num_games, players, game_config, accumulators=[]):
for _ in range(num_games):
for player in players:
player.reset_state()
catan_map = build_map(game_config.catan_map)
catan_map = build_map(game_config.map_type)
game = Game(
players,
discard_limit=game_config.discard_limit,
Expand Down Expand Up @@ -275,7 +274,10 @@ def play_batch(

accumulators.append(
CsvDataAccumulator(
output_options.output, output_options.include_board_tensor
tuple(p.color for p in players),
game_config.map_type,
output_options.output,
output_options.include_board_tensor,
)
)
elif output_options.output_format == "parquet":
Expand All @@ -284,7 +286,10 @@ def play_batch(

accumulators.append(
ParquetDataAccumulator(
output_options.output, output_options.include_board_tensor
tuple(p.color for p in players),
game_config.map_type,
output_options.output,
output_options.include_board_tensor,
)
)
elif output_options.output_format == "json":
Expand Down
6 changes: 3 additions & 3 deletions catanatron/catanatron/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
)
from catanatron.models.board import STATIC_GRAPH, get_edges, get_node_distances
from catanatron.models.map import NUM_TILES, CatanMap, build_map, number_probability
from catanatron.models.player import Player, Color, SimplePlayer
from catanatron.models.player import Color, SimplePlayer
from catanatron.models.enums import (
DEVELOPMENT_CARDS,
RESOURCES,
Expand Down Expand Up @@ -106,7 +106,7 @@ def resource_hand_features(game: Game, p0_color: Color):
]
for card in DEVELOPMENT_CARDS:
features[f"P0_{card}_IN_HAND"] = player_state[key + f"_{card}_IN_HAND"]
features[f"P0_HAS_PLAYED_DEVELOPMENT_CARD_IN_TURN"] = player_state[
features["P0_HAS_PLAYED_DEVELOPMENT_CARD_IN_TURN"] = player_state[
key + "_HAS_PLAYED_DEVELOPMENT_CARD_IN_TURN"
]

Expand All @@ -132,7 +132,7 @@ def map_tile_features(catan_map: CatanMap, robber_coordinate):
for tile_id, tile in catan_map.tiles_by_id.items():
for resource in RESOURCES:
features[f"TILE{tile_id}_IS_{resource}"] = tile.resource == resource
features[f"TILE{tile_id}_IS_DESERT"] = tile.resource == None
features[f"TILE{tile_id}_IS_DESERT"] = tile.resource is None
features[f"TILE{tile_id}_PROBA"] = (
0 if tile.resource is None else number_probability(tile.number)
)
Expand Down
39 changes: 30 additions & 9 deletions catanatron/catanatron/gym/accumulators.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,40 @@
import os
from collections import defaultdict
import time
from collections import defaultdict
from typing import Tuple, Literal

from catanatron.utils import format_secs
import numpy as np
import pandas as pd

from catanatron import Action, Color, Game
from catanatron.features import create_sample
from catanatron.game import GameAccumulator
from catanatron.gym.board_tensor_features import create_board_tensor
from catanatron.gym.envs.catanatron_env import to_action_space, to_action_type_space
from catanatron.gym.envs.action_space import to_action_space, to_action_type_space
from catanatron.gym.utils import (
DISCOUNT_FACTOR,
get_tournament_total_return,
get_victory_points_total_return,
populate_matrices,
simple_total_return,
)
from catanatron.utils import format_secs


class ReinforcementLearningAccumulator(GameAccumulator):
def __init__(
self,
player_colors: Tuple[Color],
map_type: Literal["BASE", "TOURNAMENT", "MINI"] = "BASE",
include_board_tensor=True,
total_return_fns={
"RETURN": simple_total_return,
"TOURNAMENT_RETURN": get_tournament_total_return,
"VICTORY_POINTS_RETURN": get_victory_points_total_return,
},
):
self.player_colors = player_colors
self.map_type = map_type
self.include_board_tensor = include_board_tensor
# TODO: Generalize to "rewards_fn" that can yield intermediary rewards
# while still rewarding big on terminal states.
Expand All @@ -45,14 +51,17 @@ def before(self, game):
if self.include_board_tensor:
self.data["board_tensors"] = []

def step(self, game_before_action, action):
def step(self, game_before_action: Game, action: Action):
self.data["color_action_indices"][action.color].append(
len(self.data["samples"])
)
self.data["acting_color"].append(action.color)
self.data["samples"].append(create_sample(game_before_action, action.color))
self.data["actions"].append(
[to_action_space(action), to_action_type_space(action.action_type)]
[
to_action_space(action, self.player_colors, self.map_type),
to_action_type_space(action.action_type),
]
)

if self.include_board_tensor:
Expand Down Expand Up @@ -130,8 +139,14 @@ def after(self, game):


class CsvDataAccumulator(ReinforcementLearningAccumulator):
def __init__(self, output, include_board_tensor=True):
super().__init__(include_board_tensor)
def __init__(
self,
player_colors: Tuple[Color],
map_type: Literal["BASE", "TOURNAMENT", "MINI"],
output,
include_board_tensor=True,
):
super().__init__(player_colors, map_type, include_board_tensor)
self.output = output

def after(self, game):
Expand Down Expand Up @@ -164,8 +179,14 @@ def after(self, game):


class ParquetDataAccumulator(ReinforcementLearningAccumulator):
def __init__(self, output, include_board_tensor=True):
super().__init__(include_board_tensor)
def __init__(
self,
player_colors: Tuple[Color],
map_type: Literal["BASE", "TOURNAMENT", "MINI"],
output,
include_board_tensor=True,
):
super().__init__(player_colors, map_type, include_board_tensor)
self.output = output

def after(self, game):
Expand Down
Loading