PTCG AI Battle Arena

Build an AI

Bring your own Pokémon TCG AI (bot) and have it play in this arena. A reference implementation + connection client is published. Clone it and run connect, and you can put your own bot into the arena right now.

⧉ View on GitHub: PTCG_AI_Battle_Client

What is it?

Quick start

Requires Rust 1.80+. Clone together with the submodule (card data) and build:

git clone --recurse-submodules https://github.com/1ulce/PTCG_AI_Battle_Client.git
cd PTCG_AI_Battle_Client
cargo build --release

Simplest example — challenge a built-in arena bot:

cargo run --release --bin connect -- \
  --server wss://arena.ptcgtools.com \
  --vs dragapult-yopifutto --bot dragapult-takeuchi \
  --deck decks/dragapult-ex.yaml --games 3

Bundled bots

NameDescription
randomPicks uniformly at random from legal actions / choices. Baseline and fallback.
dragapult-takeuchiFixed strategy for the Dragapult ex deck — "Kuchiito Takeuchi" persona.
dragapult-yopifuttoFixed strategy for the Dragapult ex deck — "Dr. Yopifutto" persona.

Finding an opponent (intent)

OptionMeaning
--room--room ID: pairs the two clients with the same room (best for your-own-bots).
--vs--vs NAME: name a built-in server bot as the opponent.
--participant-id--participant-id ID --auth-token TOK: ladder (rated play; token issued under "Sign up"). Join with your own --deck.

Anonymous auto-matching (open) has been removed. Specify one of room / vs / ladder. Because each connection brings its own --deck, you can also run asymmetric matches with different decks on each side.

Time control (answer in time)

The arena runs sudden death: 10 minutes total per player + a 30-second cap per move. One move = one round-trip (request→response / prompt→choice). You lose on time (FlagFall) if a single response exceeds 30s or your total exceeds 10 min. A bot that stays connected but never answers also loses after 30s. Every request/prompt carries a clock, so you can read my_deadline_unix_ms to budget your time.

Writing your own bot

The main purpose of this repo is to be the foundation on which you write your own bot. There are only two steps.

1. Implement the BotPolicy trait in src/bots/<your_bot>.rs (choose_action picks your turn's move from req.legal_actions; choose_prompt answers choices during effect resolution).

impl BotPolicy for MyBot {
    fn choose_action(&mut self, req: &RequestMsg, rng: &mut ChaCha20Rng)
        -> Result<ActionDto, TransportError> {
        // req.state … board masked to your view / req.legal_actions … legal moves
        Ok(req.legal_actions.iter()
            .find(|a| matches!(a, ActionDto::EndTurn))
            .cloned()
            .unwrap_or_else(|| req.legal_actions[0].clone()))
    }
    fn choose_prompt(&mut self, p: &PromptMsg, rng: &mut ChaCha20Rng) -> PromptChoice {
        crate::bots::RandomPolicy.choose_prompt(p, rng) // when in doubt, random
    }
}

2. Register it with one line each in build / available in src/bots/mod.rs. Then it runs with --bot my-bot.

// src/bots/mod.rs
&["random", "dragapult-takeuchi", "dragapult-yopifutto", "my-bot"] // available()
"my-bot" => Some(Box::new(MyBot)),                                 // build()

What a bot can see

Discipline (faithfulness)

Protocol

The complete reference for the JSON exchanged with the server is in docs/protocol.md in the repo (every message's keys, types, error codes, prompt responses, and information masking). You can implement a bot in another language just by reading it.

License: MIT OR Apache-2.0. Issues / Pull Requests welcome. Bring your own new bots or different archetype decks.

⧉ View on GitHub: PTCG_AI_Battle_Client