Spaces:
Sleeping
Sleeping
import gradio as gr | |
import pandas as pd | |
from typing import List, Dict, Tuple | |
# Store match data | |
matches = {} | |
current_match = None | |
def validate_player_names(players_str: str) -> Tuple[List[str], str]: | |
"""Validate and clean player names input.""" | |
if not players_str.strip(): | |
return [], "Player names cannot be empty" | |
players = [] | |
errors = [] | |
for i, p in enumerate(players_str.split(",")): | |
p = p.strip().rstrip('.').strip() # Clean the name | |
if not p: | |
continue # Skip empty entries | |
if len(p) > 20: | |
errors.append(f"Name too long: '{p}' (max 20 chars)") | |
elif not p.replace(" ", "").isalnum(): | |
errors.append(f"Invalid characters in name: '{p}'") | |
else: | |
players.append(p) | |
error_msg = "\n".join(errors) if errors else "" | |
return players, error_msg | |
def create_match(team1_name: str, team2_name: str, | |
players_team1: str, players_team2: str, | |
total_overs: int) -> Tuple[str, str, dict]: | |
"""Create a new cricket match with validation.""" | |
global matches | |
try: | |
# Validate team names | |
if not team1_name.strip() or not team2_name.strip(): | |
return "Both team names are required!", "", gr.update(choices=list(matches.keys())) | |
if team1_name.strip().lower() == team2_name.strip().lower(): | |
return "Team names must be different!", "", gr.update(choices=list(matches.keys())) | |
# Validate overs | |
try: | |
total_overs = int(total_overs) | |
if total_overs <= 0: | |
return "Overs must be a positive number!", "", gr.update(choices=list(matches.keys())) | |
except ValueError: | |
return "Overs must be a number!", "", gr.update(choices=list(matches.keys())) | |
# Process player names | |
players1, error1 = validate_player_names(players_team1) | |
players2, error2 = validate_player_names(players_team2) | |
if error1 or error2: | |
error_msg = "Errors in player names:\n" | |
if error1: | |
error_msg += f"Team 1: {error1}\n" | |
if error2: | |
error_msg += f"Team 2: {error2}" | |
return error_msg.strip(), "", gr.update(choices=list(matches.keys())) | |
if len(players1) < 1 or len(players2) < 1: | |
return "Each team must have at least 1 player!", "", gr.update(choices=list(matches.keys())) | |
# Create match ID | |
match_id = f"{team1_name.strip()} vs {team2_name.strip()}" | |
if match_id in matches: | |
return "Match already exists!", "", gr.update(choices=list(matches.keys())) | |
# Initialize match data | |
matches[match_id] = { | |
"teams": { | |
team1_name.strip(): { | |
"players": players1, | |
"runs": 0, | |
"wickets": 0, | |
"overs": 0.0, | |
"extras": 0 | |
}, | |
team2_name.strip(): { | |
"players": players2, | |
"runs": 0, | |
"wickets": 0, | |
"overs": 0.0, | |
"extras": 0 | |
} | |
}, | |
"batting_order": {team1_name.strip(): [], team2_name.strip(): []}, | |
"bowling_figures": {team1_name.strip(): {}, team2_name.strip(): {}}, | |
"current_batting": team1_name.strip(), | |
"current_bowling": team2_name.strip(), | |
"total_overs": total_overs, | |
"completed": False, | |
"first_innings": None | |
} | |
# Initialize bowling figures | |
for player in players1: | |
matches[match_id]["bowling_figures"][team1_name.strip()][player] = { | |
"runs": 0, | |
"wickets": 0, | |
"overs": 0.0, | |
"maidens": 0 | |
} | |
for player in players2: | |
matches[match_id]["bowling_figures"][team2_name.strip()][player] = { | |
"runs": 0, | |
"wickets": 0, | |
"overs": 0.0, | |
"maidens": 0 | |
} | |
return (f"Match created: {match_id}\n{team1_name} to bat first!", | |
match_id, | |
gr.update(choices=list(matches.keys()), value=match_id)) | |
except Exception as e: | |
return f"Error: {str(e)}", "", gr.update(choices=list(matches.keys())) | |
def update_team_choices(match_id: str) -> dict: | |
"""Update team choices for the batting team dropdown.""" | |
if match_id in matches: | |
teams = list(matches[match_id]["teams"].keys()) | |
return gr.update(choices=teams, value=matches[match_id]["current_batting"]) | |
return gr.update(choices=[], value=None) | |
def update_score(match_id: str, batting_team: str, runs: int, | |
is_wicket: bool, bowler: str, batsman: str, | |
balls_bowled: int, extras: int) -> Tuple[str, str, str]: | |
"""Update the match score with validation.""" | |
if match_id not in matches: | |
return "Match not found!", "", "" | |
match = matches[match_id] | |
# Validate inputs | |
try: | |
runs = int(runs) | |
balls_bowled = int(balls_bowled) | |
extras = int(extras) | |
except ValueError: | |
return "Runs, balls and extras must be numbers!", "", "" | |
if batting_team not in match["teams"]: | |
return "Invalid batting team!", "", "" | |
if bowler not in match["teams"][match["current_bowling"]]["players"]: | |
return "Bowler not found in bowling team!", "", "" | |
if batsman not in match["teams"][batting_team]["players"]: | |
return "Batsman not found in batting team!", "", "" | |
if balls_bowled <= 0 or balls_bowled > 6: | |
return "Balls bowled must be between 1-6!", "", "" | |
# Update score | |
match["teams"][batting_team]["runs"] += runs + extras | |
match["teams"][batting_team]["extras"] += extras | |
if is_wicket: | |
match["teams"][batting_team]["wickets"] += 1 | |
# Update batting order | |
if batsman not in match["batting_order"][batting_team]: | |
match["batting_order"][batting_team].append(batsman) | |
# Update bowler figures | |
bowler_stats = match["bowling_figures"][match["current_bowling"]][bowler] | |
bowler_stats["runs"] += runs + extras | |
if is_wicket: | |
bowler_stats["wickets"] += 1 | |
# Update overs (convert balls to fractional overs) | |
over_fraction = balls_bowled / 6 | |
bowler_stats["overs"] += over_fraction | |
match["teams"][batting_team]["overs"] += over_fraction | |
# Check for maiden over (0 runs in full over) | |
if balls_bowled == 6 and runs + extras == 0: | |
bowler_stats["maidens"] += 1 | |
# Check innings completion | |
if (match["teams"][batting_team]["wickets"] >= len(match["teams"][batting_team]["players"]) - 1 or | |
match["teams"][batting_team]["overs"] >= match["total_overs"]): | |
if match["first_innings"] is None: | |
# First innings completed | |
match["first_innings"] = match["teams"][batting_team]["runs"] | |
match["current_batting"] = match["current_bowling"] | |
match["current_bowling"] = batting_team | |
target = match["first_innings"] + 1 | |
return ("Innings completed! Teams will switch.\n" | |
f"Target: {target}", | |
f"{batting_team}: {match['teams'][batting_team]['runs']}/{match['teams'][batting_team]['wickets']}", | |
generate_scorecard(match_id)) | |
else: | |
# Match completed | |
match["completed"] = True | |
result = determine_result(match) | |
return ("Match completed!\n" + result, | |
f"{batting_team}: {match['teams'][batting_team]['runs']}/{match['teams'][batting_team]['wickets']}", | |
generate_scorecard(match_id)) | |
# Prepare current score display | |
score = (f"{batting_team}: {match['teams'][batting_team]['runs']}/" | |
f"{match['teams'][batting_team]['wickets']} " | |
f"({match['teams'][batting_team]['overs']:.1f} ov)") | |
if match["first_innings"] is not None: | |
runs_needed = match["first_innings"] + 1 - match["teams"][batting_team]["runs"] | |
score += f" | Target: {match['first_innings'] + 1} | Need: {runs_needed}" | |
return "Score updated!", score, generate_scorecard(match_id) | |
def determine_result(match: Dict) -> str: | |
"""Determine the result of the match.""" | |
team1, team2 = list(match["teams"].keys()) | |
runs1 = match["teams"][team1]["runs"] | |
runs2 = match["teams"][team2]["runs"] | |
if runs1 > runs2: | |
return f"{team1} won by {runs1 - runs2} runs" | |
elif runs2 > runs1: | |
return f"{team2} won by {runs2 - runs1} runs" | |
else: | |
return "Match tied!" | |
def generate_scorecard(match_id: str) -> str: | |
"""Generate detailed scorecard for the match.""" | |
if match_id not in matches: | |
return "Match not found!" | |
match = matches[match_id] | |
output = [] | |
for team_name, team_data in match["teams"].items(): | |
output.append(f"### {team_name}") | |
output.append(f"Total: {team_data['runs']}/{team_data['wickets']} " | |
f"in {team_data['overs']:.1f} overs (Extras: {team_data['extras']})") | |
# Batting details | |
output.append("\n**Batting:**") | |
for player in match["batting_order"][team_name]: | |
output.append(f"- {player}") | |
# Bowling details | |
output.append("\n**Bowling Figures:**") | |
for bowler, figures in match["bowling_figures"].get(team_name, {}).items(): | |
output.append(f"- {bowler}: {figures['runs']}-{figures['wickets']} " | |
f"({figures['overs']:.1f} ov, {figures['maidens']} maidens)") | |
# Match status | |
if match["completed"]: | |
output.append("\n**RESULT:** " + determine_result(match)) | |
elif match["first_innings"] is not None: | |
output.append(f"\nTarget: {match['first_innings'] + 1}") | |
return "\n".join(output) | |
with gr.Blocks(title="Gully Cricket Scorecard", theme="soft") as app: | |
gr.Markdown("# 🏏 Gully Cricket Scorecard") | |
gr.Markdown("Track your local cricket matches with this simple scorecard app!") | |
with gr.Tab("Create Match"): | |
with gr.Row(): | |
with gr.Column(): | |
team1 = gr.Textbox(label="Team 1 Name", placeholder="Enter team 1 name") | |
players1 = gr.Textbox(label="Team 1 Players", | |
placeholder="Comma separated names (e.g., Virat, Rohit, Bumrah)") | |
with gr.Column(): | |
team2 = gr.Textbox(label="Team 2 Name", placeholder="Enter team 2 name") | |
players2 = gr.Textbox(label="Team 2 Players", | |
placeholder="Comma separated names (e.g., Dhoni, Jadeja, Rahul)") | |
overs = gr.Number(label="Total Overs", value=5, minimum=1, maximum=50, step=1) | |
create_btn = gr.Button("Create Match", variant="primary") | |
match_output = gr.Textbox(label="Status", interactive=False) | |
match_id = gr.Textbox(visible=False) | |
match_selector_create = gr.Dropdown( | |
label="Select Match", | |
choices=list(matches.keys()), | |
interactive=True | |
) | |
with gr.Tab("Update Score"): | |
with gr.Row(): | |
with gr.Column(): | |
match_selector_update = gr.Dropdown(label="Select Match", choices=list(matches.keys()), interactive=True) | |
batting_team = gr.Dropdown(label="Batting Team", choices=[], interactive=True) | |
runs = gr.Number(label="Runs scored this ball", value=0, minimum=0, step=1) | |
is_wicket = gr.Checkbox(label="Wicket fell?", value=False) | |
extras = gr.Number(label="Extras (wides, no-balls)", value=0, step=1) | |
with gr.Column(): | |
bowler = gr.Textbox(label="Bowler", placeholder="Name of current bowler") | |
batsman = gr.Textbox(label="Batsman", placeholder="Name of current batsman") | |
balls = gr.Number(label="Balls bowled this update", value=1, minimum=1, maximum=6, step=1) | |
update_btn = gr.Button("Update Score", variant="primary") | |
score_output = gr.Textbox(label="Current Score", interactive=False) | |
scorecard = gr.Textbox(label="Scorecard", interactive=False, lines=20) | |
# Update batting team choices when match selector is changed | |
match_selector_update.change( | |
fn=update_team_choices, | |
inputs=[match_selector_update], | |
outputs=[batting_team] | |
) | |
# Create match | |
create_btn.click( | |
fn=create_match, | |
inputs=[team1, team2, players1, players2, overs], | |
outputs=[match_output, match_id, match_selector_create] | |
) | |
# Update score | |
update_btn.click( | |
fn=update_score, | |
inputs=[match_selector_update, batting_team, runs, is_wicket, bowler, batsman, balls, extras], | |
outputs=[match_output, score_output, scorecard] | |
) | |
# Launch the app | |
if __name__ == "__main__": | |
app.launch() |