Yuxuan-Zhang-Dexter commited on
Commit
5a80058
·
1 Parent(s): e974647

update app.py

Browse files
Files changed (5) hide show
  1. app.py +186 -333
  2. data_visualization.py +388 -660
  3. gallery_tab.py +255 -0
  4. leaderboard_tab.py +600 -0
  5. leaderboard_utils.py +5 -3
app.py CHANGED
@@ -28,6 +28,14 @@ from data_visualization import (
28
  normalize_values,
29
  get_combined_leaderboard_with_single_radar
30
  )
 
 
 
 
 
 
 
 
31
 
32
  # Define time points and their corresponding data files
33
  TIME_POINTS = {
@@ -60,25 +68,6 @@ leaderboard_state = {
60
  }
61
  }
62
 
63
- # Define GIF paths for the carousel
64
- GIF_PATHS = [
65
- "assets/super_mario_bros/super_mario.gif",
66
- "assets/sokoban/sokoban.gif",
67
- "assets/2048/2048.gif",
68
- "assets/candy/candy.gif",
69
- "assets/tetris/tetris.gif"
70
- ]
71
-
72
- # Print and verify GIF paths
73
- print("\nChecking GIF paths:")
74
- for gif_path in GIF_PATHS:
75
- if os.path.exists(gif_path):
76
- print(f"✓ Found: {gif_path}")
77
- # Print file size
78
- size = os.path.getsize(gif_path)
79
- print(f" Size: {size / (1024*1024):.2f} MB")
80
- else:
81
- print(f"✗ Missing: {gif_path}")
82
 
83
  # Load video links and news data
84
  with open('assets/game_video_link.json', 'r') as f:
@@ -87,42 +76,6 @@ with open('assets/game_video_link.json', 'r') as f:
87
  with open('assets/news.json', 'r') as f:
88
  NEWS_DATA = json.load(f)
89
 
90
- def load_gif(gif_path):
91
- """Load a GIF file and return it as a PIL Image"""
92
- try:
93
- img = Image.open(gif_path)
94
- print(f"Successfully loaded GIF: {gif_path}")
95
- return img
96
- except Exception as e:
97
- print(f"Error loading GIF {gif_path}: {e}")
98
- return None
99
-
100
- def create_gif_carousel():
101
- """Create a custom HTML/JS component for GIF carousel"""
102
- print("\nCreating GIF carousel with paths:", GIF_PATHS)
103
- html = f"""
104
- <div id="gif-carousel" style="width: 100%; height: 300px; position: relative; background-color: #f0f0f0;">
105
- <img id="current-gif" style="width: 100%; height: 100%; object-fit: contain;" onerror="console.error('Failed to load GIF:', this.src);">
106
- </div>
107
- <script>
108
- const gifs = {json.dumps(GIF_PATHS)};
109
- let currentIndex = 0;
110
-
111
- function updateGif() {{
112
- const img = document.getElementById('current-gif');
113
- console.log('Loading GIF:', gifs[currentIndex]);
114
- img.src = gifs[currentIndex];
115
- currentIndex = (currentIndex + 1) % gifs.length;
116
- }}
117
-
118
- // Update GIF every 5 seconds
119
- setInterval(updateGif, 5000);
120
- // Initial load
121
- updateGif();
122
- </script>
123
- """
124
- return gr.HTML(html)
125
-
126
  def load_rank_data(time_point):
127
  """Load rank data for a specific time point"""
128
  if time_point in TIME_POINTS:
@@ -133,6 +86,43 @@ def load_rank_data(time_point):
133
  return None
134
  return None
135
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  def update_leaderboard(mario_overall, mario_details,
137
  sokoban_overall, sokoban_details,
138
  _2048_overall, _2048_details,
@@ -263,6 +253,9 @@ def update_leaderboard(mario_overall, mario_details,
263
  else: # Tetris (planning only)
264
  df = get_tetris_planning_leaderboard(rank_data)
265
 
 
 
 
266
  # Always create a new chart for detailed view
267
  chart = create_horizontal_bar_chart(df, leaderboard_state["current_game"])
268
  # For detailed view, we'll use the same chart for all visualizations
@@ -271,12 +264,14 @@ def update_leaderboard(mario_overall, mario_details,
271
  else:
272
  # For overall view
273
  df, group_bar_chart = get_combined_leaderboard_with_group_bar(rank_data, selected_games)
 
 
274
  # Use the same selected_games for radar chart
275
  _, radar_chart = get_combined_leaderboard_with_single_radar(rank_data, selected_games)
276
  chart = group_bar_chart
277
 
278
  # Return exactly 16 values to match the expected outputs
279
- return (df, chart, radar_chart, group_bar_chart,
280
  current_overall["Super Mario Bros"], current_details["Super Mario Bros"],
281
  current_overall["Sokoban"], current_details["Sokoban"],
282
  current_overall["2048"], current_details["2048"],
@@ -342,6 +337,9 @@ def clear_filters():
342
  # Get the combined leaderboard and group bar chart
343
  df, group_bar_chart = get_combined_leaderboard_with_group_bar(rank_data, selected_games)
344
 
 
 
 
345
  # Get the radar chart using the same selected games
346
  _, radar_chart = get_combined_leaderboard_with_single_radar(rank_data, selected_games)
347
 
@@ -349,7 +347,7 @@ def clear_filters():
349
  leaderboard_state = get_initial_state()
350
 
351
  # Return exactly 16 values to match the expected outputs
352
- return (df, group_bar_chart, radar_chart, group_bar_chart,
353
  True, False, # mario
354
  True, False, # sokoban
355
  True, False, # 2048
@@ -465,263 +463,19 @@ def create_timeline_slider():
465
  """
466
  return gr.HTML(timeline_html)
467
 
468
- def create_video_gallery():
469
- """Create a custom HTML/JS component for video gallery"""
470
- # Extract video IDs
471
- mario_id = VIDEO_LINKS["super_mario"].split("?v=")[1]
472
- sokoban_id = VIDEO_LINKS["sokoban"].split("?v=")[1]
473
- game_2048_id = VIDEO_LINKS["2048"].split("?v=")[1]
474
- candy_id = VIDEO_LINKS["candy"].split("?v=")[1]
475
-
476
- # Get the latest video from news data
477
- latest_news = NEWS_DATA["news"][0] # First item is the latest
478
- latest_video_id = latest_news["video_link"].split("?v=")[1]
479
- latest_date = datetime.strptime(latest_news["date"], "%Y-%m-%d")
480
- formatted_latest_date = latest_date.strftime("%B %d, %Y")
481
-
482
- # Generate news HTML
483
- news_items = []
484
- for item in NEWS_DATA["news"]:
485
- video_id = item["video_link"].split("?v=")[1]
486
- date_obj = datetime.strptime(item["date"], "%Y-%m-%d")
487
- formatted_date = date_obj.strftime("%B %d, %Y")
488
- news_items.append(f'''
489
- <div class="news-item">
490
- <div class="news-date">{formatted_date}</div>
491
- <div class="news-content">
492
- <div class="news-video">
493
- <div class="video-wrapper">
494
- <iframe src="https://www.youtube.com/embed/{video_id}"></iframe>
495
- </div>
496
- </div>
497
- <div class="news-text">
498
- <a href="{item["twitter_link"]}" target="_blank" class="twitter-link">
499
- <span class="twitter-icon">📢</span>
500
- {item["twitter_text"]}
501
- </a>
502
- </div>
503
- </div>
504
- </div>
505
- ''')
506
-
507
- news_html = '\n'.join(news_items)
508
-
509
- gallery_html = f'''
510
- <div class="video-gallery-container">
511
- <style>
512
- .video-gallery-container {{
513
- width: 100%;
514
- max-width: 1400px;
515
- margin: 0 auto;
516
- padding: 20px;
517
- }}
518
- .highlight-section {{
519
- margin-bottom: 40px;
520
- }}
521
- .highlight-card {{
522
- background: #ffffff;
523
- border-radius: 10px;
524
- box-shadow: 0 4px 20px rgba(0,0,0,0.15);
525
- overflow: hidden;
526
- transition: transform 0.3s;
527
- border: 2px solid #2196F3;
528
- }}
529
- .highlight-card:hover {{
530
- transform: translateY(-5px);
531
- }}
532
- .highlight-header {{
533
- background: #2196F3;
534
- color: white;
535
- padding: 15px 20px;
536
- font-size: 1.2em;
537
- font-weight: bold;
538
- display: flex;
539
- align-items: center;
540
- gap: 10px;
541
- }}
542
- .highlight-date {{
543
- font-size: 0.9em;
544
- opacity: 0.9;
545
- }}
546
- .highlight-content {{
547
- padding: 20px;
548
- }}
549
- .video-grid {{
550
- display: grid;
551
- grid-template-columns: repeat(2, 1fr);
552
- gap: 20px;
553
- margin-top: 20px;
554
- margin-bottom: 40px;
555
- }}
556
- .video-card {{
557
- background: #ffffff;
558
- border-radius: 10px;
559
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
560
- overflow: hidden;
561
- transition: transform 0.2s;
562
- }}
563
- .video-card:hover {{
564
- transform: translateY(-5px);
565
- }}
566
- .video-wrapper {{
567
- position: relative;
568
- padding-bottom: 56.25%;
569
- height: 0;
570
- overflow: hidden;
571
- }}
572
- .video-wrapper iframe {{
573
- position: absolute;
574
- top: 0;
575
- left: 0;
576
- width: 100%;
577
- height: 100%;
578
- border: none;
579
- }}
580
- .video-title {{
581
- padding: 15px;
582
- font-size: 1.2em;
583
- font-weight: bold;
584
- color: #2c3e50;
585
- text-align: center;
586
- background: #f8f9fa;
587
- border-top: 1px solid #eee;
588
- }}
589
- .news-section {{
590
- margin-top: 40px;
591
- border-top: 2px solid #e9ecef;
592
- padding-top: 20px;
593
- }}
594
- .news-section-title {{
595
- font-size: 1.8em;
596
- font-weight: bold;
597
- color: #2c3e50;
598
- margin-bottom: 20px;
599
- text-align: center;
600
- }}
601
- .news-item {{
602
- background: #ffffff;
603
- border-radius: 10px;
604
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
605
- margin-bottom: 20px;
606
- overflow: hidden;
607
- }}
608
- .news-date {{
609
- padding: 10px 20px;
610
- background: #f8f9fa;
611
- color: #666;
612
- font-size: 0.9em;
613
- border-bottom: 1px solid #eee;
614
- }}
615
- .news-content {{
616
- display: flex;
617
- padding: 20px;
618
- align-items: center;
619
- gap: 30px;
620
- }}
621
- .news-video {{
622
- flex: 0 0 300px;
623
- }}
624
- .news-text {{
625
- flex: 1;
626
- display: flex;
627
- align-items: center;
628
- min-height: 169px;
629
- }}
630
- .twitter-link {{
631
- color: #2c3e50;
632
- text-decoration: none;
633
- display: flex;
634
- align-items: center;
635
- gap: 15px;
636
- font-size: 1.4em;
637
- font-weight: 600;
638
- line-height: 1.4;
639
- }}
640
- .twitter-link:hover {{
641
- color: #1da1f2;
642
- }}
643
- .twitter-icon {{
644
- font-size: 1.5em;
645
- color: #1da1f2;
646
- }}
647
- </style>
648
-
649
- <!-- Highlight Section -->
650
- <div class="highlight-section">
651
- <div class="highlight-card">
652
- <div class="highlight-header">
653
- <span>🌟 Latest Update</span>
654
- <span class="highlight-date">{formatted_latest_date}</span>
655
- </div>
656
- <div class="highlight-content">
657
- <div class="video-wrapper">
658
- <iframe src="https://www.youtube.com/embed/{latest_video_id}"></iframe>
659
- </div>
660
- <div class="video-title">
661
- <a href="{latest_news["twitter_link"]}" target="_blank" class="twitter-link">
662
- <span class="twitter-icon">📢</span>
663
- {latest_news["twitter_text"]}
664
- </a>
665
- </div>
666
- </div>
667
- </div>
668
- </div>
669
-
670
- <!-- Regular Video Grid -->
671
- <div class="video-grid">
672
- <div class="video-card">
673
- <div class="video-wrapper">
674
- <iframe src="https://www.youtube.com/embed/{mario_id}"></iframe>
675
- </div>
676
- <div class="video-title">🎮 Super Mario Bros</div>
677
- </div>
678
- <div class="video-card">
679
- <div class="video-wrapper">
680
- <iframe src="https://www.youtube.com/embed/{sokoban_id}"></iframe>
681
- </div>
682
- <div class="video-title">📦 Sokoban</div>
683
- </div>
684
- <div class="video-card">
685
- <div class="video-wrapper">
686
- <iframe src="https://www.youtube.com/embed/{game_2048_id}"></iframe>
687
- </div>
688
- <div class="video-title">🔢 2048</div>
689
- </div>
690
- <div class="video-card">
691
- <div class="video-wrapper">
692
- <iframe src="https://www.youtube.com/embed/{candy_id}"></iframe>
693
- </div>
694
- <div class="video-title">🍬 Candy Crash</div>
695
- </div>
696
- </div>
697
-
698
- <!-- News Section -->
699
- <div class="news-section">
700
- <div class="news-section-title">📰 Latest News</div>
701
- {news_html}
702
- </div>
703
- </div>
704
- '''
705
- return gr.HTML(gallery_html)
706
-
707
  def build_app():
708
  with gr.Blocks(css="""
709
- .visualization-container {
710
- height: 70vh !important; /* Reduced from 85vh to 70vh */
711
- max-height: 700px !important; /* Reduced from 900px to 700px */
712
- min-height: 500px !important; /* Reduced from 600px to 500px */
713
- background-color: #f8f9fa;
714
- border-radius: 10px;
715
- padding: 20px; /* Reduced padding from 25px to 20px */
716
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
717
- overflow: hidden;
718
- margin: 0 auto !important;
719
  }
720
- .visualization-container .plot {
721
- height: 100% !important;
722
- width: 100% !important;
723
- object-fit: contain !important; /* Added to ensure proper scaling */
724
  }
 
725
  .section-title {
726
  font-size: 1.5em;
727
  font-weight: bold;
@@ -736,6 +490,65 @@ def build_app():
736
  margin: 0 auto;
737
  padding: 0 20px;
738
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
739
  """) as demo:
740
  gr.Markdown("# 🎮 Game Arena: Gaming Agent 🎲")
741
 
@@ -752,19 +565,20 @@ def build_app():
752
  elem_classes="visualization-container"
753
  )
754
 
755
- # Overall view visualizations (two charts)
756
- with gr.Row(visible=True) as overall_visualizations:
757
- with gr.Column(scale=1):
758
- radar_visualization = gr.Plot(
759
- label="Comparative Analysis (Radar Chart)",
760
- elem_classes="visualization-container"
761
- )
762
- with gr.Column(scale=1):
763
- group_bar_visualization = gr.Plot(
764
- label="Comparative Analysis (Group Bar Chart)",
765
- elem_classes="visualization-container"
766
- )
767
-
 
768
  # Game selection section
769
  with gr.Row():
770
  gr.Markdown("### 🎮 Game Selection")
@@ -806,20 +620,59 @@ def build_app():
806
  # Leaderboard table
807
  with gr.Row():
808
  gr.Markdown("### 📋 Detailed Results")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
809
  with gr.Row():
810
  leaderboard_df = gr.DataFrame(
811
- value=get_combined_leaderboard(rank_data, {
812
- "Super Mario Bros": True,
813
- "Sokoban": True,
814
- "2048": True,
815
- "Candy Crash": True,
816
- "Tetris (complete)": True,
817
- "Tetris (planning only)": True
818
- }),
819
- label="Leaderboard",
820
- interactive=False
821
  )
822
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
823
  # List of all checkboxes
824
  checkbox_list = [
825
  mario_overall, mario_details,
 
28
  normalize_values,
29
  get_combined_leaderboard_with_single_radar
30
  )
31
+ from gallery_tab import create_video_gallery
32
+
33
+ # Try to import enhanced leaderboard, use standard DataFrame if not available
34
+
35
+ from gradio_leaderboard import Leaderboard, SelectColumns, ColumnFilter
36
+ from leaderboard_config import ON_LOAD_COLUMNS, TYPES
37
+ HAS_ENHANCED_LEADERBOARD = True
38
+
39
 
40
  # Define time points and their corresponding data files
41
  TIME_POINTS = {
 
68
  }
69
  }
70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71
 
72
  # Load video links and news data
73
  with open('assets/game_video_link.json', 'r') as f:
 
76
  with open('assets/news.json', 'r') as f:
77
  NEWS_DATA = json.load(f)
78
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  def load_rank_data(time_point):
80
  """Load rank data for a specific time point"""
81
  if time_point in TIME_POINTS:
 
86
  return None
87
  return None
88
 
89
+ # Function to prepare DataFrame for display
90
+ def prepare_dataframe_for_display(df, for_game=None):
91
+ """Format DataFrame for better display in the UI"""
92
+ # Clone the DataFrame to avoid modifying the original
93
+ display_df = df.copy()
94
+
95
+ # Filter out normalized score columns
96
+ norm_columns = [col for col in display_df.columns if col.startswith('norm_')]
97
+ if norm_columns:
98
+ display_df = display_df.drop(columns=norm_columns)
99
+
100
+ # Replace '_' with '-' for better display
101
+ for col in display_df.columns:
102
+ if col.endswith(' Score'):
103
+ display_df[col] = display_df[col].apply(lambda x: '-' if x == '_' else x)
104
+
105
+ # If we're in detailed view, add a formatted rank column
106
+ if for_game:
107
+ # Sort by relevant score column
108
+ score_col = f"{for_game} Score"
109
+ if score_col in display_df.columns:
110
+ # Convert to numeric for sorting, treating '-' as NaN
111
+ display_df[score_col] = pd.to_numeric(display_df[score_col], errors='coerce')
112
+ # Sort by score in descending order
113
+ display_df = display_df.sort_values(by=score_col, ascending=False)
114
+ # Add rank column based on the sort
115
+ display_df.insert(0, 'Rank', range(1, len(display_df) + 1))
116
+ # Filter out models that didn't participate
117
+ display_df = display_df[~display_df[score_col].isna()]
118
+
119
+ return display_df
120
+
121
+ # Helper function to ensure leaderboard updates maintain consistent height
122
+ def update_df_with_height(df):
123
+ """Update DataFrame with consistent height parameter."""
124
+ return gr.update(value=df, height=800)
125
+
126
  def update_leaderboard(mario_overall, mario_details,
127
  sokoban_overall, sokoban_details,
128
  _2048_overall, _2048_details,
 
253
  else: # Tetris (planning only)
254
  df = get_tetris_planning_leaderboard(rank_data)
255
 
256
+ # Format the DataFrame for display
257
+ display_df = prepare_dataframe_for_display(df, leaderboard_state["current_game"])
258
+
259
  # Always create a new chart for detailed view
260
  chart = create_horizontal_bar_chart(df, leaderboard_state["current_game"])
261
  # For detailed view, we'll use the same chart for all visualizations
 
264
  else:
265
  # For overall view
266
  df, group_bar_chart = get_combined_leaderboard_with_group_bar(rank_data, selected_games)
267
+ # Format the DataFrame for display
268
+ display_df = prepare_dataframe_for_display(df)
269
  # Use the same selected_games for radar chart
270
  _, radar_chart = get_combined_leaderboard_with_single_radar(rank_data, selected_games)
271
  chart = group_bar_chart
272
 
273
  # Return exactly 16 values to match the expected outputs
274
+ return (update_df_with_height(display_df), chart, radar_chart, group_bar_chart,
275
  current_overall["Super Mario Bros"], current_details["Super Mario Bros"],
276
  current_overall["Sokoban"], current_details["Sokoban"],
277
  current_overall["2048"], current_details["2048"],
 
337
  # Get the combined leaderboard and group bar chart
338
  df, group_bar_chart = get_combined_leaderboard_with_group_bar(rank_data, selected_games)
339
 
340
+ # Format the DataFrame for display
341
+ display_df = prepare_dataframe_for_display(df)
342
+
343
  # Get the radar chart using the same selected games
344
  _, radar_chart = get_combined_leaderboard_with_single_radar(rank_data, selected_games)
345
 
 
347
  leaderboard_state = get_initial_state()
348
 
349
  # Return exactly 16 values to match the expected outputs
350
+ return (update_df_with_height(display_df), group_bar_chart, radar_chart, group_bar_chart,
351
  True, False, # mario
352
  True, False, # sokoban
353
  True, False, # 2048
 
463
  """
464
  return gr.HTML(timeline_html)
465
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
466
  def build_app():
467
  with gr.Blocks(css="""
468
+ .visualization-container .js-plotly-plot {
469
+ margin-left: auto !important;
470
+ margin-right: auto !important;
471
+ display: block !important;
 
 
 
 
 
 
472
  }
473
+
474
+ /* Optional: limit width for better layout on large screens */
475
+ .visualization-container .js-plotly-plot {
476
+ max-width: 1000px;
477
  }
478
+
479
  .section-title {
480
  font-size: 1.5em;
481
  font-weight: bold;
 
490
  margin: 0 auto;
491
  padding: 0 20px;
492
  }
493
+
494
+ /* Enhanced table styling - SIMPLIFIED */
495
+ .table-container {
496
+ height: 800px !important;
497
+ max-height: 1000px !important;
498
+ overflow-y: auto !important; /* ONLY the outer container gets scrolling */
499
+ border-radius: 8px;
500
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
501
+ }
502
+
503
+ /* Prevent inner containers from having scrollbars */
504
+ .table-container > div,
505
+ .table-container > div > div,
506
+ .gradio-dataframe > div,
507
+ [data-testid="dataframe"] > div {
508
+ overflow: visible !important;
509
+ height: auto !important;
510
+ }
511
+
512
+ /* Fix table styling */
513
+ .table-container table {
514
+ width: 100%;
515
+ border-collapse: separate;
516
+ border-spacing: 0;
517
+ }
518
+
519
+ /* Make headers sticky */
520
+ .table-container th {
521
+ position: sticky !important;
522
+ top: 0 !important;
523
+ background-color: #f8f9fa !important;
524
+ z-index: 10 !important;
525
+ font-weight: bold;
526
+ padding: 12px;
527
+ border-bottom: 2px solid #e9ecef;
528
+ }
529
+
530
+ /* Simple cell styling */
531
+ .table-container td {
532
+ padding: 10px 12px;
533
+ border-bottom: 1px solid #e9ecef;
534
+ }
535
+
536
+ /* Visual enhancements */
537
+ .table-container tr:hover {
538
+ background-color: #f1f3f4;
539
+ }
540
+
541
+ .table-container tr:nth-child(even) {
542
+ background-color: #f8fafc;
543
+ }
544
+
545
+ /* Row containing the table */
546
+ .gradio-container .gr-row {
547
+ min-height: auto !important;
548
+ height: auto !important;
549
+ overflow: visible !important;
550
+ margin-bottom: 20px;
551
+ }
552
  """) as demo:
553
  gr.Markdown("# 🎮 Game Arena: Gaming Agent 🎲")
554
 
 
565
  elem_classes="visualization-container"
566
  )
567
 
568
+ with gr.Column(visible=True) as overall_visualizations:
569
+ with gr.Tabs():
570
+ with gr.Tab("📈 Radar Chart"):
571
+ radar_visualization = gr.Plot(
572
+ label="Comparative Analysis (Radar Chart)",
573
+ elem_classes="visualization-container"
574
+ )
575
+ with gr.Tab("📊 Group Bar Chart"):
576
+ group_bar_visualization = gr.Plot(
577
+ label="Comparative Analysis (Group Bar Chart)",
578
+ elem_classes="visualization-container"
579
+ )
580
+
581
+
582
  # Game selection section
583
  with gr.Row():
584
  gr.Markdown("### 🎮 Game Selection")
 
620
  # Leaderboard table
621
  with gr.Row():
622
  gr.Markdown("### 📋 Detailed Results")
623
+
624
+ # Add leaderboard search box in its own row
625
+ with gr.Row():
626
+ search_box = gr.Textbox(
627
+ label="🔍 Search by Player or Organization",
628
+ placeholder="Type to filter the table...",
629
+ show_label=True
630
+ )
631
+
632
+ # Get initial leaderboard dataframe
633
+ initial_df = get_combined_leaderboard(rank_data, {
634
+ "Super Mario Bros": True,
635
+ "Sokoban": True,
636
+ "2048": True,
637
+ "Candy Crash": True,
638
+ "Tetris (complete)": True,
639
+ "Tetris (planning only)": True
640
+ })
641
+
642
+ # Format the DataFrame for display
643
+ initial_display_df = prepare_dataframe_for_display(initial_df)
644
+
645
+ # Create a standard DataFrame component with enhanced styling
646
  with gr.Row():
647
  leaderboard_df = gr.DataFrame(
648
+ value=initial_display_df,
649
+ interactive=True,
650
+ elem_id="leaderboard-table",
651
+ elem_classes="table-container",
652
+ wrap=True,
653
+ column_widths={"Player": "25%", "Organization": "20%"},
654
+ height=800
 
 
 
655
  )
656
 
657
+ # Add search functionality
658
+ def filter_table(search_term, current_df):
659
+ if not search_term:
660
+ return current_df
661
+
662
+ # Filter the DataFrame by Player or Organization
663
+ filtered_df = current_df[
664
+ current_df["Player"].str.contains(search_term, case=False) |
665
+ current_df["Organization"].str.contains(search_term, case=False)
666
+ ]
667
+ return filtered_df
668
+
669
+ # Connect search box to the table
670
+ search_box.change(
671
+ filter_table,
672
+ inputs=[search_box, leaderboard_df],
673
+ outputs=[leaderboard_df]
674
+ )
675
+
676
  # List of all checkboxes
677
  checkbox_list = [
678
  mario_overall, mario_details,
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,53 +26,25 @@ GAME_SCORE_COLUMNS = {
31
  "Tetris (complete)": "Score",
32
  "Tetris (planning only)": "Score"
33
  }
 
 
 
34
 
35
  def normalize_values(values, mean, std):
36
- """
37
- Normalize values using z-score and scale to 0-100 range
38
-
39
- Args:
40
- values (list): List of values to normalize
41
- mean (float): Mean value for normalization
42
- std (float): Standard deviation for normalization
43
-
44
- Returns:
45
- list: Normalized values scaled to 0-100 range
46
- """
47
  if std == 0:
48
- return [50 if v > 0 else 0 for v in values] # Handle zero std case
49
  z_scores = [(v - mean) / std for v in values]
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, 7))
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,645 +73,406 @@ 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, top=0.85, bottom=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
- # Get color for model from MODEL_COLORS, use default if not found
149
- model_name = df_sorted.iloc[i]['Player']
150
- color = MODEL_COLORS.get(model_name, '#808080') # Default to gray if color not found
151
- bar.set_color(color) # Set the bar color
152
-
153
- ax.text(width, bar.get_y() + bar.get_height()/2,
154
- score_text,
155
- ha='left', va='center',
156
- fontsize=10,
157
- fontweight='bold',
158
- color='white',
159
- bbox=dict(facecolor=(0, 0, 0, 0.3),
160
- edgecolor='none',
161
- alpha=0.5,
162
- pad=2))
163
-
164
- # Set title and labels
165
- ax.set_title(f"{game_name} Performance",
166
- pad=20,
167
- fontsize=14,
168
- fontweight='bold',
169
- color='#2c3e50')
170
-
171
- if game_name == "Sokoban":
172
- ax.set_xlabel("Maximum Level Reached",
173
- fontsize=12,
174
- fontweight='bold',
175
- color='#2c3e50',
176
- labelpad=10)
177
- else:
178
- ax.set_xlabel(score_col,
179
- fontsize=12,
180
- fontweight='bold',
181
- color='#2c3e50',
182
- labelpad=10)
183
-
184
- # Add grid lines
185
- ax.grid(True, axis='x', linestyle='--', alpha=0.3)
186
-
187
- # Remove top and right spines
188
- ax.spines['top'].set_visible(False)
189
- ax.spines['right'].set_visible(False)
190
-
191
- # Adjust layout
192
- plt.tight_layout()
193
-
194
  return fig
195
 
196
  def create_radar_charts(df):
197
- """
198
- Create two radar charts with improved normalization using z-scores
199
- """
200
- # Close any existing figures to prevent memory leaks
201
- plt.close('all')
202
-
203
- # Define reasoning models
204
- reasoning_models = [
205
- 'claude-3-7-sonnet-20250219(thinking)',
206
- 'o1-2024-12-17',
207
- 'gemini-2.0-flash-thinking-exp-1219',
208
- 'o3-mini-2025-01-31(medium)',
209
- 'gemini-2.5-pro-exp-03-25',
210
- 'o1-mini-2024-09-12',
211
- 'deepseek-r1'
212
- ]
213
-
214
- # Split dataframe into reasoning and non-reasoning models
215
- df_reasoning = df[df['Player'].isin(reasoning_models)]
216
- df_others = df[~df['Player'].isin(reasoning_models)]
217
-
218
- # Get game columns
219
- game_columns = [col for col in df.columns if col.endswith(' Score')]
220
- categories = [col.replace(' Score', '') for col in game_columns]
221
-
222
- # Create figure with two subplots - adjusted size for new layout
223
- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 6), subplot_kw=dict(projection='polar'))
224
- fig.patch.set_facecolor('white') # Set figure background to white
225
-
226
- def get_game_stats(df, game_col):
227
- """
228
- Get mean and std for a game column, handling missing values
229
- """
230
- values = []
231
- for val in df[game_col]:
232
- if isinstance(val, str) and val == '_':
233
- values.append(0)
234
- else:
235
- try:
236
- values.append(float(val))
237
- except:
238
- values.append(0)
239
- return np.mean(values), np.std(values)
240
-
241
- def setup_radar_plot(ax, data, title):
242
- ax.set_facecolor('white') # Set subplot background to white
243
-
244
- num_vars = len(categories)
245
- angles = np.linspace(0, 2*np.pi, num_vars, endpoint=False)
246
- angles = np.concatenate((angles, [angles[0]]))
247
-
248
- # Plot grid lines with darker color
249
- grid_values = [10, 30, 50, 70, 90]
250
- ax.set_rgrids(grid_values,
251
- labels=grid_values,
252
- angle=45,
253
- fontsize=6,
254
- alpha=0.7, # Increased alpha for better visibility
255
- color='#404040') # Darker color for grid labels
256
-
257
- # Make grid lines darker but still subtle
258
- ax.grid(True, color='#404040', alpha=0.3) # Darker grid lines
259
-
260
- # Define darker, more vibrant colors for the radar plots
261
- colors = ['#1f77b4', '#d62728', '#2ca02c', '#ff7f0e', '#9467bd', '#8c564b']
262
-
263
- # Calculate game statistics once
264
- game_stats = {col: get_game_stats(df, col) for col in game_columns}
265
-
266
- # Plot data with darker lines and higher opacity for fills
267
- for idx, (_, row) in enumerate(data.iterrows()):
268
- values = []
269
- for col in game_columns:
270
- val = row[col]
271
- if isinstance(val, str) and val == '_':
272
- values.append(0)
273
- else:
274
- try:
275
- values.append(float(val))
276
- except:
277
- values.append(0)
278
-
279
- # Normalize values using game statistics
280
- normalized_values = []
281
- for i, v in enumerate(values):
282
- mean, std = game_stats[game_columns[i]]
283
- normalized_value = normalize_values([v], mean, std)[0]
284
- normalized_values.append(normalized_value)
285
-
286
- # Complete the circular plot
287
- normalized_values = np.concatenate((normalized_values, [normalized_values[0]]))
288
-
289
- model_name = simplify_model_name(row['Player'])
290
- ax.plot(angles, normalized_values, 'o-', linewidth=2.0, # Increased line width
291
- label=model_name,
292
- color=colors[idx % len(colors)],
293
- markersize=4) # Increased marker size
294
- ax.fill(angles, normalized_values,
295
- alpha=0.3, # Increased fill opacity
296
- color=colors[idx % len(colors)])
297
-
298
- # Format categories
299
- formatted_categories = []
300
- for game in categories:
301
- if game == "Tetris (planning only)":
302
- game = "Tetris\n(planning)"
303
- elif game == "Tetris (complete)":
304
- game = "Tetris\n(complete)"
305
- elif game == "Super Mario Bros":
306
- game = "Super\nMario"
307
- elif game == "Candy Crash":
308
- game = "Candy\nCrash"
309
- formatted_categories.append(game)
310
-
311
- ax.set_xticks(angles[:-1])
312
- ax.set_xticklabels(formatted_categories,
313
- fontsize=8, # Slightly larger font
314
- color='#202020', # Darker text
315
- fontweight='bold') # Bold text
316
- ax.tick_params(pad=10, colors='#202020') # Darker tick colors
317
-
318
- ax.set_title(title,
319
- pad=20,
320
- fontsize=11, # Slightly larger title
321
- color='#202020', # Darker title
322
- fontweight='bold') # Bold title
323
-
324
- legend = ax.legend(loc='upper right',
325
- bbox_to_anchor=(0.9, 1.1),
326
- fontsize=7, # Slightly larger legend
327
- framealpha=0.9, # More opaque legend
328
- edgecolor='#404040', # Darker edge
329
- ncol=1)
330
-
331
- ax.set_ylim(0, 105)
332
- ax.spines['polar'].set_color('#404040') # Darker spine
333
- ax.spines['polar'].set_alpha(0.5) # More visible spine
334
-
335
- # Setup both plots
336
- setup_radar_plot(ax1, df_reasoning, "Reasoning Models")
337
- setup_radar_plot(ax2, df_others, "Non-Reasoning Models")
338
-
339
- plt.subplots_adjust(right=0.85, wspace=0.3)
340
-
341
  return fig
342
 
343
  def get_combined_leaderboard_with_radar(rank_data, selected_games):
344
- """
345
- Get combined leaderboard and create radar charts
346
- """
347
  df = get_combined_leaderboard(rank_data, selected_games)
348
- radar_fig = create_radar_charts(df)
349
- return df, radar_fig
350
-
351
- def create_organization_radar_chart(rank_data):
352
- """
353
- Create radar chart comparing organizations
354
- """
355
- # Get combined leaderboard with all games
356
- df = get_combined_leaderboard(rank_data, {game: True for game in GAME_ORDER})
357
-
358
- # Group by organization and calculate average scores
359
- org_performance = {}
360
- for org in df["Organization"].unique():
361
- org_df = df[df["Organization"] == org]
362
- scores = {}
363
- for game in GAME_ORDER:
364
- game_scores = org_df[f"{game} Score"].apply(lambda x: float(x) if x != "_" else 0)
365
- scores[game] = game_scores.mean()
366
- org_performance[org] = scores
367
-
368
- # Create radar chart
369
- return create_radar_charts(pd.DataFrame([org_performance]))
370
 
371
- def create_top_players_radar_chart(rank_data, n=5):
372
- """
373
- Create radar chart for top N players
374
- """
375
- # Get combined leaderboard with all games
376
- df = get_combined_leaderboard(rank_data, {game: True for game in GAME_ORDER})
377
-
378
- # Get top N players
379
- top_players = df["Player"].head(n).tolist()
380
-
381
- # Create radar chart for top players
382
- return create_radar_charts(df[df["Player"].isin(top_players)])
383
 
384
- def create_player_radar_chart(rank_data, player_name):
385
- """
386
- Create radar chart for a specific player
387
- """
388
- # Get combined leaderboard with all games
389
- df = get_combined_leaderboard(rank_data, {game: True for game in GAME_ORDER})
390
-
391
- # Get player's data
392
- player_df = df[df["Player"] == player_name]
393
-
394
- if player_df.empty:
395
- return None
396
-
397
- # Create radar chart for the player
398
- return create_radar_charts(player_df)
399
 
400
- def create_group_bar_chart(df):
401
- """
402
- Create a grouped bar chart comparing AI model performance across different games
403
-
404
- Args:
405
- df (pd.DataFrame): DataFrame containing the combined leaderboard data
406
-
407
- Returns:
408
- matplotlib.figure.Figure: The generated group bar chart figure
409
- """
410
- # Close any existing figures to prevent memory leaks
411
- plt.close('all')
412
-
413
- # Create figure and axis with better styling
414
- sns.set_style("whitegrid")
415
- fig = plt.figure(figsize=(10, 7))
416
-
417
- # Create subplot with specific spacing
418
- ax = plt.subplot(111)
419
-
420
- # Adjust the subplot parameters
421
- plt.subplots_adjust(top=0.90, # Add more space at the top
422
- bottom=0.25, # Increased from 0.15 to 0.25 to add more space at the bottom
423
- right=0.70, # Reduced from 0.75 to 0.70 to make more space for legend
424
- left=0.05) # Add space on the left
425
-
426
- # Get unique models
427
- models = df['Player'].unique()
428
-
429
- # Get active games (those that have score columns in the DataFrame)
430
- active_games = []
431
- for game in GAME_ORDER:
432
- score_col = f"{game} Score" # Use the same column name for all games
433
- if score_col in df.columns:
434
- active_games.append(game)
435
-
436
- n_games = len(active_games)
437
- if n_games == 0:
438
- return fig # Return empty figure if no games are selected
439
 
440
- # Keep track of which models have data in any game
441
- models_with_data = set()
 
 
 
 
442
 
443
- # Calculate normalized scores for each game
444
- for game_idx, game in enumerate(active_games):
445
- # Get all scores for this game
446
- game_scores = []
447
-
448
- # Use the same score column name for all games
449
- score_col = f"{game} Score"
450
-
451
- for model in models:
452
- try:
453
- score = df[df['Player'] == model][score_col].values[0]
454
- if score != '_' and float(score) > 0: # Only include non-zero scores
455
- game_scores.append((model, float(score)))
456
- models_with_data.add(model) # Add model to set if it has valid data
457
- except (IndexError, ValueError):
458
- continue
459
-
460
- if not game_scores: # Skip if no valid scores for this game
461
- continue
462
-
463
- # Sort scores from highest to lowest
464
- game_scores.sort(key=lambda x: x[1], reverse=True)
465
-
466
- # Extract sorted models and scores
467
- sorted_models = [x[0] for x in game_scores]
468
- scores = [x[1] for x in game_scores]
469
-
470
- # Calculate mean and std for normalization
471
- mean = np.mean(scores)
472
- std = np.std(scores)
473
-
474
- # Normalize scores
475
- normalized_scores = normalize_values(scores, mean, std)
476
-
477
- # Calculate bar width based on number of models in this game
478
- n_models_in_game = len(sorted_models)
479
- bar_width = 0.8 / n_models_in_game if n_models_in_game > 0 else 0.8
480
-
481
- # Plot bars for each model
482
- for i, (model, score) in enumerate(zip(sorted_models, normalized_scores)):
483
- # Only add to legend if first appearance and model has data
484
- 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
485
-
486
- # Get color from MODEL_COLORS, use a default if not found
487
- color = MODEL_COLORS.get(model, f"C{i % 10}") # Use matplotlib default colors as fallback
488
-
489
- ax.bar(game_idx + i*bar_width, score,
490
- width=bar_width,
491
- label=model if should_label else "",
492
- color=color,
493
- alpha=0.8)
494
-
495
- # Customize the plot
496
- ax.set_xticks(np.arange(n_games))
497
- ax.set_xticklabels(active_games, rotation=45, ha='right', fontsize=10, fontweight='bold')
498
- ax.set_ylabel('Normalized Performance Score', fontsize=12)
499
- ax.set_title('AI Model Performance Across Games',
500
- fontsize=14, pad=20, fontweight='bold')
501
-
502
- # Add grid lines
503
- ax.grid(True, axis='y', linestyle='--', alpha=0.3)
504
-
505
- # Create legend with unique entries
506
- handles, labels = ax.get_legend_handles_labels()
507
- by_label = dict(zip(labels, handles))
508
-
509
- # Sort models by their first appearance in active games
510
- model_order = []
511
- for game in active_games:
512
- score_col = f"{game} Score" # Use the same column name for all games
513
- for model in models:
514
- try:
515
- score = df[df['Player'] == model][score_col].values[0]
516
- if score != '_' and float(score) > 0 and model not in model_order:
517
- model_order.append(model)
518
- except (IndexError, ValueError):
519
- continue
520
-
521
- # Create legend with sorted models
522
- sorted_handles = [by_label[model] for model in model_order if model in by_label]
523
- sorted_labels = [model for model in model_order if model in by_label]
524
-
525
- ax.legend(sorted_handles, sorted_labels,
526
- bbox_to_anchor=(1.00, 1),
527
- loc='upper left',
528
- fontsize=9,
529
- title='AI Models',
530
- title_fontsize=10) # Added bold font weight for model names
531
-
532
- # No need for tight_layout() as we're manually controlling the spacing
533
-
534
  return fig
535
 
536
  def get_combined_leaderboard_with_group_bar(rank_data, selected_games):
537
- """
538
- Get combined leaderboard and create group bar chart
539
-
540
- Args:
541
- rank_data (dict): Dictionary containing rank data
542
- selected_games (dict): Dictionary of game names and their selection status
543
-
544
- Returns:
545
- tuple: (DataFrame, matplotlib.figure.Figure) containing the leaderboard data and group bar chart
546
- """
547
  df = get_combined_leaderboard(rank_data, selected_games)
548
- group_bar_fig = create_group_bar_chart(df)
549
- return df, group_bar_fig
 
 
 
 
 
 
 
 
 
550
 
551
  def create_single_radar_chart(df, selected_games=None, highlight_models=None):
552
- """
553
- Create a single radar chart comparing AI model performance across selected games
554
-
555
- Args:
556
- df (pd.DataFrame): DataFrame containing the combined leaderboard data
557
- selected_games (list, optional): List of game names to include in the radar chart
558
- highlight_models (list, optional): List of model names to highlight in the chart
559
-
560
- Returns:
561
- matplotlib.figure.Figure: The generated radar chart figure
562
- """
563
- # Close any existing figures to prevent memory leaks
564
- plt.close('all')
565
-
566
- # Use provided selected_games or default to the four main games
567
  if selected_games is None:
568
  selected_games = ['Super Mario Bros', '2048', 'Candy Crash', 'Sokoban']
569
-
570
- game_columns = [f"{game} Score" for game in selected_games]
571
  categories = selected_games
572
 
573
- # Create figure
574
- fig, ax = plt.subplots(figsize=(8, 7), subplot_kw=dict(projection='polar'))
575
- fig.patch.set_facecolor('white')
576
- ax.set_facecolor('white')
577
-
578
- # Compute number of variables
579
- num_vars = len(categories)
580
- angles = np.linspace(0, 2*np.pi, num_vars, endpoint=False)
581
- angles = np.concatenate((angles, [angles[0]])) # Complete the circle
582
-
583
- # Set up the axes
584
- ax.set_xticks(angles[:-1])
585
-
586
- # Format categories with bold text
587
- formatted_categories = []
588
- for game in categories:
589
- if game == "Super Mario Bros":
590
- game = "Super\nMario"
591
- elif game == "Candy Crash":
592
- game = "Candy\nCrash"
593
- elif game == "Tetris (planning only)":
594
- game = "Tetris\n(planning)"
595
- elif game == "Tetris (complete)":
596
- game = "Tetris\n(complete)"
597
- formatted_categories.append(game)
598
-
599
- # Set bold labels for categories
600
- ax.set_xticklabels(formatted_categories, fontsize=10, fontweight='bold')
601
-
602
- # Draw grid lines
603
- ax.set_rgrids([20, 40, 60, 80, 100],
604
- labels=['20', '40', '60', '80', '100'],
605
- angle=45,
606
- fontsize=8)
607
-
608
- # Calculate game statistics for normalization
609
- def get_game_stats(df, game_col):
610
- values = []
611
- for val in df[game_col]:
612
- if isinstance(val, str) and val == '_':
613
- values.append(0)
614
- else:
615
- try:
616
- values.append(float(val))
617
- except:
618
- values.append(0)
619
- return np.mean(values), np.std(values)
620
-
621
- game_stats = {col: get_game_stats(df, col) for col in game_columns}
622
-
623
- # Split the dataframe into highlighted and non-highlighted models
624
- if highlight_models:
625
- highlighted_df = df[df['Player'].isin(highlight_models)]
626
- non_highlighted_df = df[~df['Player'].isin(highlight_models)]
627
- else:
628
- highlighted_df = pd.DataFrame()
629
- non_highlighted_df = df
630
-
631
- # Plot non-highlighted models first
632
- for _, row in non_highlighted_df.iterrows():
633
- values = []
634
- for col in game_columns:
635
- val = row[col]
636
- if isinstance(val, str) and val == '_':
637
- values.append(0)
638
- else:
639
- try:
640
- mean, std = game_stats[col]
641
- if std == 0:
642
- normalized = 50 if float(val) > 0 else 0
643
- else:
644
- z_score = (float(val) - mean) / std
645
- normalized = max(0, min(100, (z_score * 30) + 50))
646
- values.append(normalized)
647
- except:
648
- values.append(0)
649
-
650
- # Complete the circular plot
651
- values = np.concatenate((values, [values[0]]))
652
-
653
- # Get color for model, use default if not found
654
- model_name = row['Player']
655
- color = MODEL_COLORS.get(model_name, '#808080') # Default to gray if color not found
656
-
657
- # Plot with lines and markers
658
- ax.plot(angles, values, 'o-', linewidth=2, label=model_name, color=color)
659
- ax.fill(angles, values, alpha=0.25, color=color)
660
-
661
- # Plot highlighted models last (so they appear on top)
662
- for _, row in highlighted_df.iterrows():
663
- values = []
664
- for col in game_columns:
665
- val = row[col]
666
- if isinstance(val, str) and val == '_':
667
- values.append(0)
668
- else:
669
- try:
670
- mean, std = game_stats[col]
671
- if std == 0:
672
- normalized = 50 if float(val) > 0 else 0
673
- else:
674
- z_score = (float(val) - mean) / std
675
- normalized = max(0, min(100, (z_score * 30) + 30))
676
- values.append(normalized)
677
- except:
678
- values.append(0)
679
-
680
- # Complete the circular plot
681
- values = np.concatenate((values, [values[0]]))
682
-
683
- # Plot with red color and thicker line
684
- model_name = row['Player']
685
- ax.plot(angles, values, 'o-', linewidth=6, label=model_name, color='red')
686
- ax.fill(angles, values, alpha=0.25, color='red')
687
-
688
- # Add title
689
- plt.title('AI Models Performance Across Games\n(Normalized Scores)',
690
- pad=20, fontsize=14, fontweight='bold')
691
-
692
- # Get handles and labels for legend
693
- handles, labels = ax.get_legend_handles_labels()
694
-
695
- # Reorder legend to put highlighted models first
696
- if highlight_models:
697
- highlighted_handles = []
698
- highlighted_labels = []
699
- non_highlighted_handles = []
700
- non_highlighted_labels = []
701
-
702
- for handle, label in zip(handles, labels):
703
- if label in highlight_models:
704
- highlighted_handles.append(handle)
705
- highlighted_labels.append(label)
706
- else:
707
- non_highlighted_handles.append(handle)
708
- non_highlighted_labels.append(label)
709
-
710
- handles = highlighted_handles + non_highlighted_handles
711
- labels = highlighted_labels + non_highlighted_labels
712
-
713
- # Add legend with reordered handles and labels
714
- legend = plt.legend(handles, labels,
715
- loc='center left',
716
- bbox_to_anchor=(0.95, 1),
717
- fontsize=8,
718
- title='AI Models',
719
- title_fontsize=10) # Added bold font weight for model names
720
-
721
- # Adjust layout to prevent label cutoff
722
- plt.subplots_adjust(right=0.8) # Added subplot adjustment to give more space on the right
723
- plt.tight_layout()
724
-
725
  return fig
726
 
727
  def get_combined_leaderboard_with_single_radar(rank_data, selected_games, highlight_models=None):
728
- """
729
- Get combined leaderboard and create single radar chart
730
-
731
- Args:
732
- rank_data (dict): Dictionary containing rank data
733
- selected_games (dict): Dictionary of game names and their selection status
734
- highlight_models (list, optional): List of model names to highlight in the chart
735
-
736
- Returns:
737
- tuple: (DataFrame, matplotlib.figure.Figure) containing the leaderboard data and radar chart
738
- """
739
  df = get_combined_leaderboard(rank_data, selected_games)
740
- # Convert selected_games dict to list of selected game names
741
- selected_game_names = [game for game, selected in selected_games.items() if selected]
742
- radar_fig = create_single_radar_chart(df, selected_games=selected_game_names, highlight_models=highlight_models)
743
- return df, radar_fig
744
 
745
- def save_visualization(fig, filename):
746
- """
747
- Save visualization to file
748
- """
749
- fig.savefig(filename, bbox_inches='tight', dpi=300)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
750
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
  if std == 0:
35
+ return [50 if v > 0 else 0 for v in values]
36
  z_scores = [(v - mean) / std for v in values]
37
+ return [max(0, min(100, (z * 30) + 50)) for z in z_scores]
 
 
38
 
39
+ def simplify_model_name(name):
40
+ if name == "claude-3-7-sonnet-20250219(thinking)":
41
+ name ="claude-3-7-thinking"
42
+ parts = name.split('-')
43
+ return '-'.join(parts[:4]) + '-...' if len(parts) > 4 else name
 
44
 
45
  def create_horizontal_bar_chart(df, game_name):
46
+
47
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  if game_name == "Super Mario Bros":
49
  score_col = "Score"
50
  df_sorted = df.sort_values(by=score_col, ascending=True)
 
73
  df_sorted = df.sort_values(by=score_col, ascending=True)
74
  else:
75
  return None
76
+
77
+
78
+
79
+ x = df_sorted[score_col]
80
+ y = [f"{simplify_model_name(row['Player'])} [{row['Organization']}]" for _, row in df_sorted.iterrows()]
81
+ colors = [MODEL_COLORS.get(row['Player'], '#808080') for _, row in df_sorted.iterrows()]
82
+ texts = [f"{v:.1f}" if game_name == "Candy Crash" else f"{int(v)}" for v in x]
83
+
84
+ fig = go.Figure(go.Bar(
85
+ x=x,
86
+ y=y,
87
+ orientation='h',
88
+ marker_color=colors,
89
+ text=texts,
90
+ textposition='auto',
91
+ hovertemplate='%{y}<br>Score: %{x}<extra></extra>'
92
+ ))
93
+
94
+ fig.update_layout(
95
+ autosize=False,
96
+ width=800,
97
+ height=600,
98
+ margin=dict(l=150, r=150, t=40, b=200),
99
+ title=dict(
100
+ text=f"{game_name} Performance",
101
+ pad=dict(t=10)
102
+ ),
103
+ yaxis=dict(automargin=True),
104
+ legend=dict(
105
+ font=dict(size=9),
106
+ itemsizing='trace',
107
+ x=1.1,
108
+ y=1,
109
+ xanchor='left',
110
+ yanchor='top',
111
+ bgcolor='rgba(255,255,255,0.6)',
112
+ bordercolor='gray',
113
+ borderwidth=1
114
+ )
115
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
  return fig
117
 
118
  def create_radar_charts(df):
119
+ game_cols = [c for c in df.columns if c.endswith(" Score")]
120
+ categories = [c.replace(" Score", "") for c in game_cols]
121
+
122
+ for col in game_cols:
123
+ vals = df[col].replace("_", 0).astype(float)
124
+ mean, std = vals.mean(), vals.std()
125
+ df[f"norm_{col}"] = normalize_values(vals, mean, std)
126
+
127
+ fig = go.Figure()
128
+ for _, row in df.iterrows():
129
+ player = row["Player"]
130
+ r = [row[f"norm_{c}"] for c in game_cols]
131
+
132
+ color = MODEL_COLORS.get(player, '#808080') # fallback to gray
133
+ fig.add_trace(go.Scatterpolar(
134
+ r=r + [r[0]],
135
+ theta=categories + [categories[0]],
136
+ mode='lines+markers',
137
+ fill='toself',
138
+ name=player,
139
+ line=dict(color=color, width=2),
140
+ marker=dict(color=color),
141
+ fillcolor=color + '33', # add transparency to fill (33 = ~20% opacity)
142
+ opacity=0.8
143
+ ))
144
+
145
+
146
+ fig.update_layout(
147
+ autosize=False,
148
+ width=800,
149
+ height=600,
150
+ margin=dict(l=80, r=150, t=40, b=100),
151
+ title=dict(
152
+ text="Radar Chart of AI Performance (Normalized)",
153
+ pad=dict(t=10)
154
+ ),
155
+ polar=dict(radialaxis=dict(visible=True, range=[0, 100])),
156
+ legend=dict(
157
+ font=dict(size=9),
158
+ itemsizing='trace',
159
+ x=1.4,
160
+ y=1,
161
+ xanchor='left',
162
+ yanchor='top',
163
+ bgcolor='rgba(255,255,255,0.6)',
164
+ bordercolor='gray',
165
+ borderwidth=1
166
+ )
167
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  return fig
169
 
170
  def get_combined_leaderboard_with_radar(rank_data, selected_games):
 
 
 
171
  df = get_combined_leaderboard(rank_data, selected_games)
172
+ # Create a copy for visualization to avoid modifying the original
173
+ df_viz = df.copy()
174
+ return df, create_radar_charts(df_viz)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
+ def create_group_bar_chart(df):
177
+ active_games = [g for g in GAME_ORDER if f"{g} Score" in df.columns]
178
+ game_cols = [f"{g} Score" for g in active_games]
 
 
 
 
 
 
 
 
 
179
 
180
+ for col in game_cols:
181
+ vals = df[col].replace("_", 0).astype(float)
182
+ mean, std = vals.mean(), vals.std()
183
+ df[f"norm_{col}"] = normalize_values(vals, mean, std)
 
 
 
 
 
 
 
 
 
 
 
184
 
185
+ fig = go.Figure()
186
+ for _, row in df.iterrows():
187
+ player = row["Player"]
188
+ color = MODEL_COLORS.get(player, '#808080') # Default to gray if missing
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
 
190
+ fig.add_trace(go.Bar(
191
+ name=simplify_model_name(row["Player"]),
192
+ x=active_games,
193
+ y=[row[f"norm_{g} Score"] for g in active_games],
194
+ marker_color=color
195
+ ))
196
 
197
+ fig.update_layout(
198
+ autosize=False,
199
+ width=800,
200
+ height=600,
201
+ margin=dict(l=80, r=150, t=40, b=200),
202
+ title=dict(
203
+ text="Grouped Bar Chart of AI Models",
204
+ pad=dict(t=10)
205
+ ),
206
+ xaxis_title="Games",
207
+ yaxis_title="Normalized Score",
208
+ barmode='group',
209
+ legend=dict(
210
+ font=dict(size=9),
211
+ itemsizing='trace',
212
+ x=1.1,
213
+ y=1,
214
+ xanchor='left',
215
+ yanchor='top',
216
+ bgcolor='rgba(255,255,255,0.6)',
217
+ bordercolor='gray',
218
+ borderwidth=1
219
+ )
220
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  return fig
222
 
223
  def get_combined_leaderboard_with_group_bar(rank_data, selected_games):
 
 
 
 
 
 
 
 
 
 
224
  df = get_combined_leaderboard(rank_data, selected_games)
225
+ # Create a copy for visualization to avoid modifying the original
226
+ df_viz = df.copy()
227
+ return df, create_group_bar_chart(df_viz)
228
+
229
+ def hex_to_rgba(hex_color, alpha=0.2):
230
+ hex_color = hex_color.lstrip('#')
231
+ r = int(hex_color[0:2], 16)
232
+ g = int(hex_color[2:4], 16)
233
+ b = int(hex_color[4:6], 16)
234
+ return f'rgba({r}, {g}, {b}, {alpha})'
235
+
236
 
237
  def create_single_radar_chart(df, selected_games=None, highlight_models=None):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
238
  if selected_games is None:
239
  selected_games = ['Super Mario Bros', '2048', 'Candy Crash', 'Sokoban']
240
+
241
+ game_cols = [f"{game} Score" for game in selected_games]
242
  categories = selected_games
243
 
244
+ # Normalize
245
+ for col in game_cols:
246
+ vals = df[col].replace("_", 0).astype(float)
247
+ mean, std = vals.mean(), vals.std()
248
+ df[f"norm_{col}"] = normalize_values(vals, mean, std)
249
+
250
+ # Group players by prefix
251
+ model_groups = {}
252
+ for player in df["Player"]:
253
+ prefix = get_model_prefix(player)
254
+ model_groups.setdefault(prefix, []).append(player)
255
+
256
+ # Order: grouped by prefix, then alphabetically
257
+ grouped_players = []
258
+ for prefix in sorted(model_groups):
259
+ grouped_players.extend(sorted(model_groups[prefix]))
260
+
261
+ fig = go.Figure()
262
+
263
+ for player in grouped_players:
264
+ row = df[df["Player"] == player]
265
+ if row.empty:
266
+ continue
267
+ row = row.iloc[0]
268
+
269
+ is_highlighted = highlight_models and player in highlight_models
270
+ color = 'red' if is_highlighted else MODEL_COLORS.get(player, '#808080')
271
+ fillcolor = 'rgba(255, 0, 0, 0.3)' if is_highlighted else hex_to_rgba(color, 0.2)
272
+
273
+ r = [row[f"norm_{col}"] for col in game_cols]
274
+
275
+ fig.add_trace(go.Scatterpolar(
276
+ r=r + [r[0]],
277
+ theta=categories + [categories[0]],
278
+ mode='lines+markers',
279
+ fill='toself',
280
+ name=simplify_model_name(row["Player"]),
281
+ line=dict(color=color, width=4 if is_highlighted else 2),
282
+ marker=dict(color=color),
283
+ fillcolor=fillcolor,
284
+ opacity=1.0 if is_highlighted else 0.7
285
+ ))
286
+
287
+ fig.update_layout(
288
+ autosize=False,
289
+ width=800,
290
+ height=600,
291
+ margin=dict(l=80, r=150, t=40, b=100),
292
+ title=dict(
293
+ text="Single Radar Chart (Normalized Performance)",
294
+ pad=dict(t=10)
295
+ ),
296
+ polar=dict(radialaxis=dict(visible=True, range=[0, 100])),
297
+ legend=dict(
298
+ font=dict(size=9),
299
+ itemsizing='trace',
300
+ x=1.4,
301
+ y=1,
302
+ xanchor='left',
303
+ yanchor='top',
304
+ bgcolor='rgba(255,255,255,0.6)',
305
+ bordercolor='gray',
306
+ borderwidth=1
307
+ )
308
+ )
309
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
310
  return fig
311
 
312
  def get_combined_leaderboard_with_single_radar(rank_data, selected_games, highlight_models=None):
 
 
 
 
 
 
 
 
 
 
 
313
  df = get_combined_leaderboard(rank_data, selected_games)
314
+ selected_game_names = [g for g, sel in selected_games.items() if sel]
315
+ # Create a copy for visualization to avoid modifying the original
316
+ df_viz = df.copy()
317
+ return df, create_single_radar_chart(df_viz, selected_game_names, highlight_models)
318
 
319
+ def create_organization_radar_chart(rank_data):
320
+ df = get_combined_leaderboard(rank_data, {g: True for g in GAME_ORDER})
321
+ orgs = df["Organization"].unique()
322
+ game_cols = [f"{g} Score" for g in GAME_ORDER if f"{g} Score" in df.columns]
323
+ categories = [g.replace(" Score", "") for g in game_cols]
324
+
325
+ avg_df = pd.DataFrame([
326
+ {
327
+ **{col: df[df["Organization"] == org][col].replace("_", 0).astype(float).mean() for col in game_cols},
328
+ "Organization": org
329
+ }
330
+ for org in orgs
331
+ ])
332
+
333
+ for col in game_cols:
334
+ vals = avg_df[col]
335
+ mean, std = vals.mean(), vals.std()
336
+ avg_df[f"norm_{col}"] = normalize_values(vals, mean, std)
337
+
338
+ fig = go.Figure()
339
+ for _, row in avg_df.iterrows():
340
+ r = [row[f"norm_{col}"] for col in game_cols]
341
+ fig.add_trace(go.Scatterpolar(
342
+ r=r + [r[0]],
343
+ theta=categories + [categories[0]],
344
+ mode='lines+markers',
345
+ fill='toself',
346
+ name=row["Organization"]
347
+ ))
348
+
349
+ fig.update_layout(
350
+ autosize=False,
351
+ width=800,
352
+ height=600,
353
+ margin=dict(l=80, r=150, t=40, b=200),
354
+ title=dict(
355
+ text="Radar Chart: Organization Performance (Normalized)",
356
+ pad=dict(t=10)
357
+ ),
358
+ polar=dict(radialaxis=dict(visible=True, range=[0, 100])),
359
+ legend=dict(
360
+ font=dict(size=9),
361
+ itemsizing='trace',
362
+ x=1.4,
363
+ y=1,
364
+ xanchor='left',
365
+ yanchor='top',
366
+ bgcolor='rgba(255,255,255,0.6)',
367
+ bordercolor='gray',
368
+ borderwidth=1
369
+ )
370
+ )
371
+ return fig
372
+
373
+ def create_top_players_radar_chart(rank_data, n=5):
374
+ df = get_combined_leaderboard(rank_data, {g: True for g in GAME_ORDER})
375
+ top_players = df.head(n)["Player"].tolist()
376
+ top_df = df[df["Player"].isin(top_players)]
377
+
378
+ game_cols = [f"{g} Score" for g in GAME_ORDER if f"{g} Score" in df.columns]
379
+ categories = [g.replace(" Score", "") for g in game_cols]
380
+
381
+ for col in game_cols:
382
+ vals = top_df[col].replace("_", 0).astype(float)
383
+ mean, std = vals.mean(), vals.std()
384
+ top_df[f"norm_{col}"] = normalize_values(vals, mean, std)
385
+
386
+ fig = go.Figure()
387
+ for _, row in top_df.iterrows():
388
+ r = [row[f"norm_{col}"] for col in game_cols]
389
+ fig.add_trace(go.Scatterpolar(
390
+ r=r + [r[0]],
391
+ theta=categories + [categories[0]],
392
+ mode='lines+markers',
393
+ fill='toself',
394
+ name=simplify_model_name(row["Player"])
395
+ ))
396
+
397
+ fig.update_layout(
398
+ autosize=False,
399
+ width=800,
400
+ height=600,
401
+ margin=dict(l=80, r=150, t=40, b=200),
402
+ title=dict(
403
+ text=f"Top {n} Players Radar Chart (Normalized)",
404
+ pad=dict(t=10)
405
+ ),
406
+ polar=dict(radialaxis=dict(visible=True, range=[0, 100])),
407
+ legend=dict(
408
+ font=dict(size=9),
409
+ itemsizing='trace',
410
+ x=1.4,
411
+ y=1,
412
+ xanchor='left',
413
+ yanchor='top',
414
+ bgcolor='rgba(255,255,255,0.6)',
415
+ bordercolor='gray',
416
+ borderwidth=1
417
+ )
418
+ )
419
+ return fig
420
+
421
+ def create_player_radar_chart(rank_data, player_name):
422
+ df = get_combined_leaderboard(rank_data, {g: True for g in GAME_ORDER})
423
+ player_df = df[df["Player"] == player_name]
424
+
425
+ if player_df.empty:
426
+ return go.Figure().update_layout(
427
+ title=dict(text="Player not found", pad=dict(t=10)),
428
+ autosize=False,
429
+ width=800,
430
+ height=400
431
+ )
432
+
433
+ game_cols = [f"{g} Score" for g in GAME_ORDER if f"{g} Score" in df.columns]
434
+ categories = [g.replace(" Score", "") for g in game_cols]
435
+
436
+ for col in game_cols:
437
+ vals = player_df[col].replace("_", 0).astype(float)
438
+ mean, std = df[col].replace("_", 0).astype(float).mean(), df[col].replace("_", 0).astype(float).std()
439
+ player_df[f"norm_{col}"] = normalize_values(vals, mean, std)
440
 
441
+ fig = go.Figure()
442
+ for _, row in player_df.iterrows():
443
+ r = [row[f"norm_{col}"] for col in game_cols]
444
+ fig.add_trace(go.Scatterpolar(
445
+ r=r + [r[0]],
446
+ theta=categories + [categories[0]],
447
+ mode='lines+markers',
448
+ fill='toself',
449
+ name=simplify_model_name(row["Player"])
450
+ ))
451
+
452
+ fig.update_layout(
453
+ autosize=False,
454
+ width=800,
455
+ height=600,
456
+ margin=dict(l=80, r=150, t=40, b=200),
457
+ title=dict(
458
+ text=f"{simplify_model_name(player_name)} Radar Chart (Normalized)",
459
+ pad=dict(t=10)
460
+ ),
461
+ polar=dict(radialaxis=dict(visible=True, range=[0, 100])),
462
+ legend=dict(
463
+ font=dict(size=9),
464
+ itemsizing='trace',
465
+ x=1.4,
466
+ y=1,
467
+ xanchor='left',
468
+ yanchor='top',
469
+ bgcolor='rgba(255,255,255,0.6)',
470
+ bordercolor='gray',
471
+ borderwidth=1
472
+ )
473
+ )
474
+ return fig
475
+
476
+
477
+ def save_visualization(fig, filename):
478
+ 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
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