Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

557 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

saloonbot

Lint and test

Discord bot originally built for randomly generating Old West names and now wandering off into card games.

See DISCORD_SETUP.md for instructions on creating and configuring a Discord bot for this app.

Names were scraped from Mithril and Mages.

Commands

Names

  • /wwname [gender] [number]: Generates number Old West names by choosing a random first name of the given gender and a random surname. The gender argument accepts any string starting with f or m. If no gender is given, a random one is chosen. If not provided, number defaults to 1. The output is a newline-joined list of names in the form of <gender emoji> first_name last_name.

Cards

All gameplay actions have slash commands. As a convenience, join, leave, bet <amount>, hit, and stand also work typed as plain messages in the game's channel (no slash prefix).

Slash commands:

  • /help — show all available commands.
  • /newgame [num_bots] (admin) — starts a new game of blackjack. num_bots (0–4, default 0) spawns bot players with Wild West personalities. Bots use AI-powered decisions if an API key is configured, otherwise they fall back to basic strategy.
  • /joingame — sit down at the table.
  • /leavegame — leave the current game.
  • /bet <amount> — place a bet during the betting phase.
  • /hit — draw another card on your turn.
  • /stand — hold your hand on your turn.
  • /wad — privately check your current balance (only visible to you).
  • /stats — view your saloon record (hands played, winnings, etc.; only visible to you).
  • /saloon — show the saloon's name, town, and active tables.
  • /changelog — show what's new at the saloon: the last 5 entries in CHANGELOG.md, or the last week's worth, whichever is more.
  • /stopgame (admin) — end the current game immediately and return all unresolved bets to players.
  • /checkwallet <target> (admin) — check any player's or NPC's wallet balance.
  • /setwallet <target> <amount> (admin) — set a player's or NPC's wallet to an exact amount.
  • /givechips <target> <amount> (admin) — adjust a player's or NPC's wallet by a delta (negative takes chips away).
  • /npclimits [min] [max] (admin) — view or set the NPC autofill min/max per table. With min > 0, tables stay populated with NPCs even when no humans are present.
  • /npcrelationships <npc> (admin) — list an NPC's relationships (partner, type, strength, and the story behind them).
  • /addnpc [count] (admin) — add one or more roster NPCs to the current game (default: 1).
  • /removenpc [name] (admin) — remove an NPC from the current game; omit name to remove any NPC.
  • /usage [days] (admin) — show a summary of LLM token usage (default 7 days, up to 90), broken down by purpose, model, and provider.
  • /debug (admin) — dump full internal state for troubleshooting, including live LLM provider health (up/down, last success/failure, whether NPCs are currently on AI or fallback strategy).

Metadata

  • /version: Outputs the current git sha.

LLM Bot Players

SaloonBot supports AI-powered bot players with Wild West personalities. Bots use an LLM to make real gameplay decisions (hit/stand/bet) and deliver in-character quips during the game.

Personalities

Bots are drawn from a pool of 15 archetypes and 4 historical figures, each with a distinct voice and betting temperament. Bot quips appear in sepia-colored embeds to distinguish them from regular game messages.

Archetypes: The Grizzled Prospector, The Drunk Cowboy, The Snake Oil Salesman, The Prim Schoolmarm, The Bounty Hunter, The Frontier Preacher, The Railroad Baron, The Half-Broke Drifter, The Card Sharp, The Saloon Singer, The Greenhorn Deputy, The Apache Tracker, The Patent Medicine Widow, The Retired Outlaw, The Railroad Cook

Historical figures: Doc Holliday, Calamity Jane, Jesse James, Wild Bill Hickok

NPC memory

AI bots remember their nights at the table. While seated, each bot keeps track of what happens around it — bets, busts, wins, and banter — and when it leaves (including deciding on its own to "call it a night"), it condenses the session into a short memory that persists in the database. The next time that NPC sits down, its recent memories feed into its prompts, so a regular NPC can reference past sessions and the people it played with. Memory depth follows SALOON_DETAIL_LEVEL: low disables the feature, medium recalls 1 recent session, high recalls 3.

NPC relationships

Roster NPCs have history with each other. A newly created NPC has a 70% chance of arriving with 1–3 pre-existing relationships to other roster NPCs — friendships, rivalries, or something more complicated — each with an LLM-written backstory note. Relationships also form and deepen through play: sharing a table strengthens a pair's bond (+5 per session), strangers who play together have a small chance of striking up a new relationship, and every 20 strength points the relationship's note is rewritten (and its type can shift — a friendship can sour into something complicated). When two related NPCs sit at the same table, both play with their shared history in their prompts, gated by SALOON_DETAIL_LEVEL: low omits it, medium injects the type and a one-sentence note, high the full note. Admins can inspect any NPC's relationships with /npcrelationships.

Configuration

Variable Default Description
LLM_PROVIDER openai LLM provider: openai, claude, none (disables the LLM client entirely — bots always use basic strategy), or fake (deterministic offline provider for testing)
ANTHROPIC_API_KEY API key for Claude; supports ANTHROPIC_API_KEY_FILE
OPENAI_API_KEY API key for OpenAI; supports OPENAI_API_KEY_FILE
LLM_MODEL provider default Override model (claude-haiku-4-5 / gpt-4o-mini)
LLM_TIMEOUT 5 Seconds before falling back to basic strategy
LLM_HEALTHCHECK_INTERVAL 300 Seconds between periodic re-checks of the LLM provider (detects credit exhaustion/outages and recovery without a restart)
LLM_DOWN_GRACE_PERIOD 120 Seconds a provider must keep failing before it's reported down for alerting/metrics — absorbs transient blips (e.g. DNS not ready right after a restart) without paging
LLM_SESSION_MEMORY_TIMEOUT 15 Seconds allowed for the background session-memory call
BLACKJACK_NPC_DEPARTURE_BASE 0.02 Baseline per-hand chance an NPC calls it a night
BLACKJACK_NPC_DEPARTURE_RAMP 0.28 Extra departure chance once an NPC has seen a full session
METRICS_PORT 9400 Port for the Prometheus /metrics endpoint

Exactly one provider is active at a time, chosen by LLM_PROVIDER (default openai) — the corresponding API key is required for openai/claude; an unset or invalid key (or LLM_PROVIDER=none) means bot players still join the game but use basic blackjack strategy instead of AI decisions. The provider is periodically re-checked while running, so credits running out or being topped up are picked up automatically.

All four secret variables (DISCORD_TOKEN, DISCORD_GUILDS, ANTHROPIC_API_KEY, OPENAI_API_KEY) resolve in priority order: direct env var → <VAR>_FILE path → /run/secrets/<lowercase_var> → unset. Docker secrets mounted at /run/secrets/ are picked up automatically with no extra configuration.

Metrics

The server exposes a Prometheus-format /metrics endpoint on METRICS_PORT (default 9400): LLM call/token counters (saloonbot_llm_calls_total, saloonbot_llm_input_tokens_total, saloonbot_llm_output_tokens_total, labeled by purpose/model/provider) and provider health (saloonbot_llm_provider_up gauge, saloonbot_llm_provider_failures_total counter). This repo only exposes the metrics — scraping, dashboards, and alerting are configured in whatever external Prometheus/Grafana setup you point at it.

CLI

A standalone CLI client (cli.py) lets you play and test the game locally without Discord.

export REDIS_HOST=localhost REDIS_PORT=6379
python cli.py

At startup you'll be prompted for your name and how many LLM bot players to add (0–4). The CLI auto-joins the game and drops you into the command prompt.

Available commands:

Command Description
join Sit down at the table (done automatically at startup)
bet <amount> Place a bet during the betting phase
hit Draw another card
stand Hold your current hand
leave Leave the game
addnpc [count] Add roster NPC(s) to the current game
removenpc [name] Remove an NPC (by name, or any if omitted)
help Show command list
quit Exit

Development

SaloonBot provides flexible development workflows using Docker Compose configurations. The bot consists of two main components: the Discord bot (bot.py) and the server component (server.py), both communicating through Redis. The server persists state to a database — MySQL in production, SQLite locally.

SQLite for local development

The server supports SQLite as a drop-in replacement for MySQL, controlled by the USE_SQLITE environment variable. All three dev scripts enable this automatically, so no MySQL container is needed for local development.

To use SQLite manually:

export USE_SQLITE=1          # enables SQLite
export SQLITE_PATH=saloonbot.db  # optional; this is the default
python server.py

Development Scenarios

There are three development compose files, each designed for a different workflow:

  1. compose.dev-bot-local.yml - Runs server + redis in Docker, allowing you to run bot.py locally
  2. compose.dev-server-local.yml - Runs bot + redis in Docker, allowing you to run server.py locally
  3. compose.dev-redis-only.yml - Runs redis only in Docker, allowing you to run both components locally

Using Helper Scripts

For convenience, helper scripts are provided for each scenario:

Run bot locally (server in Docker)

./dev-bot.sh

This starts the server and redis containers (server uses SQLite), then runs the bot locally. Requires DISCORD_TOKEN and DISCORD_GUILDS environment variables.

Run server locally (bot in Docker)

./dev-server.sh

This starts the bot and redis containers, then runs the server locally with SQLite. Requires discord_token.txt and discord_guilds.txt files for the bot container.

Run both components locally (redis only in Docker)

./dev-redis.sh

This starts redis in Docker. You can then run bot.py and server.py separately in different terminals:

# Terminal 1
export REDIS_HOST=localhost REDIS_PORT=6379 SALOONBOT_DEBUG=1
export DISCORD_TOKEN="your-token" DISCORD_GUILDS="your-guild-ids"
python bot.py

# Terminal 2
export REDIS_HOST=localhost REDIS_PORT=6379 USE_SQLITE=1 SALOONBOT_DEBUG=1
python server.py

Manual Usage

You can also use Docker Compose directly without the helper scripts:

# Start services
docker compose -f compose.dev-bot-local.yml up -d

# View logs
docker compose -f compose.dev-bot-local.yml logs -f

# Stop services
docker compose -f compose.dev-bot-local.yml down

Production/Staging Deployment

Environment-specific compose files are provided:

  • compose-production.yml — production deployment
  • compose.staging.yml — staging deployment

Run the appropriate environment directly:

docker compose -f compose-production.yml up -d
docker compose -f compose.staging.yml up -d

If you always deploy the same environment on a given host, you can symlink compose.yml to avoid specifying -f every time:

# Example: always use production on this host
ln -s compose-production.yml compose.yml
docker compose up -d

This runs all components (bot, server, and redis) in Docker containers.

For staging hosts that should track main automatically, staging-autodeploy.sh fast-forwards the checkout to origin/main and re-runs docker compose -f compose.staging.yml up -d when anything changed. It's meant to run from a systemd timer or cron entry, complementing watchtower: watchtower auto-updates images but reuses each container's existing config, so compose-file changes (ports, env vars, new services) only land via a pull + up. Reference unit files are in systemd/ — install with:

sudo cp systemd/saloonbot-autodeploy.{service,timer} /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now saloonbot-autodeploy.timer

Schema migrations run automatically on server startup — deploy new code and restart; no manual SQL needed.

Secrets setup (one-time per host)

Secrets are sourced from /etc/saloonbot/secrets/ on the host, which persists across reboots (unlike /run/secrets, which is a tmpfs and is wiped on restart). Run this once on each host:

mkdir -p /etc/saloonbot/secrets
chmod 700 /etc/saloonbot/secrets

# Required
echo -n "your-discord-token"     > /etc/saloonbot/secrets/discord_token
echo -n "guild_id1,guild_id2"    > /etc/saloonbot/secrets/discord_guilds

# Optional — LLM bot players. Create empty files if unused.
echo -n "your-anthropic-key"     > /etc/saloonbot/secrets/anthropic_api_key
echo -n ""                       > /etc/saloonbot/secrets/openai_api_key

chmod 600 /etc/saloonbot/secrets/*

All four secret files must exist (Docker Compose requires them even if empty). Docker mounts them into each container at /run/secrets/<name>, where SaloonBot picks them up automatically.

Tests

Unit Tests

To run unit tests, run:

pytest test.py

(python test.py also works, since it's a plain unittest suite.)

End-to-End Tests

End-to-end tests validate the complete system integration using the Redis interface with real MySQL and Redis services (no mocking). These tests:

  • Start Redis and MySQL via docker-compose
  • Run the actual server process
  • Simulate client interactions through Redis pub/sub
  • Verify game logic, database persistence, and server behavior

To run end-to-end tests:

python test_e2e.py

Or use the helper script:

./run-e2e-tests.sh

Requirements:

  • Docker and Docker Compose must be installed and running
  • The tests will automatically start and stop the necessary services

Note: End-to-end tests take longer to run (~2 minutes) as they start/stop Docker containers and wait for game timing events.

Flaky Test Detection

hunt_flaky.py re-uses a single Docker stack across N runs per test case, making repeated flakiness detection roughly 100× faster than re-spinning containers each time.

source .venv/bin/activate
python hunt_flaky.py --runs 20

Options: --runs N (default 10), --output FILE (JSON results), --class ClassName (limit to specific test class).

About

Discord bot for randomly generating Old West names and playing cards and whatever

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages