Card Club

  • Flutter

The idea for this one was born on a plane with my girlfriend. We wanted to play a game together to pass the time, but up in the air with no internet connection, there was no good way to do it. Sure, it was technically possible: we could huddle around a single phone and pass it back and forth. But that isn't exactly convenient, and playing a game together while both stuck on the same device takes all the fun out of it.

We both love to play Manillen (a Belgian trick-taking card game), and that got me thinking: every multiplayer game seems to need WiFi or a data connection. But what if you're on a plane, in a car, or somewhere with no signal at all? I wondered whether it would be possible to connect a few phones directly over Bluetooth instead, with no network required. Little did I know it was entirely possible, so I built it.

Card Club home screen

The core idea

Card Club is built to work with no internet at all. It never talks to a server, and it doesn't need WiFi or mobile data at any point, not even to set up a game. One player hosts, the others join, and all the phones talk to each other directly over Bluetooth. Everything, from the lobby to the last trick, runs fully offline on the devices themselves. No accounts, no servers, no tracking; just open the app and play, even in airplane mode.

Hosting a game over Bluetooth

Technical decisions

I built the app in Flutter. I'd used it before for Cellar Xpert and Fumera, and I like that a single codebase runs on both iOS and Android, which makes distributing it to Android later on much easier. For state management I went with Riverpod, and I leaned on a clean, layered architecture that keeps the game rules completely separate from the UI and the networking.

The trickiest and most interesting part was the multiplayer. Because the phones talk over Bluetooth with no central server, I couldn't rely on a backend to be the referee. Instead I made the game host-authoritative and event-sourced: any player sends a command ("I want to play this card"), but only the host validates it against the rules and turns it into a list of events. Those events are the single source of truth, and every device, host and clients alike, runs them through the exact same pure, deterministic reducer. That keeps all the phones perfectly in sync, and if someone's Bluetooth hiccups, they can reconnect and replay the event log to catch right back up.

The actual game rules (which card wins a trick, how trump works, how the score is counted) live in their own pure layer with no Flutter and no networking mixed in. That made them a joy to test: the scoring, trick resolution, and legal-move logic are all covered by an extensive unit-test suite, so I can trust the engine even when the networking gets messy.

Choosing a trump suit

Playing with bots

You don't always have four people around, so I added bots to fill the empty seats. They come in three difficulty levels (easy, medium, and hard), and the hard bot is the one I'm most proud of. It actually counts cards: it scans the event log to track everything that's been played, works out which cards are still out there, and uses that to detect guaranteed winners, play the odds, and even adjust its strategy based on the current match score. Playing against it is genuinely challenging.

Lobby with bots and teams

The game itself

Once everyone's connected, it plays like a proper game of Manillen: pick your trump, play out the tricks, and the app handles all the bookkeeping, from whose turn it is to who won each trick and the running score after every round.

A trick being played Score overview per round

Built to grow

Manillen is just the first game. I deliberately structured the codebase so the Bluetooth transport, the lobby, and the game-picker are all game-agnostic, so everything specific to a game lives in its own little world. That means adding a second card game down the line (Wiezen is the obvious next one) is mostly a matter of writing that game's rules, without touching the networking foundation again.

What started as a small frustration on a plane turned into one of my favourite projects to build, and now, wherever we end up, we can always get a game going. You can find out more at cardclub.gillesdanneels.be.