// projects / detail

OpenScore

Universal sports scoring and scheduling engine. Pure functional, offline-first, cross-language.

tech stack
elixirswifttypescriptkotlinphoenix
SCREENSHOT_001.png

One-liner

A scoring and scheduling engine for any sport, built on one contract: apply(state, action, ruleset) → {state, events}.

Why

Every sports app reinvents scoring logic, but scoring is fundamentally a pure function: given a state and an action, produce a new state and events. Extract that layer and the engine becomes reusable across sports and platforms.

Core Design

Action-sourced, not event-sourced. The engine is passive — it doesn’t track time, only reacts to actions. History is an action sequence, not an event log. Undo = replay from scratch minus the last action, not a reversal.

State bubbling: Point scored → recursively check win conditions upward. game_won → set_won → match_won, events bubble bottom-up.

Declarative rulesets: Win conditions, display formats, special behaviors are all config, not code. Same engine, different ruleset, different sport.

Supported Sports

9 sports, 18+ rulesets:

SportRulesets
Tennisstandard, grand slam, no-ad
Badmintonstandard, doubles
BasketballNBA, FIBA, 3x3
Padelstandard, golden point
Pickleballrally scoring, side-out
Table Tennisstandard, short
Volleyballstandard
BaseballMLB
Softballfastpitch

Cross-Language Conformance

The engine is implemented in 4 languages: Elixir (all 9 sports), JavaScript, Swift, and Kotlin. All implementations run the same JSON conformance test suite (609 cases) to guarantee identical behavior.

Three-Layer Architecture

Platform Layer — Phoenix API + WebSocket + Astro frontend + iOS (SwiftUI)
Schedule Layer — Tournament engine (knockout, double elimination, round robin, swiss, group knockout)
Match Layer   — Single-match scoring engine

The schedule engine handles winner_of / loser_of dependency resolution: match completed → resolve downstream brackets → emit match_ready when both sides are determined.

Realtime Sync

Phoenix Channels handle live scoring sync. Match Channel and Session Channel let multiple viewers watch the same match’s live score. The iOS app stays in sync via WebSocket.