Skip to main content

Cricinfo

A live scorecard updates one ball at a time, and "one ball" already has enough state to trip you up: it can be a run, a wicket, a wide that doesn't count toward the over, or all three format's worth of rules about when an innings simply ends. The design keeps the ball-by-ball plumbing identical across formats and isolates only the part that actually differs.

Requirements

Functional

  • A match is created between two teams in a format: T20, ODI, or Test.
  • Deliveries are recorded one at a time; each updates the current over, the batting side's score, and the striker's/bowler's individual stats.
  • An over completes after six legal deliveries (wides and no-balls don't count toward the six); an innings ends per the format's own rule (overs limit, all out, or a declaration for Test).
  • A scorecard reports the live score, overs bowled, and current batters/bowler at any point without replaying the innings.

Non-functional

  • Recording a ball must update the scorecard incrementally - it must not recompute totals by replaying every prior delivery.
  • The rule for when an innings ends (fixed overs vs. all-out-or-declare) differs by format and must be swappable without editing Match, Innings, or Over.

Design

Match records one Ball at a time and never asks "what format are we in" to decide anything - every format-specific question (how many overs, when does this innings end) is answered by a MatchFormat strategy handed in at match creation. Over and Innings only know about balls and totals; they'd behave identically whether the strategy said "20 overs" or "unlimited, until ten wickets or a declaration."

ScorerMatchInningsOverMatchFormatrecordBall(delivery)1recordBall(delivery)2recordBall(delivery)3isInningsComplete(innings)4startNextInnings()5
  1. 1The scorer only ever talks to Match - never to an Over or Innings directly.
  2. 2Match forwards to whichever innings is currently live.
  3. 3The over updates its own ball count and the innings’ running score.
  4. 4After every ball, the innings asks the format strategy - not itself - whether it should end.
  5. 5If the format says the innings is done, the match moves on - the rule that triggered it never leaks past this line.

Scorecard is a read-only view built once and mutated ball-by-ball alongside the innings - never rebuilt - which is what keeps "get me the live score" a field read instead of a replay.

Class diagram

«interface»MatchFormat+ oversLimit(): int+ isInningsComplete(innings): boolMatch- teams: Team[2]- innings: List<Innings>- format: MatchFormat+ recordBall(delivery)+ scorecard(): ScorecardInnings- battingTeam: Team- overs: List<Over>- scorecard: Scorecard+ recordBall(delivery)+ isComplete(format): boolOver- legalBallCount: int- balls: List<Ball>+ recordBall(delivery)+ isComplete(): boolBall- bowler: Player- striker: Player- runs: int- extra: ExtraType- wicket: boolPlayer- name: string- runsScored: int- ballsFaced: int- wicketsTaken: intScorecard- totalRuns: int- wickets: int- oversBowled: double+ applyBall(delivery)T20FormatTestFormat
implementsuses
MatchFormat decides overs-limit and end-of-innings; Match/Innings/Over never branch on format.

Code

import java.util.*;
 
enum ExtraType { NONE, WIDE, NO_BALL }
 
class Player {
final String name;
int runsScored = 0;
int ballsFaced = 0;
int wicketsTaken = 0;
 
Player(String name) {
this.name = name;
}
}
 
class Ball {
final Player bowler;
final Player striker;
final int runs;
final ExtraType extra;
final boolean wicket;
 
Ball(Player bowler, Player striker, int runs, ExtraType extra, boolean wicket) {
this.bowler = bowler;
this.striker = striker;
this.runs = runs;
this.extra = extra;
this.wicket = wicket;
}
 
boolean isLegal() {
return extra == ExtraType.NONE;
}
}
 
class Scorecard {
int totalRuns = 0;
int wickets = 0;
int legalBalls = 0;
 
void applyBall(Ball ball) {
totalRuns += ball.runs + (ball.extra == ExtraType.NONE ? 0 : 1);
if (ball.wicket) wickets++;
if (ball.isLegal()) legalBalls++;
}
 
double oversBowled() {
return (legalBalls / 6) + (legalBalls % 6) / 10.0;
}
}
 
class Over {
private final List<Ball> balls = new ArrayList<>();
private int legalBallCount = 0;
 
void recordBall(Ball ball) {
balls.add(ball);
if (ball.isLegal()) legalBallCount++;
}
 
boolean isComplete() {
return legalBallCount >= 6;
}
}
 
interface MatchFormat {
int oversLimit();
boolean isInningsComplete(Innings innings);
}
 
class T20Format implements MatchFormat {
public int oversLimit() {
return 20;
}
 
public boolean isInningsComplete(Innings innings) {
return innings.scorecard.wickets >= 10 || innings.overs.size() >= oversLimit();
}
}
 
class TestFormat implements MatchFormat {
public int oversLimit() {
return Integer.MAX_VALUE;
}
 
public boolean isInningsComplete(Innings innings) {
return innings.scorecard.wickets >= 10 || innings.declared;
}
}
 
class Innings {
final Scorecard scorecard = new Scorecard();
final List<Over> overs = new ArrayList<>();
boolean declared = false;
private Over currentOver = new Over();
 
void recordBall(Ball ball) {
scorecard.applyBall(ball);
currentOver.recordBall(ball);
if (currentOver.isComplete()) {
overs.add(currentOver);
currentOver = new Over();
}
}
 
boolean isComplete(MatchFormat format) {
return format.isInningsComplete(this);
}
}
 
class Match {
private final MatchFormat format;
private final List<Innings> innings = new ArrayList<>();
private Innings current;
 
Match(MatchFormat format) {
this.format = format;
this.current = new Innings();
innings.add(current);
}
 
void recordBall(Ball ball) {
current.recordBall(ball);
if (current.isComplete(format)) {
current = new Innings();
innings.add(current);
}
}
 
Scorecard scorecard() {
return current.scorecard;
}
}

Design decisions

  • Format rules live entirely in MatchFormat, not in if format == TEST checks. T20Format/OdiFormat cap overs at a fixed number; TestFormat allows a declaration and no overs cap. Innings.isComplete asks the strategy one question - "should this innings end now" - instead of encoding three formats' worth of logic inline.
  • Over counts only legal deliveries toward its limit of six. A wide or no-ball updates the score and is appended to the ball list, but doesn't advance the over's ball counter - modeling that distinction inside Over.recordBall is what makes "six balls" mean the cricket definition instead of "six list entries."
  • Scorecard is updated incrementally by Innings, never recomputed. Every ball updates running totals directly on the scorecard object; there is no rebuildFromBalls() method, because a live scorecard that recomputes from history on every ball is the exact performance problem the non-functional requirement rules out.
  • Player stats (runs, balls faced, wickets) live on the player, keyed per match. A player who's out doesn't stop existing - Innings just stops passing them deliveries - so there's no special "retired" subclass, just a state the innings tracks about who's currently at the crease.
  • What's missing for a real system: partnerships, DLS-method rain calculations, and ball-by-ball commentary text are all real Cricinfo features that sit on top of the model above (mostly as read-only views over Scorecard/Ball history) rather than requiring changes to how a ball is recorded.
0%0 of 122 pages studied