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()