Turn a list of deliveries into a real scorecard: total, overs, strike rates, run rate.
/build/project-cricket-scorecard with the starter and the rubric already in it.Read an innings as a list of "batter:event" strings and produce the scorecard: team total and wickets, overs from legal balls only, each batter's runs, balls, fours, sixes and strike rate, extras and the run rate.
The rules are the hard part: a wide is not a ball faced, a no-ball is, and neither counts towards the over. Getting those right is exactly the "read the spec carefully" skill every data and backend job needs, on data you already understand.
You can turn event data into aggregates with dicts, handle edge cases in the rules and avoid dividing by zero.
Each step is one function in scorecard.py. In the workspace, press Check next to a step: it runs your file and tells you exactly what is still wrong.
Build against this exact list — it is the spec your published project is judged on.
Copy this into a new file — or open it in a workspace as scorecard.py. The TODO parts are yours to fill.
"""
Cricket scorecard from a ball-by-ball list.
Each entry is "batter:event". Events:
"0".."6" runs off the bat
"W" wicket (the batter is out, 0 runs)
"wd" wide: 1 extra run, not a legal ball, not faced by the batter
"nb" no-ball: 1 extra run, not a legal ball, but faced by the batter
Fill in one function per step, press Run, then press "Check step".
"""
BALLS = [
"Rohit:1", "Gill:4", "Gill:0", "Gill:wd", "Gill:6", "Gill:1", "Rohit:0",
"Rohit:4", "Rohit:4", "Rohit:nb", "Rohit:1", "Gill:W", "Kohli:0",
"Kohli:2", "Kohli:1", "Rohit:6", "Rohit:0", "Rohit:1", "Kohli:4",
"Kohli:wd", "Kohli:1", "Rohit:W", "Iyer:1", "Kohli:6",
]
EXTRAS = {"wd", "nb"}
def parse_ball(entry: str) -> tuple[str, str]:
# TODO: "Gill:4" -> ("Gill", "4"). Strip stray spaces from both parts.
raise NotImplementedError("step 1, parse_ball(): split one entry")
def is_legal(event: str) -> bool:
# TODO: wides and no-balls are not legal balls; everything else is.
raise NotImplementedError("step 2, is_legal(): does this ball count?")
def overs(balls: list[str]) -> str:
legal = 0
for b in balls:
_, event = parse_ball(b)
# TODO: add 1 to legal when is_legal(event) is True
# TODO: return overs as "O.B"; 21 legal balls is "3.3" (3 overs, 3 balls)
raise NotImplementedError("step 2, overs(): legal balls -> overs")
def runs_for(event: str) -> int:
# TODO: runs this ball adds to the TEAM total:
# "wd" / "nb" -> 1, "W" -> 0, "0".."6" -> that number
raise NotImplementedError("step 3, runs_for(): runs one ball is worth")
def innings_total(balls: list[str]) -> tuple[int, int]:
runs = wickets = 0
for b in balls:
_, event = parse_ball(b)
runs += runs_for(event)
# TODO: count a wicket for every "W"
# TODO: return (total runs including extras, number of wickets)
raise NotImplementedError("step 4, innings_total(): runs and wickets")
def batter_stats(balls: list[str]) -> dict[str, dict]:
# TODO: {batter: {"runs", "balls", "4s", "6s", "out"}} in batting order.
# - a wide is not a ball faced; a no-ball IS faced but scores 0 bat runs
# - "W" is a ball faced and sets "out" to True
# Hint: stats.setdefault(batter, {...})
raise NotImplementedError("step 5, batter_stats(): one row per batter")
def strike_rate(runs: int, balls: int) -> float:
# TODO: runs per 100 balls, 2 decimals; 0.0 if no balls faced.
raise NotImplementedError("step 6, strike_rate(): runs per 100 balls")
def main() -> None:
played = overs(BALLS)
runs, wickets = innings_total(BALLS)
print(f"India {runs}/{wickets} in {played} overs")
print(f"{'Batter':<8}{'R':>4}{'B':>4}{'4s':>4}{'6s':>4}{'SR':>8}")
for name, s in batter_stats(BALLS).items():
mark = "" if s["out"] else "*"
sr = strike_rate(s["runs"], s["balls"])
print(f"{name + mark:<8}{s['runs']:>4}{s['balls']:>4}{s['4s']:>4}{s['6s']:>4}{sr:>8.2f}")
extras = sum(1 for b in BALLS if parse_ball(b)[1] in EXTRAS)
print(f"Extras: {extras}")
# TODO: print the run rate (runs per 6 legal balls), 2 decimals:
# "Run rate: 13.14"
raise NotImplementedError("step 7, main(): print the run rate")
if __name__ == "__main__":
try:
main()
except NotImplementedError as todo:
# A fresh starter is SUPPOSED to stop here. Say which step is next
# instead of printing a traceback that looks like a bug.
print(f"Not built yet: {todo}")
print("Write that function, then press Run again. Each step you finish moves this message forward.")
Try every step first. This version passes all 7 checks; yours can look different and still pass.
"""
Cricket scorecard from a ball-by-ball list.
Each entry is "batter:event". Events:
"0".."6" runs off the bat
"W" wicket (the batter is out, 0 runs)
"wd" wide: 1 extra run, not a legal ball, not faced by the batter
"nb" no-ball: 1 extra run, not a legal ball, but faced by the batter
"""
BALLS = [
"Rohit:1", "Gill:4", "Gill:0", "Gill:wd", "Gill:6", "Gill:1", "Rohit:0",
"Rohit:4", "Rohit:4", "Rohit:nb", "Rohit:1", "Gill:W", "Kohli:0",
"Kohli:2", "Kohli:1", "Rohit:6", "Rohit:0", "Rohit:1", "Kohli:4",
"Kohli:wd", "Kohli:1", "Rohit:W", "Iyer:1", "Kohli:6",
]
EXTRAS = {"wd", "nb"}
def parse_ball(entry: str) -> tuple[str, str]:
batter, event = entry.split(":")
return batter.strip(), event.strip()
def runs_for(event: str) -> int:
if event in EXTRAS:
return 1
if event == "W":
return 0
return int(event)
def is_legal(event: str) -> bool:
return event not in EXTRAS
def overs(balls: list[str]) -> str:
legal = sum(is_legal(parse_ball(b)[1]) for b in balls)
return f"{legal // 6}.{legal % 6}"
def innings_total(balls: list[str]) -> tuple[int, int]:
runs = wickets = 0
for b in balls:
_, event = parse_ball(b)
runs += runs_for(event)
wickets += event == "W"
return runs, wickets
def batter_stats(balls: list[str]) -> dict[str, dict]:
stats: dict[str, dict] = {}
for b in balls:
batter, event = parse_ball(b)
s = stats.setdefault(batter, {"runs": 0, "balls": 0, "4s": 0, "6s": 0, "out": False})
if event == "wd":
continue
s["balls"] += 1
if event == "W":
s["out"] = True
elif event != "nb":
s["runs"] += int(event)
s["4s"] += event == "4"
s["6s"] += event == "6"
return stats
def strike_rate(runs: int, balls: int) -> float:
return round(runs * 100 / balls, 2) if balls else 0.0
def main() -> None:
played = overs(BALLS)
runs, wickets = innings_total(BALLS)
print(f"India {runs}/{wickets} in {played} overs")
print(f"{'Batter':<8}{'R':>4}{'B':>4}{'4s':>4}{'6s':>4}{'SR':>8}")
for name, s in batter_stats(BALLS).items():
mark = "" if s["out"] else "*"
sr = strike_rate(s["runs"], s["balls"])
print(f"{name + mark:<8}{s['runs']:>4}{s['balls']:>4}{s['4s']:>4}{s['6s']:>4}{sr:>8.2f}")
extras = sum(1 for b in BALLS if parse_ball(b)[1] in EXTRAS)
print(f"Extras: {extras}")
legal = sum(is_legal(parse_ball(b)[1]) for b in BALLS)
print(f"Run rate: {runs / (legal / 6):.2f}")
if __name__ == "__main__":
main()
scorecard.py and a README.md holding all 5 rubric items as a checklist.pyrun.in/u/<handle>/w/project-cricket-scorecard.pyrun.in/u/<handle> portfolio page.Your workspace saves to this browser only. Sign in to keep it across devices and to publish it.
Flip the workspace to Public and paste its URL below, or push the file to a GitHub gist or repo. Paste the code inline too if you want the optional AI review to comment on specific lines.
A published PyRun workspace URL (pyrun.in/u/<handle>/w/project-cricket-scorecard) works as the public URL too.