Pickup games depended on one patient volunteer to gather eight people, chase no-shows, make fair teams, choose a map, locate the in-game lobby, and remember the result. Momentum disappeared while people waited, hand-picked teams felt unfair, and results buried in chat could not support a ladder anyone trusted.
BDBotPickup games, run by the bot.
A self-hosted Discord product that turns one /add command into a complete pickup-game workflow—gathering players, confirming attendance, balancing teams, finding the live lobby, recording results, and updating permanent ratings.
Follow one match
Open full screenshot ↗BDBot announces the teams, captains, selected map, and lobby instructions in one message. It then adds a readable match preview and predicted win chances so players know what they are joining.
Eight players want a 4v4.
Nobody wants to organise it.
A pickup game now costs one command from each player and no coordination from an organiser. No-shows are surfaced by a timed check-in, teams follow the community’s chosen fairness rules, stalled matches clear themselves, and every completed game contributes to a persistent competitive history.
Four stages. No organiser.
The bot does not just form teams. It owns the waiting, attendance, lobby handoff, result, and rating history that usually fall on a volunteer.
Gather
Players type /add. The bot keeps the queue current and starts building a match as soon as the configured team size is reached.
Confirm
A ready check proves everyone is present. Timeouts remove no-shows and cancel incomplete matches cleanly.
Build & join
Teams are balanced, drafted, or shuffled; a non-repeating map is chosen; and the live lobby feed produces a one-click route into the game.
Settle
A reported result recalculates ratings, settles predictions, updates the leaderboard, and writes the match into permanent history.
The product explains itself where people already play.
Commands become readable community tools—players can understand the match, inspect their own history, and see the ladder without opening a separate dashboard.
Open screenshot ↗Every player gets a living profile
The rank command turns permanent match history into a useful player view: rating, results, recent changes, current form, prediction accuracy, and performance patterns across civilizations, maps, and teammates.
Open screenshot ↗The community can see what every game changes
Rating and prediction leaderboards update inside Discord, making the competitive history visible without a separate spreadsheet or an organiser manually calculating results.
Problem, workflow, and result in one view.
This is the complete case-study map: what failed before, what the bot now handles, and why the engineering choices matter to players.
Open full case-study map ↗Designed for the moments community tools usually fail.
Restarts, no-shows, conflicting entry points, and different channel rules were treated as product requirements—not edge cases.
Design for waiting and failure
A timed state machine gives every match a defined next step, deadline, and clean exit instead of allowing silent players or dead games to wait forever.
Persist what players can see
Queues and live matches are saved and restored across restarts, while results, players, rating history, and match records live in PostgreSQL or MySQL.
Give every door one brain
Discord interactions, text commands, authenticated web requests, and the one-second background loop share the same context and command logic, preventing different surfaces from disagreeing.
Configure the community, not the code
Team size, map rotation, rating method, timeouts, permissions, and language are channel-level settings that administrators can change without editing files or restarting the service.
See the system behind the Discord messages.
These diagrams document the running services, match state machine, shared command layer, domain objects, and persistence boundaries.
Open diagram ↗System architecture — a Python Discord process and Node/Express admin process share live bot state, persistence, and external services on one self-hosted server.
Open diagram ↗Match lifecycle — every pickup game has explicit states, timeouts, cancellation paths, rating updates, and a clean end.
Open diagram ↗Domain architecture — four entry points use one context interface and command layer before reaching queues, matches, configuration, statistics, and storage.
I designed the workflow and built the product behind it.
Designed and built the product workflow, asynchronous Python bot, match state machine, Node and Express admin application, persistence layer, rating strategies, external-service integrations, localisation, and self-hosted operations.
- Frame the organiser problem and design the end-to-end pickup-game experience.
- Build the Python asyncio Discord bot and explicit match lifecycle.
- Implement automatic balancing, captain drafting, random teams, map cooldowns, and three rating strategies.
- Create the Node and Express dashboard, authenticated REST access, and live runtime configuration.
- Integrate Discord, the AoE2 lobby WebSocket, AoE2Companion, Supabase, and supporting services.
- Design restart recovery, database adapters, localisation, monitoring heartbeat, and self-hosted deployment.