Skip to content

Build Your Bot

Building your own bot is the most straightforward way to prove your superiority at this game. Not trash talking. Not gaslighting your opponent into thinking they miscounted trumps. Just cold, verifiable results.

We provide ready-to-go templates for Python, TypeScript, C#, Go, and Java. If you can write a function in any of these, you can build a bot. No need to know how HTTP works, how JSON is parsed, or how game engines communicate. Pick a template, duplicate the folder, edit one file. That’s the whole thing.

  • .NET 10, required to run giretra-manage, the CLI tool you’ll use to test your bot
  • Your language’s runtime: Node 20+ for TypeScript, Python 3 for Python, Go 1.23+ for Go, Java 17+ for Java

That’s it. No databases, no message queues, no container orchestration.

Terminal window
git clone https://github.com/giretra/giretra.git
cd giretra

Everything happens inside this repo: templates, testing tools, and the bot folder where your bot will live.

Copy one of the template folders in external-bots/ and rename it:

Terminal window
cp -r external-bots/random-python-bot external-bots/my-bot

Each template folder contains:

FilePurposeEdit?
bot.meta.jsonBot identity and launch configurationYes
Bot file (bot.py, bot.ts, Bot.cs, bot.go, Bot.java)Your game logicYes
Server fileHTTP boilerplateNo
Type definitionsData structures for requests and responsesNo

Open bot.meta.json and update the identity fields:

{
"name": "my-bot",
"displayName": "My Awesome Bot",
"pun": "I never bluff... except when I do",
"author": "Your Name",
"authorGithub": "your-github-username"
}

Field reference:

FieldDescription
nameInternal identifier. Must be unique, lowercase, no spaces. This is also your folder name.
displayNameWhat shows up in leaderboards and match results.
punA one-liner that shows up next to your bot’s name. Optional but encouraged.
authorYour name.
authorGithubYour GitHub username.
initHow to install dependencies or compile. Runs once before your bot starts.
launchHow to start your bot’s server. The engine sets a PORT environment variable.

Leave init and launch alone unless you need to change how your bot builds or starts. The templates have these set up correctly.

Your bot makes exactly 3 types of decisions:

  1. Choose a cut position. Before each deal, someone cuts the deck. Pick a position between 6 and 26.
  2. Choose a negotiation action. The bidding phase. The engine gives you a list of valid actions (announce a mode, accept, double, redouble). Pick one.
  3. Choose a card to play. The heart of your strategy. The engine gives you a list of valid cards. Pick one.

Here’s a complete bot in each language. This is the only file you edit:

bot.py

import random
from bot_types import *
class Bot:
def __init__(self, match_id: str):
self.match_id = match_id
def choose_cut(self, ctx: ChooseCutContext) -> CutResult:
position = random.randint(6, 26)
return CutResult(position=position, from_top=True)
def choose_negotiation_action(
self, ctx: ChooseNegotiationActionContext
) -> NegotiationActionChoice:
# ctx.valid_actions has everything you're allowed to do
return ctx.valid_actions[0]
def choose_card(self, ctx: ChooseCardContext) -> Card:
# ctx.valid_plays has every legal card you can play
return random.choice(ctx.valid_plays)

That’s it. Three methods. The engine tells you what’s legal through validActions and validPlays, so you can’t accidentally make an illegal move. You just pick from the menu. Start dumb, iterate.

The request contexts also include your hand, the current trick, scores, and negotiation history, everything you need to build a real strategy.

All commands below assume you’re at the root of your Giretra clone. Use ./giretra-manage.sh on Linux/macOS or giretra-manage.cmd on Windows.

The first command you should run. It plays your bot through 100 matches against Kialasoa and checks everything works:

Terminal window
./giretra-manage.sh validate my-bot

You get a full report with:

  • Rule violations and crash counts (your target: zero)
  • Response times per decision type (min, avg, P50, P95, P99, max)
  • Game mode coverage (whether your bot was tested across all six game modes)
  • Performance trend (response time stability across the run)

You can tweak the run with flags like -n 500 for more matches, -o Razavavy for a tougher opponent, -d for a determinism check, or -v for verbose violation details.

Once your bot validates clean, pit it against the built-in opponents:

Terminal window
./giretra-manage.sh benchmark my-bot Kialasoa
./giretra-manage.sh benchmark my-bot Razavavy
./giretra-manage.sh benchmark my-bot Eva
BotDifficultyStrategy
KialasoaEasyPicks randomly. If you can’t beat it, something’s wrong.
RazavavyMediumTracks cards and reads partner signals.
EvaHardCard counting, void inference, positional play. The current boss.

The benchmark runs 1000 matches by default and gives you win rates with 95% confidence intervals, ELO ratings, and statistical significance. Use -n to change the match count.

See where your bot ranks against all available bots:

Terminal window
./giretra-manage.sh swiss

This discovers all bots (built-in and external) and runs a full Swiss-system tournament with a final leaderboard, win/loss records, and ELO ratings. You can also pass specific bot names to limit the participants.

You can run the full play.giretra.com web app locally to play against your bot in real game conditions. First, install the frontend dependencies:

Terminal window
cd src/Giretra.Web/ClientApp/giretra-web
npm install

Then start the app from the repository root with the --offline flag:

Terminal window
dotnet run --project src/Giretra.Web -- --offline

This launches the ASP.NET backend and the Angular frontend with mocked authentication, so you don’t need any external services. Open http://localhost:4200 in your browser to access the app.

Pay attention to the console output. If your external bot fails to load (missing dependencies, broken init script, port conflict), the error will show up in stdout. This is the fastest way to catch startup issues before they become mysterious test failures.

If you want your bot to track what other players are doing, subscribe to notifications by adding event types to bot.meta.json:

"notifications": ["card-played", "trick-completed", "deal-ended"]

Then implement the corresponding handler methods:

def on_card_played(self, ctx: CardPlayedContext) -> None:
# Track which cards have been played
pass
def on_trick_completed(self, ctx: TrickCompletedContext) -> None:
# Track tricks won by each team
pass
def on_deal_ended(self, ctx: DealEndedContext) -> None:
# Review deal results
pass

Available events: deal-started, card-played, trick-completed, deal-ended, match-ended.

These are great for building memory: tracking which cards have been played, detecting voids, counting points. But they’re completely optional. A stateless bot works just fine as a starting point.

One rule: no calling external resources during play. No API calls to a cloud AI, no phoning home to a remote server, no downloading strategy files mid-game.

Your bot runs locally, makes decisions locally. You can use whatever local libraries and dependencies you want: math libraries, data structures, even a local ML model. Just keep it self-contained.

Start with the dumbest bot that works. Validate it. Then make it smarter, one decision at a time. Beat Kialasoa, then go after Razavavy, then try to dethrone Eva.

Ready to go live? Follow the Publish Your Bot guide to open a pull request and make your bot playable on play.giretra.com.

Want to understand the protocol under the hood or build a bot in a language without a template? Check out Build from Scratch.