Yuxuan13 commited on
Commit
9eb0242
·
verified ·
1 Parent(s): 96b5bbb

update leaderboard

Browse files
Files changed (6) hide show
  1. README.md +6 -6
  2. data_visualization.py +459 -450
  3. gallery_tab.py +255 -0
  4. gradio_app_v2.py +410 -325
  5. leaderboard_tab.py +600 -0
  6. leaderboard_utils.py +5 -3
README.md CHANGED
@@ -1,6 +1,6 @@
1
- ---
2
- title: lmgame
3
- app_file: gradio_app_v2.py
4
- sdk: gradio
5
- sdk_version: 5.23.1
6
- ---
 
1
+ ---
2
+ title: lmgame
3
+ app_file: gradio_app_v2.py
4
+ sdk: gradio
5
+ sdk_version: 5.23.1
6
+ ---
data_visualization.py CHANGED
@@ -1,11 +1,7 @@
1
- import matplotlib
2
- matplotlib.use('Agg') # Use Agg backend for thread safety
3
- import matplotlib.pyplot as plt
4
  import numpy as np
5
  import pandas as pd
6
- import seaborn as sns
7
  import json
8
- import os
9
  from leaderboard_utils import (
10
  get_organization,
11
  get_mario_leaderboard,
@@ -22,7 +18,6 @@ from leaderboard_utils import (
22
  with open('assets/model_color.json', 'r') as f:
23
  MODEL_COLORS = json.load(f)
24
 
25
- # Define game score columns mapping
26
  GAME_SCORE_COLUMNS = {
27
  "Super Mario Bros": "Score",
28
  "Sokoban": "Levels Cracked",
@@ -31,6 +26,9 @@ GAME_SCORE_COLUMNS = {
31
  "Tetris (complete)": "Score",
32
  "Tetris (planning only)": "Score"
33
  }
 
 
 
34
 
35
  def normalize_values(values, mean, std):
36
  """
@@ -50,34 +48,15 @@ def normalize_values(values, mean, std):
50
  # Scale z-scores to 0-100 range, with mean at 50
51
  scaled_values = [max(0, min(100, (z * 30) + 50)) for z in z_scores]
52
  return scaled_values
53
-
54
- def simplify_model_name(model_name):
55
- """
56
- Simplify model name by either taking first 11 chars or string before third '-'
57
- """
58
- hyphen_parts = model_name.split('-')
59
- return '-'.join(hyphen_parts[:3]) if len(hyphen_parts) >= 3 else model_name[:11]
60
 
61
  def create_horizontal_bar_chart(df, game_name):
62
- """
63
- Create horizontal bar chart for detailed game view
64
-
65
- Args:
66
- df (pd.DataFrame): DataFrame containing game data
67
- game_name (str): Name of the game to display
68
-
69
- Returns:
70
- matplotlib.figure.Figure: The generated bar chart figure
71
- """
72
- # Close any existing figures to prevent memory leaks
73
- plt.close('all')
74
-
75
- # Set style
76
- plt.style.use('default')
77
- # Increase figure width to accommodate long model names
78
- fig, ax = plt.subplots(figsize=(20, 11))
79
-
80
- # Sort by score
81
  if game_name == "Super Mario Bros":
82
  score_col = "Score"
83
  df_sorted = df.sort_values(by=score_col, ascending=True)
@@ -106,445 +85,475 @@ def create_horizontal_bar_chart(df, game_name):
106
  df_sorted = df.sort_values(by=score_col, ascending=True)
107
  else:
108
  return None
109
-
110
- # Create color gradient
111
- colors = plt.cm.viridis(np.linspace(0.2, 0.8, len(df_sorted)))
112
-
113
- # Create horizontal bars
114
- bars = ax.barh(range(len(df_sorted)), df_sorted[score_col], color=colors)
115
-
116
- # Add more space for labels on the left
117
- plt.subplots_adjust(left=0.3)
118
-
119
- # Customize the chart
120
- ax.set_yticks(range(len(df_sorted)))
121
-
122
- # Format player names: keep organization info and truncate the rest if too long
123
- def format_player_name(player, org):
124
- max_length = 40 # Maximum length for player name
125
- if len(player) > max_length:
126
- # Keep the first part and last part of the name
127
- parts = player.split('-')
128
- if len(parts) > 3:
129
- formatted = f"{parts[0]}-{parts[1]}-...{parts[-1]}"
130
- else:
131
- formatted = player[:max_length-3] + "..."
132
- else:
133
- formatted = player
134
- return f"{formatted} [{org}]"
135
-
136
- player_labels = [format_player_name(row['Player'], row['Organization'])
137
- for _, row in df_sorted.iterrows()]
138
- ax.set_yticklabels(player_labels, fontsize=9)
139
-
140
- # Add value labels on the bars
141
- for i, bar in enumerate(bars):
142
- width = bar.get_width()
143
- if game_name == "Candy Crash":
144
- score_text = f'{width:.1f}'
145
- else:
146
- score_text = f'{width:.0f}'
147
-
148
- ax.text(width, bar.get_y() + bar.get_height()/2,
149
- score_text,
150
- ha='left', va='center',
151
- fontsize=10,
152
- fontweight='bold',
153
- color='white',
154
- bbox=dict(facecolor=(0, 0, 0, 0.3),
155
- edgecolor='none',
156
- alpha=0.5,
157
- pad=2))
158
-
159
- # Set title and labels
160
- ax.set_title(f"{game_name} Performance",
161
- pad=20,
162
- fontsize=14,
163
- fontweight='bold',
164
- color='#2c3e50')
165
-
166
- if game_name == "Sokoban":
167
- ax.set_xlabel("Maximum Level Reached",
168
- fontsize=12,
169
- fontweight='bold',
170
- color='#2c3e50',
171
- labelpad=10)
172
- else:
173
- ax.set_xlabel(score_col,
174
- fontsize=12,
175
- fontweight='bold',
176
- color='#2c3e50',
177
- labelpad=10)
178
-
179
- # Add grid lines
180
- ax.grid(True, axis='x', linestyle='--', alpha=0.3)
181
-
182
- # Remove top and right spines
183
- ax.spines['top'].set_visible(False)
184
- ax.spines['right'].set_visible(False)
185
-
186
- # Adjust layout
187
- plt.tight_layout()
188
-
189
  return fig
190
 
191
  def create_radar_charts(df):
192
- """
193
- Create two radar charts with improved normalization using z-scores
194
- """
195
- # Close any existing figures to prevent memory leaks
196
- plt.close('all')
197
-
198
- # Define reasoning models
199
- reasoning_models = [
200
- 'claude-3-7-sonnet-20250219(thinking)',
201
- 'o1-2024-12-17',
202
- 'gemini-2.0-flash-thinking-exp-1219',
203
- 'o3-mini-2025-01-31(medium)',
204
- 'gemini-2.5-pro-exp-03-25',
205
- 'o1-mini-2024-09-12',
206
- 'deepseek-r1'
207
- ]
208
-
209
- # Split dataframe into reasoning and non-reasoning models
210
- df_reasoning = df[df['Player'].isin(reasoning_models)]
211
- df_others = df[~df['Player'].isin(reasoning_models)]
212
-
213
- # Get game columns
214
- game_columns = [col for col in df.columns if col.endswith(' Score')]
215
- categories = [col.replace(' Score', '') for col in game_columns]
216
-
217
- # Create figure with two subplots - adjusted size for new layout
218
- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 6), subplot_kw=dict(projection='polar'))
219
- fig.patch.set_facecolor('white') # Set figure background to white
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
- def get_game_stats(df, game_col):
222
- """
223
- Get mean and std for a game column, handling missing values
224
- """
225
- values = []
226
- for val in df[game_col]:
227
- if isinstance(val, str) and val == '_':
228
- values.append(0)
229
- else:
230
- try:
231
- values.append(float(val))
232
- except:
233
- values.append(0)
234
- return np.mean(values), np.std(values)
235
-
236
- def setup_radar_plot(ax, data, title):
237
- ax.set_facecolor('white') # Set subplot background to white
238
-
239
- num_vars = len(categories)
240
- angles = np.linspace(0, 2*np.pi, num_vars, endpoint=False)
241
- angles = np.concatenate((angles, [angles[0]]))
242
-
243
- # Plot grid lines with darker color
244
- grid_values = [10, 30, 50, 70, 90]
245
- ax.set_rgrids(grid_values,
246
- labels=grid_values,
247
- angle=45,
248
- fontsize=6,
249
- alpha=0.7, # Increased alpha for better visibility
250
- color='#404040') # Darker color for grid labels
251
 
252
- # Make grid lines darker but still subtle
253
- ax.grid(True, color='#404040', alpha=0.3) # Darker grid lines
254
-
255
- # Define darker, more vibrant colors for the radar plots
256
- colors = ['#1f77b4', '#d62728', '#2ca02c', '#ff7f0e', '#9467bd', '#8c564b']
257
-
258
- # Calculate game statistics once
259
- game_stats = {col: get_game_stats(df, col) for col in game_columns}
260
-
261
- # Plot data with darker lines and higher opacity for fills
262
- for idx, (_, row) in enumerate(data.iterrows()):
263
- values = []
264
- for col in game_columns:
265
- val = row[col]
266
- if isinstance(val, str) and val == '_':
267
- values.append(0)
268
- else:
269
- try:
270
- values.append(float(val))
271
- except:
272
- values.append(0)
273
-
274
- # Normalize values using game statistics
275
- normalized_values = []
276
- for i, v in enumerate(values):
277
- mean, std = game_stats[game_columns[i]]
278
- normalized_value = normalize_values([v], mean, std)[0]
279
- normalized_values.append(normalized_value)
280
-
281
- # Complete the circular plot
282
- normalized_values = np.concatenate((normalized_values, [normalized_values[0]]))
283
 
284
- model_name = simplify_model_name(row['Player'])
285
- ax.plot(angles, normalized_values, 'o-', linewidth=2.0, # Increased line width
286
- label=model_name,
287
- color=colors[idx % len(colors)],
288
- markersize=4) # Increased marker size
289
- ax.fill(angles, normalized_values,
290
- alpha=0.3, # Increased fill opacity
291
- color=colors[idx % len(colors)])
292
-
293
- # Format categories
294
- formatted_categories = []
295
- for game in categories:
296
- if game == "Tetris (planning only)":
297
- game = "Tetris\n(planning)"
298
- elif game == "Tetris (complete)":
299
- game = "Tetris\n(complete)"
300
- elif game == "Super Mario Bros":
301
- game = "Super\nMario"
302
- elif game == "Candy Crash":
303
- game = "Candy\nCrash"
304
- formatted_categories.append(game)
305
-
306
- ax.set_xticks(angles[:-1])
307
- ax.set_xticklabels(formatted_categories,
308
- fontsize=8, # Slightly larger font
309
- color='#202020', # Darker text
310
- fontweight='bold') # Bold text
311
- ax.tick_params(pad=10, colors='#202020') # Darker tick colors
312
-
313
- ax.set_title(title,
314
- pad=20,
315
- fontsize=11, # Slightly larger title
316
- color='#202020', # Darker title
317
- fontweight='bold') # Bold title
318
-
319
- legend = ax.legend(loc='upper right',
320
- bbox_to_anchor=(1.3, 1.1),
321
- fontsize=7, # Slightly larger legend
322
- framealpha=0.9, # More opaque legend
323
- edgecolor='#404040', # Darker edge
324
- ncol=1)
325
-
326
- ax.set_ylim(0, 105)
327
- ax.spines['polar'].set_color('#404040') # Darker spine
328
- ax.spines['polar'].set_alpha(0.5) # More visible spine
329
-
330
- # Setup both plots
331
- setup_radar_plot(ax1, df_reasoning, "Reasoning Models")
332
- setup_radar_plot(ax2, df_others, "Non-Reasoning Models")
333
-
334
- plt.subplots_adjust(right=0.85, wspace=0.3)
335
-
336
  return fig
337
 
338
- def get_combined_leaderboard_with_radar(rank_data, selected_games):
339
- """
340
- Get combined leaderboard and create radar charts
341
- """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  df = get_combined_leaderboard(rank_data, selected_games)
343
- radar_fig = create_radar_charts(df)
344
- return df, radar_fig
 
 
345
 
346
  def create_organization_radar_chart(rank_data):
347
- """
348
- Create radar chart comparing organizations
349
- """
350
- # Get combined leaderboard with all games
351
- df = get_combined_leaderboard(rank_data, {game: True for game in GAME_ORDER})
352
-
353
- # Group by organization and calculate average scores
354
- org_performance = {}
355
- for org in df["Organization"].unique():
356
- org_df = df[df["Organization"] == org]
357
- scores = {}
358
- for game in GAME_ORDER:
359
- game_scores = org_df[f"{game} Score"].apply(lambda x: float(x) if x != "_" else 0)
360
- scores[game] = game_scores.mean()
361
- org_performance[org] = scores
362
-
363
- # Create radar chart
364
- return create_radar_charts(pd.DataFrame([org_performance]))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
365
 
366
  def create_top_players_radar_chart(rank_data, n=5):
367
- """
368
- Create radar chart for top N players
369
- """
370
- # Get combined leaderboard with all games
371
- df = get_combined_leaderboard(rank_data, {game: True for game in GAME_ORDER})
372
-
373
- # Get top N players
374
- top_players = df["Player"].head(n).tolist()
375
-
376
- # Create radar chart for top players
377
- return create_radar_charts(df[df["Player"].isin(top_players)])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
378
 
379
  def create_player_radar_chart(rank_data, player_name):
380
- """
381
- Create radar chart for a specific player
382
- """
383
- # Get combined leaderboard with all games
384
- df = get_combined_leaderboard(rank_data, {game: True for game in GAME_ORDER})
385
-
386
- # Get player's data
387
  player_df = df[df["Player"] == player_name]
388
-
389
  if player_df.empty:
390
- return None
391
-
392
- # Create radar chart for the player
393
- return create_radar_charts(player_df)
 
 
394
 
395
- def create_group_bar_chart(df):
396
- """
397
- Create a grouped bar chart comparing AI model performance across different games
398
-
399
- Args:
400
- df (pd.DataFrame): DataFrame containing the combined leaderboard data
401
-
402
- Returns:
403
- matplotlib.figure.Figure: The generated group bar chart figure
404
- """
405
- # Close any existing figures to prevent memory leaks
406
- plt.close('all')
407
-
408
- # Create figure and axis with better styling
409
- sns.set_style("whitegrid")
410
- fig = plt.figure(figsize=(20, 11))
411
-
412
- # Create subplot with specific spacing
413
- ax = plt.subplot(111)
414
-
415
- # Adjust the subplot parameters
416
- plt.subplots_adjust(top=0.90, # Add more space at the top
417
- bottom=0.15, # Add more space at the bottom
418
- right=0.85, # Add more space for legend
419
- left=0.05) # Add space on the left
420
-
421
- # Get unique models
422
- models = df['Player'].unique()
423
-
424
- # Get active games (those that have score columns in the DataFrame)
425
- active_games = []
426
- for game in GAME_ORDER:
427
- score_col = f"{game} Score" # Use the same column name for all games
428
- if score_col in df.columns:
429
- active_games.append(game)
430
-
431
- n_games = len(active_games)
432
- if n_games == 0:
433
- return fig # Return empty figure if no games are selected
434
 
435
- # Keep track of which models have data in any game
436
- models_with_data = set()
 
 
437
 
438
- # Calculate normalized scores for each game
439
- for game_idx, game in enumerate(active_games):
440
- # Get all scores for this game
441
- game_scores = []
442
-
443
- # Use the same score column name for all games
444
- score_col = f"{game} Score"
445
-
446
- for model in models:
447
- try:
448
- score = df[df['Player'] == model][score_col].values[0]
449
- if score != '_' and float(score) > 0: # Only include non-zero scores
450
- game_scores.append((model, float(score)))
451
- models_with_data.add(model) # Add model to set if it has valid data
452
- except (IndexError, ValueError):
453
- continue
454
-
455
- if not game_scores: # Skip if no valid scores for this game
456
- continue
457
-
458
- # Sort scores from highest to lowest
459
- game_scores.sort(key=lambda x: x[1], reverse=True)
460
-
461
- # Extract sorted models and scores
462
- sorted_models = [x[0] for x in game_scores]
463
- scores = [x[1] for x in game_scores]
464
-
465
- # Calculate mean and std for normalization
466
- mean = np.mean(scores)
467
- std = np.std(scores)
468
-
469
- # Normalize scores
470
- normalized_scores = normalize_values(scores, mean, std)
471
-
472
- # Calculate bar width based on number of models in this game
473
- n_models_in_game = len(sorted_models)
474
- bar_width = 0.8 / n_models_in_game if n_models_in_game > 0 else 0.8
475
-
476
- # Plot bars for each model
477
- for i, (model, score) in enumerate(zip(sorted_models, normalized_scores)):
478
- # Only add to legend if first appearance and model has data
479
- should_label = model in models_with_data and model not in [l.get_text() for l in ax.get_legend().get_texts()] if ax.get_legend() else True
480
-
481
- # Get color from MODEL_COLORS, use a default if not found
482
- color = MODEL_COLORS.get(model, f"C{i % 10}") # Use matplotlib default colors as fallback
483
-
484
- ax.bar(game_idx + i*bar_width, score,
485
- width=bar_width,
486
- label=model if should_label else "",
487
- color=color,
488
- alpha=0.8)
489
-
490
- # Customize the plot
491
- ax.set_xticks(np.arange(n_games))
492
- ax.set_xticklabels(active_games, rotation=45, ha='right', fontsize=10)
493
- ax.set_ylabel('Normalized Performance Score', fontsize=12)
494
- ax.set_title('AI Model Performance Comparison Across Gaming Tasks',
495
- fontsize=14, pad=20)
496
-
497
- # Add grid lines
498
- ax.grid(True, axis='y', linestyle='--', alpha=0.3)
499
-
500
- # Create legend with unique entries
501
- handles, labels = ax.get_legend_handles_labels()
502
- by_label = dict(zip(labels, handles))
503
-
504
- # Sort models by their first appearance in active games
505
- model_order = []
506
- for game in active_games:
507
- score_col = f"{game} Score" # Use the same column name for all games
508
- for model in models:
509
- try:
510
- score = df[df['Player'] == model][score_col].values[0]
511
- if score != '_' and float(score) > 0 and model not in model_order:
512
- model_order.append(model)
513
- except (IndexError, ValueError):
514
- continue
515
-
516
- # Create legend with sorted models
517
- sorted_handles = [by_label[model] for model in model_order if model in by_label]
518
- sorted_labels = [model for model in model_order if model in by_label]
519
-
520
- ax.legend(sorted_handles, sorted_labels,
521
- bbox_to_anchor=(1.00, 1), # Moved from (1.15, 1) to (1.05, 1) to shift left
522
- loc='upper left',
523
- fontsize=9,
524
- title='AI Models',
525
- title_fontsize=10)
526
-
527
- # No need for tight_layout() as we're manually controlling the spacing
528
-
529
  return fig
530
 
531
- def get_combined_leaderboard_with_group_bar(rank_data, selected_games):
532
- """
533
- Get combined leaderboard and create group bar chart
534
-
535
- Args:
536
- rank_data (dict): Dictionary containing rank data
537
- selected_games (dict): Dictionary of game names and their selection status
538
-
539
- Returns:
540
- tuple: (DataFrame, matplotlib.figure.Figure) containing the leaderboard data and group bar chart
541
- """
542
- df = get_combined_leaderboard(rank_data, selected_games)
543
- group_bar_fig = create_group_bar_chart(df)
544
- return df, group_bar_fig
545
 
546
  def save_visualization(fig, filename):
547
- """
548
- Save visualization to file
549
- """
550
- fig.savefig(filename, bbox_inches='tight', dpi=300)
 
1
+ import plotly.graph_objects as go
 
 
2
  import numpy as np
3
  import pandas as pd
 
4
  import json
 
5
  from leaderboard_utils import (
6
  get_organization,
7
  get_mario_leaderboard,
 
18
  with open('assets/model_color.json', 'r') as f:
19
  MODEL_COLORS = json.load(f)
20
 
 
21
  GAME_SCORE_COLUMNS = {
22
  "Super Mario Bros": "Score",
23
  "Sokoban": "Levels Cracked",
 
26
  "Tetris (complete)": "Score",
27
  "Tetris (planning only)": "Score"
28
  }
29
+ def get_model_prefix(name):
30
+ return name.split('-')[0]
31
+
32
 
33
  def normalize_values(values, mean, std):
34
  """
 
48
  # Scale z-scores to 0-100 range, with mean at 50
49
  scaled_values = [max(0, min(100, (z * 30) + 50)) for z in z_scores]
50
  return scaled_values
51
+ def simplify_model_name(name):
52
+ if name == "claude-3-7-sonnet-20250219(thinking)":
53
+ name ="claude-3-7-thinking"
54
+ parts = name.split('-')
55
+ return '-'.join(parts[:4]) + '-...' if len(parts) > 4 else name
 
 
56
 
57
  def create_horizontal_bar_chart(df, game_name):
58
+
59
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  if game_name == "Super Mario Bros":
61
  score_col = "Score"
62
  df_sorted = df.sort_values(by=score_col, ascending=True)
 
85
  df_sorted = df.sort_values(by=score_col, ascending=True)
86
  else:
87
  return None
88
+
89
+
90
+
91
+ x = df_sorted[score_col]
92
+ y = [f"{simplify_model_name(row['Player'])} [{row['Organization']}]" for _, row in df_sorted.iterrows()]
93
+ colors = [MODEL_COLORS.get(row['Player'], '#808080') for _, row in df_sorted.iterrows()]
94
+ texts = [f"{v:.1f}" if game_name == "Candy Crash" else f"{int(v)}" for v in x]
95
+
96
+ fig = go.Figure(go.Bar(
97
+ x=x,
98
+ y=y,
99
+ orientation='h',
100
+ marker_color=colors,
101
+ text=texts,
102
+ textposition='auto',
103
+ hovertemplate='%{y}<br>Score: %{x}<extra></extra>'
104
+ ))
105
+
106
+ fig.update_layout(
107
+ autosize=False,
108
+ width=800,
109
+ height=600,
110
+ margin=dict(l=150, r=150, t=40, b=200),
111
+ title=dict(
112
+ text=f"{game_name} Performance",
113
+ pad=dict(t=10)
114
+ ),
115
+ yaxis=dict(automargin=True),
116
+ legend=dict(
117
+ font=dict(size=9),
118
+ itemsizing='trace',
119
+ x=1.1,
120
+ y=1,
121
+ xanchor='left',
122
+ yanchor='top',
123
+ bgcolor='rgba(255,255,255,0.6)',
124
+ bordercolor='gray',
125
+ borderwidth=1
126
+ )
127
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  return fig
129
 
130
  def create_radar_charts(df):
131
+ game_cols = [c for c in df.columns if c.endswith(" Score")]
132
+ categories = [c.replace(" Score", "") for c in game_cols]
133
+
134
+ for col in game_cols:
135
+ vals = df[col].replace("_", 0).astype(float)
136
+ mean, std = vals.mean(), vals.std()
137
+ df[f"norm_{col}"] = normalize_values(vals, mean, std)
138
+
139
+ fig = go.Figure()
140
+ for _, row in df.iterrows():
141
+ player = row["Player"]
142
+ r = [row[f"norm_{c}"] for c in game_cols]
143
+
144
+ color = MODEL_COLORS.get(player, '#808080') # fallback to gray
145
+ fig.add_trace(go.Scatterpolar(
146
+ r=r + [r[0]],
147
+ theta=categories + [categories[0]],
148
+ mode='lines+markers',
149
+ fill='toself',
150
+ name=player,
151
+ line=dict(color=color, width=2),
152
+ marker=dict(color=color),
153
+ fillcolor=color + '33', # add transparency to fill (33 = ~20% opacity)
154
+ opacity=0.8
155
+ ))
156
+
157
+
158
+ fig.update_layout(
159
+ autosize=False,
160
+ width=800,
161
+ height=600,
162
+ margin=dict(l=80, r=150, t=40, b=100),
163
+ title=dict(
164
+ text="Radar Chart of AI Performance (Normalized)",
165
+ pad=dict(t=10)
166
+ ),
167
+ polar=dict(radialaxis=dict(visible=True, range=[0, 100])),
168
+ legend=dict(
169
+ font=dict(size=9),
170
+ itemsizing='trace',
171
+ x=1.4,
172
+ y=1,
173
+ xanchor='left',
174
+ yanchor='top',
175
+ bgcolor='rgba(255,255,255,0.6)',
176
+ bordercolor='gray',
177
+ borderwidth=1
178
+ )
179
+ )
180
+ return fig
181
+
182
+ def get_combined_leaderboard_with_radar(rank_data, selected_games):
183
+ df = get_combined_leaderboard(rank_data, selected_games)
184
+ # Create a copy for visualization to avoid modifying the original
185
+ df_viz = df.copy()
186
+ return df, create_radar_charts(df_viz)
187
+
188
+ def create_group_bar_chart(df):
189
+ game_cols = {}
190
+ for game in GAME_ORDER:
191
+ col = f"{game} Score"
192
+ if col in df.columns:
193
+ df[col] = df[col].replace("_", np.nan).astype(float)
194
+ if df[col].notna().any():
195
+ game_cols[game] = col
196
+
197
+ if not game_cols:
198
+ return go.Figure().update_layout(title="No data available")
199
+
200
+ # Drop players with no data
201
+ df = df.dropna(subset=game_cols.values(), how='all')
202
+
203
+ # Normalize scores per game
204
+ for game, col in game_cols.items():
205
+ valid = df[col].dropna()
206
+ norm_col = f"norm_{col}"
207
+ if valid.empty:
208
+ df[norm_col] = np.nan
209
+ else:
210
+ mean, std = valid.mean(), valid.std()
211
+ normalized = normalize_values(valid, mean, std)
212
+ df[norm_col] = np.nan
213
+ df.loc[valid.index, norm_col] = normalized
214
+
215
+ # Build consistent game order (X-axis)
216
+ sorted_games = [game for game in GAME_ORDER if f"norm_{game} Score" in df.columns]
217
+
218
+ # Format game names with line breaks
219
+ formatted_games = []
220
+ for game in sorted_games:
221
+ if len(game) > 10 and ' ' in game:
222
+ parts = game.split(' ')
223
+ midpoint = len(parts) // 2
224
+ formatted_name = ' '.join(parts[:midpoint]) + '<br>' + ' '.join(parts[midpoint:])
225
+ formatted_games.append(formatted_name)
226
+ else:
227
+ formatted_games.append(game)
228
 
229
+ # Create mapping from original to formatted names
230
+ game_display_map = dict(zip(sorted_games, formatted_games))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
 
232
+ # Group models by prefix, then sort alphabetically
233
+ model_groups = {}
234
+ for player in df["Player"].unique():
235
+ prefix = player.split('-')[0]
236
+ model_groups.setdefault(prefix, []).append(player)
237
+
238
+ ordered_players = []
239
+ for prefix in sorted(model_groups):
240
+ ordered_players.extend(sorted(model_groups[prefix]))
241
+
242
+ # Create one trace per player
243
+ fig = go.Figure()
244
+ for player in ordered_players:
245
+ row = df[df["Player"] == player]
246
+ if row.empty:
247
+ continue
248
+ row = row.iloc[0]
249
+
250
+ y_vals = []
251
+ has_data = False
252
+ for game in sorted_games:
253
+ col = f"norm_{game} Score"
254
+ val = row.get(col, np.nan)
255
+ if not np.isnan(val):
256
+ has_data = True
257
+ y_vals.append(val if not np.isnan(val) else 0)
258
+
259
+ if not has_data:
260
+ continue
 
 
261
 
262
+ fig.add_trace(go.Bar(
263
+ name=simplify_model_name(player),
264
+ x=[game_display_map[game] for game in sorted_games],
265
+ y=y_vals,
266
+ marker_color=MODEL_COLORS.get(player, '#808080'),
267
+ hovertemplate="<b>%{fullData.name}</b><br>Score: %{y:.1f}<extra></extra>"
268
+ ))
269
+
270
+ fig.update_layout(
271
+ autosize=False,
272
+ width=1000,
273
+ height=600,
274
+ margin=dict(l=80, r=150, t=40, b=200),
275
+ title=dict(text="Grouped Bar Chart of AI Models (Consistent Trace Grouping)", pad=dict(t=10)),
276
+ xaxis_title="Games",
277
+ yaxis_title="Normalized Score",
278
+ xaxis=dict(
279
+ categoryorder='array',
280
+ categoryarray=[game_display_map[g] for g in sorted_games],
281
+ tickangle=0 # Keep text horizontal since we're using line breaks
282
+ ),
283
+ barmode='group',
284
+ bargap=0.2, # Gap between game categories
285
+ bargroupgap=0.05, # Gap between bars in a group
286
+ uniformtext=dict(mode='hide', minsize=8), # Hide text that doesn't fit
287
+ legend=dict(
288
+ font=dict(size=9),
289
+ itemsizing='trace',
290
+ x=1.1,
291
+ y=1,
292
+ xanchor='left',
293
+ yanchor='top',
294
+ bgcolor='rgba(255,255,255,0.6)',
295
+ bordercolor='gray',
296
+ borderwidth=1
297
+ )
298
+ )
299
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
  return fig
301
 
302
+
303
+
304
+ def get_combined_leaderboard_with_group_bar(rank_data, selected_games):
305
+ df = get_combined_leaderboard(rank_data, selected_games)
306
+ # Create a copy for visualization to avoid modifying the original
307
+ df_viz = df.copy()
308
+ return df, create_group_bar_chart(df_viz)
309
+
310
+ def hex_to_rgba(hex_color, alpha=0.2):
311
+ hex_color = hex_color.lstrip('#')
312
+ r = int(hex_color[0:2], 16)
313
+ g = int(hex_color[2:4], 16)
314
+ b = int(hex_color[4:6], 16)
315
+ return f'rgba({r}, {g}, {b}, {alpha})'
316
+
317
+
318
+ def create_single_radar_chart(df, selected_games=None, highlight_models=None):
319
+ if selected_games is None:
320
+ selected_games = ['Super Mario Bros', '2048', 'Candy Crash', 'Sokoban']
321
+
322
+ game_cols = [f"{game} Score" for game in selected_games]
323
+ categories = selected_games
324
+
325
+ # Normalize
326
+ for col in game_cols:
327
+ vals = df[col].replace("_", 0).astype(float)
328
+ mean, std = vals.mean(), vals.std()
329
+ df[f"norm_{col}"] = normalize_values(vals, mean, std)
330
+
331
+ # Group players by prefix
332
+ model_groups = {}
333
+ for player in df["Player"]:
334
+ prefix = get_model_prefix(player)
335
+ model_groups.setdefault(prefix, []).append(player)
336
+
337
+ # Order: grouped by prefix, then alphabetically
338
+ grouped_players = []
339
+ for prefix in sorted(model_groups):
340
+ grouped_players.extend(sorted(model_groups[prefix]))
341
+
342
+ fig = go.Figure()
343
+
344
+ for player in grouped_players:
345
+ row = df[df["Player"] == player]
346
+ if row.empty:
347
+ continue
348
+ row = row.iloc[0]
349
+
350
+ is_highlighted = highlight_models and player in highlight_models
351
+ color = 'red' if is_highlighted else MODEL_COLORS.get(player, '#808080')
352
+ fillcolor = 'rgba(255, 0, 0, 0.3)' if is_highlighted else hex_to_rgba(color, 0.2)
353
+
354
+ r = [row[f"norm_{col}"] for col in game_cols]
355
+
356
+ fig.add_trace(go.Scatterpolar(
357
+ r=r + [r[0]],
358
+ theta=categories + [categories[0]],
359
+ mode='lines+markers',
360
+ fill='toself',
361
+ name=simplify_model_name(row["Player"]),
362
+ line=dict(color=color, width=4 if is_highlighted else 2),
363
+ marker=dict(color=color),
364
+ fillcolor=fillcolor,
365
+ opacity=1.0 if is_highlighted else 0.7
366
+ ))
367
+
368
+ fig.update_layout(
369
+ autosize=False,
370
+ width=800,
371
+ height=600,
372
+ margin=dict(l=80, r=150, t=40, b=100),
373
+ title=dict(
374
+ text="Single Radar Chart (Normalized Performance)",
375
+ pad=dict(t=10)
376
+ ),
377
+ polar=dict(radialaxis=dict(visible=True, range=[0, 100])),
378
+ legend=dict(
379
+ font=dict(size=9),
380
+ itemsizing='trace',
381
+ x=1.4,
382
+ y=1,
383
+ xanchor='left',
384
+ yanchor='top',
385
+ bgcolor='rgba(255,255,255,0.6)',
386
+ bordercolor='gray',
387
+ borderwidth=1
388
+ )
389
+ )
390
+
391
+ return fig
392
+
393
+ def get_combined_leaderboard_with_single_radar(rank_data, selected_games, highlight_models=None):
394
  df = get_combined_leaderboard(rank_data, selected_games)
395
+ selected_game_names = [g for g, sel in selected_games.items() if sel]
396
+ # Create a copy for visualization to avoid modifying the original
397
+ df_viz = df.copy()
398
+ return df, create_single_radar_chart(df_viz, selected_game_names, highlight_models)
399
 
400
  def create_organization_radar_chart(rank_data):
401
+ df = get_combined_leaderboard(rank_data, {g: True for g in GAME_ORDER})
402
+ orgs = df["Organization"].unique()
403
+ game_cols = [f"{g} Score" for g in GAME_ORDER if f"{g} Score" in df.columns]
404
+ categories = [g.replace(" Score", "") for g in game_cols]
405
+
406
+ avg_df = pd.DataFrame([
407
+ {
408
+ **{col: df[df["Organization"] == org][col].replace("_", 0).astype(float).mean() for col in game_cols},
409
+ "Organization": org
410
+ }
411
+ for org in orgs
412
+ ])
413
+
414
+ for col in game_cols:
415
+ vals = avg_df[col]
416
+ mean, std = vals.mean(), vals.std()
417
+ avg_df[f"norm_{col}"] = normalize_values(vals, mean, std)
418
+
419
+ fig = go.Figure()
420
+ for _, row in avg_df.iterrows():
421
+ r = [row[f"norm_{col}"] for col in game_cols]
422
+ fig.add_trace(go.Scatterpolar(
423
+ r=r + [r[0]],
424
+ theta=categories + [categories[0]],
425
+ mode='lines+markers',
426
+ fill='toself',
427
+ name=row["Organization"]
428
+ ))
429
+
430
+ fig.update_layout(
431
+ autosize=False,
432
+ width=800,
433
+ height=600,
434
+ margin=dict(l=80, r=150, t=40, b=200),
435
+ title=dict(
436
+ text="Radar Chart: Organization Performance (Normalized)",
437
+ pad=dict(t=10)
438
+ ),
439
+ polar=dict(radialaxis=dict(visible=True, range=[0, 100])),
440
+ legend=dict(
441
+ font=dict(size=9),
442
+ itemsizing='trace',
443
+ x=1.4,
444
+ y=1,
445
+ xanchor='left',
446
+ yanchor='top',
447
+ bgcolor='rgba(255,255,255,0.6)',
448
+ bordercolor='gray',
449
+ borderwidth=1
450
+ )
451
+ )
452
+ return fig
453
 
454
  def create_top_players_radar_chart(rank_data, n=5):
455
+ df = get_combined_leaderboard(rank_data, {g: True for g in GAME_ORDER})
456
+ top_players = df.head(n)["Player"].tolist()
457
+ top_df = df[df["Player"].isin(top_players)]
458
+
459
+ game_cols = [f"{g} Score" for g in GAME_ORDER if f"{g} Score" in df.columns]
460
+ categories = [g.replace(" Score", "") for g in game_cols]
461
+
462
+ for col in game_cols:
463
+ vals = top_df[col].replace("_", 0).astype(float)
464
+ mean, std = vals.mean(), vals.std()
465
+ top_df[f"norm_{col}"] = normalize_values(vals, mean, std)
466
+
467
+ fig = go.Figure()
468
+ for _, row in top_df.iterrows():
469
+ r = [row[f"norm_{col}"] for col in game_cols]
470
+ fig.add_trace(go.Scatterpolar(
471
+ r=r + [r[0]],
472
+ theta=categories + [categories[0]],
473
+ mode='lines+markers',
474
+ fill='toself',
475
+ name=simplify_model_name(row["Player"])
476
+ ))
477
+
478
+ fig.update_layout(
479
+ autosize=False,
480
+ width=800,
481
+ height=600,
482
+ margin=dict(l=80, r=150, t=40, b=200),
483
+ title=dict(
484
+ text=f"Top {n} Players Radar Chart (Normalized)",
485
+ pad=dict(t=10)
486
+ ),
487
+ polar=dict(radialaxis=dict(visible=True, range=[0, 100])),
488
+ legend=dict(
489
+ font=dict(size=9),
490
+ itemsizing='trace',
491
+ x=1.4,
492
+ y=1,
493
+ xanchor='left',
494
+ yanchor='top',
495
+ bgcolor='rgba(255,255,255,0.6)',
496
+ bordercolor='gray',
497
+ borderwidth=1
498
+ )
499
+ )
500
+ return fig
501
 
502
  def create_player_radar_chart(rank_data, player_name):
503
+ df = get_combined_leaderboard(rank_data, {g: True for g in GAME_ORDER})
 
 
 
 
 
 
504
  player_df = df[df["Player"] == player_name]
505
+
506
  if player_df.empty:
507
+ return go.Figure().update_layout(
508
+ title=dict(text="Player not found", pad=dict(t=10)),
509
+ autosize=False,
510
+ width=800,
511
+ height=400
512
+ )
513
 
514
+ game_cols = [f"{g} Score" for g in GAME_ORDER if f"{g} Score" in df.columns]
515
+ categories = [g.replace(" Score", "") for g in game_cols]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
516
 
517
+ for col in game_cols:
518
+ vals = player_df[col].replace("_", 0).astype(float)
519
+ mean, std = df[col].replace("_", 0).astype(float).mean(), df[col].replace("_", 0).astype(float).std()
520
+ player_df[f"norm_{col}"] = normalize_values(vals, mean, std)
521
 
522
+ fig = go.Figure()
523
+ for _, row in player_df.iterrows():
524
+ r = [row[f"norm_{col}"] for col in game_cols]
525
+ fig.add_trace(go.Scatterpolar(
526
+ r=r + [r[0]],
527
+ theta=categories + [categories[0]],
528
+ mode='lines+markers',
529
+ fill='toself',
530
+ name=simplify_model_name(row["Player"])
531
+ ))
532
+
533
+ fig.update_layout(
534
+ autosize=False,
535
+ width=800,
536
+ height=600,
537
+ margin=dict(l=80, r=150, t=40, b=200),
538
+ title=dict(
539
+ text=f"{simplify_model_name(player_name)} Radar Chart (Normalized)",
540
+ pad=dict(t=10)
541
+ ),
542
+ polar=dict(radialaxis=dict(visible=True, range=[0, 100])),
543
+ legend=dict(
544
+ font=dict(size=9),
545
+ itemsizing='trace',
546
+ x=1.4,
547
+ y=1,
548
+ xanchor='left',
549
+ yanchor='top',
550
+ bgcolor='rgba(255,255,255,0.6)',
551
+ bordercolor='gray',
552
+ borderwidth=1
553
+ )
554
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
555
  return fig
556
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
557
 
558
  def save_visualization(fig, filename):
559
+ fig.write_image(filename)
 
 
 
gallery_tab.py ADDED
@@ -0,0 +1,255 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from datetime import datetime
3
+ import json
4
+
5
+ # Load video links and news data
6
+ with open('assets/game_video_link.json', 'r') as f:
7
+ VIDEO_LINKS = json.load(f)
8
+
9
+ with open('assets/news.json', 'r') as f:
10
+ NEWS_DATA = json.load(f)
11
+
12
+ def create_video_gallery():
13
+ """Create a custom HTML/JS component for video gallery"""
14
+ # Extract video IDs
15
+ mario_id = VIDEO_LINKS["super_mario"].split("?v=")[1]
16
+ sokoban_id = VIDEO_LINKS["sokoban"].split("?v=")[1]
17
+ game_2048_id = VIDEO_LINKS["2048"].split("?v=")[1]
18
+ candy_id = VIDEO_LINKS["candy"].split("?v=")[1]
19
+
20
+ # Get the latest video from news data
21
+ latest_news = NEWS_DATA["news"][0] # First item is the latest
22
+ latest_video_id = latest_news["video_link"].split("?v=")[1]
23
+ latest_date = datetime.strptime(latest_news["date"], "%Y-%m-%d")
24
+ formatted_latest_date = latest_date.strftime("%B %d, %Y")
25
+
26
+ # Generate news HTML
27
+ news_items = []
28
+ for item in NEWS_DATA["news"]:
29
+ video_id = item["video_link"].split("?v=")[1]
30
+ date_obj = datetime.strptime(item["date"], "%Y-%m-%d")
31
+ formatted_date = date_obj.strftime("%B %d, %Y")
32
+ news_items.append(f'''
33
+ <div class="news-item">
34
+ <div class="news-date">{formatted_date}</div>
35
+ <div class="news-content">
36
+ <div class="news-video">
37
+ <div class="video-wrapper">
38
+ <iframe src="https://www.youtube.com/embed/{video_id}"></iframe>
39
+ </div>
40
+ </div>
41
+ <div class="news-text">
42
+ <a href="{item["twitter_link"]}" target="_blank" class="twitter-link">
43
+ <span class="twitter-icon">📢</span>
44
+ {item["twitter_text"]}
45
+ </a>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ ''')
50
+
51
+ news_html = '\n'.join(news_items)
52
+
53
+ gallery_html = f'''
54
+ <div class="video-gallery-container">
55
+ <style>
56
+ .video-gallery-container {{
57
+ width: 100%;
58
+ max-width: 1400px;
59
+ margin: 0 auto;
60
+ padding: 20px;
61
+ }}
62
+ .highlight-section {{
63
+ margin-bottom: 40px;
64
+ }}
65
+ .highlight-card {{
66
+ background: #ffffff;
67
+ border-radius: 10px;
68
+ box-shadow: 0 4px 20px rgba(0,0,0,0.15);
69
+ overflow: hidden;
70
+ transition: transform 0.3s;
71
+ border: 2px solid #2196F3;
72
+ }}
73
+ .highlight-card:hover {{
74
+ transform: translateY(-5px);
75
+ }}
76
+ .highlight-header {{
77
+ background: #2196F3;
78
+ color: white;
79
+ padding: 15px 20px;
80
+ font-size: 1.2em;
81
+ font-weight: bold;
82
+ display: flex;
83
+ align-items: center;
84
+ gap: 10px;
85
+ }}
86
+ .highlight-date {{
87
+ font-size: 0.9em;
88
+ opacity: 0.9;
89
+ }}
90
+ .highlight-content {{
91
+ padding: 20px;
92
+ }}
93
+ .video-grid {{
94
+ display: grid;
95
+ grid-template-columns: repeat(2, 1fr);
96
+ gap: 20px;
97
+ margin-top: 20px;
98
+ margin-bottom: 40px;
99
+ }}
100
+ .video-card {{
101
+ background: #ffffff;
102
+ border-radius: 10px;
103
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
104
+ overflow: hidden;
105
+ transition: transform 0.2s;
106
+ }}
107
+ .video-card:hover {{
108
+ transform: translateY(-5px);
109
+ }}
110
+ .video-wrapper {{
111
+ position: relative;
112
+ padding-bottom: 56.25%;
113
+ height: 0;
114
+ overflow: hidden;
115
+ }}
116
+ .video-wrapper iframe {{
117
+ position: absolute;
118
+ top: 0;
119
+ left: 0;
120
+ width: 100%;
121
+ height: 100%;
122
+ border: none;
123
+ }}
124
+ .video-title {{
125
+ padding: 15px;
126
+ font-size: 1.2em;
127
+ font-weight: bold;
128
+ color: #2c3e50;
129
+ text-align: center;
130
+ background: #f8f9fa;
131
+ border-top: 1px solid #eee;
132
+ }}
133
+ .news-section {{
134
+ margin-top: 40px;
135
+ border-top: 2px solid #e9ecef;
136
+ padding-top: 20px;
137
+ }}
138
+ .news-section-title {{
139
+ font-size: 1.8em;
140
+ font-weight: bold;
141
+ color: #2c3e50;
142
+ margin-bottom: 20px;
143
+ text-align: center;
144
+ }}
145
+ .news-item {{
146
+ background: #ffffff;
147
+ border-radius: 10px;
148
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
149
+ margin-bottom: 20px;
150
+ overflow: hidden;
151
+ }}
152
+ .news-date {{
153
+ padding: 10px 20px;
154
+ background: #f8f9fa;
155
+ color: #666;
156
+ font-size: 0.9em;
157
+ border-bottom: 1px solid #eee;
158
+ }}
159
+ .news-content {{
160
+ display: flex;
161
+ padding: 20px;
162
+ align-items: center;
163
+ gap: 30px;
164
+ }}
165
+ .news-video {{
166
+ flex: 0 0 300px;
167
+ }}
168
+ .news-text {{
169
+ flex: 1;
170
+ display: flex;
171
+ align-items: center;
172
+ min-height: 169px;
173
+ }}
174
+ .twitter-link {{
175
+ color: #2c3e50;
176
+ text-decoration: none;
177
+ display: flex;
178
+ align-items: center;
179
+ gap: 15px;
180
+ font-size: 1.4em;
181
+ font-weight: 600;
182
+ line-height: 1.4;
183
+ }}
184
+ .twitter-link:hover {{
185
+ color: #1da1f2;
186
+ }}
187
+ .twitter-icon {{
188
+ font-size: 1.5em;
189
+ color: #1da1f2;
190
+ }}
191
+ </style>
192
+
193
+ <!-- Highlight Section -->
194
+ <div class="highlight-section">
195
+ <div class="highlight-card">
196
+ <div class="highlight-header">
197
+ <span>🌟 Latest Update</span>
198
+ <span class="highlight-date">{formatted_latest_date}</span>
199
+ </div>
200
+ <div class="highlight-content">
201
+ <div class="video-wrapper">
202
+ <iframe src="https://www.youtube.com/embed/{latest_video_id}"></iframe>
203
+ </div>
204
+ <div class="video-title">
205
+ <a href="{latest_news["twitter_link"]}" target="_blank" class="twitter-link">
206
+ <span class="twitter-icon">📢</span>
207
+ {latest_news["twitter_text"]}
208
+ </a>
209
+ </div>
210
+ </div>
211
+ </div>
212
+ </div>
213
+
214
+ <!-- Regular Video Grid -->
215
+ <div class="video-grid">
216
+ <div class="video-card">
217
+ <div class="video-wrapper">
218
+ <iframe src="https://www.youtube.com/embed/{mario_id}"></iframe>
219
+ </div>
220
+ <div class="video-title">🎮 Super Mario Bros</div>
221
+ </div>
222
+ <div class="video-card">
223
+ <div class="video-wrapper">
224
+ <iframe src="https://www.youtube.com/embed/{sokoban_id}"></iframe>
225
+ </div>
226
+ <div class="video-title">📦 Sokoban</div>
227
+ </div>
228
+ <div class="video-card">
229
+ <div class="video-wrapper">
230
+ <iframe src="https://www.youtube.com/embed/{game_2048_id}"></iframe>
231
+ </div>
232
+ <div class="video-title">🔢 2048</div>
233
+ </div>
234
+ <div class="video-card">
235
+ <div class="video-wrapper">
236
+ <iframe src="https://www.youtube.com/embed/{candy_id}"></iframe>
237
+ </div>
238
+ <div class="video-title">🍬 Candy Crash</div>
239
+ </div>
240
+ </div>
241
+
242
+ <!-- News Section -->
243
+ <div class="news-section">
244
+ <div class="news-section-title">📰 Latest News</div>
245
+ {news_html}
246
+ </div>
247
+ </div>
248
+ '''
249
+ return gr.HTML(gallery_html)
250
+
251
+ def create_gallery_tab():
252
+ """Create and return the gallery tab component"""
253
+ with gr.Tab("🎥 Gallery") as gallery_tab:
254
+ video_gallery = create_video_gallery()
255
+ return gallery_tab
gradio_app_v2.py CHANGED
@@ -25,8 +25,15 @@ from data_visualization import (
25
  create_top_players_radar_chart,
26
  create_player_radar_chart,
27
  create_horizontal_bar_chart,
28
- normalize_values
 
29
  )
 
 
 
 
 
 
30
 
31
  # Define time points and their corresponding data files
32
  TIME_POINTS = {
@@ -59,25 +66,6 @@ leaderboard_state = {
59
  }
60
  }
61
 
62
- # Define GIF paths for the carousel
63
- GIF_PATHS = [
64
- "assets/super_mario_bros/super_mario.gif",
65
- "assets/sokoban/sokoban.gif",
66
- "assets/2048/2048.gif",
67
- "assets/candy/candy.gif",
68
- "assets/tetris/tetris.gif"
69
- ]
70
-
71
- # Print and verify GIF paths
72
- print("\nChecking GIF paths:")
73
- for gif_path in GIF_PATHS:
74
- if os.path.exists(gif_path):
75
- print(f"✓ Found: {gif_path}")
76
- # Print file size
77
- size = os.path.getsize(gif_path)
78
- print(f" Size: {size / (1024*1024):.2f} MB")
79
- else:
80
- print(f"✗ Missing: {gif_path}")
81
 
82
  # Load video links and news data
83
  with open('assets/game_video_link.json', 'r') as f:
@@ -86,42 +74,6 @@ with open('assets/game_video_link.json', 'r') as f:
86
  with open('assets/news.json', 'r') as f:
87
  NEWS_DATA = json.load(f)
88
 
89
- def load_gif(gif_path):
90
- """Load a GIF file and return it as a PIL Image"""
91
- try:
92
- img = Image.open(gif_path)
93
- print(f"Successfully loaded GIF: {gif_path}")
94
- return img
95
- except Exception as e:
96
- print(f"Error loading GIF {gif_path}: {e}")
97
- return None
98
-
99
- def create_gif_carousel():
100
- """Create a custom HTML/JS component for GIF carousel"""
101
- print("\nCreating GIF carousel with paths:", GIF_PATHS)
102
- html = f"""
103
- <div id="gif-carousel" style="width: 100%; height: 300px; position: relative; background-color: #f0f0f0;">
104
- <img id="current-gif" style="width: 100%; height: 100%; object-fit: contain;" onerror="console.error('Failed to load GIF:', this.src);">
105
- </div>
106
- <script>
107
- const gifs = {json.dumps(GIF_PATHS)};
108
- let currentIndex = 0;
109
-
110
- function updateGif() {{
111
- const img = document.getElementById('current-gif');
112
- console.log('Loading GIF:', gifs[currentIndex]);
113
- img.src = gifs[currentIndex];
114
- currentIndex = (currentIndex + 1) % gifs.length;
115
- }}
116
-
117
- // Update GIF every 5 seconds
118
- setInterval(updateGif, 5000);
119
- // Initial load
120
- updateGif();
121
- </script>
122
- """
123
- return gr.HTML(html)
124
-
125
  def load_rank_data(time_point):
126
  """Load rank data for a specific time point"""
127
  if time_point in TIME_POINTS:
@@ -132,6 +84,76 @@ def load_rank_data(time_point):
132
  return None
133
  return None
134
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  def update_leaderboard(mario_overall, mario_details,
136
  sokoban_overall, sokoban_details,
137
  _2048_overall, _2048_details,
@@ -212,6 +234,29 @@ def update_leaderboard(mario_overall, mario_details,
212
  leaderboard_state["previous_details"][changed_game] = False
213
  if leaderboard_state["current_game"] == changed_game:
214
  leaderboard_state["current_game"] = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
  # Build dictionary for selected games
217
  selected_games = {
@@ -223,7 +268,7 @@ def update_leaderboard(mario_overall, mario_details,
223
  "Tetris (planning only)": current_overall["Tetris (planning only)"]
224
  }
225
 
226
- # Get the appropriate DataFrame and chart based on current state
227
  if leaderboard_state["current_game"]:
228
  # For detailed view
229
  if leaderboard_state["current_game"] == "Super Mario Bros":
@@ -239,14 +284,26 @@ def update_leaderboard(mario_overall, mario_details,
239
  else: # Tetris (planning only)
240
  df = get_tetris_planning_leaderboard(rank_data)
241
 
 
 
 
242
  # Always create a new chart for detailed view
243
  chart = create_horizontal_bar_chart(df, leaderboard_state["current_game"])
 
 
 
244
  else:
245
  # For overall view
246
- df, chart = get_combined_leaderboard_with_group_bar(rank_data, selected_games)
 
 
 
 
 
 
247
 
248
- # Return exactly 14 values to match the expected outputs
249
- return (df, chart,
250
  current_overall["Super Mario Bros"], current_details["Super Mario Bros"],
251
  current_overall["Sokoban"], current_details["Sokoban"],
252
  current_overall["2048"], current_details["2048"],
@@ -274,24 +331,9 @@ def update_leaderboard_with_time(time_point, mario_overall, mario_details,
274
  tetris_overall, tetris_details,
275
  tetris_plan_overall, tetris_plan_details)
276
 
277
- def clear_filters():
278
- global leaderboard_state
279
-
280
- # Reset all checkboxes to default state
281
- selected_games = {
282
- "Super Mario Bros": True,
283
- "Sokoban": True,
284
- "2048": True,
285
- "Candy Crash": True,
286
- "Tetris (complete)": True,
287
- "Tetris (planning only)": True
288
- }
289
-
290
- # Get the combined leaderboard and group bar chart
291
- df, chart = get_combined_leaderboard_with_group_bar(rank_data, selected_games)
292
-
293
- # Reset the leaderboard state to match the default checkbox states
294
- leaderboard_state = {
295
  "current_game": None,
296
  "previous_overall": {
297
  "Super Mario Bros": True,
@@ -310,9 +352,34 @@ def clear_filters():
310
  "Tetris (planning only)": False
311
  }
312
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
 
314
- # Return exactly 14 values to match the expected outputs
315
- return (df, chart,
 
 
 
 
 
 
 
 
 
316
  True, False, # mario
317
  True, False, # sokoban
318
  True, False, # 2048
@@ -428,200 +495,53 @@ def create_timeline_slider():
428
  """
429
  return gr.HTML(timeline_html)
430
 
431
- def create_video_gallery():
432
- """Create a custom HTML/JS component for video gallery"""
433
- # Extract video IDs
434
- mario_id = VIDEO_LINKS["super_mario"].split("?v=")[1]
435
- sokoban_id = VIDEO_LINKS["sokoban"].split("?v=")[1]
436
- game_2048_id = VIDEO_LINKS["2048"].split("?v=")[1]
437
- candy_id = VIDEO_LINKS["candy"].split("?v=")[1]
438
-
439
- # Generate news HTML
440
- news_items = []
441
- for item in NEWS_DATA["news"]:
442
- video_id = item["video_link"].split("?v=")[1]
443
- date_obj = datetime.strptime(item["date"], "%Y-%m-%d")
444
- formatted_date = date_obj.strftime("%B %d, %Y")
445
- news_items.append(f'''
446
- <div class="news-item">
447
- <div class="news-date">{formatted_date}</div>
448
- <div class="news-content">
449
- <div class="news-video">
450
- <div class="video-wrapper">
451
- <iframe src="https://www.youtube.com/embed/{video_id}"></iframe>
452
- </div>
453
- </div>
454
- <div class="news-text">
455
- <a href="{item["twitter_link"]}" target="_blank" class="twitter-link">
456
- <span class="twitter-icon">📢</span>
457
- {item["twitter_text"]}
458
- </a>
459
- </div>
460
- </div>
461
- </div>
462
- ''')
463
-
464
- news_html = '\n'.join(news_items)
465
-
466
- gallery_html = f'''
467
- <div class="video-gallery-container">
468
- <style>
469
- .video-gallery-container {{
470
- width: 100%;
471
- max-width: 1400px;
472
- margin: 0 auto;
473
- padding: 20px;
474
- }}
475
- .video-grid {{
476
- display: grid;
477
- grid-template-columns: repeat(2, 1fr);
478
- gap: 20px;
479
- margin-top: 20px;
480
- margin-bottom: 40px;
481
- }}
482
- .video-card {{
483
- background: #ffffff;
484
- border-radius: 10px;
485
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
486
- overflow: hidden;
487
- transition: transform 0.2s;
488
- }}
489
- .video-card:hover {{
490
- transform: translateY(-5px);
491
- }}
492
- .video-wrapper {{
493
- position: relative;
494
- padding-bottom: 56.25%;
495
- height: 0;
496
- overflow: hidden;
497
- }}
498
- .video-wrapper iframe {{
499
- position: absolute;
500
- top: 0;
501
- left: 0;
502
- width: 100%;
503
- height: 100%;
504
- border: none;
505
- }}
506
- .video-title {{
507
- padding: 15px;
508
- font-size: 1.2em;
509
- font-weight: bold;
510
- color: #2c3e50;
511
- text-align: center;
512
- background: #f8f9fa;
513
- border-top: 1px solid #eee;
514
- }}
515
- .news-section {{
516
- margin-top: 40px;
517
- border-top: 2px solid #e9ecef;
518
- padding-top: 20px;
519
- }}
520
- .news-section-title {{
521
- font-size: 1.8em;
522
- font-weight: bold;
523
- color: #2c3e50;
524
- margin-bottom: 20px;
525
- text-align: center;
526
- }}
527
- .news-item {{
528
- background: #ffffff;
529
- border-radius: 10px;
530
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
531
- margin-bottom: 20px;
532
- overflow: hidden;
533
- }}
534
- .news-date {{
535
- padding: 10px 20px;
536
- background: #f8f9fa;
537
- color: #666;
538
- font-size: 0.9em;
539
- border-bottom: 1px solid #eee;
540
- }}
541
- .news-content {{
542
- display: flex;
543
- padding: 20px;
544
- align-items: center;
545
- gap: 30px;
546
- }}
547
- .news-video {{
548
- flex: 0 0 300px;
549
- }}
550
- .news-text {{
551
- flex: 1;
552
- display: flex;
553
- align-items: center;
554
- min-height: 169px; /* Match 16:9 video height */
555
- }}
556
- .twitter-link {{
557
- color: #2c3e50;
558
- text-decoration: none;
559
- display: flex;
560
- align-items: center;
561
- gap: 15px;
562
- font-size: 1.4em;
563
- font-weight: 600;
564
- line-height: 1.4;
565
- }}
566
- .twitter-link:hover {{
567
- color: #1da1f2;
568
- }}
569
- .twitter-icon {{
570
- font-size: 1.5em;
571
- color: #1da1f2;
572
- }}
573
- </style>
574
- <div class="video-grid">
575
- <div class="video-card">
576
- <div class="video-wrapper">
577
- <iframe src="https://www.youtube.com/embed/{mario_id}"></iframe>
578
- </div>
579
- <div class="video-title">🎮 Super Mario Bros</div>
580
- </div>
581
- <div class="video-card">
582
- <div class="video-wrapper">
583
- <iframe src="https://www.youtube.com/embed/{sokoban_id}"></iframe>
584
- </div>
585
- <div class="video-title">📦 Sokoban</div>
586
- </div>
587
- <div class="video-card">
588
- <div class="video-wrapper">
589
- <iframe src="https://www.youtube.com/embed/{game_2048_id}"></iframe>
590
- </div>
591
- <div class="video-title">🔢 2048</div>
592
- </div>
593
- <div class="video-card">
594
- <div class="video-wrapper">
595
- <iframe src="https://www.youtube.com/embed/{candy_id}"></iframe>
596
- </div>
597
- <div class="video-title">🍬 Candy Crash</div>
598
- </div>
599
- </div>
600
- <div class="news-section">
601
- <div class="news-section-title">📰 Latest News</div>
602
- {news_html}
603
- </div>
604
- </div>
605
- '''
606
- return gr.HTML(gallery_html)
607
-
608
  def build_app():
609
  with gr.Blocks(css="""
610
- .visualization-container {
611
- height: 85vh !important;
612
- max-height: 900px !important;
613
- min-height: 600px !important;
614
- background-color: #f8f9fa;
615
- border-radius: 10px;
616
- padding: 25px; /* Increased padding */
617
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
618
- overflow: hidden;
619
- margin: 0 auto !important; /* Center the visualization */
620
- }
621
- .visualization-container .plot {
622
  height: 100% !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
623
  width: 100% !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
624
  }
 
 
625
  .section-title {
626
  font-size: 1.5em;
627
  font-weight: bold;
@@ -629,41 +549,126 @@ def build_app():
629
  margin-bottom: 15px;
630
  padding-bottom: 10px;
631
  border-bottom: 2px solid #e9ecef;
632
- text-align: center; /* Center the title */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
633
  }
634
- /* Add container for the entire app */
635
- .container {
636
- max-width: 1400px;
637
- margin: 0 auto;
638
- padding: 0 20px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
639
  }
640
  """) as demo:
641
  gr.Markdown("# 🎮 Game Arena: Gaming Agent 🎲")
642
 
643
  with gr.Tabs():
644
  with gr.Tab("🏆 Leaderboard"):
645
- # Visualization section at the very top
646
  with gr.Row():
647
  gr.Markdown("### 📊 Data Visualization")
648
- with gr.Row():
649
- visualization = gr.Plot(
650
- value=get_combined_leaderboard_with_group_bar(rank_data, {
651
- "Super Mario Bros": True,
652
- "Sokoban": True,
653
- "2048": True,
654
- "Candy Crash": True,
655
- "Tetris (complete)": True,
656
- "Tetris (planning only)": True
657
- })[1],
658
- label="Performance Visualization",
659
- elem_classes="visualization-container"
660
- )
 
 
 
 
 
 
 
 
 
 
 
661
 
662
  # Game selection section
663
  with gr.Row():
664
  gr.Markdown("### 🎮 Game Selection")
665
  with gr.Row():
666
- # For each game, we have two checkboxes: one for overall and one for detailed view.
667
  with gr.Column():
668
  gr.Markdown("**🎮 Super Mario Bros**")
669
  mario_overall = gr.Checkbox(label="Super Mario Bros Score", value=True)
@@ -688,8 +693,8 @@ def build_app():
688
  gr.Markdown("**📋 Tetris (planning)**")
689
  tetris_plan_overall = gr.Checkbox(label="Tetris (planning) Score", value=True)
690
  tetris_plan_details = gr.Checkbox(label="Tetris (planning) Details", value=False)
691
-
692
- # Time progression display and control buttons - Moved below game selection
693
  with gr.Row():
694
  with gr.Column(scale=2):
695
  gr.Markdown("**⏰ Time Tracker**")
@@ -697,57 +702,137 @@ def build_app():
697
  with gr.Column(scale=1):
698
  gr.Markdown("**🔄 Controls**")
699
  clear_btn = gr.Button("Reset Filters", variant="secondary")
700
-
701
- # Leaderboard table section
702
  with gr.Row():
703
  gr.Markdown("### 📋 Detailed Results")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
704
  with gr.Row():
705
- leaderboard_board = gr.DataFrame(
706
- value=get_combined_leaderboard(rank_data, {
707
- "Super Mario Bros": True,
708
- "Sokoban": True,
709
- "2048": True,
710
- "Candy Crash": True,
711
- "Tetris (complete)": True,
712
- "Tetris (planning only)": True
713
- }),
714
  interactive=True,
 
 
715
  wrap=True,
716
- label="Leaderboard"
 
 
 
 
 
717
  )
718
-
719
- # List of all checkboxes (in order)
720
- checkbox_list = [mario_overall, mario_details,
721
- sokoban_overall, sokoban_details,
722
- _2048_overall, _2048_details,
723
- candy_overall, candy_details,
724
- tetris_overall, tetris_details,
725
- tetris_plan_overall, tetris_plan_details]
726
-
727
- # Initialize the leaderboard state when the app starts
728
- clear_filters()
729
-
730
- # Update both the leaderboard and visualization when checkboxes change
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
731
  for checkbox in checkbox_list:
732
  checkbox.change(
733
- fn=update_leaderboard,
734
  inputs=checkbox_list,
735
- outputs=[leaderboard_board, visualization] + checkbox_list
736
  )
737
-
738
- # Update both when clear button is clicked
 
 
 
 
 
 
 
 
 
 
 
 
 
739
  clear_btn.click(
 
 
 
 
 
 
 
 
 
 
 
 
740
  fn=clear_filters,
741
  inputs=[],
742
- outputs=[leaderboard_board, visualization] + checkbox_list
 
 
 
 
 
743
  )
744
-
745
  with gr.Tab("🎥 Gallery"):
746
  video_gallery = create_video_gallery()
747
-
748
  return demo
749
 
750
  if __name__ == "__main__":
751
  demo_app = build_app()
752
  # Add file serving configuration
753
- demo_app.launch(debug=True, show_error=True, share=True)
 
 
 
 
 
 
 
25
  create_top_players_radar_chart,
26
  create_player_radar_chart,
27
  create_horizontal_bar_chart,
28
+ normalize_values,
29
+ get_combined_leaderboard_with_single_radar
30
  )
31
+ from gallery_tab import create_video_gallery
32
+
33
+
34
+
35
+ HAS_ENHANCED_LEADERBOARD = True
36
+
37
 
38
  # Define time points and their corresponding data files
39
  TIME_POINTS = {
 
66
  }
67
  }
68
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
 
70
  # Load video links and news data
71
  with open('assets/game_video_link.json', 'r') as f:
 
74
  with open('assets/news.json', 'r') as f:
75
  NEWS_DATA = json.load(f)
76
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
  def load_rank_data(time_point):
78
  """Load rank data for a specific time point"""
79
  if time_point in TIME_POINTS:
 
84
  return None
85
  return None
86
 
87
+ # Add a note about score values
88
+ def add_score_note():
89
+ return gr.Markdown("*Note: '-1' in the table indicates no data point for that model.*", elem_classes="score-note")
90
+
91
+ # Function to prepare DataFrame for display
92
+ def prepare_dataframe_for_display(df, for_game=None):
93
+ """Format DataFrame for better display in the UI"""
94
+ # Clone the DataFrame to avoid modifying the original
95
+ display_df = df.copy()
96
+
97
+ # Filter out normalized score columns
98
+ norm_columns = [col for col in display_df.columns if col.startswith('norm_')]
99
+ if norm_columns:
100
+ display_df = display_df.drop(columns=norm_columns)
101
+
102
+ # Replace '_' with '-' for better display
103
+ for col in display_df.columns:
104
+ if col.endswith(' Score'):
105
+ display_df[col] = display_df[col].apply(lambda x: '-' if x == '_' else x)
106
+
107
+ # If we're in detailed view, add a formatted rank column
108
+ if for_game:
109
+ # Sort by relevant score column
110
+ score_col = f"{for_game} Score"
111
+ if score_col in display_df.columns:
112
+ # Convert to numeric for sorting, treating '-' as NaN
113
+ display_df[score_col] = pd.to_numeric(display_df[score_col], errors='coerce')
114
+ # Sort by score in descending order
115
+ display_df = display_df.sort_values(by=score_col, ascending=False)
116
+ # Add rank column based on the sort
117
+ display_df.insert(0, 'Rank', range(1, len(display_df) + 1))
118
+ # Filter out models that didn't participate
119
+ display_df = display_df[~display_df[score_col].isna()]
120
+
121
+ # Add line breaks to column headers
122
+ new_columns = {}
123
+ for col in display_df.columns:
124
+ if col.endswith(' Score'):
125
+ # Replace 'Game Name Score' with 'Game Name\nScore'
126
+ game_name = col.replace(' Score', '')
127
+ new_col = f"{game_name}\nScore"
128
+ new_columns[col] = new_col
129
+ elif col == 'Organization':
130
+ new_columns[col] = 'Organi-\nzation'
131
+
132
+ # Rename columns with new line breaks
133
+ if new_columns:
134
+ display_df = display_df.rename(columns=new_columns)
135
+
136
+ return display_df
137
+
138
+ # Helper function to ensure leaderboard updates maintain consistent height
139
+ def update_df_with_height(df):
140
+ """Update DataFrame with consistent height parameter."""
141
+ # Create column widths array
142
+ col_widths = ["40px"] # Row number column width
143
+ col_widths.append("230px") # Player column - reduced by 20px
144
+ col_widths.append("120px") # Organization column
145
+ # Add game score columns
146
+ for _ in range(len(df.columns) - 2):
147
+ col_widths.append("120px")
148
+
149
+ return gr.update(value=df,
150
+ show_row_numbers=True,
151
+ show_fullscreen_button=True,
152
+ line_breaks=True,
153
+ show_search="search",
154
+ max_height=None, # Remove height limitation
155
+ column_widths=col_widths)
156
+
157
  def update_leaderboard(mario_overall, mario_details,
158
  sokoban_overall, sokoban_details,
159
  _2048_overall, _2048_details,
 
234
  leaderboard_state["previous_details"][changed_game] = False
235
  if leaderboard_state["current_game"] == changed_game:
236
  leaderboard_state["current_game"] = None
237
+ # When exiting details view, reset to show all games
238
+ for game in current_overall.keys():
239
+ current_overall[game] = True
240
+ current_details[game] = False
241
+ leaderboard_state["previous_overall"][game] = True
242
+ leaderboard_state["previous_details"][game] = False
243
+
244
+ # Special case: If all games are selected and we're trying to view details
245
+ all_games_selected = all(current_overall.values()) and not any(current_details.values())
246
+ if all_games_selected and changed_game and current_details[changed_game]:
247
+ # Reset all other games' states
248
+ for game in current_overall.keys():
249
+ if game != changed_game:
250
+ current_overall[game] = False
251
+ current_details[game] = False
252
+ leaderboard_state["previous_overall"][game] = False
253
+ leaderboard_state["previous_details"][game] = False
254
+
255
+ # Update state for the selected game
256
+ leaderboard_state["current_game"] = changed_game
257
+ leaderboard_state["previous_overall"][changed_game] = True
258
+ leaderboard_state["previous_details"][changed_game] = True
259
+ current_overall[changed_game] = True
260
 
261
  # Build dictionary for selected games
262
  selected_games = {
 
268
  "Tetris (planning only)": current_overall["Tetris (planning only)"]
269
  }
270
 
271
+ # Get the appropriate DataFrame and charts based on current state
272
  if leaderboard_state["current_game"]:
273
  # For detailed view
274
  if leaderboard_state["current_game"] == "Super Mario Bros":
 
284
  else: # Tetris (planning only)
285
  df = get_tetris_planning_leaderboard(rank_data)
286
 
287
+ # Format the DataFrame for display
288
+ display_df = prepare_dataframe_for_display(df, leaderboard_state["current_game"])
289
+
290
  # Always create a new chart for detailed view
291
  chart = create_horizontal_bar_chart(df, leaderboard_state["current_game"])
292
+ # Use the same chart for all visualizations in detailed view
293
+ radar_chart = chart
294
+ group_bar_chart = chart
295
  else:
296
  # For overall view
297
+ df, _ = get_combined_leaderboard_with_group_bar(rank_data, selected_games)
298
+ # Format the DataFrame for display
299
+ display_df = prepare_dataframe_for_display(df)
300
+ # Use the same selected_games for radar chart
301
+ _, radar_chart = get_combined_leaderboard_with_single_radar(rank_data, selected_games)
302
+ chart = radar_chart
303
+ group_bar_chart = radar_chart # Use radar chart instead of bar chart
304
 
305
+ # Return exactly 16 values to match the expected outputs
306
+ return (update_df_with_height(display_df), chart, radar_chart, radar_chart,
307
  current_overall["Super Mario Bros"], current_details["Super Mario Bros"],
308
  current_overall["Sokoban"], current_details["Sokoban"],
309
  current_overall["2048"], current_details["2048"],
 
331
  tetris_overall, tetris_details,
332
  tetris_plan_overall, tetris_plan_details)
333
 
334
+ def get_initial_state():
335
+ """Get the initial state for the leaderboard"""
336
+ return {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
337
  "current_game": None,
338
  "previous_overall": {
339
  "Super Mario Bros": True,
 
352
  "Tetris (planning only)": False
353
  }
354
  }
355
+
356
+ def clear_filters():
357
+ global leaderboard_state
358
+
359
+ # Reset all checkboxes to default state
360
+ selected_games = {
361
+ "Super Mario Bros": True,
362
+ "Sokoban": True,
363
+ "2048": True,
364
+ "Candy Crash": True,
365
+ "Tetris (complete)": True,
366
+ "Tetris (planning only)": True
367
+ }
368
+
369
+ # Get the combined leaderboard and group bar chart
370
+ df, group_bar_chart = get_combined_leaderboard_with_group_bar(rank_data, selected_games)
371
 
372
+ # Format the DataFrame for display
373
+ display_df = prepare_dataframe_for_display(df)
374
+
375
+ # Get the radar chart using the same selected games
376
+ _, radar_chart = get_combined_leaderboard_with_single_radar(rank_data, selected_games)
377
+
378
+ # Reset the leaderboard state to match the default checkbox states
379
+ leaderboard_state = get_initial_state()
380
+
381
+ # Return exactly 16 values to match the expected outputs
382
+ return (update_df_with_height(display_df), radar_chart, radar_chart, radar_chart,
383
  True, False, # mario
384
  True, False, # sokoban
385
  True, False, # 2048
 
495
  """
496
  return gr.HTML(timeline_html)
497
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
  def build_app():
499
  with gr.Blocks(css="""
500
+ /* Fix for disappearing scrollbar */
501
+ html, body {
502
+ overflow-y: auto !important;
503
+ overflow-x: hidden !important;
504
+ width: 100% !important;
 
 
 
 
 
 
 
505
  height: 100% !important;
506
+ }
507
+
508
+ /* Prevent content from shrinking to center */
509
+ .gradio-container {
510
+ width: 100% !important;
511
+ max-width: 1200px !important;
512
+ margin-left: auto !important;
513
+ margin-right: auto !important;
514
+ min-height: 100vh !important;
515
+ }
516
+
517
+ /* Clean up table styling */
518
+ .table-container {
519
  width: 100% !important;
520
+ overflow: visible !important;
521
+ border-radius: 8px;
522
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
523
+ }
524
+
525
+ /* Remove duplicate scrollbars */
526
+ .gradio-dataframe [data-testid="table"],
527
+ [data-testid="dataframe"] [data-testid="table"],
528
+ .gradio-dataframe tbody,
529
+ [data-testid="dataframe"] tbody,
530
+ .table-container > div,
531
+ .table-container > div > div {
532
+ overflow: visible !important;
533
+ max-height: none !important;
534
+ }
535
+
536
+ /* Visualization styling */
537
+ .visualization-container .js-plotly-plot {
538
+ margin-left: auto !important;
539
+ margin-right: auto !important;
540
+ display: block !important;
541
+ max-width: 1000px;
542
  }
543
+
544
+ /* Section styling */
545
  .section-title {
546
  font-size: 1.5em;
547
  font-weight: bold;
 
549
  margin-bottom: 15px;
550
  padding-bottom: 10px;
551
  border-bottom: 2px solid #e9ecef;
552
+ text-align: center;
553
+ }
554
+
555
+ /* Fix table styling */
556
+ .table-container table {
557
+ width: 100%;
558
+ border-collapse: separate;
559
+ border-spacing: 0;
560
+ table-layout: fixed !important;
561
+ }
562
+
563
+ /* Column width customization - adjust for row numbers being first column */
564
+ .table-container th:nth-child(2),
565
+ .table-container td:nth-child(2) {
566
+ width: 230px !important;
567
+ min-width: 200px !important;
568
+ max-width: 280px !important;
569
+ padding-left: 8px !important;
570
+ padding-right: 8px !important;
571
+ }
572
+
573
+ .table-container th:nth-child(3),
574
+ .table-container td:nth-child(3) {
575
+ width: 120px !important;
576
+ min-width: 100px !important;
577
+ max-width: 140px !important;
578
+ }
579
+
580
+ /* Game score columns */
581
+ .table-container th:nth-child(n+4),
582
+ .table-container td:nth-child(n+4) {
583
+ width: 120px !important;
584
+ min-width: 100px !important;
585
+ max-width: 140px !important;
586
+ text-align: center !important;
587
+ }
588
+
589
+ /* Make headers sticky */
590
+ .table-container th {
591
+ position: sticky !important;
592
+ top: 0 !important;
593
+ background-color: #f8f9fa !important;
594
+ z-index: 10 !important;
595
+ font-weight: bold;
596
+ padding: 16px 10px !important;
597
+ border-bottom: 2px solid #e9ecef;
598
+ white-space: pre-wrap !important;
599
+ word-wrap: break-word !important;
600
+ line-height: 1.2 !important;
601
+ height: auto !important;
602
+ min-height: 60px !important;
603
+ vertical-align: middle !important;
604
+ }
605
+
606
+ /* Simple cell styling */
607
+ .table-container td {
608
+ padding: 8px 8px;
609
+ border-bottom: 1px solid #e9ecef;
610
  }
611
+
612
+ /* Visual enhancements */
613
+ .table-container tr:hover {
614
+ background-color: #f1f3f4;
615
+ }
616
+
617
+ .table-container tr:nth-child(even) {
618
+ background-color: #f8fafc;
619
+ }
620
+
621
+ /* Row number column styling */
622
+ .gradio-dataframe thead tr th[id="0"],
623
+ .gradio-dataframe tbody tr td:nth-child(1),
624
+ [data-testid="dataframe"] thead tr th[id="0"],
625
+ [data-testid="dataframe"] tbody tr td:nth-child(1),
626
+ .svelte-1gfkn6j thead tr th:first-child,
627
+ .svelte-1gfkn6j tbody tr td:first-child {
628
+ width: 40px !important;
629
+ min-width: 40px !important;
630
+ max-width: 40px !important;
631
+ padding: 4px !important;
632
+ text-align: center !important;
633
+ font-size: 0.85em !important;
634
  }
635
  """) as demo:
636
  gr.Markdown("# 🎮 Game Arena: Gaming Agent 🎲")
637
 
638
  with gr.Tabs():
639
  with gr.Tab("🏆 Leaderboard"):
640
+ # Visualization section
641
  with gr.Row():
642
  gr.Markdown("### 📊 Data Visualization")
643
+
644
+ # Detailed view visualization (single chart)
645
+ detailed_visualization = gr.Plot(
646
+ label="Performance Visualization",
647
+ visible=False,
648
+ elem_classes="visualization-container"
649
+ )
650
+
651
+ with gr.Column(visible=True) as overall_visualizations:
652
+ with gr.Tabs():
653
+ with gr.Tab("📈 Radar Chart"):
654
+ radar_visualization = gr.Plot(
655
+ label="Comparative Analysis (Radar Chart)",
656
+ elem_classes="visualization-container"
657
+ )
658
+ # Comment out the Group Bar Chart tab
659
+ # with gr.Tab("📊 Group Bar Chart"):
660
+ # group_bar_visualization = gr.Plot(
661
+ # label="Comparative Analysis (Group Bar Chart)",
662
+ # elem_classes="visualization-container"
663
+ # )
664
+
665
+ # Hidden placeholder for group bar visualization (to maintain code references)
666
+ group_bar_visualization = gr.Plot(visible=False)
667
 
668
  # Game selection section
669
  with gr.Row():
670
  gr.Markdown("### 🎮 Game Selection")
671
  with gr.Row():
 
672
  with gr.Column():
673
  gr.Markdown("**🎮 Super Mario Bros**")
674
  mario_overall = gr.Checkbox(label="Super Mario Bros Score", value=True)
 
693
  gr.Markdown("**📋 Tetris (planning)**")
694
  tetris_plan_overall = gr.Checkbox(label="Tetris (planning) Score", value=True)
695
  tetris_plan_details = gr.Checkbox(label="Tetris (planning) Details", value=False)
696
+
697
+ # Controls
698
  with gr.Row():
699
  with gr.Column(scale=2):
700
  gr.Markdown("**⏰ Time Tracker**")
 
702
  with gr.Column(scale=1):
703
  gr.Markdown("**🔄 Controls**")
704
  clear_btn = gr.Button("Reset Filters", variant="secondary")
705
+
706
+ # Leaderboard table
707
  with gr.Row():
708
  gr.Markdown("### 📋 Detailed Results")
709
+
710
+ # Add reference to Jupyter notebook
711
+ with gr.Row():
712
+ gr.Markdown("*All data analysis can be replicated by checking [this Jupyter notebook](https://colab.research.google.com/drive/1yoa3nZpAtmzZqPD6V-rnPQG7wI4nbt40#scrollTo=ac7EVIaJTxpp)*")
713
+
714
+ # Get initial leaderboard dataframe
715
+ initial_df = get_combined_leaderboard(rank_data, {
716
+ "Super Mario Bros": True,
717
+ "Sokoban": True,
718
+ "2048": True,
719
+ "Candy Crash": True,
720
+ "Tetris (complete)": True,
721
+ "Tetris (planning only)": True
722
+ })
723
+
724
+ # Format the DataFrame for display
725
+ initial_display_df = prepare_dataframe_for_display(initial_df)
726
+
727
+ # Custom column widths including row numbers
728
+ col_widths = ["40px"] # Row number column width
729
+ col_widths.append("230px") # Player column - reduced by 20px
730
+ col_widths.append("120px") # Organization column
731
+ # Add game score columns
732
+ for _ in range(len(initial_display_df.columns) - 2):
733
+ col_widths.append("120px")
734
+
735
+ # Create a standard DataFrame component with enhanced styling
736
  with gr.Row():
737
+ leaderboard_df = gr.DataFrame(
738
+ value=initial_display_df,
 
 
 
 
 
 
 
739
  interactive=True,
740
+ elem_id="leaderboard-table",
741
+ elem_classes="table-container",
742
  wrap=True,
743
+ show_row_numbers=True,
744
+ show_fullscreen_button=True,
745
+ line_breaks=True,
746
+ max_height=None, # Remove height limitation to avoid scrollbar
747
+ show_search="search",
748
+ column_widths=col_widths
749
  )
750
+
751
+ # Add the score note below the table
752
+ with gr.Row():
753
+ score_note = add_score_note()
754
+
755
+ # List of all checkboxes
756
+ checkbox_list = [
757
+ mario_overall, mario_details,
758
+ sokoban_overall, sokoban_details,
759
+ _2048_overall, _2048_details,
760
+ candy_overall, candy_details,
761
+ tetris_overall, tetris_details,
762
+ tetris_plan_overall, tetris_plan_details
763
+ ]
764
+
765
+ # Update visualizations when checkboxes change
766
+ def update_visualizations(*checkbox_states):
767
+ # Check if any details checkbox is selected
768
+ is_details_view = any([
769
+ checkbox_states[1], checkbox_states[3], checkbox_states[5],
770
+ checkbox_states[7], checkbox_states[9], checkbox_states[11]
771
+ ])
772
+
773
+ # Update visibility of visualization blocks
774
+ return {
775
+ detailed_visualization: gr.update(visible=is_details_view),
776
+ overall_visualizations: gr.update(visible=not is_details_view)
777
+ }
778
+
779
+ # Add change event to all checkboxes
780
  for checkbox in checkbox_list:
781
  checkbox.change(
782
+ update_visualizations,
783
  inputs=checkbox_list,
784
+ outputs=[detailed_visualization, overall_visualizations]
785
  )
786
+
787
+ # Update leaderboard and visualizations when checkboxes change
788
+ for checkbox in checkbox_list:
789
+ checkbox.change(
790
+ update_leaderboard,
791
+ inputs=checkbox_list,
792
+ outputs=[
793
+ leaderboard_df,
794
+ detailed_visualization,
795
+ radar_visualization,
796
+ group_bar_visualization
797
+ ] + checkbox_list
798
+ )
799
+
800
+ # Update when clear button is clicked
801
  clear_btn.click(
802
+ clear_filters,
803
+ inputs=[],
804
+ outputs=[
805
+ leaderboard_df,
806
+ detailed_visualization,
807
+ radar_visualization,
808
+ group_bar_visualization
809
+ ] + checkbox_list
810
+ )
811
+
812
+ # Initialize the app
813
+ demo.load(
814
  fn=clear_filters,
815
  inputs=[],
816
+ outputs=[
817
+ leaderboard_df,
818
+ detailed_visualization,
819
+ radar_visualization,
820
+ group_bar_visualization
821
+ ] + checkbox_list
822
  )
823
+
824
  with gr.Tab("🎥 Gallery"):
825
  video_gallery = create_video_gallery()
826
+
827
  return demo
828
 
829
  if __name__ == "__main__":
830
  demo_app = build_app()
831
  # Add file serving configuration
832
+ demo_app.launch(
833
+ debug=True,
834
+ show_error=True,
835
+ share=True,
836
+ height="100%",
837
+ width="100%"
838
+ )
leaderboard_tab.py ADDED
@@ -0,0 +1,600 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ from leaderboard_utils import (
4
+ get_mario_leaderboard,
5
+ get_sokoban_leaderboard,
6
+ get_2048_leaderboard,
7
+ get_candy_leaderboard,
8
+ get_tetris_leaderboard,
9
+ get_tetris_planning_leaderboard,
10
+ get_combined_leaderboard,
11
+ GAME_ORDER
12
+ )
13
+ from data_visualization import (
14
+ get_combined_leaderboard_with_group_bar,
15
+ create_horizontal_bar_chart,
16
+ get_combined_leaderboard_with_single_radar
17
+ )
18
+ import pandas as pd
19
+
20
+ # Define time points and their corresponding data files
21
+ TIME_POINTS = {
22
+ "03/25/2025": "rank_data_03_25_2025.json",
23
+ # Add more time points here as they become available
24
+ }
25
+
26
+ # Load the initial JSON file with rank data
27
+ with open(TIME_POINTS["03/25/2025"], "r") as f:
28
+ rank_data = json.load(f)
29
+
30
+ # Add leaderboard state at the top level
31
+ leaderboard_state = {
32
+ "current_game": None,
33
+ "previous_overall": {
34
+ "Super Mario Bros": True,
35
+ "Sokoban": True,
36
+ "2048": True,
37
+ "Candy Crash": True,
38
+ "Tetris (complete)": True,
39
+ "Tetris (planning only)": True
40
+ },
41
+ "previous_details": {
42
+ "Super Mario Bros": False,
43
+ "Sokoban": False,
44
+ "2048": False,
45
+ "Candy Crash": False,
46
+ "Tetris (complete)": False,
47
+ "Tetris (planning only)": False
48
+ }
49
+ }
50
+
51
+ def load_rank_data(time_point):
52
+ """Load rank data for a specific time point"""
53
+ if time_point in TIME_POINTS:
54
+ try:
55
+ with open(TIME_POINTS[time_point], "r") as f:
56
+ return json.load(f)
57
+ except FileNotFoundError:
58
+ return None
59
+ return None
60
+
61
+ def update_leaderboard(mario_overall, mario_details,
62
+ sokoban_overall, sokoban_details,
63
+ _2048_overall, _2048_details,
64
+ candy_overall, candy_details,
65
+ tetris_overall, tetris_details,
66
+ tetris_plan_overall, tetris_plan_details):
67
+ global leaderboard_state
68
+
69
+ # Convert current checkbox states to dictionary for easier comparison
70
+ current_overall = {
71
+ "Super Mario Bros": mario_overall,
72
+ "Sokoban": sokoban_overall,
73
+ "2048": _2048_overall,
74
+ "Candy Crash": candy_overall,
75
+ "Tetris (complete)": tetris_overall,
76
+ "Tetris (planning only)": tetris_plan_overall
77
+ }
78
+
79
+ current_details = {
80
+ "Super Mario Bros": mario_details,
81
+ "Sokoban": sokoban_details,
82
+ "2048": _2048_details,
83
+ "Candy Crash": candy_details,
84
+ "Tetris (complete)": tetris_details,
85
+ "Tetris (planning only)": tetris_plan_details
86
+ }
87
+
88
+ # Find which game's state changed
89
+ changed_game = None
90
+ for game in current_overall.keys():
91
+ if (current_overall[game] != leaderboard_state["previous_overall"][game] or
92
+ current_details[game] != leaderboard_state["previous_details"][game]):
93
+ changed_game = game
94
+ break
95
+
96
+ if changed_game:
97
+ # If a game's details checkbox was checked
98
+ if current_details[changed_game] and not leaderboard_state["previous_details"][changed_game]:
99
+ # Reset all other games' states
100
+ for game in current_overall.keys():
101
+ if game != changed_game:
102
+ current_overall[game] = False
103
+ current_details[game] = False
104
+ leaderboard_state["previous_overall"][game] = False
105
+ leaderboard_state["previous_details"][game] = False
106
+
107
+ # Update state for the selected game
108
+ leaderboard_state["current_game"] = changed_game
109
+ leaderboard_state["previous_overall"][changed_game] = True
110
+ leaderboard_state["previous_details"][changed_game] = True
111
+ current_overall[changed_game] = True
112
+
113
+ # If a game's overall checkbox was checked
114
+ elif current_overall[changed_game] and not leaderboard_state["previous_overall"][changed_game]:
115
+ # If we were in details view for another game, switch to overall view
116
+ if leaderboard_state["current_game"] and leaderboard_state["previous_details"][leaderboard_state["current_game"]]:
117
+ # Reset previous game's details
118
+ leaderboard_state["previous_details"][leaderboard_state["current_game"]] = False
119
+ current_details[leaderboard_state["current_game"]] = False
120
+ leaderboard_state["current_game"] = None
121
+
122
+ # Update state
123
+ leaderboard_state["previous_overall"][changed_game] = True
124
+ leaderboard_state["previous_details"][changed_game] = False
125
+
126
+ # If a game's overall checkbox was unchecked
127
+ elif not current_overall[changed_game] and leaderboard_state["previous_overall"][changed_game]:
128
+ # If we're in details view, don't allow unchecking the overall checkbox
129
+ if leaderboard_state["current_game"] == changed_game:
130
+ current_overall[changed_game] = True
131
+ else:
132
+ leaderboard_state["previous_overall"][changed_game] = False
133
+ if leaderboard_state["current_game"] == changed_game:
134
+ leaderboard_state["current_game"] = None
135
+
136
+ # If a game's details checkbox was unchecked
137
+ elif not current_details[changed_game] and leaderboard_state["previous_details"][changed_game]:
138
+ leaderboard_state["previous_details"][changed_game] = False
139
+ if leaderboard_state["current_game"] == changed_game:
140
+ leaderboard_state["current_game"] = None
141
+ # When exiting details view, reset to show all games
142
+ for game in current_overall.keys():
143
+ current_overall[game] = True
144
+ current_details[game] = False
145
+ leaderboard_state["previous_overall"][game] = True
146
+ leaderboard_state["previous_details"][game] = False
147
+
148
+ # Special case: If all games are selected and we're trying to view details
149
+ all_games_selected = all(current_overall.values()) and not any(current_details.values())
150
+ if all_games_selected and changed_game and current_details[changed_game]:
151
+ # Reset all other games' states
152
+ for game in current_overall.keys():
153
+ if game != changed_game:
154
+ current_overall[game] = False
155
+ current_details[game] = False
156
+ leaderboard_state["previous_overall"][game] = False
157
+ leaderboard_state["previous_details"][game] = False
158
+
159
+ # Update state for the selected game
160
+ leaderboard_state["current_game"] = changed_game
161
+ leaderboard_state["previous_overall"][changed_game] = True
162
+ leaderboard_state["previous_details"][changed_game] = True
163
+ current_overall[changed_game] = True
164
+
165
+ # Build dictionary for selected games
166
+ selected_games = {
167
+ "Super Mario Bros": current_overall["Super Mario Bros"],
168
+ "Sokoban": current_overall["Sokoban"],
169
+ "2048": current_overall["2048"],
170
+ "Candy Crash": current_overall["Candy Crash"],
171
+ "Tetris (complete)": current_overall["Tetris (complete)"],
172
+ "Tetris (planning only)": current_overall["Tetris (planning only)"]
173
+ }
174
+
175
+ # Get the appropriate DataFrame and charts based on current state
176
+ if leaderboard_state["current_game"]:
177
+ # For detailed view
178
+ if leaderboard_state["current_game"] == "Super Mario Bros":
179
+ df = get_mario_leaderboard(rank_data)
180
+ elif leaderboard_state["current_game"] == "Sokoban":
181
+ df = get_sokoban_leaderboard(rank_data)
182
+ elif leaderboard_state["current_game"] == "2048":
183
+ df = get_2048_leaderboard(rank_data)
184
+ elif leaderboard_state["current_game"] == "Candy Crash":
185
+ df = get_candy_leaderboard(rank_data)
186
+ elif leaderboard_state["current_game"] == "Tetris (complete)":
187
+ df = get_tetris_leaderboard(rank_data)
188
+ else: # Tetris (planning only)
189
+ df = get_tetris_planning_leaderboard(rank_data)
190
+
191
+ # Always create a new chart for detailed view
192
+ chart = create_horizontal_bar_chart(df, leaderboard_state["current_game"])
193
+ # For detailed view, we'll use the same chart for all visualizations
194
+ radar_chart = chart
195
+ group_bar_chart = chart
196
+ else:
197
+ # For overall view
198
+ df, group_bar_chart = get_combined_leaderboard_with_group_bar(rank_data, selected_games)
199
+ # Use the same selected_games for radar chart
200
+ _, radar_chart = get_combined_leaderboard_with_single_radar(rank_data, selected_games)
201
+ chart = group_bar_chart
202
+
203
+ # Return exactly 16 values to match the expected outputs
204
+ return (df, chart, radar_chart, group_bar_chart,
205
+ current_overall["Super Mario Bros"], current_details["Super Mario Bros"],
206
+ current_overall["Sokoban"], current_details["Sokoban"],
207
+ current_overall["2048"], current_details["2048"],
208
+ current_overall["Candy Crash"], current_details["Candy Crash"],
209
+ current_overall["Tetris (complete)"], current_details["Tetris (complete)"],
210
+ current_overall["Tetris (planning only)"], current_details["Tetris (planning only)"])
211
+
212
+ def update_leaderboard_with_time(time_point, mario_overall, mario_details,
213
+ sokoban_overall, sokoban_details,
214
+ _2048_overall, _2048_details,
215
+ candy_overall, candy_details,
216
+ tetris_overall, tetris_details,
217
+ tetris_plan_overall, tetris_plan_details):
218
+ # Load rank data for the selected time point
219
+ global rank_data
220
+ new_rank_data = load_rank_data(time_point)
221
+ if new_rank_data is not None:
222
+ rank_data = new_rank_data
223
+
224
+ # Use the existing update_leaderboard function
225
+ return update_leaderboard(mario_overall, mario_details,
226
+ sokoban_overall, sokoban_details,
227
+ _2048_overall, _2048_details,
228
+ candy_overall, candy_details,
229
+ tetris_overall, tetris_details,
230
+ tetris_plan_overall, tetris_plan_details)
231
+
232
+ def get_initial_state():
233
+ """Get the initial state for the leaderboard"""
234
+ return {
235
+ "current_game": None,
236
+ "previous_overall": {
237
+ "Super Mario Bros": True,
238
+ "Sokoban": True,
239
+ "2048": True,
240
+ "Candy Crash": True,
241
+ "Tetris (complete)": True,
242
+ "Tetris (planning only)": True
243
+ },
244
+ "previous_details": {
245
+ "Super Mario Bros": False,
246
+ "Sokoban": False,
247
+ "2048": False,
248
+ "Candy Crash": False,
249
+ "Tetris (complete)": False,
250
+ "Tetris (planning only)": False
251
+ }
252
+ }
253
+
254
+ def clear_filters():
255
+ global leaderboard_state
256
+
257
+ # Reset all checkboxes to default state
258
+ selected_games = {
259
+ "Super Mario Bros": True,
260
+ "Sokoban": True,
261
+ "2048": True,
262
+ "Candy Crash": True,
263
+ "Tetris (complete)": True,
264
+ "Tetris (planning only)": True
265
+ }
266
+
267
+ # Get the combined leaderboard and group bar chart
268
+ df, group_bar_chart = get_combined_leaderboard_with_group_bar(rank_data, selected_games)
269
+
270
+ # Get the radar chart using the same selected games
271
+ _, radar_chart = get_combined_leaderboard_with_single_radar(rank_data, selected_games)
272
+
273
+ # Reset the leaderboard state to match the default checkbox states
274
+ leaderboard_state = get_initial_state()
275
+
276
+ # Return exactly 16 values to match the expected outputs
277
+ return (df, group_bar_chart, radar_chart, group_bar_chart,
278
+ True, False, # mario
279
+ True, False, # sokoban
280
+ True, False, # 2048
281
+ True, False, # candy
282
+ True, False, # tetris
283
+ True, False) # tetris plan
284
+
285
+ def create_timeline_slider():
286
+ """Create a custom timeline slider component"""
287
+ timeline_html = """
288
+ <div class="timeline-container">
289
+ <style>
290
+ .timeline-container {
291
+ width: 85%; /* Increased from 70% to 85% */
292
+ padding: 8px;
293
+ font-family: Arial, sans-serif;
294
+ height: 40px;
295
+ display: flex;
296
+ align-items: center;
297
+ }
298
+ .timeline-track {
299
+ position: relative;
300
+ height: 6px;
301
+ background: #e0e0e0;
302
+ border-radius: 3px;
303
+ margin: 0;
304
+ width: 100%;
305
+ }
306
+ .timeline-progress {
307
+ position: absolute;
308
+ height: 100%;
309
+ background: #2196F3;
310
+ border-radius: 3px;
311
+ width: 100%;
312
+ }
313
+ .timeline-handle {
314
+ position: absolute;
315
+ right: 0;
316
+ top: 50%;
317
+ transform: translate(50%, -50%);
318
+ width: 20px;
319
+ height: 20px;
320
+ background: #2196F3;
321
+ border: 3px solid white;
322
+ border-radius: 50%;
323
+ cursor: pointer;
324
+ box-shadow: 0 2px 6px rgba(0,0,0,0.3);
325
+ }
326
+ .timeline-date {
327
+ position: absolute;
328
+ top: -25px;
329
+ transform: translateX(-50%);
330
+ background: #2196F3; /* Changed to match slider blue color */
331
+ color: #ffffff !important;
332
+ padding: 3px 8px;
333
+ border-radius: 4px;
334
+ font-size: 12px;
335
+ white-space: nowrap;
336
+ font-weight: 600;
337
+ box-shadow: 0 2px 6px rgba(0,0,0,0.2);
338
+ letter-spacing: 0.5px;
339
+ text-shadow: 0 1px 2px rgba(0,0,0,0.2);
340
+ }
341
+ </style>
342
+ <div class="timeline-track">
343
+ <div class="timeline-progress"></div>
344
+ <div class="timeline-handle">
345
+ <div class="timeline-date">03/25/2025</div>
346
+ </div>
347
+ </div>
348
+ </div>
349
+ <script>
350
+ (function() {
351
+ const container = document.querySelector('.timeline-container');
352
+ const track = container.querySelector('.timeline-track');
353
+ const handle = container.querySelector('.timeline-handle');
354
+ let isDragging = false;
355
+
356
+ // For now, we only have one time point
357
+ const timePoints = {
358
+ "03/25/2025": 1.0
359
+ };
360
+
361
+ function updatePosition(e) {
362
+ if (!isDragging) return;
363
+
364
+ const rect = track.getBoundingClientRect();
365
+ let x = (e.clientX - rect.left) / rect.width;
366
+ x = Math.max(0, Math.min(1, x));
367
+
368
+ // For now, snap to the only available time point
369
+ x = 1.0;
370
+
371
+ handle.style.right = `${(1 - x) * 100}%`;
372
+ }
373
+
374
+ handle.addEventListener('mousedown', (e) => {
375
+ isDragging = true;
376
+ e.preventDefault();
377
+ });
378
+
379
+ document.addEventListener('mousemove', updatePosition);
380
+ document.addEventListener('mouseup', () => {
381
+ isDragging = false;
382
+ });
383
+
384
+ // Prevent text selection while dragging
385
+ container.addEventListener('selectstart', (e) => {
386
+ if (isDragging) e.preventDefault();
387
+ });
388
+ })();
389
+ </script>
390
+ """
391
+ return gr.HTML(timeline_html)
392
+
393
+ def create_leaderboard_tab():
394
+ """Create and return the leaderboard tab component"""
395
+ with gr.Tab("🏆 Leaderboard") as leaderboard_tab:
396
+ # Leaderboard header
397
+ with gr.Row():
398
+ gr.Markdown("### 📊 Leaderboard Overview")
399
+
400
+ # Get initial data
401
+ df = get_combined_leaderboard(rank_data, {game: True for game in GAME_ORDER})
402
+
403
+ # Create interactive DataFrame component
404
+ leaderboard_df = gr.DataFrame(
405
+ value=df,
406
+ label="Leaderboard",
407
+ interactive=True, # Enable sorting and filtering
408
+ wrap=True, # Enable text wrapping
409
+ column_widths=["200px", "150px"] + ["100px"] * len(GAME_ORDER), # Set column widths
410
+ headers=["Model", "Organization"] + GAME_ORDER, # Set column headers
411
+ datatype=["str", "str"] + ["number"] * len(GAME_ORDER) # Set column types
412
+ )
413
+
414
+ # Game selection section
415
+ with gr.Row():
416
+ gr.Markdown("### 🎮 Game Selection")
417
+ with gr.Row():
418
+ with gr.Column():
419
+ gr.Markdown("**🎮 Super Mario Bros**")
420
+ mario_overall = gr.Checkbox(label="Super Mario Bros Score", value=True)
421
+ mario_details = gr.Checkbox(label="Super Mario Bros Details", value=False)
422
+ with gr.Column():
423
+ gr.Markdown("**📦 Sokoban**")
424
+ sokoban_overall = gr.Checkbox(label="Sokoban Score", value=True)
425
+ sokoban_details = gr.Checkbox(label="Sokoban Details", value=False)
426
+ with gr.Column():
427
+ gr.Markdown("**🔢 2048**")
428
+ _2048_overall = gr.Checkbox(label="2048 Score", value=True)
429
+ _2048_details = gr.Checkbox(label="2048 Details", value=False)
430
+ with gr.Column():
431
+ gr.Markdown("**🍬 Candy Crash**")
432
+ candy_overall = gr.Checkbox(label="Candy Crash Score", value=True)
433
+ candy_details = gr.Checkbox(label="Candy Crash Details", value=False)
434
+ with gr.Column():
435
+ gr.Markdown("**🎯 Tetris (complete)**")
436
+ tetris_overall = gr.Checkbox(label="Tetris (complete) Score", value=True)
437
+ tetris_details = gr.Checkbox(label="Tetris (complete) Details", value=False)
438
+ with gr.Column():
439
+ gr.Markdown("**📋 Tetris (planning)**")
440
+ tetris_plan_overall = gr.Checkbox(label="Tetris (planning) Score", value=True)
441
+ tetris_plan_details = gr.Checkbox(label="Tetris (planning) Details", value=False)
442
+
443
+ # Controls
444
+ with gr.Row():
445
+ with gr.Column(scale=2):
446
+ gr.Markdown("**⏰ Time Tracker**")
447
+ timeline = create_timeline_slider()
448
+ with gr.Column(scale=1):
449
+ gr.Markdown("**🔄 Controls**")
450
+ clear_btn = gr.Button("Reset Filters", variant="secondary")
451
+
452
+ # List of all checkboxes
453
+ checkbox_list = [
454
+ mario_overall, mario_details,
455
+ sokoban_overall, sokoban_details,
456
+ _2048_overall, _2048_details,
457
+ candy_overall, candy_details,
458
+ tetris_overall, tetris_details,
459
+ tetris_plan_overall, tetris_plan_details
460
+ ]
461
+
462
+ def update_leaderboard(*checkbox_states):
463
+ # Convert checkbox states to selected games dictionary
464
+ selected_games = {
465
+ "Super Mario Bros": checkbox_states[0],
466
+ "Sokoban": checkbox_states[2],
467
+ "2048": checkbox_states[4],
468
+ "Candy Crash": checkbox_states[6],
469
+ "Tetris (complete)": checkbox_states[8],
470
+ "Tetris (planning only)": checkbox_states[10]
471
+ }
472
+
473
+ # Get updated DataFrame
474
+ df = get_combined_leaderboard(rank_data, selected_games)
475
+
476
+ # Format scores
477
+ for game in GAME_ORDER:
478
+ score_col = f"{game} Score"
479
+ if score_col in df.columns:
480
+ df[score_col] = df[score_col].apply(lambda x: float(x) if x != '_' else 0)
481
+
482
+ return df
483
+
484
+ # Update leaderboard when checkboxes change
485
+ for checkbox in checkbox_list:
486
+ checkbox.change(
487
+ update_leaderboard,
488
+ inputs=checkbox_list,
489
+ outputs=[leaderboard_df]
490
+ )
491
+
492
+ # Reset filters when clear button is clicked
493
+ def reset_filters():
494
+ # Reset all checkboxes to default state
495
+ checkbox_states = [True, False] * len(GAME_ORDER)
496
+ # Get DataFrame with all games selected
497
+ df = get_combined_leaderboard(rank_data, {game: True for game in GAME_ORDER})
498
+ return [df] + checkbox_states
499
+
500
+ clear_btn.click(
501
+ reset_filters,
502
+ inputs=[],
503
+ outputs=[leaderboard_df] + checkbox_list
504
+ )
505
+
506
+ return leaderboard_tab
507
+
508
+ def make_leaderboard_md(df, last_updated_time):
509
+ """
510
+ Create markdown for the gaming leaderboard
511
+ """
512
+ total_models = len(df)
513
+ space = "&nbsp;&nbsp;&nbsp;"
514
+
515
+ # Calculate total games played
516
+ total_games = sum(1 for col in df.columns if col.endswith(' Score'))
517
+
518
+ leaderboard_md = f"""
519
+ # 🎮 Gaming Performance Leaderboard
520
+ Total #models: **{total_models}**.{space} Total #games: **{total_games}**.{space} Last updated: {last_updated_time}.
521
+ """
522
+ return leaderboard_md
523
+
524
+ def make_category_leaderboard_md(df, game_name):
525
+ """
526
+ Create markdown for a specific game category
527
+ """
528
+ # Filter for models that participated in this game
529
+ score_col = f"{game_name} Score"
530
+ game_df = df[df[score_col] != '_']
531
+ total_models = len(game_df)
532
+
533
+ # Calculate average score
534
+ avg_score = game_df[score_col].astype(float).mean()
535
+
536
+ space = "&nbsp;&nbsp;&nbsp;"
537
+ leaderboard_md = f"""
538
+ ### {game_name}
539
+ #### {space} #models: **{total_models}** {space} Average Score: **{avg_score:.1f}**{space}
540
+ """
541
+ return leaderboard_md
542
+
543
+ def make_full_leaderboard_md():
544
+ """
545
+ Create markdown explaining the leaderboard metrics
546
+ """
547
+ leaderboard_md = """
548
+ The leaderboard displays performance across multiple games:
549
+ - **Super Mario Bros**: Platform game performance
550
+ - **Sokoban**: Puzzle-solving ability
551
+ - **2048**: Number puzzle game
552
+ - **Candy Crash**: Matching game
553
+ - **Tetris**: Classic block-stacking game
554
+
555
+ Scores are normalized within each game for fair comparison. Higher values indicate better performance.
556
+ """
557
+ return leaderboard_md
558
+
559
+ def create_leaderboard_table(df):
560
+ """
561
+ Create a formatted table of the leaderboard
562
+ """
563
+ # Select relevant columns
564
+ columns = ['Player', 'Organization']
565
+ for game in GAME_ORDER:
566
+ columns.append(f"{game} Score")
567
+
568
+ # Create table
569
+ table = df[columns].copy()
570
+
571
+ # Format scores
572
+ for game in GAME_ORDER:
573
+ score_col = f"{game} Score"
574
+ table[score_col] = table[score_col].apply(lambda x: f"{float(x):.1f}" if x != '_' else '-')
575
+
576
+ return table
577
+
578
+ def update_leaderboard(rank_data, selected_games):
579
+ """
580
+ Update the leaderboard with new data
581
+ """
582
+ # Get the combined leaderboard data
583
+ df = get_combined_leaderboard(rank_data, selected_games)
584
+
585
+ # Create markdown sections
586
+ last_updated = pd.Timestamp.now().strftime("%Y-%m-%d %H:%M:%S")
587
+ leaderboard_md = make_leaderboard_md(df, last_updated)
588
+
589
+ # Add category sections
590
+ for game in GAME_ORDER:
591
+ if selected_games.get(game, False):
592
+ leaderboard_md += make_category_leaderboard_md(df, game)
593
+
594
+ # Add explanation
595
+ leaderboard_md += make_full_leaderboard_md()
596
+
597
+ # Create table
598
+ table = create_leaderboard_table(df)
599
+
600
+ return leaderboard_md, table
leaderboard_utils.py CHANGED
@@ -22,6 +22,8 @@ def get_organization(model_name):
22
  return "openai"
23
  elif "deepseek" in m:
24
  return "deepseek"
 
 
25
  else:
26
  return "unknown"
27
 
@@ -173,7 +175,7 @@ def calculate_rank_and_completeness(rank_data, selected_games):
173
  ranks.append(rank)
174
  player_data[f"{game} Score"] = player_score
175
  else:
176
- player_data[f"{game} Score"] = "_"
177
 
178
  # Calculate average rank and completeness for sorting only
179
  if ranks:
@@ -262,7 +264,7 @@ def get_combined_leaderboard(rank_data, selected_games):
262
  elif game in ["Tetris (complete)", "Tetris (planning only)"]:
263
  player_data[f"{game} Score"] = df[df["Player"] == player]["Score"].iloc[0]
264
  else:
265
- player_data[f"{game} Score"] = "_"
266
 
267
  results.append(player_data)
268
 
@@ -276,7 +278,7 @@ def get_combined_leaderboard(rank_data, selected_games):
276
  for game in GAME_ORDER:
277
  if f"{game} Score" in df_results.columns:
278
  df_results["Total Score"] += df_results[f"{game} Score"].apply(
279
- lambda x: float(x) if x != "_" else 0
280
  )
281
 
282
  # Sort by total score in descending order
 
22
  return "openai"
23
  elif "deepseek" in m:
24
  return "deepseek"
25
+ elif "llama" in m:
26
+ return "meta"
27
  else:
28
  return "unknown"
29
 
 
175
  ranks.append(rank)
176
  player_data[f"{game} Score"] = player_score
177
  else:
178
+ player_data[f"{game} Score"] = -1
179
 
180
  # Calculate average rank and completeness for sorting only
181
  if ranks:
 
264
  elif game in ["Tetris (complete)", "Tetris (planning only)"]:
265
  player_data[f"{game} Score"] = df[df["Player"] == player]["Score"].iloc[0]
266
  else:
267
+ player_data[f"{game} Score"] = -1
268
 
269
  results.append(player_data)
270
 
 
278
  for game in GAME_ORDER:
279
  if f"{game} Score" in df_results.columns:
280
  df_results["Total Score"] += df_results[f"{game} Score"].apply(
281
+ lambda x: float(x) if x != -1 else 0
282
  )
283
 
284
  # Sort by total score in descending order