Design CricInfo

Last Updated: December 19, 2025

Ashish

Ashish Pratap Singh

hard

In this chapter, we will explore the low-level design of cricinfo like service.

Let's start by clarifying the requirements:

1. Clarifying Requirements

Before starting the design, it's important to ask thoughtful questions to uncover hidden assumptions, clarify ambiguities, and define the system's scope more precisely.

Here is an example of how a conversation between the candidate and the interviewer might unfold:

After gathering the details, we can summarize the key system requirements.

1.1 Functional Requirements

  • Support multiple match formats: Test, ODI, and T20
  • System should be able to track the stats of all players, teams, and matches.
  • The system should be able to track all scores or wickets that occurred for each ball.
  • The system should provide ball-by-ball live commentary for ongoing matches.
  • Allow users to follow multiple live matches simultaneously
  • Support viewing of team compositions, player profiles, and records

1.2 Non-Functional Requirements

  • Modularity: The system should be composed of well-separated components
  • Extensibility: The system should be extensible to support future features
  • Maintainability: Code should follow object-oriented principles and be easy to test, debug, and extend

2. Identifying Core Entities

Core entities are the fundamental building blocks of our system. We identify them by analyzing key nouns (e.g., match, team, player, venue, series, scorecard, commentary) and actions (e.g., list, display, follow, update) from the functional requirements. These typically translate into classes, enums, or interfaces in an object-oriented design.

Let’s walk through the functional requirements and extract the relevant entities:

1. Support multiple match formats (Test, ODI, T20) and track concurrent matches.

This immediately points to a central Match entity. A Match will encapsulate all details of a single game, including the participating teams, venue, and start time. To handle different formats, we can use a MatchFormat enum (TEST, ODI, T20) and a corresponding MatchFormatStrategy interface to encapsulate format-specific rules (e.g., overs per innings). To manage the state of a game, a MatchStatus enum (UPCOMING, LIVE, COMPLETED) is essential.

2. Provide detailed, ball-by-ball tracking, including scores and wickets.

This fine-grained requirement necessitates a Ball entity to represent a single delivery. Each Ball object will capture who bowled it, who faced it, the runs scored, and any extras. If a dismissal occurs, a Wicket entity can be associated with the Ball to detail how the player got out. Since a match is divided into turns, an Inning entity is needed to group all the balls bowled by one team and track the batting team's total score and wickets.

3. Display real-time scorecards, live commentary, and player statistics.

To present the match data, we need several entities. A Scorecard entity will aggregate the statistics for each Inning, including batting and bowling figures. For live text updates, a Commentary entity is needed to hold the descriptive text for each Ball. Individual player performance within a match can be tracked using a PlayerStats object.

4. Maintain historical data for players, teams, and matches.

The system needs to model the core participants. This leads to Player and Team entities. A Player will have attributes like name and role, while a Team will be a collection of players. To support historical queries, a StatsService or repository layer is needed to retrieve past match data and career statistics.

5. Provide a simplified entry point to manage the system.

To manage the creation of matches, processing of ball-by-ball data, and subscriptions, a high-level facade is useful. A CricInfoService entity can serve as this single entry point, simplifying interactions for the client and hiding the system's internal complexity.

These core entities define the essential abstractions of a CricInfo-like system and will guide the structure of your low-level design and class diagrams.

3. Designing Classes and Relationships

This section breaks down the system's architecture into its fundamental classes, their responsibilities, and the relationships that connect them. We also explore the key design patterns that provide robustness and flexibility to the solution.

3.1 Class Definitions

The system is composed of several types of classes, each with a distinct role.

Enums

Enums
  • MatchType: Defines the format of the match (e.g., T20, ODI).
  • MatchStatus: Represents the real-time status of a match (SCHEDULED, LIVE, FINISHED).
  • PlayerRole: Classifies players based on their primary skill (BATSMAN, BOWLER).
  • WicketType: Specifies the method of dismissal (BOWLED, CAUGHT).
  • ExtraType: Denotes types of extra runs (WIDE, NO_BALL).

Data Classes

Player

Represents a cricket player, containing their ID, name, role, and an associated PlayerStats object.

Player

PlayerStats

A mutable data class that tracks a player's performance within a match (runs scored, wickets taken, etc.).

PlayerStats

Team

Represents a cricket team, containing its ID, name, and a list of Players.

Wicket

A data class capturing the details of a dismissal, including the type, player out, and other involved players.

Wicket

Its construction is managed by the Builder pattern.

Ball

Represents a single delivery in an innings.

Ball

It encapsulates all outcomes of that delivery: runs, extras, and any wicket that fell. It is also constructed using the Builder pattern.

Core Classes

Innings

Manages the entire course of one team's batting turn.

Innings

It contains the list of all balls bowled, tracks the team's score and wickets, and maintains the individual statistics for all players involved.

Match

The central engine of the system.

Match

It acts as the Context for the State pattern (delegating actions to its currentState object) and the Subject for the Observer pattern (notifying all subscribed observers of updates).

CommentaryManager

CommentaryManager

A centralized service that dynamically generates descriptive text commentary for each ball based on its outcome, using a template-based approach.

MatchRepository, PlayerRepository

MatchRepository
PlayerRepository

Simple data access classes that simulate a persistence layer for storing and retrieving match and player data.

CricInfoService (Singleton & Facade)

The primary entry point for the application.

CricInfoService

It simplifies all client interactions by providing a high-level API for creating matches, processing ball updates, and subscribing observers, hiding the complex internal workings.

3.2 Class Relationships

The relationships between classes define the system's structure and data flow.

Composition

  • A Team is composed of a list of Players.
  • A Match is composed of one or more Innings.
  • An Innings is composed of a list of Balls and manages PlayerStats.
  • A Player has their own PlayerStats.

Association

  • A Match is associated with two Teams and one MatchFormatStrategy.
  • A Match has a current MatchState.
  • A Match (Subject) is associated with a list of MatchObservers.
  • A Ball is associated with the Player who bowled it and the Player who faced it.

Inheritance

  • Concrete MatchState classes implement the MatchState interface.
  • Concrete MatchObserver classes implement the MatchObserver interface.
  • Concrete MatchFormatStrategy classes implement the MatchFormatStrategy interface.

Dependency

  • The CricInfoService facade depends on Match, Player, and the repositories to orchestrate the application.
  • The Ball.BallBuilder depends on the CommentaryManager to generate commentary during its construction.
  • The LiveState depends on Innings to process ball updates and check for end-of-match conditions.

3.3 Key Design Patterns

Strategy Pattern

MatchFormatStrategy

The MatchFormatStrategy allows the rules of a match (T20, ODI, etc.) to be encapsulated in separate objects. This makes the system flexible and easy to extend with new cricket formats without modifying the core Match logic.

State Pattern

MatchState

The lifecycle of a Match is managed using the State pattern. The Match (Context) delegates its behavior to different MatchState objects (LiveState, InBreakState, FinishedState). This cleanly separates state-specific logic and makes managing transitions robust and easy to understand.

Observer Pattern

MatchObservers

This pattern is fundamental for providing live updates. The Match (Subject) notifies all registered MatchObservers (ScorecardDisplay, CommentaryDisplay, etc.) after every ball. This decouples the core match engine from the various ways data can be presented to the user.

Builder Pattern

Used for the step-by-step construction of complex objects like Ball and Wicket. It provides a fluent API, handles optional parameters gracefully (e.g., a Wicket may or may not have a caughtBy player), and centralizes the creation logic.

Facade Pattern

The CricInfoService class serves as a facade. It provides a simple, high-level API (createMatch, processBallUpdate, subscribeToMatch) that hides the complex internal workflows involving states, strategies, observers, and repositories.

Singleton Pattern

CricInfoService and CommentaryManager are implemented as singletons. This ensures a single, globally accessible point of control for managing the application's flow and for generating commentary, preventing inconsistent states and resource duplication.

3.4 Full Class Diagram

Cricinfo Class Diagram

4. Implementation

4.1 Enums

1class MatchType(Enum):
2    T20 = "T20"
3    ODI = "ODI"
4    TEST = "TEST"
5
6class MatchStatus(Enum):
7    SCHEDULED = "SCHEDULED"
8    LIVE = "LIVE"
9    IN_BREAK = "IN_BREAK"
10    FINISHED = "FINISHED"
11    ABANDONED = "ABANDONED"
12
13class PlayerRole(Enum):
14    BATSMAN = "BATSMAN"
15    BOWLER = "BOWLER"
16    ALL_ROUNDER = "ALL_ROUNDER"
17    WICKET_KEEPER = "WICKET_KEEPER"
18
19class WicketType(Enum):
20    BOWLED = "BOWLED"
21    CAUGHT = "CAUGHT"
22    LBW = "LBW"
23    RUN_OUT = "RUN_OUT"
24    STUMPED = "STUMPED"
25    HIT_WICKET = "HIT_WICKET"
26
27class ExtraType(Enum):
28    WIDE = "WIDE"
29    NO_BALL = "NO_BALL"
30    BYE = "BYE"
31    LEG_BYE = "LEG_BYE"

These enums define fixed domain values such as match format, player roles, and ball outcomes. They ensure consistency and simplify condition checks.

4.2 PlayerStats

Tracks stats for each player per match.

1class PlayerStats:
2    def __init__(self):
3        self.runs = 0
4        self.balls_played = 0
5        self.wickets = 0
6
7    def update_runs(self, run_scored):
8        self.runs += run_scored
9
10    def increment_balls_played(self):
11        self.balls_played += 1
12
13    def increment_wickets(self):
14        self.wickets += 1
15
16    def get_runs(self):
17        return self.runs
18
19    def get_balls_played(self):
20        return self.balls_played
21
22    def get_wickets(self):
23        return self.wickets
24
25    def __str__(self):
26        return f"Runs: {self.runs}, Balls Played: {self.balls_played}, Wickets: {self.wickets}"

Stats are mutable and updated on every ball.

4.3 Player and Team

1class Player:
2    def __init__(self, player_id, name, role):
3        self.id = player_id
4        self.name = name
5        self.role = role
6        self.stats = PlayerStats()
7
8    def get_id(self):
9        return self.id
10
11    def get_name(self):
12        return self.name
13
14    def get_role(self):
15        return self.role
16
17    def get_stats(self):
18        return self.stats
1class Team:
2    def __init__(self, team_id, name, players):
3        self.id = team_id
4        self.name = name
5        self.players = players
6
7    def get_id(self):
8        return self.id
9
10    def get_name(self):
11        return self.name
12
13    def get_players(self):
14        return self.players
  • Each Player has a unique ID, role, and in-memory stat object.
  • A Team encapsulates players and their name/identity.

4.4 Wicket (Builder Pattern)

A flexible design for constructing different types of wicket events using the Builder Pattern, allowing optional fields for context-specific dismissals.

1class Wicket:
2    class Builder:
3        def __init__(self, wicket_type, player_out):
4            self.wicket_type = wicket_type
5            self.player_out = player_out
6            self.caught_by = None
7            self.runout_by = None
8
9        def caught_by(self, player):
10            self.caught_by = player
11            return self
12
13        def runout_by(self, player):
14            self.runout_by = player
15            return self
16
17        def build(self):
18            return Wicket(self)
19
20    def __init__(self, builder):
21        self.wicket_type = builder.wicket_type
22        self.player_out = builder.player_out
23        self.caught_by = builder.caught_by
24        self.runout_by = builder.runout_by
25
26    def get_wicket_type(self):
27        return self.wicket_type
28
29    def get_player_out(self):
30        return self.player_out
31
32    def get_caught_by(self):
33        return self.caught_by
34
35    def get_runout_by(self):
36        return self.runout_by

4.5 Ball (Builder + Auto-Generated Commentary)

Each Ball captures one legal delivery (or extra) with its result.

1class Ball:
2    class BallBuilder:
3        def __init__(self):
4            self.ball_number = 0
5            self.bowled_by = None
6            self.faced_by = None
7            self.runs_scored = 0
8            self.wicket = None
9            self.extra_type = None
10            self.commentary = None
11
12        def with_ball_number(self, ball_number):
13            self.ball_number = ball_number
14            return self
15
16        def bowled_by(self, bowler):
17            self.bowled_by = bowler
18            return self
19
20        def faced_by(self, batsman):
21            self.faced_by = batsman
22            return self
23
24        def with_runs(self, runs):
25            self.runs_scored = runs
26            return self
27
28        def with_wicket(self, wicket):
29            self.wicket = wicket
30            return self
31
32        def with_extra_type(self, extra):
33            self.extra_type = extra
34            return self
35
36        def with_commentary(self, commentary):
37            self.commentary = commentary
38            return self
39
40        def build(self):
41            temp_ball = Ball(self)
42            
43            if self.commentary is None:
44                self.commentary = CommentaryManager.get_instance().generate_commentary(temp_ball)
45            
46            return Ball(self)
47
48    def __init__(self, builder):
49        self.ball_number = builder.ball_number
50        self.bowled_by = builder.bowled_by
51        self.faced_by = builder.faced_by
52        self.runs_scored = builder.runs_scored
53        self.wicket = builder.wicket
54        self.extra_type = builder.extra_type
55        self.commentary = builder.commentary
56
57    def is_wicket(self):
58        return self.wicket is not None
59
60    def is_boundary(self):
61        return self.runs_scored == 4 or self.runs_scored == 6
62
63    def get_ball_number(self):
64        return self.ball_number
65
66    def get_bowled_by(self):
67        return self.bowled_by
68
69    def get_faced_by(self):
70        return self.faced_by
71
72    def get_runs_scored(self):
73        return self.runs_scored
74
75    def get_wicket(self):
76        return self.wicket
77
78    def get_extra_type(self):
79        return self.extra_type
80
81    def get_commentary(self):
82        return self.commentary

Commentary is dynamically generated using the CommentaryManager.

4.6 Innings

Manages team performance and ball-by-ball progression.

1class Innings:
2    def __init__(self, batting_team, bowling_team):
3        self.batting_team = batting_team
4        self.bowling_team = bowling_team
5        self.score = 0
6        self.wickets = 0
7        self.balls = []
8        self.player_stats = {}
9        
10        for player in batting_team.get_players():
11            self.player_stats[player] = PlayerStats()
12        for player in bowling_team.get_players():
13            self.player_stats[player] = PlayerStats()
14
15    def add_ball(self, ball):
16        self.balls.append(ball)
17        runs_scored = ball.get_runs_scored()
18        self.score += runs_scored
19        
20        if ball.get_extra_type() in [ExtraType.WIDE, ExtraType.NO_BALL]:
21            self.score += 1
22        else:
23            ball.get_faced_by().get_stats().update_runs(runs_scored)
24            ball.get_faced_by().get_stats().increment_balls_played()
25            self.player_stats[ball.get_faced_by()].update_runs(runs_scored)
26            self.player_stats[ball.get_faced_by()].increment_balls_played()
27        
28        if ball.is_wicket():
29            self.wickets += 1
30            ball.get_bowled_by().get_stats().increment_wickets()
31            self.player_stats[ball.get_bowled_by()].increment_wickets()
32
33    def print_player_stats(self):
34        for player, stats in self.player_stats.items():
35            if stats.get_balls_played() > 0 or stats.get_wickets() > 0:
36                print(f"Player: {player.get_name()} - Stats: {stats}")
37
38    def get_overs(self):
39        valid_balls = sum(1 for b in self.balls 
40                         if b.get_extra_type() not in [ExtraType.WIDE, ExtraType.NO_BALL])
41        
42        completed_overs = valid_balls // 6
43        balls_in_current_over = valid_balls % 6
44        
45        return completed_overs + (balls_in_current_over / 10.0)
46
47    def get_batting_team(self):
48        return self.batting_team
49
50    def get_bowling_team(self):
51        return self.bowling_team
52
53    def get_score(self):
54        return self.score
55
56    def get_wickets(self):
57        return self.wickets
58
59    def get_balls(self):
60        return self.balls

Updates both player and team-level statistics.

4.7 Match

1class Match:
2    def __init__(self, match_id, team1, team2, format_strategy):
3        self.id = match_id
4        self.team1 = team1
5        self.team2 = team2
6        self.format_strategy = format_strategy
7        self.innings = [Innings(team1, team2)]
8        self.current_state = ScheduledState()
9        self.current_status = None
10        self.observers = []
11        self.winner = None
12        self.result_message = ""
13
14    def process_ball(self, ball):
15        self.current_state.process_ball(self, ball)
16
17    def start_next_innings(self):
18        self.current_state.start_next_innings(self)
19
20    def create_new_innings(self):
21        if len(self.innings) >= self.format_strategy.get_total_innings():
22            print("Cannot create a new innings, match has already reached its limit.")
23            return
24        
25        next_innings = Innings(self.team2, self.team1)
26        self.innings.append(next_innings)
27
28    def add_observer(self, observer):
29        self.observers.append(observer)
30
31    def remove_observer(self, observer):
32        self.observers.remove(observer)
33
34    def notify_observers(self, ball):
35        for observer in self.observers:
36            observer.update(self, ball)
37
38    def get_current_innings(self):
39        return self.innings[-1]
40
41    def get_id(self):
42        return self.id
43
44    def get_team1(self):
45        return self.team1
46
47    def get_team2(self):
48        return self.team2
49
50    def get_format_strategy(self):
51        return self.format_strategy
52
53    def get_innings(self):
54        return self.innings
55
56    def get_current_status(self):
57        return self.current_status
58
59    def get_winner(self):
60        return self.winner
61
62    def get_result_message(self):
63        return self.result_message
64
65    def set_state(self, state):
66        self.current_state = state
67
68    def set_current_status(self, status):
69        self.current_status = status
70
71    def set_winner(self, winner):
72        self.winner = winner
73
74    def set_result_message(self, message):
75        self.result_message = message

The core engine of a cricket match:

  • Uses State Pattern to handle transitions (Live, InBreak, Finished).
  • Uses Observer Pattern to push updates to live displays.

4.8 MatchFormatStrategy (Strategy Pattern)

Provides flexibility to support multiple match formats like T20, ODI using the Strategy Pattern.

1class MatchFormatStrategy(ABC):
2    @abstractmethod
3    def get_total_innings(self):
4        pass
5
6    @abstractmethod
7    def get_total_overs(self):
8        pass
9
10    @abstractmethod
11    def get_format_name(self):
12        pass
13
14class T20FormatStrategy(MatchFormatStrategy):
15    def get_total_innings(self):
16        return 2
17
18    def get_total_overs(self):
19        return 20
20
21    def get_format_name(self):
22        return "T20"
23
24class ODIFormatStrategy(MatchFormatStrategy):
25    def get_total_innings(self):
26        return 2
27
28    def get_total_overs(self):
29        return 50
30
31    def get_format_name(self):
32        return "ODI"

4.9 MatchObserver

Each observer responds differently to match updates.

1class MatchObserver(ABC):
2    @abstractmethod
3    def update(self, match, last_ball):
4        pass
5
6class CommentaryDisplay(MatchObserver):
7    def update(self, match, last_ball):
8        if match.get_current_status() == MatchStatus.FINISHED:
9            print("[COMMENTARY]: Match has finished!")
10        elif match.get_current_status() == MatchStatus.IN_BREAK:
11            print("[COMMENTARY]: Inning has ended!")
12        else:
13            print(f"[COMMENTARY]: {last_ball.get_commentary()}")
14
15class UserNotifier(MatchObserver):
16    def update(self, match, last_ball):
17        if match.get_current_status() == MatchStatus.FINISHED:
18            print("[NOTIFICATION]: Match has finished!")
19        elif match.get_current_status() == MatchStatus.IN_BREAK:
20            print("[NOTIFICATION]: Inning has ended!")
21        elif last_ball and last_ball.is_wicket():
22            print("[NOTIFICATION]: Wicket! A player is out.")
23        elif last_ball and last_ball.is_boundary():
24            print(f"[NOTIFICATION]: It's a boundary! {last_ball.get_runs_scored()} runs.")
25
26class ScorecardDisplay(MatchObserver):
27    def update(self, match, last_ball):
28        if match.get_current_status() == MatchStatus.FINISHED:
29            print("\n--- MATCH RESULT ---")
30            print(match.get_result_message().upper())
31            print("--------------------")
32            
33            print("Player Stats:")
34            counter = 1
35            for inning in match.get_innings():
36                print(f"Inning {counter}")
37                inning.print_player_stats()
38                counter += 1
39        elif match.get_current_status() == MatchStatus.IN_BREAK:
40            print("\n--- END OF INNINGS ---")
41            last_innings = match.get_innings()[-1]
42            print(f"Final Score: {last_innings.get_batting_team().get_name()}: "
43                  f"{last_innings.get_score()}/{last_innings.get_wickets()} "
44                  f"(Overs: {last_innings.get_overs():.1f})")
45            print("------------------------")
46        else:
47            print("\n--- SCORECARD UPDATE ---")
48            current_innings = match.get_current_innings()
49            print(f"{current_innings.get_batting_team().get_name()}: "
50                  f"{current_innings.get_score()}/{current_innings.get_wickets()} "
51                  f"(Overs: {current_innings.get_overs():.1f})")
52            print("------------------------")
  • Commentary: prints ball-wise commentary
  • UserNotifier: alerts for wickets/boundaries
  • ScorecardDisplay: prints cumulative score updates or final results

4.10 Repository

In-memory storage for matches and players. Simulates persistence.

1class MatchRepository:
2    def __init__(self):
3        self.matches = {}
4
5    def save(self, match):
6        self.matches[match.get_id()] = match
7
8    def find_by_id(self, match_id):
9        return self.matches.get(match_id)
10
11class PlayerRepository:
12    def __init__(self):
13        self.players = {}
14
15    def save(self, player):
16        self.players[player.get_id()] = player
17
18    def find_by_id(self, player_id):
19        return self.players.get(player_id)

4.11 MatchState

Manages match flow using State Pattern.

1class MatchState(ABC):
2    @abstractmethod
3    def process_ball(self, match, ball):
4        pass
5
6    def start_next_innings(self, match):
7        print("ERROR: Cannot start the next innings from the current state.")
8
9class ScheduledState(MatchState):
10    def process_ball(self, match, ball):
11        print("ERROR: Cannot process a ball for a match that has not started.")
12
13class InBreakState(MatchState):
14    def process_ball(self, match, ball):
15        print("ERROR: Cannot process a ball. The match is currently in a break.")
16
17    def start_next_innings(self, match):
18        print("Starting the next innings...")
19        match.create_new_innings()
20        match.set_state(LiveState())
21        match.set_current_status(MatchStatus.LIVE)
22
23class FinishedState(MatchState):
24    def process_ball(self, match, ball):
25        print("ERROR: Cannot process a ball for a finished match.")
26
27class LiveState(MatchState):
28    def process_ball(self, match, ball):
29        current_innings = match.get_current_innings()
30        current_innings.add_ball(ball)
31        match.notify_observers(ball)
32        self.check_for_match_end(match)
33
34    def check_for_match_end(self, match):
35        current_innings = match.get_current_innings()
36        innings_count = len(match.get_innings())
37        is_final_innings = (innings_count == match.get_format_strategy().get_total_innings())
38
39        # Win condition: Chasing team surpasses the target
40        if is_final_innings:
41            target_score = match.get_innings()[0].get_score() + 1
42            if current_innings.get_score() >= target_score:
43                wickets_remaining = (len(current_innings.get_batting_team().get_players()) - 1) - current_innings.get_wickets()
44                self.declare_winner(match, current_innings.get_batting_team(), f"won by {wickets_remaining} wickets")
45                return
46
47        # End of innings condition
48        if self.is_innings_over(match):
49            if is_final_innings:
50                score1 = match.get_innings()[0].get_score()
51                score2 = current_innings.get_score()
52
53                if score1 > score2:
54                    self.declare_winner(match, match.get_team1(), f"won by {score1 - score2} runs")
55                elif score2 > score1:
56                    wickets_remaining = (len(current_innings.get_batting_team().get_players()) - 1) - current_innings.get_wickets()
57                    self.declare_winner(match, current_innings.get_batting_team(), f"won by {wickets_remaining} wickets")
58                else:
59                    self.declare_winner(match, None, "Match Tied")
60            else:
61                print("End of the innings!")
62                match.set_state(InBreakState())
63                match.set_current_status(MatchStatus.IN_BREAK)
64                match.notify_observers(None)
65
66    def declare_winner(self, match, winning_team, message):
67        print("MATCH FINISHED!")
68        match.set_winner(winning_team)
69        result_message = f"{winning_team.get_name()} {message}" if winning_team else message
70        match.set_result_message(result_message)
71
72        match.set_state(FinishedState())
73        match.set_current_status(MatchStatus.FINISHED)
74        match.notify_observers(None)
75
76    def is_innings_over(self, match):
77        current_innings = match.get_current_innings()
78        all_out = current_innings.get_wickets() >= len(current_innings.get_batting_team().get_players()) - 1
79        overs_finished = int(current_innings.get_overs()) >= match.get_format_strategy().get_total_overs()
80        return all_out or overs_finished

Transitions include:

  • Scheduled → Live
  • Live → InBreak or Finished
  • InBreak → Live (startNextInnings)

4.12 CommentaryManager

Dynamically generates commentary using event-specific templates. Centralized via Singleton Pattern and extensible through a template engine.

1class CommentaryManager:
2    _instance = None
3
4    def __init__(self):
5        self.random = random.Random()
6        self.commentary_templates = {}
7        self.initialize_templates()
8
9    @classmethod
10    def get_instance(cls):
11        if cls._instance is None:
12            cls._instance = CommentaryManager()
13        return cls._instance
14
15    def initialize_templates(self):
16        self.commentary_templates["RUNS_0"] = [
17            "%s defends solidly.",
18            "No run, good fielding by the cover fielder.",
19            "A dot ball to end the over.",
20            "Pushed to mid-on, but no run."
21        ]
22        self.commentary_templates["RUNS_1"] = [
23            "Tucked away to the leg side for a single.",
24            "Quick single taken by %s.",
25            "Pushed to long-on for one."
26        ]
27        self.commentary_templates["RUNS_2"] = [
28            "Two runs taken!",
29            "Quick double taken by %s.",
30            "Pushed to mid-on for two."
31        ]
32        self.commentary_templates["RUNS_4"] = [
33            "FOUR! %s smashes it through the covers!",
34            "Beautiful shot! That's a boundary.",
35            "Finds the gap perfectly. Four runs."
36        ]
37        self.commentary_templates["RUNS_6"] = [
38            "SIX! That's out of the park!",
39            "%s sends it sailing over the ropes!",
40            "Massive hit! It's a maximum."
41        ]
42
43        self.commentary_templates[f"WICKET_{WicketType.BOWLED.value}"] = [
44            "BOWLED HIM! %s misses completely and the stumps are shattered!",
45            "Cleaned up! A perfect yorker from %s."
46        ]
47        self.commentary_templates[f"WICKET_{WicketType.CAUGHT.value}"] = [
48            "CAUGHT! %s skies it and the fielder takes a comfortable catch.",
49            "Out! A brilliant catch in the deep by %s."
50        ]
51        self.commentary_templates[f"WICKET_{WicketType.LBW.value}"] = [
52            "LBW! That one kept low and struck %s right in front.",
53            "%s completely misjudged the line and pays the price."
54        ]
55        self.commentary_templates[f"WICKET_{WicketType.STUMPED.value}"] = [
56            "STUMPED! %s misses it, and the keeper does the rest!",
57            "Gone! Lightning-fast work by the keeper to stump %s."
58        ]
59
60        self.commentary_templates[f"EXTRA_{ExtraType.WIDE.value}"] = [
61            "That's a wide. The umpire signals an extra run.",
62            "Too far down the leg side, that'll be a wide."
63        ]
64        self.commentary_templates[f"EXTRA_{ExtraType.NO_BALL.value}"] = [
65            "No ball! %s has overstepped. It's a free hit.",
66            "It's a no-ball for overstepping."
67        ]
68
69    def generate_commentary(self, ball):
70        key = self.get_event_key(ball)
71        templates = self.commentary_templates.get(key, ["Just a standard delivery."])
72        
73        template = self.random.choice(templates)
74        
75        batsman_name = ball.get_faced_by().get_name() if ball.get_faced_by() else ""
76        bowler_name = ball.get_bowled_by().get_name() if ball.get_bowled_by() else ""
77        
78        try:
79            return template % batsman_name
80        except:
81            return template.replace("%s", batsman_name)
82
83    def get_event_key(self, ball):
84        if ball.is_wicket():
85            return f"WICKET_{ball.get_wicket().get_wicket_type().value}"
86        if ball.get_extra_type():
87            return f"EXTRA_{ball.get_extra_type().value}"
88        if 0 <= ball.get_runs_scored() <= 6:
89            return f"RUNS_{ball.get_runs_scored()}"
90        return "DEFAULT"

4.13 CricInfoService

The system uses centralized services and a facade to manage the overall application flow.

1class CricInfoService:
2    _instance = None
3
4    def __init__(self):
5        self.match_repository = MatchRepository()
6        self.player_repository = PlayerRepository()
7
8    @classmethod
9    def get_instance(cls):
10        if cls._instance is None:
11            cls._instance = CricInfoService()
12        return cls._instance
13
14    def create_match(self, team1, team2, format_strategy):
15        match_id = str(uuid.uuid4())
16        match = Match(match_id, team1, team2, format_strategy)
17        self.match_repository.save(match)
18        print(f"Match {format_strategy.get_format_name()} created between {team1.get_name()} and {team2.get_name()}.")
19        return match
20
21    def start_match(self, match_id):
22        match = self.match_repository.find_by_id(match_id)
23        if match:
24            match.set_state(LiveState())
25            print(f"Match {match_id} is now LIVE.")
26
27    def process_ball_update(self, match_id, ball):
28        match = self.match_repository.find_by_id(match_id)
29        if match:
30            match.process_ball(ball)
31
32    def start_next_innings(self, match_id):
33        match = self.match_repository.find_by_id(match_id)
34        if match:
35            match.start_next_innings()
36
37    def subscribe_to_match(self, match_id, observer):
38        match = self.match_repository.find_by_id(match_id)
39        if match:
40            match.add_observer(observer)
41
42    def end_match(self, match_id):
43        match = self.match_repository.find_by_id(match_id)
44        if match:
45            match.set_state(FinishedState())
46            print(f"Match {match_id} has FINISHED.")
47
48    def add_player(self, player_id, player_name, player_role):
49        player = Player(player_id, player_name, player_role)
50        self.player_repository.save(player)
51        return player
  • Facade Pattern: The CricInfoService acts as a Facade. It provides a simple, high-level API (createMatch, processBallUpdate) that hides the complexity of dealing with repositories, states, strategies, and observers.
  • Singleton Pattern: The services (CricInfoService, CommentaryManager) are Singletons to ensure a single, globally accessible point of control and data management. Double-checked locking ensures thread-safe lazy initialization.

4.14 CricinfoDemo

The demo class validates the end-to-end functionality by simulating a cricket match.

1class CricinfoDemo:
2    @staticmethod
3    def main():
4        service = CricInfoService.get_instance()
5
6        # Setup Players and Teams
7        p1 = service.add_player("P1", "Virat", PlayerRole.BATSMAN)
8        p2 = service.add_player("P2", "Rohit", PlayerRole.BATSMAN)
9        p3 = service.add_player("P3", "Bumrah", PlayerRole.BOWLER)
10        p4 = service.add_player("P4", "Jadeja", PlayerRole.ALL_ROUNDER)
11
12        p5 = service.add_player("P5", "Warner", PlayerRole.BATSMAN)
13        p6 = service.add_player("P6", "Smith", PlayerRole.BATSMAN)
14        p7 = service.add_player("P7", "Starc", PlayerRole.BOWLER)
15        p8 = service.add_player("P8", "Maxwell", PlayerRole.ALL_ROUNDER)
16
17        india = Team("T1", "India", [p1, p2, p3, p4])
18        australia = Team("T2", "Australia", [p5, p6, p7, p8])
19
20        # Create a T20 Match
21        t20_match = service.create_match(india, australia, T20FormatStrategy())
22        match_id = t20_match.get_id()
23
24        # Create and subscribe observers
25        scorecard = ScorecardDisplay()
26        commentary = CommentaryDisplay()
27        notifier = UserNotifier()
28
29        service.subscribe_to_match(match_id, scorecard)
30        service.subscribe_to_match(match_id, commentary)
31        service.subscribe_to_match(match_id, notifier)
32
33        # Start the match
34        service.start_match(match_id)
35
36        print("\n--- SIMULATING FIRST INNINGS ---")
37        service.process_ball_update(match_id, Ball.BallBuilder()
38                                   .bowled_by(p7).faced_by(p1).with_runs(2).build())
39        service.process_ball_update(match_id, Ball.BallBuilder()
40                                   .bowled_by(p7).faced_by(p1).with_runs(1).build())
41        service.process_ball_update(match_id, Ball.BallBuilder()
42                                   .bowled_by(p7).faced_by(p2).with_runs(6).build())
43
44        p2_wicket = Wicket.Builder(WicketType.BOWLED, p2).build()
45        service.process_ball_update(match_id, Ball.BallBuilder()
46                                   .bowled_by(p7).faced_by(p2).with_runs(0).with_wicket(p2_wicket).build())
47
48        p3_wicket = Wicket.Builder(WicketType.LBW, p3).build()
49        service.process_ball_update(match_id, Ball.BallBuilder()
50                                   .bowled_by(p7).faced_by(p3).with_runs(0).with_wicket(p3_wicket).build())
51
52        service.process_ball_update(match_id, Ball.BallBuilder()
53                                   .bowled_by(p7).faced_by(p4).with_runs(4).build())
54
55        p4_wicket = Wicket.Builder(WicketType.CAUGHT, p4).caught_by(p6).build()
56        service.process_ball_update(match_id, Ball.BallBuilder()
57                                   .bowled_by(p7).faced_by(p4).with_runs(0).with_wicket(p4_wicket).build())
58
59        print("\n\n--- INNINGS BREAK ---")
60        print("Players are off the field. Preparing for the second innings.")
61
62        # Start the second innings
63        service.start_next_innings(match_id)
64
65        print("\n--- SIMULATING SECOND INNINGS ---")
66        service.process_ball_update(match_id, Ball.BallBuilder()
67                                   .bowled_by(p3).faced_by(p5).with_runs(4).build())
68
69        service.process_ball_update(match_id, Ball.BallBuilder()
70                                   .bowled_by(p3).faced_by(p5).with_runs(1).build())
71
72        p5_wicket = Wicket.Builder(WicketType.BOWLED, p5).build()
73        service.process_ball_update(match_id, Ball.BallBuilder()
74                                   .bowled_by(p3).faced_by(p5).with_runs(0).with_wicket(p5_wicket).build())
75
76        p7_wicket = Wicket.Builder(WicketType.LBW, p7).build()
77        service.process_ball_update(match_id, Ball.BallBuilder()
78                                   .bowled_by(p3).faced_by(p7).with_runs(0).with_wicket(p7_wicket).build())
79
80        p8_wicket = Wicket.Builder(WicketType.STUMPED, p8).build()
81        service.process_ball_update(match_id, Ball.BallBuilder()
82                                   .bowled_by(p3).faced_by(p8).with_runs(0).with_wicket(p8_wicket).build())
83
84        service.end_match(match_id)
85
86if __name__ == "__main__":
87    CricinfoDemo.main()

5. Run and Test

Files29
entities
enums
observers
repositories
states
strategies
commentary_manager.py
cricinfo_demo.py
main
cricinfo_service.py
cricinfo_demo.pymain
Output

6. Quiz

Design Cricinfo - Quiz

1 / 21
Multiple Choice

Which entity is primarily responsible for representing a single delivery in a cricket match?

How helpful was this article?

Comments


0/2000

No comments yet. Be the first to comment!

Copilot extension content script