Inspiration
For a couple weeks I've been hanging around online communities where people are trying to come up with new tile-based games. The same pattern kept happening: someone would suggest a variation, "what if bishops could jump," "what if pawns moved sideways," and there was no good way to tell whether the idea was actually fun or just novel. Trying it out meant having an AI build/update a website, republishing it, and only then finding out if the rule was any good. There was no shared framework, so every suggestion reinvented the wheel, and every small idea change cost a full deploy cycle before anyone could sit down and play it.
AI agents already made the idea part faster: you could describe a rule change to a model and it understood you immediately. What was missing was a place for that understanding to land. Re-publishing a whole website for "what if the bishop jumps" is annoying at best. I wanted to create a board game website that was entirely modularly rule-defined such that telling an agent what you wanted and actually playing it online with a friend could be the same five minutes.
The other half of this project is just personal: I've always liked breaking a game's rules apart and rebuilding them slightly different, trying to find the more-fun version than the original. And I know this isn't uncommon; if you look at the Wikipedia list of chess variants, you'll see hundreds of people who did exactly this.
What I learned
An important idea turned out to be that the game has to be entirely data and never code. If a rule can be an arbitrary function, you can't validate it, you can't hand it to an agent safely, and you can't run a search against it. A rule someone wrote has to be immediately safe to open and function in the same exact way as alternative rules.
I also learned that giving an agent a tool is a design problem in its own right, not a wrapper around an API. The tool listing gets re-sent on every single turn of a conversation, so every tool that isn't earning its place is a tax paid forever. I ended up cutting the workbench's tool listing roughly in half by removing tools for things that the agent doesn't need to do: publishing, voting, starting a match, and keeping only what an agent is actually there for: reading a game, building one, checking it, and handing over a game of it.
How I built it
Everything centers on a single rule format; board, pieces, movement, capture, and win conditions are expressed as one finite JSON document. That document is read by three different engines that all have to agree: a TypeScript reader in the browser, a Go reader on the server (the authority), and a Rust core compiled to WebAssembly for speed (so bot engine can search millions of positions a second for better quality play). Keeping three implementations honest is done with a shared conformance corpus: a fixed set of recorded positions and legal moves that all three readers are tested against, so a format change that lands in one and not the others shows up as a failing test rather than a live bug.
The Rust engine exists because an interpreted alpha-beta search over a general rule format is too slow to be useful. Compiling the document down to a program with names already resolved (offsets baked, kinds as indices) was worth roughly 40× the speed of just trying to make the interpreter faster. That speed is what makes it possible to validate a brand-new game variant by having a bot play hundreds of games against itself in well under a second, checking that one side doesn't always win and the game doesn't always draw. Alpha-beta pruning is what makes that search tractable at all: with good move ordering it cuts the size of the game tree explored from O(b^d) down to roughly O(b^{d/2}) for branching factor b and depth d. The difference between a bot that can check a variant in milliseconds and one that can't.
The same rule format also generates the rulebook. Because moves, captures, and win conditions are structured data rather than arbitrary prose, the site can snapshot "interesting" positions from bot self-play and use them to show a stranger how a brand-new game is actually won, without anyone writing documentation/explanations by hand.
On top of that sits a shared workbench: a person and an agent editing the same document, through the same functions, at the same time, with an activity ledger, pause, and a confirmation step before anything gets published.
Challenges
The hardest problem wasn't any single piece of code, it was scope discipline in the format itself. I really wanted to support every game, but if I add support for every possible game interaction or mechanic the rule-based game is actually worse than if it was just hard coded. If there's too many options, it's too hard to use and we don't get the benefit of the shared mechanics!
Keeping three engines byte-for-byte consistent was a constant source of subtle bugs. Every format change had to be replicated in three places, so I planned ahead and leaned hard on automated equivalence tests between the interpreted and compiled paths rather than trusting careful reading.
Measuring whether the bot was actually competent was tricky. Because there are many different possible games and I wanted a single bot that could learn any ruleset and play decently well, I had to weigh the eval parameters against known strategies for existing games, tying them to specific win conditions and piece values (when possible in the ruleset). This took a lot of trial and error in many different games, and it's not perfect, but it doesn't have to be! The point of the computation engine isn't to beat StockFish at chess, it's to be good enough to know when the game is bad. When the human is testing the game, it doesn't matter if they beat the bot 80% of the time, because just having an opponent is enough to validate if it would be a fun game and if they should invite their friends to play (with the built in challenge link).
Built With
- go
- nginx
- react
- rust
- sqlite
- typescript
- ubuntu
- webassembly
- webmcp
- websockets
Log in or sign up for Devpost to join the conversation.