File size: 13,209 Bytes
a352029
d3a4c87
9463cbc
a352029
c55ef47
 
 
1612d24
9463cbc
 
 
 
d3a4c87
9463cbc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4018b70
9463cbc
c33ea12
9463cbc
c33ea12
 
4018b70
c33ea12
 
4018b70
c33ea12
 
 
 
 
4018b70
c33ea12
4018b70
c33ea12
 
 
 
 
 
 
 
 
 
 
4018b70
c33ea12
 
4018b70
c33ea12
 
 
 
4018b70
c33ea12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c4cae84
c33ea12
 
 
 
 
 
 
 
 
 
9463cbc
 
 
c33ea12
 
 
 
 
9463cbc
 
 
c33ea12
9463cbc
c33ea12
 
 
4018b70
c55ef47
c33ea12
4018b70
c4cae84
4018b70
c4cae84
 
 
4018b70
 
e344aea
9463cbc
 
 
 
c55ef47
 
e344aea
c55ef47
1612d24
9463cbc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c55ef47
 
d3a4c87
9463cbc
c55ef47
 
1612d24
c55ef47
9463cbc
 
 
 
 
 
 
 
 
1612d24
9463cbc
 
 
1612d24
9463cbc
 
c55ef47
9463cbc
 
 
 
 
 
 
 
 
 
 
c55ef47
9463cbc
c55ef47
9463cbc
 
 
 
1612d24
9463cbc
 
 
 
d3a4c87
9463cbc
 
 
d3a4c87
c55ef47
 
c4cae84
 
 
 
 
 
 
 
 
 
 
 
 
9463cbc
 
c55ef47
 
d3a4c87
c55ef47
 
d3a4c87
9463cbc
 
 
 
c55ef47
9463cbc
c55ef47
9463cbc
c55ef47
 
9463cbc
c55ef47
9463cbc
 
 
4af4421
9463cbc
 
 
 
 
c55ef47
 
 
9463cbc
 
 
c55ef47
 
9463cbc
 
 
 
 
 
 
 
7bb7d8e
 
 
 
 
c4cae84
c33ea12
 
 
 
c55ef47
 
7bb7d8e
 
c4cae84
7bb7d8e
 
 
4018b70
7bb7d8e
4018b70
 
7bb7d8e
 
9463cbc
7bb7d8e
 
 
4018b70
c4cae84
4018b70
 
 
c4cae84
d3a4c87
4018b70
7bb7d8e
4018b70
7bb7d8e
4018b70
7bb7d8e
 
4018b70
7bb7d8e
4018b70
c4cae84
7bb7d8e
c4cae84
7bb7d8e
 
 
c4cae84
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
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()