Cockatrice: Building a Cheat-Resistant Card Game Simulator with Qt and Server Authority
Hook
Most virtual tabletops trust the client. Cockatrice doesn’t—and for competitive card games where a single illegal draw can decide a match, that architectural decision changes everything.
Context
Before digital card games like Magic: The Gathering Arena automated every rule interaction, players had two options: buy into Wizards of the Coast’s aging MTGO client (Windows-only, paid cards, clunky interface) or meet in person. For casual players, testing decks, or playing older formats not supported by official clients, neither option worked well. You couldn’t easily play Legacy or Vintage online without investing hundreds of dollars in digital cards you already owned in paper.
Cockatrice emerged to fill this gap: a free, open-source virtual tabletop that simulates the physical card game experience. Unlike rules-enforcing engines like XMage that attempt to automate Magic’s 20,000+ card interactions, ‘Cockatrice takes a different philosophy: provide the table, cards, and dice, but let players handle the game rules themselves. This manual approach trades automation for flexibility and performance, creating a lightweight simulator that works across platforms and supports any card ever printed—including custom sets and proxies.
Technical Insight
Cockatrice’s architecture centers on a client-server model built with Qt, where the server maintains authoritative game state while clients render the UI. This isn’t just a convenience—it’s a security model. In a virtual card game, clients could theoretically manipulate their hand, deck order, or life total. By making the server the single source of truth, Cockatrice prevents common cheating vectors while maintaining responsive gameplay.
The client communicates with the server through a protocol defined in the servatrice component (named after Serval cats, following the project’s feline theme). Game actions—drawing cards, playing permanents, declaring attacks—are sent as protobuf messages. The server validates these actions against basic game constraints (you can’t draw from an empty library, you can’t target cards that don’t exist) before broadcasting state changes to all players. Here’s a simplified example of how the protocol handles a card draw:
// From cockatrice/src/game/pb/game_event.proto
message Event_DrawCards : Event {
required int32 player_id = 1;
repeated CardInfo cards = 2;
message CardInfo {
required int32 card_id = 1;
optional string card_name = 2; // Only sent to card owner
}
}
// Server-side validation in servatrice
void GameRoom::handleDrawRequest(Player* player, int numCards) {
if (player->deck().size() < numCards) {
// Deck empty - send failure response
sendGameEvent(Event_DeckEmpty(player->id()));
return;
}
auto drawnCards = player->deck().drawTop(numCards);
// Send full card info to drawing player
sendToPlayer(player, Event_DrawCards(player->id(), drawnCards));
// Send redacted info (no names) to opponents
auto redacted = redactCardInfo(drawnCards);
sendToOpponents(player, Event_DrawCards(player->id(), redacted));
}
The data architecture separates concerns elegantly. Card information lives in external repositories (Magic-Token, Magic-Spoiler) that parse MTGJSON data into XML formats Cockatrice understands. The client downloads these XML files and caches them locally, meaning the server never needs to know about individual card rules—it just tracks game objects by ID. This modularity allows the community to update card databases independently from client releases, crucial for a game that releases new sets quarterly.
Qt’s cross-platform capabilities shine here. The same C++ codebase compiles to native applications on Windows, macOS, and Linux using platform-specific rendering backends. Qt’s signal-slot mechanism handles the event-driven nature of card game interactions cleanly:
// Simplified from cockatrice/src/game/tab_game.cpp
connect(player->hand(), &CardZone::cardAdded,
this, &TabGame::updateHandDisplay);
connect(player->battlefield(), &CardZone::cardTapped,
[this](Card* card) {
// Animate tapping, update game state
card->setRotation(90);
sendGameAction(Action_TapCard(card->id()));
});
The offline mode demonstrates another architectural strength: the same game engine runs locally without a server, swapping the network protocol layer for in-memory state management. Deck builders can test hands, simulate draws, and goldfish games without connectivity. This dual-mode operation is achieved through abstraction—the game logic interacts with a GameStateInterface that’s implemented by both LocalGame and NetworkGame classes.
One subtle but important design choice: Cockatrice doesn’t enforce turn structure. Unlike Arena or MTGO with their rigid priority passing, Cockatrice lets players act simultaneously, trusting them to follow the game’s timing rules. This requires less server complexity and feels more like paper Magic, where experienced players shortcut routine actions. The tradeoff is that new players might accidentally take illegal actions, but the target audience—enfranchised Magic players—generally knows the rules.
Gotcha
Cockatrice’s manual approach is simultaneously its greatest strength and most frustrating limitation. The software won’t stop you from drawing eight cards, forgetting a trigger, or resolving an interaction incorrectly. For casual play among friends, this flexibility works fine—you can even house-rule interactions on the fly. But for competitive practice or playing with strangers, you’re relying entirely on player integrity and rules knowledge. Complex stack interactions, layers, and state-based actions all require manual tracking. If you’re testing a deck with intricate combos involving multiple replacement effects, you’re doing the mental math yourself or consulting a judge chat.
The project’s development velocity reflects its volunteer nature. The webclient mentioned in the repository (written in TypeScript) has seen minimal progress—checking the commits shows it’s essentially dormant. If you need browser-based access, you’re out of luck for now. The desktop client remains actively maintained, but feature requests can languish for months. The community is helpful and the Discord active, but this isn’t a commercial product with SLAs or a roadmap. You’re participating in an open-source project, with all the irregular release schedules and occasional breaking changes that entails. The Flatpak distribution helps Linux users avoid dependency hell, but expect to occasionally encounter rough edges in the installation process on less common platforms.
Verdict
Use Cockatrice if you’re an experienced Magic player who wants to test decks, play formats not supported by official clients (Legacy, Vintage, Pauper, Commander), or organize private games without buying digital cards. The server-authoritative architecture makes it trustworthy for semi-competitive play, and the cross-platform support means your playgroup can join from any OS. It’s particularly excellent for deck prototyping and goldfishing—the offline mode loads instantly and doesn’t require server connectivity. If you’re comfortable managing game rules manually and value format flexibility over automation, Cockatrice is the best free option available.
Skip it if you need rules enforcement, want to learn Magic through software guardrails, or require a polished commercial experience. New players will find the manual tracking overwhelming—Arena’s automation better teaches proper sequencing and priority. Also skip it if browser access is mandatory (the webclient is vaporware) or you need 100% uptime guarantees (community servers sometimes go down, though self-hosting is possible). For formats officially supported by Arena or MTGO where you want ladder grinding and tournament play, those platforms offer superior competitive infrastructure despite their costs.