How We Built an Offline-First Cricket Scorer App with 0ms Latency
When Slogicmind Studio set out to engineer CricFlow Scorer, our primary design principle was non-negotiable: 100% offline-first reliability.
Cricket matches are played on wide grassy plains, concrete public parks, and remote sports clubs. These are notorious dead zones for cellular networks. If our app required an active internet connection to record scores, calculate strike rotations, or add extras, it would fail tournament coordinators the moment a signal dropped.
Here is the exhaustive engineering walkthrough of how we built a highly reliable, zero-latency offline scoring engine.
1. The Offline Caching Architecture (SQLite Core Engine)
To guarantee that match data is protected against unexpected battery drainage, phone calls, or app suspensions, we bypassed unstable browser memory in favor of an embedded SQLite Database Engine.
-- Explicit SQL database schema models for CricFlow Scoring Engine
CREATE TABLE IF NOT EXISTS matches (
id TEXT PRIMARY KEY,
team_a_name TEXT NOT NULL,
team_b_name TEXT NOT NULL,
overs_limit INTEGER DEFAULT 20,
status TEXT DEFAULT 'active'
);
CREATE TABLE IF NOT EXISTS players (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
team_id TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS ball_deliveries (
id TEXT PRIMARY KEY,
match_id TEXT NOT NULL,
over_number INTEGER NOT NULL,
ball_number INTEGER NOT NULL,
striker_id TEXT NOT NULL,
non_striker_id TEXT NOT NULL,
bowler_id TEXT NOT NULL,
runs INTEGER DEFAULT 0,
extra_runs INTEGER DEFAULT 0,
extra_type TEXT CHECK(extra_type IN ('none', 'wide', 'no_ball', 'bye', 'leg_bye')),
wicket_type TEXT CHECK(wicket_type IN ('none', 'bowled', 'caught', 'run_out', 'stumped')),
FOREIGN KEY(match_id) REFERENCES matches(id)
);2. Solving The Strike-Rotation Algorithm
In cricket scoring, tracking which batter faces the next delivery (the striker) and who stands at the opposite crease (the non-striker) is highly complex due to rapid game shifts:
To ensure 100% mathematical accuracy under MCC regulations, we structured a pure, fully testable TypeScript scoring matrix:
export interface BatterState {
strikerId: string;
nonStrikerId: string;
}
export interface DeliveryInput {
runs: number;
extraType: 'none' | 'wide' | 'no_ball' | 'bye' | 'leg_bye';
isWicket: boolean;
wicketType: 'none' | 'bowled' | 'caught' | 'run_out' | 'stumped';
}
export function computeNextStrikeState(
current: BatterState,
input: DeliveryInput,
ballsInOver: number
): BatterState {
let nextStriker = current.strikerId;
let nextNonStriker = current.nonStrikerId;
// 1. Calculate physical crease swaps during the delivery runs
const isCreaseSwapRequired = input.runs % 2 !== 0;
if (isCreaseSwapRequired) {
const temp = nextStriker;
nextStriker = nextNonStriker;
nextNonStriker = temp;
}
// 2. Handle specific wicket rotation rules (e.g. Run Out)
if (input.isWicket && input.wicketType === 'run_out') {
// Under custom league rules, the surviving batter is calculated based on run coordinates
}
// 3. Handle over boundary swap (Valid balls only, Wides/No-Balls don't count)
const isValidBall = input.extraType !== 'wide' && input.extraType !== 'no_ball';
const nextBallsCount = isValidBall ? ballsInOver + 1 : ballsInOver;
if (nextBallsCount === 6) {
// Over completes. Rotate striker crease facing side
const temp = nextStriker;
nextStriker = nextNonStriker;
nextNonStriker = temp;
}
return { strikerId: nextStriker, nonStrikerId: nextNonStriker };
}3. High-Density UI and Hardware Acceleration
Operating entirely offline allowed us to achieve sub-millisecond rendering loops.