Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
7a27ae6
Adviser framework
hopshackle Apr 30, 2026
b76f27e
Initial framework for Advisers
hopshackle May 3, 2026
71ec089
Added logging to GameAdviser
hopshackle May 3, 2026
c24a749
UnderdogAdviser, and refactor to support override with reusing MCTS t…
hopshackle May 4, 2026
6588f64
JSSON serialisation for BGGameState
hopshackle May 6, 2026
d470a2d
Initial JSON serialization for core game state
hopshackle May 6, 2026
91bf58c
JSON serialisation refactor for Dice and Tokens
hopshackle May 7, 2026
964e7f1
Added custom heuristic to lose a game narrowly
hopshackle May 9, 2026
407accd
Added ability to specify a Heuristic as a target in AbstractLearner/E…
hopshackle May 10, 2026
45819ff
Metrics for DDA
hopshackle May 10, 2026
af3db9f
Merge remote-tracking branch 'origin/Advisers' into Advisers
hopshackle May 11, 2026
583c400
Initial JSON serialization for core game state
hopshackle May 12, 2026
3b040e2
Initial JSON serialization for core game state
hopshackle May 12, 2026
c39170e
JSON prettyPrint now copes with nested JSONArrays
hopshackle May 12, 2026
63878f8
BGParameters fix for equals
hopshackle May 12, 2026
4dfc70d
Rerun games from saved JSON state
hopshackle May 12, 2026
29f90ef
GameID now updated correctly when rerunning games from same state
hopshackle May 12, 2026
a0a4063
Merge remote-tracking branch 'origin/master' into Advisers
hopshackle Jun 3, 2026
81bbf00
Correct creation of log file in GameAdviser
hopshackle Jun 10, 2026
3afa2ec
Metrics for BG Actions (to reduce R code)
hopshackle Jun 11, 2026
49f3040
SavedState directory no longer global, and part of destDir in RunGames
hopshackle Jun 12, 2026
cff0365
Removed old video filed not actually being used
hopshackle Jun 12, 2026
87d4d39
Fixed incorrect action string in adviser log
hopshackle Jun 12, 2026
3b1f71c
Backgammon save states, and robust dice
hopshackle Jun 19, 2026
b7dd4fb
Load Game State added to Frontend plus refactor
hopshackle Jun 19, 2026
6a38b56
Support for actionsInProgress serialization and deserialization
hopshackle Jun 20, 2026
8445808
Connect4 corrected to use a 7x6 board as the default
hopshackle Jun 24, 2026
e00cb36
Removed error on actionsInProgress
hopshackle Jun 24, 2026
28e48f0
Connect4 serializable
hopshackle Jun 24, 2026
43453f6
Connect4 serializable
hopshackle Jun 24, 2026
8ae392e
Exploding Kittens and Deck serializable
hopshackle Jun 24, 2026
c7c3ff4
Claude file
hopshackle Jun 24, 2026
18d4dca
small fix for restarts after iterations done
hopshackle Jun 25, 2026
0446d8d
Merge remote-tracking branch 'origin/Advisers' into Advisers
hopshackle Jun 25, 2026
f5fb156
Fixed Backgammon Action metrics
hopshackle Jun 25, 2026
74cba1e
Smartened up XII GUI
hopshackle Jun 25, 2026
6e0f245
XII not to crash in Frontend
hopshackle Jun 25, 2026
9c1817b
Test details to Claude.md
hopshackle Jun 25, 2026
e40f100
Fixed bug from JSON change; GameAdviser now copes more cleanly with R…
hopshackle Jun 26, 2026
9009199
Added ability to render a saved state to png
hopshackle Jun 28, 2026
bc5d0c7
Tweaked javadoc
hopshackle Jun 29, 2026
df31d25
Fixed columns in GameAdviser report, and added generation of annotate…
hopshackle Jun 29, 2026
ff85400
Added extra data to GameAdviser Reporting
hopshackle Jun 30, 2026
b0519f1
MCTSDecisionReporter
hopshackle Jun 30, 2026
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
228 changes: 228 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Start here

A more detailed, up-to-date summary of the codebase structure is maintained at
**https://deepwiki.com/GAIGResearch/TabletopGames**. Consult it first (e.g. via WebFetch) before
doing any detailed analysis of the source — it is faster than reading the code directly and gives a
fuller architectural picture than the overview below.

## What this is

TAG (Tabletop Games Framework) is a Java benchmark for board/card game AI research. It provides a
common API so that ~40 games and a range of AI agents can be run against each other, tuned, and
analysed through a shared infrastructure. This fork (`Advisers` branch) adds **advisers** (players
that suggest moves to other players) and an **LLM** module that generates/uses heuristics via
langchain4j.

## Build, test, run

Maven project, **Java 21**.

```bash
mvn package # builds target/TAG.jar (jar-with-dependencies, main class core.TAG)
```

Tests are **skipped by default** (`maven.test.skip=true` in pom.xml). To run them you must override:

```bash
mvn test -Dmaven.test.skip=false # all tests (JUnit 4 + Mockito)
mvn test -Dmaven.test.skip=false -Dtest=CustomDiceTest # single test class
mvn test -Dmaven.test.skip=false -Dtest=CustomDiceTest#methodName # single method
```

Running the framework — `core.TAG` dispatches on its first argument to a sub-module:

```bash
java -jar target/TAG.jar <EntryPoint> [args...]
# EntryPoints: RunGames, ParameterSearch, FrontEnd, FrontEndSimple,
# ExpertIteration, OneStepDeviations, SkillLadder
java -jar target/TAG.jar RunGames --help # each entry point has its own --help
```

From an IDE, run `core.TAG`, or run `gui.Frontend` directly for the GUI. Most entry points are
configured via JSON config files (see `--help` / `RunArg`) or command-line `key=value` args parsed
by `evaluation/RunArg.java`. A `Dockerfile` builds and packages the same jar with the `data/`
directory.

## Core architecture

A game is the combination of four cooperating pieces, all in `core`:

- **`AbstractGameState`** — all mutable game data. Implement `_copy(playerId)` (redeterminises hidden
info for that player's perspective), `_getAllComponents()`, `getGameScore()`, `_getHeuristicScore()`,
`_equals()`. State is the single source of truth; the forward model never holds game data.
- **`AbstractForwardModel`** — the rules engine. Implement `_setup()`, `_next(state, action)`,
`_computeAvailableActions(state)`, `endPlayerTurn(state)`. Most games extend
**`StandardForwardModel`** (use `StandardForwardModelWithTurnOrder` only for legacy turn-order games).
- **`AbstractParameters`** — tunable game parameters (implements `ITunableParameters` so games can be
optimised/searched).
- **`AbstractGUIManager`** subclass — Swing rendering (optional but expected).

`core.Game` is the orchestrator: it owns the state, forward model, and the list of `AbstractPlayer`s,
drives the turn loop, broadcasts `Event`s to `IGameListener`s, and tracks per-tick statistics.

**Actions** (`core.actions`) implement `AbstractAction`; multi-step decisions use
`IExtendedSequence` to keep an action "in progress" across several player decisions.

**Registering a game**: every game lives in `games.<name>` (e.g. `games.catan`) and is wired into the
central enum **`games.GameType`**, which maps the game to its state/forward-model/parameters/GUI
classes plus metadata (min/max players, categories). This enum is the canonical list of games — add
new games here.

## Players (agents)

`core.AbstractPlayer` defines an agent via `_getAction(state, possibleActions)` and `copy()`. Notable
implementations under `players/`:

- `simple` — `RandomPlayer`, `OSLAPlayer` (one-step look-ahead), `FirstActionPlayer`.
- `mcts` (full MCTS, heavily configurable), `basicMCTS`, `rhea` (rolling-horizon EA), `rmhc`.
- `heuristics` — `IStateHeuristic` / `IActionHeuristic` implementations (linear, logistic, decision-tree,
learned-value, etc.) used to bias the search agents.
- `decorators` (implement `IPlayerDecorator`) — wrap a player to **filter the action list** before it
decides (e.g. forbidding action classes, epsilon-randomness).
- `observers` — **the Advisers feature** (`Advisers` branch). `GameAdviser` is an `IGameListener`
composed of an `AbstractPlayer` (decides what is "best") plus an `IAdviceFilter` (decides *which*
players are advised and *when* to intervene), writing advice to a file.

`players.PlayerFactory.createPlayer(jsonFileOrName)` constructs agents from JSON descriptors — this is
how tournaments and the GUI instantiate configurable players (see `json/players/`).

## Evaluation & analytics

`evaluation/` is the experiment-running side:

- `RunGames` — run tournaments of agents over one or more games (round-robin, one-vs-all, etc.).
- `tournaments/` — `RoundRobinTournament`, `SkillLadder`/`SkillGrid` (vary budget), result analyses.
- `optimisation/` — `ParameterSearch` (NTBEA-based tuning of tunable parameters), `OneStepDeviations`.
- `metrics/`, `listeners/`, `loggers/`, `summarisers/` — `IGameListener` + `Event` pipeline that
records game data to files/stats for analysis. Listener configs live in `metrics/*.json`.
- `features/`, `heuristics/` — state/action feature extraction feeding learned heuristics and
`ExpertIteration`.

## LLM module

`llm/` integrates langchain4j (OpenAI, Anthropic, Gemini, Mistral backends; see pom.xml). `JavaCoder`
asks an LLM to write Java heuristic code; `GamePromptGenerator` builds game-state prompts;
`StringHeuristic` compiles LLM-produced heuristic strings (uses `javaparser`). Requires provider API
keys in the environment.

## Config & data locations

- `json/` — player definitions, search spaces, listener and learner configs consumed at runtime.
- `metrics/` — metric/listener JSON configs; `metrics/out/` is the default results directory.
- `data/` — game assets (images, card/board definitions) loaded by some games; shipped into the
Docker image.

## Unit testing

Tests live under `src/test/java/` mirroring the main source tree (e.g. game tests in
`src/test/java/games/<name>/`, core tests in `src/test/java/core/`). The framework is **JUnit 4**
with **Mockito 5**; there is no JUnit 5 in the project.

**Any test written during development should be committed to the repository** unless it is purely
throwaway scaffolding (e.g. a one-off render harness or a `main` used for manual inspection or to check a rendered image). If a
test helped verify a bug fix or a new feature, it belongs permanently in the test suite.

### Naming

- Class: `<Subject>Test` or `Test<Subject>` (both styles exist; prefer suffix form for new work).
- Methods: descriptive camelCase, no mandatory `test` prefix — `availableActionsExcludeBlockedMoves()`
is clearer than `testAvailableActionsExcludeBlockedMoves()`.

### Structure

```java
public class XIIScriptaForwardModelTest {

BGGameState state;
BGForwardModel fm;

@Before
public void setup() {
Game game = GameType.XIIScripta.createGameInstance(2);
game.reset(List.of(new RandomPlayer(), new RandomPlayer()));
state = (BGGameState) game.getGameState();
fm = (BGForwardModel) game.getForwardModel();
}

@Test
public void bearOffOnlyWhenAllPiecesHome() { ... }
}
```

- Use `@Before` (not `@BeforeEach`) for shared setup.
- Use standard JUnit 4 assertions (`assertEquals`, `assertTrue`, etc.) — no external assertion
libraries are used in this project.
- No shared base test class; each test class is self-contained.

### Setting up game state

Two patterns are in use — pick the one that fits:

1. **Factory + reset** (preferred for integration-level tests):
```java
Game game = GameType.CantStop.createGameInstance(3, 34, params);
game.reset(players);
BGGameState state = (BGGameState) game.getGameState();
```

2. **Direct instantiation** (preferred for tightly scoped unit tests):
```java
DominionGameState state = new DominionGameState(new DominionFGParameters(), 2);
new DominionForwardModel().setup(state);
```

### Setting up game state from a saved JSON file

For games that support JSON serialisation, load a saved state rather than simulating to reach a
scenario — simulation can land on awkward mid-tick states (e.g. null dice rolls). Edit the JSON
fields to vary conditions rather than forcing values via reflection:

```java
Game game = GameType.XIIScripta.createGameInstance(2);
game.reset(List.of(new RandomPlayer(), new RandomPlayer()));
BGGameState state = (BGGameState) game.getGameState();
BGStateJSON.loadFromJSON(state, JSONUtils.loadJSONFile("P0_Tick340.json"));
// Edit e.g. "piecesBorneOff" : [4, 7] in the JSON to test bear-off logic
```

For GUI/board-view verification, paint the view to a `BufferedImage` and write a PNG:

```java
MyBoardView view = new MyBoardView((MyForwardModel) game.getForwardModel());
view.update(state);
view.setSize(view.getPreferredSize());
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
view.paint(img.getGraphics());
ImageIO.write(img, "png", new File("render.png"));
```

Remove any throwaway render harness files and output PNGs before committing.

### Running tests

**Never run the full suite during development** — it includes slow integration tests. Always target
a specific class or method.

```bash
# 1. Build the classpath once (output to cp.txt, committed to .gitignore)
mvn dependency:build-classpath -Dmdep.outputFile=cp.txt

# 2. Compile tests
mvn test-compile -Dmaven.test.skip=false

# 3. Run a single test class
java -cp "target/classes;target/test-classes;$(cat cp.txt)" \
org.junit.runner.JUnitCore games.cantstop.TestCantStop
```

`java` may not be on PATH; use the full JDK path if needed
(e.g. `C:/Users/<you>/.jdks/ms-21.0.10/bin/java`). Alternatively, run directly from the IDE
by right-clicking the test class or method.

## Python

`core.PyTAG` + `players/python` expose a Python-facing API (PyTAG) for RL/gym-style use.
32 changes: 16 additions & 16 deletions data/descent2e/heroes.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
{
"id": "Widow Tarha",
"type": ["String", "Hero"],
"name": "Widow Tarha",
"tokenType": "Hero",
"archetype": ["String", "Mage"],
"MovePoints": ["Integer", 4],
"Health": ["Integer", 10],
Expand All @@ -17,8 +17,8 @@
"quote": ["String", "\"I have nothing left to lose. Yet, there is so much more power for me to gain.\""]
},
{
"id": "Leoric of the Book",
"type": ["String", "Hero"],
"name": "Leoric of the Book",
"tokenType": "Hero",
"archetype": ["String", "Mage"],
"MovePoints": ["Integer", 4],
"Health": ["Integer", 8],
Expand All @@ -34,8 +34,8 @@
"quote": ["String", "\"If my years of study have taught me anything, it is that I am worthy of the knowledge I possess.\""]
},
{
"id": "Avric Albright",
"type": ["String", "Hero"],
"name": "Avric Albright",
"tokenType": "Hero",
"archetype": ["String", "Healer"],
"MovePoints": ["Integer", 4],
"Health": ["Integer", 12],
Expand All @@ -51,8 +51,8 @@
"quote": ["String", "\"I pledge myself to those in need. I will be their shield, their light in the darkness.\""]
},
{
"id": "Ashrian",
"type": ["String", "Hero"],
"name": "Ashrian",
"tokenType": "Hero",
"archetype": ["String", "Healer"],
"MovePoints": ["Integer", 5],
"Health": ["Integer", 10],
Expand All @@ -68,8 +68,8 @@
"quote": ["String", "\"I no more understand why I can hear the spirits speak than I understand why you cannot.\""]
},
{
"id": "Syndrael",
"type": ["String", "Hero"],
"name": "Syndrael",
"tokenType": "Hero",
"archetype": ["String", "Warrior"],
"MovePoints": ["Integer", 4],
"Health": ["Integer", 12],
Expand All @@ -85,8 +85,8 @@
"quote": ["String", "\"You swear undying loyalty, yet you are mortal. What can you know of commitment?\""]
},
{
"id": "Grisban the Thirsty",
"type": ["String", "Hero"],
"name": "Grisban the Thirsty",
"tokenType": "Hero",
"archetype": ["String", "Warrior"],
"MovePoints": ["Integer", 3],
"Health": ["Integer", 14],
Expand All @@ -102,8 +102,8 @@
"quote": ["String", "\"All this killing is thirsty work. Drink with me!\""]
},
{
"id": "Tomble Burrowell",
"type": ["String", "Hero"],
"name": "Tomble Burrowell",
"tokenType": "Hero",
"archetype": ["String", "Scout"],
"MovePoints": ["Integer", 4],
"Health": ["Integer", 8],
Expand All @@ -119,8 +119,8 @@
"quote": ["String", "\"Don't think of this as you being robbed. Instead, think of it as you donating to a worthy cause.\""]
},
{
"id": "Jain Fairwood",
"type": ["String", "Hero"],
"name": "Jain Fairwood",
"tokenType": "Hero",
"archetype": ["String", "Scout"],
"MovePoints": ["Integer", 5],
"Health": ["Integer", 8],
Expand Down
Loading