BUGOUT also features a multiplayer option for playing against human friends, if you're lucky enough to have them.
The project is open-sourced, currently under MIT license.
This write-up was originally posted at terkwood.farm.
I started playing GO sometime during high school. Several of my friends also learned to play. We would often play chess, for which I had a neat little carrying case with a simple game clock, a rollout board, and sturdy pieces. I came into a possession of a GO board of standard dimensions, and used plastic pieces that were just heavy enough to feel like they mattered.
We would sometimes meet at a Chinese dive on West 10th Street in Indianapolis, consume delicious, sugary food, and play.
GO felt different than chess. There was a sense of immense possibility. There was time to think about the future. Winning was hard. Getting your skill up with a same-stage newbie was exhilarating, because you grew together.
Eventually I got my board signed by VICTOR WOOTEN while attending a concert performance given by Béla Fleck and the Flecktones at the Indiana Roof Ballroom: we had been playing some small rounds prior to the start of the concert.
It must have been... 1999? Victor Wooten was super gracious, and inspired everyone to a wholesome life of technical mastery. I was young. I wore an undersized fedora.
The music was phenomenal, and Wooten's signature fueled my fire.
In college I dreamt that one day I would wake to the sound of an obstreperous trumpet, having been mired in games and strenuous efforts to build some sort of ultimate strategy. I dreamt that I would wake to a vision of clear dawn.
But the actual time I've spent playing GO in my life is tiny. Life goes on, and its daily demands obscure our roots.
Still, the aesthetic delight of GO has been enjoyed by millions of individuals over the centuries, and it's this very beauty which has inspired my work over the last nine months.
GO is a game which helps me combat the lassitude of experience and boredom.
GO is not a game which must be won. It's enough to face a player whose skill exceeds yours, and to learn. When I play 9x9 against KataGo running on a 5W computer, I'm happy if I manage not to lose an embarrassing number of pieces.
GO keeps me sharp.
BUGOUT started out as an excuse to implement something (anything) using Kafka. My previous two professional engagements had barely skirted opportunities to legitimately include a Kafka install: one was simple enough to rely on HTTP microservices, another had opted for Kinesis.
Fresh into successful semi-retirement, I didn't want to bother with finding a use case for my implementation. I wanted what I couldn't have reasonably argued for in past, commercial settings. I wanted to play with Kafka Streams!
It turns out that mapping the tiny domain model of a GO game to kafka streams is... easy. Initially BUGOUT was envisioned as a multiplayer GO board which could easily run in any modern browser and provide an enjoyable, boutique alternative to more popular servers and venues. I wanted to play again with my old friends, just like when we were kids. So the project quickly incorporated a game lobby system which allowed players to choose between joining a quick 19x19 game with the next person to visit the web page, or creating a nicely-formatted URL (
https://example.com/?join=nXblGBE7erWyocXYYpRN1YOzdD) for their private game and sharing it with a friend.
Finally, both players can be fairly assigned a side, and the game can begin.
It basically worked. I spent time making sure the websocket connection between the browser and the BUGOUT gateway server was solid. I had to guarantee that Kafka and all its dependent apps started up in an orderly fashion. And I enjoyed writing functional-ish Kafka Streams code in Kotlin: from a cognitive perspective, it felt clean and tidy, even if the topology graphs quickly got out of hand.
It's pretty obvious to anyone that spends 30 minutes in the Kafka literature, that a production deployment of Kafka involves at least three machines. I was running a single node, because my average user load was zero. What's more, I was paying for AWS compute time using a personal credit card, so I've never been exactly fired up about running an individual host that comes anywhere near the specs recommended by the vendor.
Some additional reading turned up that 6GB of RAM is a reasonable minimum for any single host:
In most cases, Kafka can run optimally with 6 GB of RAM for heap space. For especially heavy production loads, use machines with 32 GB or more. Extra RAM will be used to bolster OS page cache and improve client throughput. While Kafka can run with less RAM, its ability to handle load is hampered when less memory is available.
Ever the miser, I settled on running a
t3.medium compute host for my degenerate Kafka cluster with its single, lonely node. 4GB of RAM certainly worked well enough for my theoretically scalable backend -- at least as long as it didn't become popular.
Anyway, who cares about vendor reccomended minima or a realistic-looking deployment? I was liberated from corporate hierarchy, profit motive, etc, and was ready to experience the raw freedom that I'd always dreamed about as a junior developer. I was ready to play with toys.
The only problem was that running even a
t3.medium around the clock cost about $1/day.
🤑 That's expensive! 🤑
The windmills were ready to tilt.
I spent a non-trivial amount of effort using rusoto to control the startup and shutdown of my "very expensive"
t3.medium instance. Whenever the system detects that there are no users connected, a timer starts, and after a few minutes, it shuts down the
t3.medium running Kafka, and all of the memory-hogging Kafka Streams apps supporting gameplay.
Whenever someone comes back to the website and wants to play a game, the system loyally boots the Kafka host.
And that's great. I can still afford to eat. Working with rusoto was painless. But the user experience of waiting for BUGOUT's Kafka backend to initialize is... abysmal. It takes about 90 seconds for the EC2 instance and its various docker containers to start up. That's 81 seconds longer than eternity.
Enough was enough. I had no boss. I had no users. I had no chains. My self-respect was questionable.
I would re-implement the backend with redis streams, so that my game could be online 24/7, using leftover CPU and RAM on the
t3.micro instance that I was already paying for.
Why not? Redis is awesome! And by choosing
rust for this portion of the impl, I knew I could keep my memory and CPU footprint low.
The effort is ongoing. It will eventually be completed in the way that one completes a crocheted scarf.
After months of relatively steady operation, I came to the sobering conclusion that most of my close friends were busy with their own lives, and playing a synchronous session of GO with them, often across international time zones, was an exercise in scheduling prowess that surpasses even the most skilled project manager.
So I began to wonder about playing against an AlphaZero-like AI through my browser.
If I can't play against my friends...
...why not BUILD MY OWN FRIEND?
That's fine. I like building things on my own -- or in this case, integrating other people's things on my own. So I purchased an NVIDIA Jetson Nano System-on-a-Chip and set about linking it to my existing infrastructure.
Implement a system which allows playing against a KataGo instance running on an NVIDIA Jetson Nano.
- [x] build KataGo on the nano
- [x] wrap katago stdin/stdout #190
- [x] make a basic docker image #189
- [x] gateway: backend routing (#205)
- [x] gateway: send & recv bot attachment and move-making messages
- [x] use websockets correctly #210
- [x] create botlink service and push game states via websocket (#193, #196, #197)
- [x] connect tinybrain to Botlink service via websocket and process game states (#194)
- [x] document reverse-proxy setup
- [x] clean up micro-changelog init (#187)
- [ ] extend Sabaki initial menu, send attach bot command, process makemoves as normal,
listenForMovewhen human plays white
- [x] fix parse error #220
- [x] fix all coords #223
Use KataGo analysis engine to respond to queries.
An existing service for playing against bots
The effort proceeded smoothly. Soon I was suffering regular blows to my pride, delivered easily by a 128-core GPU that can subside on a mere 5W of power. NVIDIA's pre-installed CUDA libs made compilation of KataGo relatively easy. Hooking up the tiny SoC in my office to my dirt-cheap AWS
t3.micro felt pleasantly conservative.
There isn't much work left before the AI subsystem is complete. It's already possible to play against KataGo through the web interface, if you're patient enough to run a read-eval-print-loop and put Kata's pieces on the board manually.
After just a few more hours of work, I'll be able to head to my website from anywhere in the world, and play a power-efficient little bot that's just right for my skill level.
And then, free from the lamentable desire for human interaction, I'll play against my own, new type of friend: a friend born of silicon of and parallel arithmetic.