Overview
Zugzwang is a complete chess tournament management application, built for arbiters and organisers. It covers the whole life cycle of a tournament: player registration, automatic round-by-round pairings, result entry, real-time standings with tiebreaks, prize allocation and official PDF exports.
At the heart of the software is its pairing engine, compliant with the FIDE Dutch Swiss system. Pairing a round is far from trivial: it means satisfying dozens of rules — colour balance, float handling, byes — while guaranteeing that a valid solution is always found. This combinatorial problem, at the crossroads of regulatory rigour and algorithm design, is the central technical challenge of the project.
Architecture
Zugzwang is built on Clean Architecture across eight projects. At the centre sits a pure C# domain — entities, business rules and the pairing engine — with no external dependency, and therefore fully testable in isolation. Around it, an Application layer defines the contracts (repository interfaces, services), and an Infrastructure layer implements them: EF Core persistence, exports, integrations.
The user interface lives in a single Razor component library shared by all three targets. The Windows desktop application hosts it in a BlazorWebView (WPF + Blazor Hybrid); the public website serves it through Blazor Server; a mobile edition in MAUI is planned along the same lines. One UI codebase, three platforms.
On the data side, the local workstation relies on SQLite through EF Core 9, for fully offline operation — an arbiter must be able to work in a venue with no reliable network. Data is then synchronised to Turso (hosted, libSQL-compatible SQLite) in the cloud, where the public website reads it.
Technical decisions
Offline-first. Rather than an always-connected application, I chose a local SQLite foundation with deferred synchronisation to the cloud. The constraint comes from the field: tournament venues rarely have a reliable network, and pairing a round cannot wait. The cloud becomes a sharing and backup service, never a point of failure.
A frugal pairing engine. The Dutch system allows a factorial number of combinations. Rather than enumerating them all, the engine performs a greedy pairing, then a limited backtracking pass that generates permutations on demand and stops as soon as a valid solution is found. Memory stays constant and the computation near-instant, even for large tournaments.
One interface, several systems. The different pairing systems — Dutch Swiss, round-robin, team — sit behind a single interface (Strategy pattern): adding one does not touch existing code. Likewise, a single Razor component library serves both desktop and web with no duplication. These isolation choices keep the software open to extension and costly to break.